diff --git a/.gitignore b/.gitignore index 9dd7486..3dce2e4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,11 @@ Thumbs.db # Logs *.log +# Translation tool backups +*backup* +*.po~ +*.pot~ + # Local config .env .env.local diff --git a/build-plugin.sh b/build-plugin.sh new file mode 100755 index 0000000..67f12b2 --- /dev/null +++ b/build-plugin.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# Build a WordPress.org-ready zip archive for the WP Queue plugin. +# +# Usage: +# cd wp-content/plugins/wp-queue +# ./build-plugin.sh +# +# The archive is written to build/wp-queue..zip +# and contains only the files required for publishing/installing the plugin. +# + +set -euo pipefail + +# Ensure the script is run from the plugin root. +if [[ ! -f "wp-queue.php" ]]; then + echo "Error: wp-queue.php not found. Please run this script from the plugin root directory." >&2 + exit 1 +fi + +PLUGIN_SLUG="wp-queue" +VERSION=$(grep -m1 "Version:" wp-queue.php | sed -E 's/^[^:]*Version:[[:space:]]*//') +BUILD_DIR="build" +ZIP_NAME="${PLUGIN_SLUG}.${VERSION}.zip" +ZIP_PATH="${BUILD_DIR}/${ZIP_NAME}" + +if [[ -z "${VERSION}" ]]; then + echo "Error: could not detect plugin version from wp-queue.php" >&2 + exit 1 +fi + +TMP_DIR=$(mktemp -d) +STAGING_DIR="${TMP_DIR}/${PLUGIN_SLUG}" +mkdir -p "${STAGING_DIR}" + +echo "Building ${ZIP_NAME}..." + +# Required plugin files. +cp wp-queue.php readme.txt LICENSE "${STAGING_DIR}/" + +# Source code. +cp -r src "${STAGING_DIR}/" + +# Runtime admin assets only (CSS/JS). Images are excluded because they are +# either WordPress.org promo assets or unused in the admin UI. +mkdir -p "${STAGING_DIR}/assets" +cp -r assets/css assets/js "${STAGING_DIR}/assets/" + +# Translations: include compiled and source files, remove editor backups. +cp -r languages "${STAGING_DIR}/" +find "${STAGING_DIR}/languages" -type f \( \ + -name '*backup*' \ + -o -name '*.po~' \ + -o -name '*.pot~' \ + -o -name '*.mo~' \ +\) -delete + +# Verify the main plugin file exists in the staging directory. +if [[ ! -f "${STAGING_DIR}/wp-queue.php" ]]; then + echo "Error: staged plugin file is missing." >&2 + rm -rf "${TMP_DIR}" + exit 1 +fi + +# Prepare build output directory. +mkdir -p "${BUILD_DIR}" +rm -f "${ZIP_PATH}" + +# Create the zip with the plugin slug as the top-level directory. +( + cd "${TMP_DIR}" + zip -r "${OLDPWD}/${ZIP_PATH}" "${PLUGIN_SLUG}" -q +) + +# Clean up staging directory. +rm -rf "${TMP_DIR}" + +echo "Archive created: ${ZIP_PATH}" +echo "" +echo "Contents:" +unzip -l "${ZIP_PATH}" diff --git a/readme.txt b/readme.txt index 7b485b2..a5f9575 100644 --- a/readme.txt +++ b/readme.txt @@ -1,11 +1,11 @@ -=== WP Queue - Background Job Manager === +=== Queue Manager === Contributors: rwsite Donate link: https://rwsite.ru/donate Tags: queue, cron, background-processing, jobs, scheduler -Requires at least: 6.0 -Tested up to: 6.9 +Requires at least: 6.2 +Tested up to: 7.0 Requires PHP: 8.3 -Stable tag: 1.2.0 +Stable tag: 1.2.1 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -147,6 +147,14 @@ Yes, WP Queue works with WordPress multisite installations. == Changelog == += 1.2.1 = +* Added ABSPATH guards to all PHP files +* Fixed WordPress Plugin Check errors: escaping, i18n placeholders, input sanitization +* Refactored database queries to use identifier placeholders +* Fixed arbitrary class instantiation in REST API run job endpoint +* Renamed plugin display name to Queue Manager +* Updated "Tested up to" to 7.0 and "Requires at least" to 6.2 + = 1.2.0 = * Added runtime modes: cron_loopback, daemon, auto * Added loopback dispatch for immediate queue processing after dispatch() @@ -173,6 +181,9 @@ Yes, WP Queue works with WordPress multisite installations. == Upgrade Notice == += 1.2.1 = +Maintenance release with WordPress Plugin Check fixes and plugin rename to Queue Manager. + = 1.2.0 = New runtime modes. Use `define('WP_QUEUE_RUNTIME_MODE', 'daemon')` with a separate worker process, or keep the default `cron_loopback` mode for shared hosting. diff --git a/src/Admin/AdminPage.php b/src/Admin/AdminPage.php index 93c8335..61bbd52 100644 --- a/src/Admin/AdminPage.php +++ b/src/Admin/AdminPage.php @@ -8,6 +8,10 @@ use WPQueue\Runtime\RuntimeMode; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + /** * Admin Page with Rank Math style UI * 3 main tabs: Queues, Scheduler, System @@ -206,8 +210,8 @@ public function enqueueAssets(string $hook): void public function renderPage(): void { - $tab = sanitize_key($_GET['tab'] ?? 'queues'); - $section = sanitize_key($_GET['section'] ?? ''); + $tab = sanitize_key(wp_unslash($_GET['tab'] ?? 'queues')); + $section = sanitize_key(wp_unslash($_GET['section'] ?? '')); if (! isset($this->tabs[$tab])) { $tab = 'queues'; @@ -219,8 +223,8 @@ public function renderPage(): void } // Проверка на детальный просмотр очереди - $queueView = sanitize_key($_GET['queue'] ?? ''); - $jobView = sanitize_key($_GET['job'] ?? ''); + $queueView = sanitize_key(wp_unslash($_GET['queue'] ?? '')); + $jobView = sanitize_key(wp_unslash($_GET['job'] ?? '')); ?>
@@ -337,7 +341,7 @@ protected function renderQueuesOverview(): void $metrics = WPQueue::logs()->metrics(); $queues = $this->getQueuesStatus(); $driver = WPQueue::manager()->getDefaultDriver(); - $filter = sanitize_key($_GET['status'] ?? ''); + $filter = sanitize_key(wp_unslash($_GET['status'] ?? '')); ?>
@@ -407,7 +411,7 @@ protected function renderQueuesOverview(): void protected function renderQueueDetail(string $queueName, string $jobId = ''): void { $jobs = $this->getQueueJobs($queueName); - $page = max(1, (int) ($_GET['paged'] ?? 1)); + $page = max(1, (int) wp_unslash($_GET['paged'] ?? 1)); $totalJobs = count($jobs); $totalPages = max(1, (int) ceil($totalJobs / self::JOBS_PER_PAGE)); $offset = ($page - 1) * self::JOBS_PER_PAGE; @@ -420,7 +424,11 @@ protected function renderQueueDetail(string $queueName, string $jobId = ''): voi
-

+

- +
- + failed(); - $page = max(1, (int) ($_GET['paged'] ?? 1)); + $page = max(1, (int) wp_unslash($_GET['paged'] ?? 1)); $totalLogs = count($failedLogs); $totalPages = max(1, (int) ceil($totalLogs / self::LOGS_PER_PAGE)); $offset = ($page - 1) * self::LOGS_PER_PAGE; @@ -1031,7 +1047,8 @@ protected function renderQueuesDrivers(): void

@@ -1072,7 +1089,7 @@ protected function renderQueuesDrivers(): void - renderDriverStatusBadge($status, $info); ?> + renderDriverStatusBadge($status, $info)); ?> @@ -1425,7 +1442,7 @@ protected function renderSchedulerOverview(): void protected function renderSchedulerEvents(): void { $monitor = new CronMonitor(); - $filter = sanitize_key($_GET['filter'] ?? 'all'); + $filter = sanitize_key(wp_unslash($_GET['filter'] ?? 'all')); $events = match ($filter) { 'wordpress' => array_filter($monitor->getAllEvents(), fn ($e) => $e['source'] === 'wordpress'), diff --git a/src/Admin/CronMonitor.php b/src/Admin/CronMonitor.php index 2f2528d..91da64d 100644 --- a/src/Admin/CronMonitor.php +++ b/src/Admin/CronMonitor.php @@ -4,6 +4,10 @@ namespace WPQueue\Admin; +if (! defined('ABSPATH')) { + exit; +} + class CronMonitor { /** diff --git a/src/Admin/RestApi.php b/src/Admin/RestApi.php index 6136c25..4267c54 100644 --- a/src/Admin/RestApi.php +++ b/src/Admin/RestApi.php @@ -9,6 +9,10 @@ use WP_REST_Response; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + class RestApi { public function __construct() @@ -329,7 +333,7 @@ public function runJob(WP_REST_Request $request): WP_REST_Response|WP_Error { $jobClass = urldecode($request->get_param('job')); - if (! class_exists($jobClass)) { + if (! class_exists($jobClass) || ! is_subclass_of($jobClass, \WPQueue\Jobs\Job::class)) { return new WP_Error('invalid_job', 'Job class not found', ['status' => 404]); } diff --git a/src/Admin/SystemStatus.php b/src/Admin/SystemStatus.php index 21aa91c..696b960 100644 --- a/src/Admin/SystemStatus.php +++ b/src/Admin/SystemStatus.php @@ -4,6 +4,10 @@ namespace WPQueue\Admin; +if (! defined('ABSPATH')) { + exit; +} + class SystemStatus { /** diff --git a/src/Attributes/Queue.php b/src/Attributes/Queue.php index 92c9d1c..b3d412a 100644 --- a/src/Attributes/Queue.php +++ b/src/Attributes/Queue.php @@ -6,6 +6,10 @@ use Attribute; +if (! defined('ABSPATH')) { + exit; +} + #[Attribute(Attribute::TARGET_CLASS)] final readonly class Queue { diff --git a/src/Attributes/Retries.php b/src/Attributes/Retries.php index a86d9ba..6a90a37 100644 --- a/src/Attributes/Retries.php +++ b/src/Attributes/Retries.php @@ -6,6 +6,10 @@ use Attribute; +if (! defined('ABSPATH')) { + exit; +} + #[Attribute(Attribute::TARGET_CLASS)] final readonly class Retries { diff --git a/src/Attributes/Schedule.php b/src/Attributes/Schedule.php index 402e433..ec41349 100644 --- a/src/Attributes/Schedule.php +++ b/src/Attributes/Schedule.php @@ -6,6 +6,10 @@ use Attribute; +if (! defined('ABSPATH')) { + exit; +} + #[Attribute(Attribute::TARGET_CLASS)] final readonly class Schedule { diff --git a/src/Attributes/Timeout.php b/src/Attributes/Timeout.php index a45db4d..c0630fa 100644 --- a/src/Attributes/Timeout.php +++ b/src/Attributes/Timeout.php @@ -6,6 +6,10 @@ use Attribute; +if (! defined('ABSPATH')) { + exit; +} + #[Attribute(Attribute::TARGET_CLASS)] final readonly class Timeout { diff --git a/src/Attributes/UniqueJob.php b/src/Attributes/UniqueJob.php index a5827d2..c2d34b4 100644 --- a/src/Attributes/UniqueJob.php +++ b/src/Attributes/UniqueJob.php @@ -6,6 +6,10 @@ use Attribute; +if (! defined('ABSPATH')) { + exit; +} + #[Attribute(Attribute::TARGET_CLASS)] final readonly class UniqueJob { diff --git a/src/CLI/CronCommand.php b/src/CLI/CronCommand.php index 102e38d..0c36cf1 100644 --- a/src/CLI/CronCommand.php +++ b/src/CLI/CronCommand.php @@ -8,6 +8,10 @@ use WP_CLI\Utils; use WPQueue\Admin\CronMonitor; +if (! defined('ABSPATH')) { + exit; +} + /** * Manage WP-Cron events. * diff --git a/src/CLI/QueueCommand.php b/src/CLI/QueueCommand.php index 86d21ab..0500383 100644 --- a/src/CLI/QueueCommand.php +++ b/src/CLI/QueueCommand.php @@ -9,6 +9,10 @@ use WPQueue\Admin\SystemStatus; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + /** * Manage WP Queue jobs and cron events. * diff --git a/src/Contracts/JobInterface.php b/src/Contracts/JobInterface.php index f0ac3cd..6c19406 100644 --- a/src/Contracts/JobInterface.php +++ b/src/Contracts/JobInterface.php @@ -4,6 +4,10 @@ namespace WPQueue\Contracts; +if (! defined('ABSPATH')) { + exit; +} + interface JobInterface { /** diff --git a/src/Contracts/QueueInterface.php b/src/Contracts/QueueInterface.php index e27dc9c..c17be51 100644 --- a/src/Contracts/QueueInterface.php +++ b/src/Contracts/QueueInterface.php @@ -4,6 +4,10 @@ namespace WPQueue\Contracts; +if (! defined('ABSPATH')) { + exit; +} + interface QueueInterface { /** diff --git a/src/Contracts/ShouldQueue.php b/src/Contracts/ShouldQueue.php index 825d57f..3c22830 100644 --- a/src/Contracts/ShouldQueue.php +++ b/src/Contracts/ShouldQueue.php @@ -4,6 +4,10 @@ namespace WPQueue\Contracts; +if (! defined('ABSPATH')) { + exit; +} + /** * Marker interface for queueable jobs. */ diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 7647c9c..754e59f 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -8,6 +8,10 @@ use WPQueue\Jobs\PendingDispatch; use WPQueue\Queue\SyncQueue; +if (! defined('ABSPATH')) { + exit; +} + class Dispatcher { public function __construct( diff --git a/src/Events/JobFailed.php b/src/Events/JobFailed.php index 781d350..f768470 100644 --- a/src/Events/JobFailed.php +++ b/src/Events/JobFailed.php @@ -7,6 +7,10 @@ use Throwable; use WPQueue\Contracts\JobInterface; +if (! defined('ABSPATH')) { + exit; +} + final readonly class JobFailed { public function __construct( diff --git a/src/Events/JobProcessed.php b/src/Events/JobProcessed.php index 8a39c15..b06aa47 100644 --- a/src/Events/JobProcessed.php +++ b/src/Events/JobProcessed.php @@ -6,6 +6,10 @@ use WPQueue\Contracts\JobInterface; +if (! defined('ABSPATH')) { + exit; +} + final readonly class JobProcessed { public function __construct( diff --git a/src/Events/JobProcessing.php b/src/Events/JobProcessing.php index 277ae80..e77564d 100644 --- a/src/Events/JobProcessing.php +++ b/src/Events/JobProcessing.php @@ -6,6 +6,10 @@ use WPQueue\Contracts\JobInterface; +if (! defined('ABSPATH')) { + exit; +} + final readonly class JobProcessing { public function __construct( diff --git a/src/Events/JobRetrying.php b/src/Events/JobRetrying.php index 9129e5f..04937ca 100644 --- a/src/Events/JobRetrying.php +++ b/src/Events/JobRetrying.php @@ -7,6 +7,10 @@ use Throwable; use WPQueue\Contracts\JobInterface; +if (! defined('ABSPATH')) { + exit; +} + final readonly class JobRetrying { public function __construct( diff --git a/src/Jobs/ChainedJob.php b/src/Jobs/ChainedJob.php index 53421b0..85e2372 100644 --- a/src/Jobs/ChainedJob.php +++ b/src/Jobs/ChainedJob.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + class ChainedJob extends Job { /** diff --git a/src/Jobs/Job.php b/src/Jobs/Job.php index fe56bdf..6883ac8 100644 --- a/src/Jobs/Job.php +++ b/src/Jobs/Job.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\Contracts\ShouldQueue; +if (! defined('ABSPATH')) { + exit; +} + abstract class Job implements JobInterface, ShouldQueue { protected string $id; diff --git a/src/Jobs/PendingDispatch.php b/src/Jobs/PendingDispatch.php index d12cb7b..ad6f828 100644 --- a/src/Jobs/PendingDispatch.php +++ b/src/Jobs/PendingDispatch.php @@ -8,6 +8,10 @@ use WPQueue\Loopback\LoopbackDispatcher; use WPQueue\QueueManager; +if (! defined('ABSPATH')) { + exit; +} + class PendingDispatch { protected bool $shouldDispatch = true; diff --git a/src/Loopback/LoopbackDispatcher.php b/src/Loopback/LoopbackDispatcher.php index 006f379..24f4ace 100644 --- a/src/Loopback/LoopbackDispatcher.php +++ b/src/Loopback/LoopbackDispatcher.php @@ -7,6 +7,10 @@ use WPQueue\Runtime\RuntimeMode; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + /** * Spawns non-blocking loopback requests to trigger immediate queue processing. * diff --git a/src/Loopback/LoopbackHandler.php b/src/Loopback/LoopbackHandler.php index d7f5b59..bbfac64 100644 --- a/src/Loopback/LoopbackHandler.php +++ b/src/Loopback/LoopbackHandler.php @@ -6,6 +6,10 @@ use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + /** * Handles loopback requests that trigger immediate queue processing. * @@ -29,7 +33,7 @@ public function handle(): void // Don't lock up other requests while processing. session_write_close(); - $queue = LoopbackDispatcher::sanitizeQueue($_REQUEST['queue'] ?? 'default'); + $queue = LoopbackDispatcher::sanitizeQueue(wp_unslash($_REQUEST['queue'] ?? 'default')); if (! $this->verifyNonce($queue)) { wp_die('Unauthorized', 'Unauthorized', ['response' => 403]); @@ -50,12 +54,12 @@ public function handle(): void */ protected function verifyNonce(string $queue): bool { - $nonce = $_REQUEST['nonce'] ?? ''; + $nonce = wp_unslash($_REQUEST['nonce'] ?? ''); if (empty($nonce)) { return false; } - return wp_verify_nonce(sanitize_text_field(wp_unslash($nonce)), LoopbackDispatcher::nonceAction($queue)) !== false; + return wp_verify_nonce(sanitize_text_field($nonce), LoopbackDispatcher::nonceAction($queue)) !== false; } } diff --git a/src/PendingBatch.php b/src/PendingBatch.php index 6b6414c..a086a53 100644 --- a/src/PendingBatch.php +++ b/src/PendingBatch.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\Loopback\LoopbackDispatcher; +if (! defined('ABSPATH')) { + exit; +} + class PendingBatch { protected string $queue = 'default'; diff --git a/src/PendingChain.php b/src/PendingChain.php index 33a2280..57a268a 100644 --- a/src/PendingChain.php +++ b/src/PendingChain.php @@ -8,6 +8,14 @@ use WPQueue\Jobs\ChainedJob; use WPQueue\Loopback\LoopbackDispatcher; +if (! defined('ABSPATH')) { + exit; +} + +if (! defined('ABSPATH')) { + exit; +} + class PendingChain { protected string $queue = 'default'; diff --git a/src/Queue/DatabaseQueue.php b/src/Queue/DatabaseQueue.php index 970655f..b2a9e44 100644 --- a/src/Queue/DatabaseQueue.php +++ b/src/Queue/DatabaseQueue.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\Contracts\QueueInterface; +if (! defined('ABSPATH')) { + exit; +} + class DatabaseQueue implements QueueInterface { protected const PREFIX = 'wp_queue_'; diff --git a/src/Queue/MemcachedQueue.php b/src/Queue/MemcachedQueue.php index 85482e0..67d199e 100644 --- a/src/Queue/MemcachedQueue.php +++ b/src/Queue/MemcachedQueue.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\Contracts\QueueInterface; +if (! defined('ABSPATH')) { + exit; +} + /** * Memcached-based queue implementation. * diff --git a/src/Queue/Redis/PhpRedisClient.php b/src/Queue/Redis/PhpRedisClient.php index 320fbe9..528b5dd 100644 --- a/src/Queue/Redis/PhpRedisClient.php +++ b/src/Queue/Redis/PhpRedisClient.php @@ -4,6 +4,10 @@ namespace WPQueue\Queue\Redis; +if (! defined('ABSPATH')) { + exit; +} + /** * phpredis extension adapter. */ @@ -94,7 +98,7 @@ public function connect(): void $this->redis->setOption(\Redis::OPT_PREFIX, $this->config['prefix']); } catch (\RedisException $e) { $this->connected = false; - throw new \RuntimeException('Redis connection failed: '.$e->getMessage(), 0, $e); + throw new \RuntimeException(esc_html('Redis connection failed: '.$e->getMessage()), 0, $e); } } diff --git a/src/Queue/Redis/PredisClient.php b/src/Queue/Redis/PredisClient.php index 926ce2a..2eab19a 100644 --- a/src/Queue/Redis/PredisClient.php +++ b/src/Queue/Redis/PredisClient.php @@ -4,6 +4,10 @@ namespace WPQueue\Queue\Redis; +if (! defined('ABSPATH')) { + exit; +} + /** * Predis library adapter. * @@ -88,7 +92,7 @@ public function connect(): void $this->connected = true; } catch (\Throwable $e) { $this->connected = false; - throw new \RuntimeException('Predis connection failed: '.$e->getMessage(), 0, $e); + throw new \RuntimeException(esc_html('Predis connection failed: '.$e->getMessage()), 0, $e); } } diff --git a/src/Queue/Redis/RedisClientFactory.php b/src/Queue/Redis/RedisClientFactory.php index 3afe62f..950a6dc 100644 --- a/src/Queue/Redis/RedisClientFactory.php +++ b/src/Queue/Redis/RedisClientFactory.php @@ -4,6 +4,10 @@ namespace WPQueue\Queue\Redis; +if (! defined('ABSPATH')) { + exit; +} + /** * Factory for creating Redis client adapters. * diff --git a/src/Queue/Redis/RedisClientInterface.php b/src/Queue/Redis/RedisClientInterface.php index f9f9a3c..c26fb2c 100644 --- a/src/Queue/Redis/RedisClientInterface.php +++ b/src/Queue/Redis/RedisClientInterface.php @@ -4,6 +4,10 @@ namespace WPQueue\Queue\Redis; +if (! defined('ABSPATH')) { + exit; +} + /** * Interface for Redis client adapters. * diff --git a/src/Queue/RedisQueue.php b/src/Queue/RedisQueue.php index f554c46..cc2839e 100644 --- a/src/Queue/RedisQueue.php +++ b/src/Queue/RedisQueue.php @@ -9,6 +9,10 @@ use WPQueue\Queue\Redis\RedisClientFactory; use WPQueue\Queue\Redis\RedisClientInterface; +if (! defined('ABSPATH')) { + exit; +} + /** * Redis-based queue implementation. * diff --git a/src/Queue/SyncQueue.php b/src/Queue/SyncQueue.php index fc1bea9..04e1924 100644 --- a/src/Queue/SyncQueue.php +++ b/src/Queue/SyncQueue.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\Contracts\QueueInterface; +if (! defined('ABSPATH')) { + exit; +} + /** * Synchronous queue - executes jobs immediately. */ diff --git a/src/QueueManager.php b/src/QueueManager.php index fac6690..830e622 100644 --- a/src/QueueManager.php +++ b/src/QueueManager.php @@ -11,6 +11,10 @@ use WPQueue\Queue\RedisQueue; use WPQueue\Queue\SyncQueue; +if (! defined('ABSPATH')) { + exit; +} + /** * Queue Manager - manages queue connections and drivers. * @@ -107,16 +111,6 @@ public function getDefaultDriver(): string return $driver; } - // Log warning about fallback (only once per request) - static $warned = []; - if (! isset($warned[$driver]) && function_exists('error_log')) { - error_log(sprintf( - '[WP Queue] Driver "%s" is configured but not available. Falling back to "database". Run: wp queue drivers', - $driver, - )); - $warned[$driver] = true; - } - return 'database'; } @@ -286,15 +280,17 @@ protected function checkRedisViaPhpRedis(string $host, int $port): array 'status' => self::STATUS_READY, 'extension' => true, 'server' => true, - 'message' => sprintf(__('Connected to Redis at %s:%d (phpredis)', 'wp-queue'), $host, $port), + // translators: 1: Redis host, 2: Redis port. + 'message' => sprintf(__('Connected to Redis at %1$s:%2$d (phpredis)', 'wp-queue'), $host, $port), ]; } catch (\Throwable $e) { return [ 'status' => self::STATUS_NO_SERVER, 'extension' => true, 'server' => false, + // translators: 1: Redis host, 2: Redis port, 3: Error message. 'message' => sprintf( - __('Cannot connect to Redis at %s:%d - %s', 'wp-queue'), + __('Cannot connect to Redis at %1$s:%2$d - %3$s', 'wp-queue'), $host, $port, $e->getMessage(), @@ -346,7 +342,8 @@ protected function checkRedisViaPlugin(string $host, int $port): array 'status' => self::STATUS_READY, 'extension' => true, 'server' => true, - 'message' => sprintf(__('Connected to Redis at %s:%d (via redis-cache plugin)', 'wp-queue'), $host, $port), + // translators: 1: Redis host, 2: Redis port. + 'message' => sprintf(__('Connected to Redis at %1$s:%2$d (via redis-cache plugin)', 'wp-queue'), $host, $port), ]; } @@ -364,15 +361,17 @@ protected function checkRedisViaPlugin(string $host, int $port): array 'status' => self::STATUS_READY, 'extension' => true, 'server' => true, - 'message' => sprintf(__('Connected to Redis at %s:%d (via redis-cache plugin)', 'wp-queue'), $host, $port), + // translators: 1: Redis host, 2: Redis port. + 'message' => sprintf(__('Connected to Redis at %1$s:%2$d (via redis-cache plugin)', 'wp-queue'), $host, $port), ]; } catch (\Throwable $e) { return [ 'status' => self::STATUS_NO_SERVER, 'extension' => true, 'server' => false, + // translators: 1: Redis host, 2: Redis port, 3: Error message. 'message' => sprintf( - __('Cannot connect to Redis at %s:%d - %s', 'wp-queue'), + __('Cannot connect to Redis at %1$s:%2$d - %3$s', 'wp-queue'), $host, $port, $e->getMessage(), @@ -437,15 +436,17 @@ protected function checkRedisViaPredis(string $host, int $port): array 'status' => self::STATUS_READY, 'extension' => true, 'server' => true, - 'message' => sprintf(__('Connected to Redis at %s:%d (Predis)', 'wp-queue'), $host, $port), + // translators: 1: Redis host, 2: Redis port. + 'message' => sprintf(__('Connected to Redis at %1$s:%2$d (Predis)', 'wp-queue'), $host, $port), ]; } catch (\Throwable $e) { return [ 'status' => self::STATUS_NO_SERVER, 'extension' => true, 'server' => false, + // translators: 1: Redis host, 2: Redis port, 3: Error message. 'message' => sprintf( - __('Cannot connect to Redis at %s:%d - %s', 'wp-queue'), + __('Cannot connect to Redis at %1$s:%2$d - %3$s', 'wp-queue'), $host, $port, $e->getMessage(), @@ -486,7 +487,8 @@ protected function getMemcachedStatus(): array 'status' => self::STATUS_READY, 'extension' => true, 'server' => true, - 'message' => sprintf(__('Connected to Memcached at %s:%d', 'wp-queue'), $host, $port), + // translators: 1: Memcached host, 2: Memcached port. + 'message' => sprintf(__('Connected to Memcached at %1$s:%2$d', 'wp-queue'), $host, $port), ]; } catch (\Throwable $e) { $host = defined('WP_MEMCACHED_HOST') ? WP_MEMCACHED_HOST : '127.0.0.1'; @@ -496,8 +498,9 @@ protected function getMemcachedStatus(): array 'status' => self::STATUS_NO_SERVER, 'extension' => true, 'server' => false, + // translators: 1: Memcached host, 2: Memcached port, 3: Error message. 'message' => sprintf( - __('Cannot connect to Memcached at %s:%d - %s', 'wp-queue'), + __('Cannot connect to Memcached at %1$s:%2$d - %3$s', 'wp-queue'), $host, $port, $e->getMessage(), @@ -584,9 +587,7 @@ public function discoverQueues(): array break; } } catch (\Throwable $e) { - if (function_exists('error_log')) { - error_log('[WP Queue] Failed to discover queues: '.$e->getMessage()); - } + // Silently ignore discovery errors; default queue will be used. } // Always include default queue @@ -669,7 +670,10 @@ protected function resolve(string $name): QueueInterface 'redis' => new RedisQueue(), 'memcached' => new MemcachedQueue(), 'auto' => $this->resolve($this->detectBestDriver()), - default => throw new InvalidArgumentException("Queue driver [{$name}] is not supported."), + default => throw new InvalidArgumentException( + // translators: %s: Queue driver name. + esc_html(sprintf(__('Queue driver "%s" is not supported.', 'wp-queue'), $name)), + ), }; } } diff --git a/src/Runtime/RuntimeMode.php b/src/Runtime/RuntimeMode.php index 4785687..dbcd9fb 100644 --- a/src/Runtime/RuntimeMode.php +++ b/src/Runtime/RuntimeMode.php @@ -4,6 +4,10 @@ namespace WPQueue\Runtime; +if (! defined('ABSPATH')) { + exit; +} + final class RuntimeMode { public const MODE_CRON_LOOPBACK = 'cron_loopback'; diff --git a/src/ScheduledJob.php b/src/ScheduledJob.php index c5dcaf2..cd6786d 100644 --- a/src/ScheduledJob.php +++ b/src/ScheduledJob.php @@ -4,6 +4,10 @@ namespace WPQueue; +if (! defined('ABSPATH')) { + exit; +} + class ScheduledJob { protected string $interval = ''; diff --git a/src/Scheduler.php b/src/Scheduler.php index 4ba080d..ad49838 100644 --- a/src/Scheduler.php +++ b/src/Scheduler.php @@ -11,6 +11,10 @@ use WPQueue\Attributes\Timeout; use WPQueue\Contracts\JobInterface; +if (! defined('ABSPATH')) { + exit; +} + class Scheduler { /** diff --git a/src/Storage/LogStorage.php b/src/Storage/LogStorage.php index 47bf44d..ec8d7b9 100644 --- a/src/Storage/LogStorage.php +++ b/src/Storage/LogStorage.php @@ -7,6 +7,10 @@ use WPQueue\Contracts\JobInterface; use WPQueue\WPQueue; +if (! defined('ABSPATH')) { + exit; +} + class LogStorage { /** @@ -56,12 +60,12 @@ public function all(): array $table = $this->getTableName(); - $sql = "SELECT * FROM {$table} ORDER BY created_at ASC"; - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $sql = $wpdb->prepare('SELECT * FROM %i ORDER BY created_at ASC', $table); + $rows = $this->getResults($sql); if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $rows = $this->getResults($sql); } return array_map(static function (array $row): array { @@ -112,12 +116,12 @@ public function failed(): array $table = $this->getTableName(); - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE status = %s ORDER BY created_at DESC", 'failed'); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $sql = $wpdb->prepare('SELECT * FROM %i WHERE status = %s ORDER BY created_at DESC', $table, 'failed'); + $rows = $this->getResults($sql); if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $rows = $this->getResults($sql); } return array_map(static function (array $row): array { @@ -145,12 +149,12 @@ public function completed(): array $table = $this->getTableName(); - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE status = %s ORDER BY created_at DESC", 'completed'); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $sql = $wpdb->prepare('SELECT * FROM %i WHERE status = %s ORDER BY created_at DESC', $table, 'completed'); + $rows = $this->getResults($sql); if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $rows = $this->getResults($sql); } return array_map(static function (array $row): array { @@ -177,12 +181,14 @@ public function clearOld(int $daysOld = 7): int $table = $this->getTableName(); $cutoff = gmdate('Y-m-d H:i:s', time() - ($daysOld * DAY_IN_SECONDS)); - $sql = $wpdb->prepare("DELETE FROM {$table} WHERE created_at < %s", $cutoff); - $deleted = $wpdb->query($sql); + $sql = $wpdb->prepare('DELETE FROM %i WHERE created_at < %s', $table, $cutoff); + $this->executeQuery($sql); + $deleted = $wpdb->rows_affected; if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $deleted = $wpdb->query($sql); + $this->executeQuery($sql); + $deleted = $wpdb->rows_affected; } return (int) $deleted; @@ -196,12 +202,12 @@ public function clear(): void global $wpdb; $table = $this->getTableName(); - $sql = "TRUNCATE TABLE {$table}"; - $wpdb->query($sql); + $sql = $wpdb->prepare('TRUNCATE TABLE %i', $table); + $this->executeQuery($sql); if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $wpdb->query($sql); + $this->executeQuery($sql); } } @@ -216,12 +222,12 @@ public function metrics(): array $table = $this->getTableName(); - $sql = "SELECT queue, job_class, status FROM {$table}"; - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $sql = $wpdb->prepare('SELECT queue, job_class, status FROM %i', $table); + $rows = $this->getResults($sql); if ($this->isMissingTableError($wpdb->last_error)) { WPQueue::install(); - $rows = $wpdb->get_results($sql, ARRAY_A) ?: []; + $rows = $this->getResults($sql); } $metrics = [ @@ -255,6 +261,30 @@ public function metrics(): array return $metrics; } + /** + * Execute a read query against the logs table. + * + * @return array> + */ + protected function getResults(string $sql): array + { + global $wpdb; + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_results($sql, ARRAY_A) ?: []; + } + + /** + * Execute a write query against the logs table. + */ + protected function executeQuery(string $sql): void + { + global $wpdb; + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query($sql); + } + protected function getTableName(): string { global $wpdb; diff --git a/src/WPQueue.php b/src/WPQueue.php index 9741853..7715777 100644 --- a/src/WPQueue.php +++ b/src/WPQueue.php @@ -12,6 +12,10 @@ use WPQueue\Runtime\RuntimeMode; use WPQueue\Storage\LogStorage; +if (! defined('ABSPATH')) { + exit; +} + /** * Main facade for WP Queue. * @@ -191,7 +195,8 @@ public static function uninstall(): void global $wpdb; $table = $wpdb->prefix.'queue_logs'; - $wpdb->query("DROP TABLE IF EXISTS {$table}"); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange + $wpdb->query($wpdb->prepare('DROP TABLE IF EXISTS %i', $table)); } protected static function createLogsTable(): void @@ -216,6 +221,7 @@ protected static function createLogsTable(): void KEY created_at (created_at) ) {$charsetCollate};"; + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query($sql); } diff --git a/src/Worker.php b/src/Worker.php index 9901f72..0d79e1a 100644 --- a/src/Worker.php +++ b/src/Worker.php @@ -12,6 +12,10 @@ use WPQueue\Events\JobRetrying; use WPQueue\Storage\LogStorage; +if (! defined('ABSPATH')) { + exit; +} + class Worker { protected int $startTime; diff --git a/wp-queue.php b/wp-queue.php index 9a67827..2db6490 100644 --- a/wp-queue.php +++ b/wp-queue.php @@ -3,17 +3,17 @@ declare(strict_types=1); /** - * Plugin Name: WP Queue + * Plugin Name: Queue Manager * Plugin URI: https://github.com/rwsite/wp-queue * Description: Background job processing and WP-Cron management for WordPress. Schedule tasks, manage queues, and monitor cron events. - * Version: 1.2.0 + * Version: 1.2.1 * Author: Aleksei Tikhomirov * Author URI: https://rwsite.ru * License: GPL-2.0-or-later * Text Domain: wp-queue * Domain Path: /languages/ * Requires PHP: 8.3 - * Requires at least: 6.0 + * Requires at least: 6.2 */ if (! defined('ABSPATH')) { exit; @@ -26,7 +26,7 @@ return; } -define('WP_QUEUE_VERSION', '1.2.0'); +define('WP_QUEUE_VERSION', '1.2.1'); define('WP_QUEUE_FILE', __FILE__); define('WP_QUEUE_PATH', plugin_dir_path(__FILE__)); define('WP_QUEUE_URL', plugin_dir_url(__FILE__));