Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Lint

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: none

- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist

- name: Run Laravel Pint
run: vendor/bin/pint --test
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"yoast/phpunit-polyfills": "^2.0"
"yoast/phpunit-polyfills": "^2.0",
"laravel/pint": "^1.20"
},
"autoload": {
"psr-4": {
Expand Down
68 changes: 67 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 25 additions & 24 deletions escalated.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Plugin Name: Escalated
* Plugin URI: https://github.com/escalated-dev/escalated-wordpress
Expand All @@ -13,53 +14,53 @@
* Requires at least: 6.0
* Requires PHP: 8.1
*/

if ( ! defined( 'ABSPATH' ) ) {
if (! defined('ABSPATH')) {
exit;
}

define( 'ESCALATED_VERSION', '1.0.1' );
define( 'ESCALATED_PLUGIN_FILE', __FILE__ );
define( 'ESCALATED_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'ESCALATED_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'ESCALATED_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
define('ESCALATED_VERSION', '1.0.1');
define('ESCALATED_PLUGIN_FILE', __FILE__);
define('ESCALATED_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('ESCALATED_PLUGIN_URL', plugin_dir_url(__FILE__));
define('ESCALATED_PLUGIN_BASENAME', plugin_basename(__FILE__));

require_once ESCALATED_PLUGIN_DIR . 'vendor/autoload.php';
require_once ESCALATED_PLUGIN_DIR.'vendor/autoload.php';

spl_autoload_register(
static function ( string $class ): void {
static function (string $class): void {
$prefix = 'Escalated\\';

if ( strpos( $class, $prefix ) !== 0 ) {
if (strpos($class, $prefix) !== 0) {
return;
}

$relative = substr( $class, strlen( $prefix ) );
$segments = explode( '\\', $relative );
$class_name = array_pop( $segments );
$subdir = ! empty( $segments ) ? implode( '/', $segments ) . '/' : '';
$relative = substr($class, strlen($prefix));
$segments = explode('\\', $relative);
$class_name = array_pop($segments);
$subdir = ! empty($segments) ? implode('/', $segments).'/' : '';

// Prefer PSR-4-style files (e.g. includes/Services/TicketService.php).
$psr4_file = ESCALATED_PLUGIN_DIR . 'includes/' . $subdir . $class_name . '.php';
if ( file_exists( $psr4_file ) ) {
$psr4_file = ESCALATED_PLUGIN_DIR.'includes/'.$subdir.$class_name.'.php';
if (file_exists($psr4_file)) {
require_once $psr4_file;

return;
}

// Fallback for WordPress-style class files (e.g. includes/Api/class-ticket-controller.php).
$normalized = preg_replace( '/([a-z0-9])([A-Z])/', '$1-$2', $class_name );
$normalized = strtolower( str_replace( '_', '-', (string) $normalized ) );
$legacy_file = ESCALATED_PLUGIN_DIR . 'includes/' . $subdir . 'class-' . $normalized . '.php';
$normalized = preg_replace('/([a-z0-9])([A-Z])/', '$1-$2', $class_name);
$normalized = strtolower(str_replace('_', '-', (string) $normalized));
$legacy_file = ESCALATED_PLUGIN_DIR.'includes/'.$subdir.'class-'.$normalized.'.php';

if ( file_exists( $legacy_file ) ) {
if (file_exists($legacy_file)) {
require_once $legacy_file;
}
}
);

register_activation_hook( __FILE__, [ \Escalated\Activator::class, 'activate' ] );
register_deactivation_hook( __FILE__, [ \Escalated\Deactivator::class, 'deactivate' ] );
register_activation_hook(__FILE__, [\Escalated\Activator::class, 'activate']);
register_deactivation_hook(__FILE__, [\Escalated\Deactivator::class, 'deactivate']);

add_action( 'plugins_loaded', function () {
add_action('plugins_loaded', function () {
\Escalated\Escalated::instance()->boot();
} );
});
78 changes: 41 additions & 37 deletions includes/Admin/class-admin-api-tokens.php
Original file line number Diff line number Diff line change
@@ -1,89 +1,93 @@
<?php

namespace Escalated\Admin;

use Escalated\Models\ApiToken;

class Admin_Api_Tokens {

public function __construct() {
add_action( 'admin_init', [ $this, 'handle_actions' ] );
class Admin_Api_Tokens
{
public function __construct()
{
add_action('admin_init', [$this, 'handle_actions']);
}

/**
* Render the API tokens admin page.
*/
public function render(): void {
$tokens = ApiToken::all();
public function render(): void
{
$tokens = ApiToken::all();
$plain_token = null;
$message = isset( $_GET['message'] ) ? sanitize_text_field( wp_unslash( $_GET['message'] ) ) : '';
$message = isset($_GET['message']) ? sanitize_text_field(wp_unslash($_GET['message'])) : '';

// Check transient for newly created plain token.
$token_transient = get_transient( 'escalated_new_token_' . get_current_user_id() );
if ( $token_transient ) {
$token_transient = get_transient('escalated_new_token_'.get_current_user_id());
if ($token_transient) {
$plain_token = $token_transient;
delete_transient( 'escalated_new_token_' . get_current_user_id() );
delete_transient('escalated_new_token_'.get_current_user_id());
}

include ESCALATED_PLUGIN_DIR . 'templates/admin/api-tokens.php';
include ESCALATED_PLUGIN_DIR.'templates/admin/api-tokens.php';
}

/**
* Handle POST actions: create, delete.
*/
public function handle_actions(): void {
if ( ! isset( $_POST['escalated_token_action'] ) ) {
public function handle_actions(): void
{
if (! isset($_POST['escalated_token_action'])) {
return;
}

if ( ! current_user_can( 'escalated_manage_api_tokens' ) ) {
wp_die( esc_html__( 'Permission denied.', 'escalated' ) );
if (! current_user_can('escalated_manage_api_tokens')) {
wp_die(esc_html__('Permission denied.', 'escalated'));
}

$action = sanitize_text_field( wp_unslash( $_POST['escalated_token_action'] ) );
$redirect = admin_url( 'admin.php?page=escalated-api-tokens' );
$action = sanitize_text_field(wp_unslash($_POST['escalated_token_action']));
$redirect = admin_url('admin.php?page=escalated-api-tokens');

switch ( $action ) {
switch ($action) {
case 'create':
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_escalated_nonce'] ?? '' ) ), 'escalated_token_create' ) ) {
wp_die( esc_html__( 'Security check failed.', 'escalated' ) );
if (! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_escalated_nonce'] ?? '')), 'escalated_token_create')) {
wp_die(esc_html__('Security check failed.', 'escalated'));
}

$name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) );
$abilities = isset( $_POST['abilities'] ) ? array_map( 'sanitize_text_field', (array) $_POST['abilities'] ) : [ '*' ];
$name = sanitize_text_field(wp_unslash($_POST['name'] ?? ''));
$abilities = isset($_POST['abilities']) ? array_map('sanitize_text_field', (array) $_POST['abilities']) : ['*'];

$expires_at = null;
if ( ! empty( $_POST['expires_at'] ) ) {
$expires_at = sanitize_text_field( wp_unslash( $_POST['expires_at'] ) );
if (! empty($_POST['expires_at'])) {
$expires_at = sanitize_text_field(wp_unslash($_POST['expires_at']));
}

$result = ApiToken::create_token( get_current_user_id(), $name, $abilities );
$result = ApiToken::create_token(get_current_user_id(), $name, $abilities);

if ( $result ) {
if ($result) {
// Store plain token temporarily so it can be displayed once.
set_transient( 'escalated_new_token_' . get_current_user_id(), $result['token'], 60 );
set_transient('escalated_new_token_'.get_current_user_id(), $result['token'], 60);

if ( $expires_at ) {
ApiToken::update( $result['record']->id, [ 'expires_at' => $expires_at ] );
if ($expires_at) {
ApiToken::update($result['record']->id, ['expires_at' => $expires_at]);
}

$redirect = add_query_arg( 'message', 'created', $redirect );
$redirect = add_query_arg('message', 'created', $redirect);
} else {
$redirect = add_query_arg( 'message', 'error', $redirect );
$redirect = add_query_arg('message', 'error', $redirect);
}
break;

case 'delete':
$id = absint( $_POST['id'] ?? 0 );
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_escalated_nonce'] ?? '' ) ), 'escalated_token_delete_' . $id ) ) {
wp_die( esc_html__( 'Security check failed.', 'escalated' ) );
$id = absint($_POST['id'] ?? 0);
if (! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_escalated_nonce'] ?? '')), 'escalated_token_delete_'.$id)) {
wp_die(esc_html__('Security check failed.', 'escalated'));
}

ApiToken::delete( $id );
$redirect = add_query_arg( 'message', 'deleted', $redirect );
ApiToken::delete($id);
$redirect = add_query_arg('message', 'deleted', $redirect);
break;
}

wp_safe_redirect( $redirect );
wp_safe_redirect($redirect);
exit;
}
}
Loading
Loading