From 53cd6e807d1387556ab795a43c8f0e04cdb39ea9 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 00:49:18 +0900 Subject: [PATCH 01/15] Implement scheduled tasks --- common/framework/Queue.php | 380 ++++++++++++++------ common/framework/drivers/QueueInterface.php | 4 +- common/framework/drivers/queue/db.php | 133 ++++++- common/framework/drivers/queue/dummy.php | 4 +- common/framework/drivers/queue/redis.php | 4 +- common/scripts/cron.php | 4 +- modules/module/schemas/task_schedule.xml | 12 + 7 files changed, 419 insertions(+), 122 deletions(-) create mode 100644 modules/module/schemas/task_schedule.xml diff --git a/common/framework/Queue.php b/common/framework/Queue.php index 56410e13f..98aa15ce7 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -25,7 +25,7 @@ class Queue } /** - * Get the default driver. + * Get a Queue driver instance. * * @param string $name * @return ?Drivers\QueueInterface @@ -49,6 +49,16 @@ class Queue } } + /** + * Get the DB driver instance, for managing scheduled tasks. + * + * @return Drivers\Queue\DB + */ + public static function getDbDriver(): Drivers\Queue\DB + { + return self::getDriver('db'); + } + /** * Get the list of supported Queue drivers. * @@ -86,7 +96,9 @@ class Queue } /** - * Add a task. + * Add a task to the queue. + * + * The queued task will be executed as soon as possible. * * The handler can be in one of the following formats: * - Global function, e.g. myHandler @@ -128,29 +140,58 @@ class Queue } /** - * Get the first task to execute immediately. + * Add a task to be executed at a specific time. * - * If no tasks are pending, this method will return null. - * Detailed scheduling of tasks will be handled by each driver. + * The queued task will be executed once at the configured time. + * The rest is identical to addTask(). * - * @param int $blocking - * @return ?object + * @param int $time + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int */ - public static function getTask(int $blocking = 0): ?object + public static function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int { - $driver_name = config('queue.driver'); - if (!$driver_name) + if (!config('queue.enabled')) { throw new Exceptions\FeatureDisabled('Queue not configured'); } - $driver = self::getDriver($driver_name); - if (!$driver) + // This feature always uses the DB driver. + $driver = self::getDbDriver(); + return $driver->addTaskAt($time, $handler, $args, $options); + } + + /** + * Add a task to be executed at an interval. + * + * The queued task will be executed repeatedly at the scheduled interval. + * The synax for specifying the interval is the same as crontab. + * The rest is identical to addTask(). + * + * @param string $interval + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int + */ + public static function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int + { + if (!config('queue.enabled')) { throw new Exceptions\FeatureDisabled('Queue not configured'); } - return $driver->getTask($blocking); + // Validate the interval syntax. + if (!self::checkIntervalSyntax($interval)) + { + throw new Exceptions\InvalidRequest('Invalid interval syntax: ' . $interval); + } + + // This feature always uses the DB driver. + $driver = self::getDbDriver(); + return $driver->addTaskAtInterval($interval, $handler, $args, $options); } /** @@ -165,7 +206,80 @@ class Queue } /** - * Process the queue. + * Check the interval syntax. + * + * This method returns true if the interval string is well-formed, + * and false otherwise. However, it does not check that all the numbers + * are in the correct range (e.g. 0-59 for minutes). + * + * @param string $interval + * @return bool + */ + public static function checkIntervalSyntax(string $interval): bool + { + $parts = preg_split('/\s+/', $interval); + if (!$parts || count($parts) !== 5) + { + return false; + } + foreach ($parts as $part) + { + if (!preg_match('!^(?:\\*(?:/\d+)?|\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*)$!', $part)) + { + return false; + } + } + return true; + } + + /** + * Parse an interval string check it against a timestamp. + * + * This method returns true if the interval covers the given timestamp, + * and false otherwise. + * + * @param string $interval + * @param ?int $time + * @return bool + */ + public static function parseInterval(string $interval, ?int $time): bool + { + $parts = preg_split('/\s+/', $interval); + if (!$parts || count($parts) !== 5) + { + return false; + } + + $current_time = explode(' ', ltrim(date('i G j n N', $time ?? time()), '0')); + foreach ($parts as $i => $part) + { + $subparts = explode(',', $part); + foreach ($subparts as $subpart) + { + if ($subpart === '*' || $subpart === $current_time[$i]) + { + continue 2; + } + if ($subpart === '7' && $i === 4 && $current_time[$i] === '0') + { + continue 2; + } + if (preg_match('!^\\*/(\d+)?$!', $subpart, $matches) && ($div = intval($matches[1])) && (intval($current_time[$i]) % $div === 0)) + { + continue 2; + } + if (preg_match('!^(\d+)-(\d+)$!', $subpart, $matches) && $current_time[$i] >= intval($matches[1]) && $current_time[$i] <= intval($matches[2])) + { + continue 2; + } + } + return false; + } + return true; + } + + /** + * Process queued and scheduled tasks. * * This will usually be called by a separate script, run every minute * through an external scheduler such as crontab or systemd. @@ -173,115 +287,54 @@ class Queue * If you are on a shared hosting service, you may also call a URL * using a "web cron" service provider. * + * @param int $index + * @param int $count * @param int $timeout * @return void */ - public static function process(int $timeout): void + public static function process(int $index, int $count, int $timeout): void { // This part will run in a loop until timeout. $process_start_time = microtime(true); + + // Get default driver instance. + $driver_name = config('queue.driver'); + $driver = self::getDriver($driver_name); + if (!$driver_name || !$driver) + { + throw new Exceptions\FeatureDisabled('Queue not configured'); + } + + // Process scheduled tasks. + if ($index === 0) + { + $db_driver = self::getDbDriver(); + $tasks = $db_driver->getScheduledTasks('once'); + foreach ($tasks as $task) + { + self::_executeTask($task); + } + } + if ($index === 1 || $count < 2) + { + $db_driver = self::getDbDriver(); + $tasks = $db_driver->getScheduledTasks('interval'); + foreach ($tasks as $task) + { + $db_driver->updateLastRunTimestamp($task->id); + self::_executeTask($task); + } + } + + // Process queued tasks. while (true) { - // Get a task from the driver. + // Get a task from the driver, with a 1 second delay at maximum. $loop_start_time = microtime(true); - $task = self::getTask(1); - - // Wait 1 second and loop back. + $task = $driver->getNextTask(1); if ($task) { - // Find the handler for the task. - $task->handler = trim($task->handler, '\\()'); - $handler = null; - try - { - if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches)) - { - $class_name = '\\' . $matches[1]; - $method_name = $matches[2]; - if (class_exists($class_name) && method_exists($class_name, $method_name)) - { - $handler = [$class_name, $method_name]; - } - else - { - error_log('RxQueue: task handler not found: ' . $task->handler); - } - } - elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches)) - { - $class_name = '\\' . $matches[1]; - $initializer_name = $matches[2]; - $method_name = $matches[3]; - if (class_exists($class_name) && method_exists($class_name, $initializer_name)) - { - $obj = $class_name::$initializer_name(); - if (method_exists($obj, $method_name)) - { - $handler = [$obj, $method_name]; - } - else - { - error_log('RxQueue: task handler not found: ' . $task->handler); - } - } - else - { - error_log('RxQueue: task handler not found: ' . $task->handler); - } - } - elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches)) - { - $class_name = '\\' . $matches[1]; - $method_name = $matches[2]; - if (class_exists($class_name) && method_exists($class_name, $method_name)) - { - $obj = new $class_name(); - $handler = [$obj, $method_name]; - } - else - { - error_log('RxQueue: task handler not found: ' . $task->handler); - } - } - else - { - if (function_exists('\\' . $task->handler)) - { - $handler = '\\' . $task->handler; - } - else - { - error_log('RxQueue: task handler not found: ' . $task->handler); - } - } - } - catch (\Throwable $th) - { - error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [ - $task->handler, - get_class($th), - $th->getFile(), - $th->getLine(), - ])); - } - - // Call the handler. - try - { - if ($handler) - { - call_user_func($handler, $task->args, $task->options); - } - } - catch (\Throwable $th) - { - error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [ - $task->handler, - get_class($th), - $th->getFile(), - $th->getLine(), - ])); - } + self::_executeTask($task); } // If the timeout is imminent, break the loop. @@ -299,4 +352,107 @@ class Queue } } } + + /** + * Execute a task. + * + * @param object $task + * @return void + */ + protected static function _executeTask(object $task): void + { + // Find the handler for the task. + $task->handler = trim($task->handler, '\\()'); + $handler = null; + try + { + if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches)) + { + $class_name = '\\' . $matches[1]; + $method_name = $matches[2]; + if (class_exists($class_name) && method_exists($class_name, $method_name)) + { + $handler = [$class_name, $method_name]; + } + else + { + error_log('RxQueue: task handler not found: ' . $task->handler); + } + } + elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches)) + { + $class_name = '\\' . $matches[1]; + $initializer_name = $matches[2]; + $method_name = $matches[3]; + if (class_exists($class_name) && method_exists($class_name, $initializer_name)) + { + $obj = $class_name::$initializer_name(); + if (method_exists($obj, $method_name)) + { + $handler = [$obj, $method_name]; + } + else + { + error_log('RxQueue: task handler not found: ' . $task->handler); + } + } + else + { + error_log('RxQueue: task handler not found: ' . $task->handler); + } + } + elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches)) + { + $class_name = '\\' . $matches[1]; + $method_name = $matches[2]; + if (class_exists($class_name) && method_exists($class_name, $method_name)) + { + $obj = new $class_name(); + $handler = [$obj, $method_name]; + } + else + { + error_log('RxQueue: task handler not found: ' . $task->handler); + } + } + else + { + if (function_exists('\\' . $task->handler)) + { + $handler = '\\' . $task->handler; + } + else + { + error_log('RxQueue: task handler not found: ' . $task->handler); + } + } + } + catch (\Throwable $th) + { + error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [ + $task->handler, + get_class($th), + $th->getFile(), + $th->getLine(), + ])); + } + + // Call the handler. + try + { + if ($handler) + { + call_user_func($handler, $task->args, $task->options); + } + } + catch (\Throwable $th) + { + error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [ + $task->handler, + get_class($th), + $th->getFile(), + $th->getLine(), + ])); + } + } } diff --git a/common/framework/drivers/QueueInterface.php b/common/framework/drivers/QueueInterface.php index 71d71a452..ebcbafacb 100644 --- a/common/framework/drivers/QueueInterface.php +++ b/common/framework/drivers/QueueInterface.php @@ -62,10 +62,10 @@ interface QueueInterface public function addTask(string $handler, ?object $args = null, ?object $options = null): int; /** - * Get the first task. + * Get the next task from the queue. * * @param int $blocking * @return ?object */ - public function getTask(int $blocking = 0): ?object; + public function getNextTask(int $blocking = 0): ?object; } diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index a55bd71ad..43545523a 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -4,6 +4,7 @@ namespace Rhymix\Framework\Drivers\Queue; use Rhymix\Framework\DB as RFDB; use Rhymix\Framework\Drivers\QueueInterface; +use Rhymix\Framework\Queue; /** * The DB queue driver. @@ -99,12 +100,68 @@ class DB implements QueueInterface } /** - * Get the first task. + * Add a task to be executed at a specific time. + * + * @param int $time + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int + */ + public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int + { + $oDB = RFDB::getInstance(); + $stmt = $oDB->prepare(trim(<<execute([ + 'once', + date('Y-m-d H:i:s', $time), + $handler, + serialize($args), + serialize($options), + date('Y-m-d H:i:s'), + ]); + return $result ? $oDB->getInsertID() : 0; + } + + /** + * Add a task to be executed at an interval. + * + * @param string $interval + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int + */ + public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int + { + $oDB = RFDB::getInstance(); + $stmt = $oDB->prepare(trim(<<execute([ + 'interval', + $interval, + $handler, + serialize($args), + serialize($options), + date('Y-m-d H:i:s'), + ]); + return $result ? $oDB->getInsertID() : 0; + } + + /** + * Get the next task from the queue. * * @param int $blocking * @return ?object */ - public function getTask(int $blocking = 0): ?object + public function getNextTask(int $blocking = 0): ?object { $oDB = RFDB::getInstance(); $oDB->beginTransaction(); @@ -128,4 +185,76 @@ class DB implements QueueInterface return null; } } + + /** + * Get scheduled tasks. + * + * @param string $type + * @return array + */ + public function getScheduledTasks(string $type): array + { + $oDB = RFDB::getInstance(); + $tasks = []; + $ids = []; + + // Get tasks to be executed once at the current time. + if ($type === 'once') + { + $oDB->beginTransaction(); + $stmt = $oDB->query("SELECT * FROM task_schedule WHERE `type` = 'once' AND `first_run` <= ? ORDER BY id FOR UPDATE", [$timestamp]); + while ($task = $stmt->fetchObject()) + { + $task->args = unserialize($task->args); + $task->options = unserialize($task->options); + $tasks[] = $task; + $ids[] = $task->id; + } + if (count($ids)) + { + $stmt = $oDB->prepare('DELETE FROM task_schedule WHERE id IN (' . implode(', ', array_fill(0, count($ids), '?')) . ')'); + $stmt->execute($ids); + } + $oDB->commit(); + } + + // Get tasks to be executed at an interval. + if ($type === 'interval') + { + $stmt = $oDB->query("SELECT id, `interval` FROM task_schedule WHERE `type` = 'interval' ORDER BY id"); + while ($task = $stmt->fetchObject()) + { + if (Queue::parseInterval($task->interval, \RX_TIME)) + { + $ids[] = $task->id; + } + } + if (count($ids)) + { + $stmt = $oDB->prepare('SELECT * FROM task_schedule WHERE id IN (' . implode(', ', array_fill(0, count($ids), '?')) . ')'); + $stmt->execute($ids); + while ($task = $stmt->fetchObject()) + { + $task->args = unserialize($task->args); + $task->options = unserialize($task->options); + $tasks[] = $task; + } + } + } + + return $tasks; + } + + /** + * Update the last executed timestamp of a scheduled task. + * + * @param int $id + * @return void + */ + public function updateLastRunTimestamp(int $id): void + { + $oDB = RFDB::getInstance(); + $stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE id = ?'); + $stmt->execute([date('Y-m-d H:i:s'), $id]); + } } diff --git a/common/framework/drivers/queue/dummy.php b/common/framework/drivers/queue/dummy.php index 0abb74792..76d39dc63 100644 --- a/common/framework/drivers/queue/dummy.php +++ b/common/framework/drivers/queue/dummy.php @@ -105,12 +105,12 @@ class Dummy implements QueueInterface } /** - * Get the first task. + * Get the next task from the queue. * * @param int $blocking * @return ?object */ - public function getTask(int $blocking = 0): ?object + public function getNextTask(int $blocking = 0): ?object { $result = $this->_dummy_queue; $this->_dummy_queue = null; diff --git a/common/framework/drivers/queue/redis.php b/common/framework/drivers/queue/redis.php index 58c122184..cd4b8256e 100644 --- a/common/framework/drivers/queue/redis.php +++ b/common/framework/drivers/queue/redis.php @@ -155,12 +155,12 @@ class Redis implements QueueInterface } /** - * Get the first task. + * Get the next task from the queue. * * @param int $blocking * @return ?object */ - public function getTask(int $blocking = 0): ?object + public function getNextTask(int $blocking = 0): ?object { if ($this->_conn) { diff --git a/common/scripts/cron.php b/common/scripts/cron.php index 19ea7b1c9..971f889a4 100644 --- a/common/scripts/cron.php +++ b/common/scripts/cron.php @@ -73,7 +73,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && } elseif ($pid == 0) { - Rhymix\Framework\Queue::process($timeout); + Rhymix\Framework\Queue::process($i, $process_count, $timeout); exit; } else @@ -96,7 +96,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && } else { - Rhymix\Framework\Queue::process($timeout); + Rhymix\Framework\Queue::process(0, 1, $timeout); } // If called over the network, display a simple OK message to indicate success. diff --git a/modules/module/schemas/task_schedule.xml b/modules/module/schemas/task_schedule.xml new file mode 100644 index 000000000..35e29db0b --- /dev/null +++ b/modules/module/schemas/task_schedule.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + +
From f87429687ad594c0041f0071cf317aa0be4ef066 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 01:49:38 +0900 Subject: [PATCH 02/15] Assign task_srl to scheduled tasks --- common/framework/Queue.php | 2 +- common/framework/drivers/queue/db.php | 47 +++++++++++++----------- modules/module/schemas/task_schedule.xml | 8 ++-- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/common/framework/Queue.php b/common/framework/Queue.php index 98aa15ce7..dd32e9fe4 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -321,7 +321,7 @@ class Queue $tasks = $db_driver->getScheduledTasks('interval'); foreach ($tasks as $task) { - $db_driver->updateLastRunTimestamp($task->id); + $db_driver->updateLastRunTimestamp($task->task_srl); self::_executeTask($task); } } diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index 43545523a..85eb22f58 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -111,12 +111,14 @@ class DB implements QueueInterface public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int { $oDB = RFDB::getInstance(); + $task_srl = getNextSequence(); $stmt = $oDB->prepare(trim(<<execute([ + $task_srl, 'once', date('Y-m-d H:i:s', $time), $handler, @@ -124,7 +126,7 @@ class DB implements QueueInterface serialize($options), date('Y-m-d H:i:s'), ]); - return $result ? $oDB->getInsertID() : 0; + return $result ? $task_srl : 0; } /** @@ -139,12 +141,14 @@ class DB implements QueueInterface public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int { $oDB = RFDB::getInstance(); + $task_srl = getNextSequence(); $stmt = $oDB->prepare(trim(<<execute([ + $task_srl, 'interval', $interval, $handler, @@ -152,7 +156,7 @@ class DB implements QueueInterface serialize($options), date('Y-m-d H:i:s'), ]); - return $result ? $oDB->getInsertID() : 0; + return $result ? $task_srl : 0; } /** @@ -196,24 +200,25 @@ class DB implements QueueInterface { $oDB = RFDB::getInstance(); $tasks = []; - $ids = []; + $task_srls = []; // Get tasks to be executed once at the current time. if ($type === 'once') { $oDB->beginTransaction(); - $stmt = $oDB->query("SELECT * FROM task_schedule WHERE `type` = 'once' AND `first_run` <= ? ORDER BY id FOR UPDATE", [$timestamp]); + $timestamp = date('Y-m-d H:i:s'); + $stmt = $oDB->query("SELECT * FROM task_schedule WHERE task_type = 'once' AND first_run <= ? ORDER BY first_run FOR UPDATE", [$timestamp]); while ($task = $stmt->fetchObject()) { $task->args = unserialize($task->args); $task->options = unserialize($task->options); $tasks[] = $task; - $ids[] = $task->id; + $task_srls[] = $task->task_srl; } - if (count($ids)) + if (count($task_srls)) { - $stmt = $oDB->prepare('DELETE FROM task_schedule WHERE id IN (' . implode(', ', array_fill(0, count($ids), '?')) . ')'); - $stmt->execute($ids); + $stmt = $oDB->prepare('DELETE FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')'); + $stmt->execute($task_srls); } $oDB->commit(); } @@ -221,18 +226,18 @@ class DB implements QueueInterface // Get tasks to be executed at an interval. if ($type === 'interval') { - $stmt = $oDB->query("SELECT id, `interval` FROM task_schedule WHERE `type` = 'interval' ORDER BY id"); + $stmt = $oDB->query("SELECT task_srl, run_interval FROM task_schedule WHERE task_type = 'interval' ORDER BY task_srl"); while ($task = $stmt->fetchObject()) { - if (Queue::parseInterval($task->interval, \RX_TIME)) + if (Queue::parseInterval($task->run_interval, time())) { - $ids[] = $task->id; + $task_srls[] = $task->task_srl; } } - if (count($ids)) + if (count($task_srls)) { - $stmt = $oDB->prepare('SELECT * FROM task_schedule WHERE id IN (' . implode(', ', array_fill(0, count($ids), '?')) . ')'); - $stmt->execute($ids); + $stmt = $oDB->prepare('SELECT * FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')'); + $stmt->execute($task_srls); while ($task = $stmt->fetchObject()) { $task->args = unserialize($task->args); @@ -251,10 +256,10 @@ class DB implements QueueInterface * @param int $id * @return void */ - public function updateLastRunTimestamp(int $id): void + public function updateLastRunTimestamp(int $task_srl): void { $oDB = RFDB::getInstance(); - $stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE id = ?'); - $stmt->execute([date('Y-m-d H:i:s'), $id]); + $stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE task_srl = ?'); + $stmt->execute([date('Y-m-d H:i:s'), $task_srl]); } } diff --git a/modules/module/schemas/task_schedule.xml b/modules/module/schemas/task_schedule.xml index 35e29db0b..cfb694756 100644 --- a/modules/module/schemas/task_schedule.xml +++ b/modules/module/schemas/task_schedule.xml @@ -1,10 +1,10 @@ - - - + + + + - From ffeb9133ab8f6c73b7c22d9cddaee4dc5a72f4b7 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 01:55:55 +0900 Subject: [PATCH 03/15] Update first_run timestamp of tasks run at an interval --- common/framework/Queue.php | 2 +- common/framework/drivers/queue/db.php | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/common/framework/Queue.php b/common/framework/Queue.php index dd32e9fe4..a862e05c5 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -321,7 +321,7 @@ class Queue $tasks = $db_driver->getScheduledTasks('interval'); foreach ($tasks as $task) { - $db_driver->updateLastRunTimestamp($task->task_srl); + $db_driver->updateLastRunTimestamp($task); self::_executeTask($task); } } diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index 85eb22f58..43f68be6b 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -253,13 +253,21 @@ class DB implements QueueInterface /** * Update the last executed timestamp of a scheduled task. * - * @param int $id + * @param object $task * @return void */ - public function updateLastRunTimestamp(int $task_srl): void + public function updateLastRunTimestamp(object $task): void { $oDB = RFDB::getInstance(); - $stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE task_srl = ?'); - $stmt->execute([date('Y-m-d H:i:s'), $task_srl]); + if ($task->first_run) + { + $stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE task_srl = ?'); + $stmt->execute([date('Y-m-d H:i:s'), $task->task_srl]); + } + else + { + $stmt = $oDB->prepare('UPDATE task_schedule SET first_run = ?, last_run = ?, run_count = run_count + 1 WHERE task_srl = ?'); + $stmt->execute([date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), $task->task_srl]); + } } } From b21b700570629ec953e16383b6b8fb672cef9d61 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 01:56:21 +0900 Subject: [PATCH 04/15] Add placeholder methods to dummy queue driver --- common/framework/drivers/queue/dummy.php | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/common/framework/drivers/queue/dummy.php b/common/framework/drivers/queue/dummy.php index 76d39dc63..30512f1a7 100644 --- a/common/framework/drivers/queue/dummy.php +++ b/common/framework/drivers/queue/dummy.php @@ -104,6 +104,34 @@ class Dummy implements QueueInterface return 0; } + /** + * Add a task to be executed at a specific time. + * + * @param int $time + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int + */ + public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int + { + return 1; + } + + /** + * Add a task to be executed at an interval. + * + * @param string $interval + * @param string $handler + * @param ?object $args + * @param ?object $options + * @return int + */ + public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int + { + return 2; + } + /** * Get the next task from the queue. * @@ -116,4 +144,26 @@ class Dummy implements QueueInterface $this->_dummy_queue = null; return $result; } + + /** + * Get scheduled tasks. + * + * @param string $type + * @return array + */ + public function getScheduledTasks(string $type): array + { + return []; + } + + /** + * Update the last executed timestamp of a scheduled task. + * + * @param object $task + * @return void + */ + public function updateLastRunTimestamp(object $task): void + { + + } } From a2cdd3d897d0b0dab6b958b1a022dc85fbda1f90 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 01:58:05 +0900 Subject: [PATCH 05/15] Update unit tests for dummy queue driver --- tests/unit/framework/QueueTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/framework/QueueTest.php b/tests/unit/framework/QueueTest.php index 5019a7da3..5b07f8b78 100644 --- a/tests/unit/framework/QueueTest.php +++ b/tests/unit/framework/QueueTest.php @@ -12,12 +12,12 @@ class QueueTest extends \Codeception\Test\Unit Rhymix\Framework\Queue::addTask($handler, $args, $options); - $output = Rhymix\Framework\Queue::getTask(); + $output = Rhymix\Framework\Queue::getDriver('dummy')->getNextTask(); $this->assertEquals('myfunc', $output->handler); $this->assertEquals('bar', $output->args->foo); $this->assertEquals('val', $output->options->key); - $output = Rhymix\Framework\Queue::getTask(); + $output = Rhymix\Framework\Queue::getDriver('dummy')->getNextTask(); $this->assertNull($output); } } From caf882fed078b22679845c6d56891151385d66ab Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 11:18:50 +0900 Subject: [PATCH 06/15] Delete unnecessary methods from dummy queue driver --- common/framework/drivers/queue/dummy.php | 50 ------------------------ 1 file changed, 50 deletions(-) diff --git a/common/framework/drivers/queue/dummy.php b/common/framework/drivers/queue/dummy.php index 30512f1a7..76d39dc63 100644 --- a/common/framework/drivers/queue/dummy.php +++ b/common/framework/drivers/queue/dummy.php @@ -104,34 +104,6 @@ class Dummy implements QueueInterface return 0; } - /** - * Add a task to be executed at a specific time. - * - * @param int $time - * @param string $handler - * @param ?object $args - * @param ?object $options - * @return int - */ - public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int - { - return 1; - } - - /** - * Add a task to be executed at an interval. - * - * @param string $interval - * @param string $handler - * @param ?object $args - * @param ?object $options - * @return int - */ - public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int - { - return 2; - } - /** * Get the next task from the queue. * @@ -144,26 +116,4 @@ class Dummy implements QueueInterface $this->_dummy_queue = null; return $result; } - - /** - * Get scheduled tasks. - * - * @param string $type - * @return array - */ - public function getScheduledTasks(string $type): array - { - return []; - } - - /** - * Update the last executed timestamp of a scheduled task. - * - * @param object $task - * @return void - */ - public function updateLastRunTimestamp(object $task): void - { - - } } From 5ff2f15485134a2621b1002bf0360b3f689c3732 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 11:25:29 +0900 Subject: [PATCH 07/15] Add methods to manage scheduled tasks --- common/framework/Queue.php | 24 +++++++++++++++++++++++ common/framework/drivers/queue/db.php | 28 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/common/framework/Queue.php b/common/framework/Queue.php index a862e05c5..f9e8735df 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -194,6 +194,30 @@ class Queue return $driver->addTaskAtInterval($interval, $handler, $args, $options); } + /** + * Get information about a scheduled task if it exists. + * + * @param int $task_srl + * @return ?object + */ + public static function getScheduledTask(int $task_srl): ?object + { + $driver = self::getDbDriver(); + return $driver->getScheduledTask($task_srl); + } + + /** + * Cancel a scheduled task. + * + * @param int $task_srl + * @return bool + */ + public static function cancelScheduledTask(int $task_srl): bool + { + $driver = self::getDbDriver(); + return $driver->cancelScheduledTask($task_srl); + } + /** * Check the process key. * diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index 43f68be6b..6d05aa365 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -190,6 +190,21 @@ class DB implements QueueInterface } } + /** + * Get a scheduled task by its task_srl. + * + * @param int $task_srl + * @return ?object + */ + public function getScheduledTask(int $task_srl): ?object + { + $oDB = RFDB::getInstance(); + $stmt = $oDB->query('SELECT * FROM task_schedule WHERE task_srl = ?', [$task_srl]); + $task = $stmt->fetchObject(); + $stmt->closeCursor(); + return $task ?: null; + } + /** * Get scheduled tasks. * @@ -270,4 +285,17 @@ class DB implements QueueInterface $stmt->execute([date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), $task->task_srl]); } } + + /** + * Cancel a scheduled task. + * + * @param int $task_srl + * @return bool + */ + public function cancelScheduledTask(int $task_srl): bool + { + $oDB = RFDB::getInstance(); + $stmt = $oDB->query('DELETE FROM task_schedule WHERE task_srl = ?', [$task_srl]); + return ($stmt && $stmt->rowCount()) ? true : false; + } } From 924c98bf4e3de397fee0e42e61fb5a24d0104295 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 21:06:38 +0900 Subject: [PATCH 08/15] Decode args and options in getScheduledTask() --- common/framework/drivers/queue/db.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index 6d05aa365..68632704d 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -202,7 +202,17 @@ class DB implements QueueInterface $stmt = $oDB->query('SELECT * FROM task_schedule WHERE task_srl = ?', [$task_srl]); $task = $stmt->fetchObject(); $stmt->closeCursor(); - return $task ?: null; + + if ($task) + { + $task->args = unserialize($task->args); + $task->options = unserialize($task->options); + return $task; + } + else + { + return null; + } } /** From 90fd5502cea660205007b3584e7991bb83f6fd68 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 21:28:31 +0900 Subject: [PATCH 09/15] Fix parsing of intervals containing day of week and/or leading zero --- common/framework/Queue.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/framework/Queue.php b/common/framework/Queue.php index f9e8735df..ec58a6aeb 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -274,25 +274,25 @@ class Queue return false; } - $current_time = explode(' ', ltrim(date('i G j n N', $time ?? time()), '0')); + $current_time = explode(' ', date('i G j n w', $time ?? time())); foreach ($parts as $i => $part) { $subparts = explode(',', $part); foreach ($subparts as $subpart) { - if ($subpart === '*' || $subpart === $current_time[$i]) + if ($subpart === '*' || ltrim($subpart, '0') === ltrim($current_time[$i], '0')) { continue 2; } - if ($subpart === '7' && $i === 4 && $current_time[$i] === '0') + if ($subpart === '7' && $i === 4 && intval($current_time[$i], 10) === 0) { continue 2; } - if (preg_match('!^\\*/(\d+)?$!', $subpart, $matches) && ($div = intval($matches[1])) && (intval($current_time[$i]) % $div === 0)) + if (preg_match('!^\\*/(\d+)?$!', $subpart, $matches) && ($div = intval($matches[1], 10)) && (intval($current_time[$i], 10) % $div === 0)) { continue 2; } - if (preg_match('!^(\d+)-(\d+)$!', $subpart, $matches) && $current_time[$i] >= intval($matches[1]) && $current_time[$i] <= intval($matches[2])) + if (preg_match('!^(\d+)-(\d+)$!', $subpart, $matches) && intval($current_time[$i], 10) >= intval($matches[1], 10) && intval($current_time[$i], 10) <= intval($matches[2], 10)) { continue 2; } From 50974a325e8f6a1f59dcbe558095abe7584a4155 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 21:28:43 +0900 Subject: [PATCH 10/15] Add unit tests for scheduled tasks --- tests/unit/framework/QueueTest.php | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/unit/framework/QueueTest.php b/tests/unit/framework/QueueTest.php index 5b07f8b78..72dfd25ab 100644 --- a/tests/unit/framework/QueueTest.php +++ b/tests/unit/framework/QueueTest.php @@ -20,4 +20,85 @@ class QueueTest extends \Codeception\Test\Unit $output = Rhymix\Framework\Queue::getDriver('dummy')->getNextTask(); $this->assertNull($output); } + + public function testScheduledTaskAt() + { + $timestamp = time() + 43200; + $handler = 'MyClass::myFunc'; + $args = (object)['foo' => 'bar']; + $options = null; + + $task_srl = Rhymix\Framework\Queue::addTaskAt($timestamp, $handler, $args, $options); + $this->assertGreaterThan(0, $task_srl); + + $output = Rhymix\Framework\Queue::getScheduledTask($task_srl); + $this->assertTrue(is_object($output)); + $this->assertEquals('once', $output->task_type); + $this->assertEquals(date('Y-m-d H:i:s', $timestamp), $output->first_run); + $this->assertEquals('MyClass::myFunc', $output->handler); + $this->assertEquals('bar', $output->args->foo); + $this->assertNull($output->options); + + $output = Rhymix\Framework\Queue::cancelScheduledTask($task_srl); + $this->assertTrue($output); + } + + public function testScheduledTaskAtInterval() + { + $interval = '30 9 1-15 */2 *'; + $handler = 'MyClass::getInstance()->myMethod'; + $args = (object)['foo' => 'bar']; + $options = null; + + $task_srl = Rhymix\Framework\Queue::addTaskAtInterval($interval, $handler, $args, $options); + $this->assertGreaterThan(0, $task_srl); + + $output = Rhymix\Framework\Queue::getScheduledTask($task_srl); + $this->assertTrue(is_object($output)); + $this->assertEquals('interval', $output->task_type); + $this->assertEquals($interval, $output->run_interval); + $this->assertEquals('MyClass::getInstance()->myMethod', $output->handler); + $this->assertEquals('bar', $output->args->foo); + $this->assertNull($output->options); + + $output = Rhymix\Framework\Queue::cancelScheduledTask($task_srl); + $this->assertTrue($output); + } + + public function testCheckIntervalSyntax() + { + $this->assertTrue(Rhymix\Framework\Queue::checkIntervalSyntax('* * * * *')); + $this->assertTrue(Rhymix\Framework\Queue::checkIntervalSyntax('*/2 15 * * 0,3')); + $this->assertTrue(Rhymix\Framework\Queue::checkIntervalSyntax('10-19,40-49 * 1-12 * *')); + $this->assertTrue(Rhymix\Framework\Queue::checkIntervalSyntax('*/10 */4 * * *')); + $this->assertFalse(Rhymix\Framework\Queue::checkIntervalSyntax('* * * *')); + $this->assertFalse(Rhymix\Framework\Queue::checkIntervalSyntax('1 2 3 4 5,6 */7')); + $this->assertFalse(Rhymix\Framework\Queue::checkIntervalSyntax('@hourly')); + $this->assertFalse(Rhymix\Framework\Queue::checkIntervalSyntax('5/* 12h * * *')); + } + + public function testParseInterval() + { + $interval = '* * * * *'; + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-01-01 00:00:00'))); + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-31 23:59:59'))); + + $interval = '*/2 15 * * 0,3'; + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 15:00:00'))); + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-04 15:30:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-02 15:00:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 03:00:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-04 15:31:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-11-30 15:44:00'))); + + $interval = '3-5,14-21,*/10 * * 12 *'; + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 12:05:00'))); + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 14:19:00'))); + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 16:30:00'))); + $this->assertTrue(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 18:00:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 09:01:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-12-01 11:13:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-01-02 06:20:00'))); + $this->assertFalse(Rhymix\Framework\Queue::parseInterval($interval, strtotime('2024-11-30 18:50:00'))); + } } From eebd9a00051a0df15e99329d3a6fbe4161fb2b46 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 21:31:31 +0900 Subject: [PATCH 11/15] Modify current queue config for unit testing --- tests/unit/framework/QueueTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/framework/QueueTest.php b/tests/unit/framework/QueueTest.php index 72dfd25ab..05ba3efc0 100644 --- a/tests/unit/framework/QueueTest.php +++ b/tests/unit/framework/QueueTest.php @@ -23,6 +23,9 @@ class QueueTest extends \Codeception\Test\Unit public function testScheduledTaskAt() { + config('queue.enabled', true); + config('queue.driver', 'dummy'); + $timestamp = time() + 43200; $handler = 'MyClass::myFunc'; $args = (object)['foo' => 'bar']; @@ -45,6 +48,9 @@ class QueueTest extends \Codeception\Test\Unit public function testScheduledTaskAtInterval() { + config('queue.enabled', true); + config('queue.driver', 'db'); + $interval = '30 9 1-15 */2 *'; $handler = 'MyClass::getInstance()->myMethod'; $args = (object)['foo' => 'bar']; From 746afdacb39cc619a54a3e70d020a1ae9dad647c Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 12 Dec 2024 21:37:53 +0900 Subject: [PATCH 12/15] Fix queue config interfering with unit tests --- tests/unit/framework/MailTest.php | 13 +++++++++++++ tests/unit/framework/QueueTest.php | 22 ++++++++++++++-------- tests/unit/framework/SMSTest.php | 13 +++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tests/unit/framework/MailTest.php b/tests/unit/framework/MailTest.php index dc529cda6..4764620fb 100644 --- a/tests/unit/framework/MailTest.php +++ b/tests/unit/framework/MailTest.php @@ -2,6 +2,19 @@ class MailTest extends \Codeception\Test\Unit { + protected $_prev_queue_config; + + public function _before() + { + $this->_prev_queue_config = config('queue'); + config('queue.enabled', false); + } + + public function _after() + { + config('queue', $this->_prev_queue_config); + } + public function testGetSetDefaultDriver() { $driver = Rhymix\Framework\Mail::getDefaultDriver(); diff --git a/tests/unit/framework/QueueTest.php b/tests/unit/framework/QueueTest.php index 05ba3efc0..412c06e71 100644 --- a/tests/unit/framework/QueueTest.php +++ b/tests/unit/framework/QueueTest.php @@ -2,10 +2,22 @@ class QueueTest extends \Codeception\Test\Unit { + protected $_prev_queue_config; + + public function _before() + { + $this->_prev_queue_config = config('queue'); + config('queue.enabled', true); + config('queue.driver', 'dummy'); + } + + public function _after() + { + config('queue', $this->_prev_queue_config); + } + public function testDummyQueue() { - config('queue.driver', 'dummy'); - $handler = 'myfunc'; $args = (object)['foo' => 'bar']; $options = (object)['key' => 'val']; @@ -23,9 +35,6 @@ class QueueTest extends \Codeception\Test\Unit public function testScheduledTaskAt() { - config('queue.enabled', true); - config('queue.driver', 'dummy'); - $timestamp = time() + 43200; $handler = 'MyClass::myFunc'; $args = (object)['foo' => 'bar']; @@ -48,9 +57,6 @@ class QueueTest extends \Codeception\Test\Unit public function testScheduledTaskAtInterval() { - config('queue.enabled', true); - config('queue.driver', 'db'); - $interval = '30 9 1-15 */2 *'; $handler = 'MyClass::getInstance()->myMethod'; $args = (object)['foo' => 'bar']; diff --git a/tests/unit/framework/SMSTest.php b/tests/unit/framework/SMSTest.php index df947740a..a60610f63 100644 --- a/tests/unit/framework/SMSTest.php +++ b/tests/unit/framework/SMSTest.php @@ -2,6 +2,19 @@ class SMSTest extends \Codeception\Test\Unit { + protected $_prev_queue_config; + + public function _before() + { + $this->_prev_queue_config = config('queue'); + config('queue.enabled', false); + } + + public function _after() + { + config('queue', $this->_prev_queue_config); + } + public function testGetSetDefaultDriver() { $driver = Rhymix\Framework\SMS::getDefaultDriver(); From 78229aa9447155dc28b46917ff9c177400d0cf69 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 14 Dec 2024 23:53:02 +0900 Subject: [PATCH 13/15] Fix exposed lang code in editor module group list #2447 --- modules/editor/editor.admin.view.php | 4 ++++ modules/editor/editor.view.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/modules/editor/editor.admin.view.php b/modules/editor/editor.admin.view.php index 3c070725b..7224658d4 100644 --- a/modules/editor/editor.admin.view.php +++ b/modules/editor/editor.admin.view.php @@ -111,6 +111,10 @@ class EditorAdminView extends Editor // Get a group list to set a group $group_list = MemberModel::getGroups(0); + foreach ($group_list ?: [] as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } Context::set('group_list', $group_list); // Get a mid list diff --git a/modules/editor/editor.view.php b/modules/editor/editor.view.php index 3284c1720..2acfb5220 100644 --- a/modules/editor/editor.view.php +++ b/modules/editor/editor.view.php @@ -165,6 +165,10 @@ class EditorView extends Editor // Get a group list $group_list = MemberModel::getGroups(); Context::set('group_list', $group_list); + foreach ($group_list ?: [] as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } //Security $security = new Security(); From 4d5fac5d776017c8c6f66872015089b283d11dcf Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sat, 14 Dec 2024 23:56:16 +0900 Subject: [PATCH 14/15] Fix exposed lang code in group names --- modules/module/tpl/include.module_grant_setup.html | 2 +- modules/module/tpl/module_grant_setup.html | 2 +- modules/module/tpl/module_grants.html | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/module/tpl/include.module_grant_setup.html b/modules/module/tpl/include.module_grant_setup.html index 67cc4048d..0cd3eb457 100644 --- a/modules/module/tpl/include.module_grant_setup.html +++ b/modules/module/tpl/include.module_grant_setup.html @@ -21,7 +21,7 @@ diff --git a/modules/module/tpl/module_grant_setup.html b/modules/module/tpl/module_grant_setup.html index 89db445e4..417bb8f4e 100644 --- a/modules/module/tpl/module_grant_setup.html +++ b/modules/module/tpl/module_grant_setup.html @@ -26,7 +26,7 @@ diff --git a/modules/module/tpl/module_grants.html b/modules/module/tpl/module_grants.html index 3fd88cc3d..8a148f2fd 100644 --- a/modules/module/tpl/module_grants.html +++ b/modules/module/tpl/module_grants.html @@ -65,7 +65,10 @@ From 8c5f96f8c5c337d7d544406046deb3fcf10dab07 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Sun, 15 Dec 2024 00:16:57 +0900 Subject: [PATCH 15/15] Fix incorrect Content-Type in JSON callback responses #2448 --- classes/display/DisplayHandler.class.php | 2 +- modules/menu/tpl/sitemap.html | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/classes/display/DisplayHandler.class.php b/classes/display/DisplayHandler.class.php index 341badb43..d9ed8241c 100644 --- a/classes/display/DisplayHandler.class.php +++ b/classes/display/DisplayHandler.class.php @@ -97,7 +97,7 @@ class DisplayHandler extends Handler } else { - if($responseMethod == 'JSON' || $responseMethod == 'JS_CALLBACK' || isset($_SERVER['HTTP_X_AJAX_COMPAT']) || isset($_POST['_rx_ajax_compat'])) + if($responseMethod == 'JSON' || isset($_SERVER['HTTP_X_AJAX_COMPAT']) || isset($_POST['_rx_ajax_compat'])) { self::_printJSONHeader(); } diff --git a/modules/menu/tpl/sitemap.html b/modules/menu/tpl/sitemap.html index 126795823..051feab82 100644 --- a/modules/menu/tpl/sitemap.html +++ b/modules/menu/tpl/sitemap.html @@ -350,7 +350,9 @@

{$lang->menu_img_btn}

{$lang->about_imgbtn}

-
+ + + @@ -368,7 +370,9 @@
-
+ + + @@ -386,7 +390,9 @@ -
+ + +