From 71e14594f56991e115b8555f8ba8271570d9ac4a Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Wed, 6 May 2026 12:39:08 +0530 Subject: [PATCH 01/11] =?UTF-8?q?Added:=20=C2=A0Added:=20Add=20CronTaskMan?= =?UTF-8?q?ager=20module=20in=20default=20QloApps=C2=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Name --- modules/qlocrontaskmanager/CHANGELOG.txt | 7 + modules/qlocrontaskmanager/LICENSE.md | 15 + modules/qlocrontaskmanager/README.md | 20 + .../classes/QctmCronTask.php | 125 ++++ .../classes/QctmCronTaskLog.php | 100 +++ .../classes/QctmCronTaskManagerDb.php | 103 +++ modules/qlocrontaskmanager/classes/index.php | 32 + modules/qlocrontaskmanager/composer.json | 7 + .../admin/AdminCronTaskManagerController.php | 273 ++++++++ .../controllers/admin/index.php | 32 + .../qlocrontaskmanager/controllers/index.php | 32 + modules/qlocrontaskmanager/cron.php | 41 ++ modules/qlocrontaskmanager/define.php | 27 + modules/qlocrontaskmanager/index.php | 32 + .../qlocrontaskmanager/qlocrontaskmanager.php | 392 ++++++++++++ modules/qlocrontaskmanager/upgrade/index.php | 32 + .../qlocrontaskmanager/vendor/autoload.php | 12 + .../vendor/composer/ClassLoader.php | 572 +++++++++++++++++ .../vendor/composer/InstalledVersions.php | 352 +++++++++++ .../vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 10 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 10 + .../vendor/composer/autoload_real.php | 38 ++ .../vendor/composer/autoload_static.php | 36 ++ .../vendor/composer/installed.json | 73 +++ .../vendor/composer/installed.php | 38 ++ .../vendor/composer/platform_check.php | 26 + .../cron-expression/CHANGELOG.md | 240 +++++++ .../dragonmantank/cron-expression/LICENSE | 19 + .../dragonmantank/cron-expression/README.md | 131 ++++ .../cron-expression/composer.json | 50 ++ .../src/Cron/AbstractField.php | 345 ++++++++++ .../src/Cron/CronExpression.php | 591 ++++++++++++++++++ .../src/Cron/DayOfMonthField.php | 164 +++++ .../src/Cron/DayOfWeekField.php | 194 ++++++ .../cron-expression/src/Cron/FieldFactory.php | 52 ++ .../src/Cron/FieldFactoryInterface.php | 8 + .../src/Cron/FieldInterface.php | 46 ++ .../cron-expression/src/Cron/HoursField.php | 221 +++++++ .../cron-expression/src/Cron/MinutesField.php | 97 +++ .../cron-expression/src/Cron/MonthField.php | 61 ++ .../views/css/admin/index.php | 32 + .../css/admin/qlo_cron_task_manager_admin.css | 33 + .../qlocrontaskmanager/views/css/index.php | 32 + modules/qlocrontaskmanager/views/index.php | 32 + .../views/js/admin/index.php | 32 + .../js/admin/qlo_cron_task_manager_admin.js | 116 ++++ modules/qlocrontaskmanager/views/js/index.php | 32 + .../views/templates/admin/configure.tpl | 60 ++ .../admin/cron_task_manager/index.php | 32 + .../cron_task_manager/qctm_badge_status.tpl | 31 + .../admin/cron_task_manager/qctm_view_log.tpl | 23 + .../views/templates/admin/index.php | 32 + .../views/templates/index.php | 32 + 55 files changed, 5205 insertions(+) create mode 100644 modules/qlocrontaskmanager/CHANGELOG.txt create mode 100644 modules/qlocrontaskmanager/LICENSE.md create mode 100644 modules/qlocrontaskmanager/README.md create mode 100644 modules/qlocrontaskmanager/classes/QctmCronTask.php create mode 100644 modules/qlocrontaskmanager/classes/QctmCronTaskLog.php create mode 100644 modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php create mode 100644 modules/qlocrontaskmanager/classes/index.php create mode 100644 modules/qlocrontaskmanager/composer.json create mode 100644 modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php create mode 100644 modules/qlocrontaskmanager/controllers/admin/index.php create mode 100644 modules/qlocrontaskmanager/controllers/index.php create mode 100644 modules/qlocrontaskmanager/cron.php create mode 100644 modules/qlocrontaskmanager/define.php create mode 100644 modules/qlocrontaskmanager/index.php create mode 100644 modules/qlocrontaskmanager/qlocrontaskmanager.php create mode 100644 modules/qlocrontaskmanager/upgrade/index.php create mode 100644 modules/qlocrontaskmanager/vendor/autoload.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/ClassLoader.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/LICENSE create mode 100644 modules/qlocrontaskmanager/vendor/composer/autoload_classmap.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/autoload_namespaces.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/autoload_real.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/autoload_static.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/installed.json create mode 100644 modules/qlocrontaskmanager/vendor/composer/installed.php create mode 100644 modules/qlocrontaskmanager/vendor/composer/platform_check.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/CHANGELOG.md create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/LICENSE create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/README.md create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/composer.json create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/AbstractField.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactory.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldInterface.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/HoursField.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MinutesField.php create mode 100644 modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MonthField.php create mode 100644 modules/qlocrontaskmanager/views/css/admin/index.php create mode 100644 modules/qlocrontaskmanager/views/css/admin/qlo_cron_task_manager_admin.css create mode 100644 modules/qlocrontaskmanager/views/css/index.php create mode 100644 modules/qlocrontaskmanager/views/index.php create mode 100644 modules/qlocrontaskmanager/views/js/admin/index.php create mode 100644 modules/qlocrontaskmanager/views/js/admin/qlo_cron_task_manager_admin.js create mode 100644 modules/qlocrontaskmanager/views/js/index.php create mode 100644 modules/qlocrontaskmanager/views/templates/admin/configure.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/index.php create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_badge_status.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/index.php create mode 100644 modules/qlocrontaskmanager/views/templates/index.php diff --git a/modules/qlocrontaskmanager/CHANGELOG.txt b/modules/qlocrontaskmanager/CHANGELOG.txt new file mode 100644 index 000000000..7ad8ca469 --- /dev/null +++ b/modules/qlocrontaskmanager/CHANGELOG.txt @@ -0,0 +1,7 @@ +# v1.0.0 - 2026-03-24 +- Initial release +- Centralized cron task management for QloApps modules +- Single server cron entry dispatches all module tasks +- Admin panel for viewing tasks, logs, and configuration +- Automatic task discovery on module install/uninstall +- Cron expression support via dragonmantank/cron-expression library diff --git a/modules/qlocrontaskmanager/LICENSE.md b/modules/qlocrontaskmanager/LICENSE.md new file mode 100644 index 000000000..86ba7fd7d --- /dev/null +++ b/modules/qlocrontaskmanager/LICENSE.md @@ -0,0 +1,15 @@ +SOFTWARE LICENCE AGREEMENT +=============================== + +This source file is subject to the Open Software License version 3.0 +that is bundled with this package in the file LICENSE.md +It is also available through the world-wide-web at this URL: +https://opensource.org/license/osl-3-0-php + +If you did not receive a copy of the license and are unable to +obtain it through the world-wide-web, please send an email +to support@qloapps.com so we can send you a copy immediately. + +@author Webkul IN +@copyright Since 2010 Webkul +@license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 diff --git a/modules/qlocrontaskmanager/README.md b/modules/qlocrontaskmanager/README.md new file mode 100644 index 000000000..c27efc9fe --- /dev/null +++ b/modules/qlocrontaskmanager/README.md @@ -0,0 +1,20 @@ + +### Support Policy: +https://store.webkul.com/support.html + +### Store link: + + +### Explore Modules: + + +### Explore Addons: +https://qloapps.com/addons/ + + +**QloApps Cron Task Manager 1.0.0** + +- Module V1.0.0 compatible with QloApps version V1.8.x. + +### Refund Policy: +https://store.webkul.com/refund-policy.html/ diff --git a/modules/qlocrontaskmanager/classes/QctmCronTask.php b/modules/qlocrontaskmanager/classes/QctmCronTask.php new file mode 100644 index 000000000..36f538606 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/QctmCronTask.php @@ -0,0 +1,125 @@ + 'qctm_cron_task', + 'primary' => 'id_cron_task', + 'fields' => array( + 'module_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName','size' => 64,'required' => true), + 'task_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName','size' => 128,'required' => true), + 'description' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 255, 'required' => true), + 'cron_expression' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 64, 'required' => true), + 'callback' => array('type' => self::TYPE_STRING,'validate' => 'isGenericName','size' => 128, 'required' => true), + 'active' => array('type' => self::TYPE_BOOL,'validate' => 'isBool'), + 'date_add' => array('type' => self::TYPE_DATE,'validate' => 'isDate'), + 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), + ), + ); + + + public static function getActiveTasks() + { + return Db::getInstance()->executeS( + 'SELECT `id_cron_task`, `module_name`, `task_name`, `description`, + `cron_expression`, `callback`, `active` + FROM `' . _DB_PREFIX_ . 'qctm_cron_task` + WHERE `active` = 1' + ); + } + + /** + * Find a task by module name and task name + * + * @param string $moduleName + * @param string $taskName + * @return int|false + */ + public static function getByModuleAndName($moduleName, $taskName) + { + return Db::getInstance()->getValue( + 'SELECT `id_cron_task` + FROM `' . _DB_PREFIX_ . 'qctm_cron_task` + WHERE `module_name` = \'' . pSQL($moduleName) . '\' + AND `task_name` = \'' . pSQL($taskName) . '\'' + ); + } + + /** + * Delete all tasks belonging to a module + * + * @param string $moduleName + * @return bool + */ + public static function deleteByModule($moduleName) + { + $taskIds = Db::getInstance()->executeS( + 'SELECT `id_cron_task` + FROM `' . _DB_PREFIX_ . 'qctm_cron_task` + WHERE `module_name` = \'' . pSQL($moduleName) . '\'' + ); + + $db = Db::getInstance(); + $db->execute('START TRANSACTION'); + + try { + if ($taskIds) { + $ids = array_column($taskIds, 'id_cron_task'); + $idsString = implode(',', array_map('intval', $ids)); + $db->execute( + 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` + WHERE `id_cron_task` IN (' . $idsString . ')' + ); + } + + $result = $db->execute( + 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task` + WHERE `module_name` = \'' . pSQL($moduleName) . '\'' + ); + + $db->execute('COMMIT'); + + return $result; + } catch (Exception $e) { + $db->execute('ROLLBACK'); + + return false; + } + } + +} diff --git a/modules/qlocrontaskmanager/classes/QctmCronTaskLog.php b/modules/qlocrontaskmanager/classes/QctmCronTaskLog.php new file mode 100644 index 000000000..1b0e17df1 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/QctmCronTaskLog.php @@ -0,0 +1,100 @@ + 'qctm_cron_task_log', + 'primary' => 'id_cron_task_log', + 'fields' => array( + 'id_cron_task' => array('type' => self::TYPE_INT,'validate' => 'isUnsignedId','required' => true), + 'status' => array('type' => self::TYPE_INT,'validate' => 'isUnsignedId','required' => true), + 'error_message' => array('type' => self::TYPE_STRING,'validate' => 'isString'), + 'execution_time' => array('type' => self::TYPE_FLOAT,'validate' => 'isFloat'), + 'date_add' => array('type' => self::TYPE_DATE,'validate' => 'isDate'), + ), + ); + + /** + * Add a log entry for a task execution + * + * @param int $idCronTask + * @param int $status + * @param string|null $errorMessage + * @param float $executionTime + * @return bool + */ + public static function addLog($idCronTask, $status, $errorMessage = null, $executionTime = 0) + { + $log = new self(); + $log->id_cron_task = (int) $idCronTask; + $log->status = $status; + $log->error_message = $errorMessage; + $log->execution_time = (float) $executionTime; + + return $log->add(); + } + + /** + * Delete logs older than specified number of days + * + * @param int $days + * @return bool + */ + public static function deleteOlderThan($days) + { + return Db::getInstance()->execute( + 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` + WHERE `date_add` < DATE_SUB(NOW(), INTERVAL ' . (int) $days . ' DAY)' + ); + } + + public static function getStatuses() + { + static $statuses = null; + + if ($statuses === null) { + $module = Module::getInstanceByName('qlocrontaskmanager'); + $statuses = array( + self::QCTM_CRON_TASK_LOG_STATUS_SUCCESS => $module->l('Success'), + self::QCTM_CRON_TASK_LOG_STATUS_ERROR => $module->l('Error'), + ); + } + + return $statuses; + } +} diff --git a/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php b/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php new file mode 100644 index 000000000..078eadb01 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php @@ -0,0 +1,103 @@ +execute($query)) { + return false; + } + } + + return true; + } + + public function dropTables() + { + $sql = array( + 'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'qctm_cron_task_log`', + 'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'qctm_cron_task`' + ); + + foreach ($sql as $query) { + if (!Db::getInstance()->execute($query)) { + return false; + } + } + + return true; + } + + public function installDefaultData() + { + Configuration::updateValue('QCTM_CRON_TASK_MANAGER_TOKEN', Tools::passwdGen(32)); + Configuration::updateValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS', 30); + Configuration::updateValue('QCTM_LOG_CLEANUP_LAST_RUN', 0); + + return true; + } + + public function deleteConfigurations() + { + Configuration::deleteByName('QCTM_CRON_TASK_MANAGER_TOKEN'); + Configuration::deleteByName('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); + Configuration::deleteByName('QCTM_LOG_CLEANUP_LAST_RUN'); + + return true; + } +} diff --git a/modules/qlocrontaskmanager/classes/index.php b/modules/qlocrontaskmanager/classes/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/index.php @@ -0,0 +1,32 @@ +table = 'qctm_cron_task'; + $this->className = 'QctmCronTask'; + $this->identifier = 'id_cron_task'; + } + + public function __construct() + { + $this->bootstrap = true; + + parent::__construct(); + + if (Tools::getValue('id_cron_task')) { + $this->showLogs = true; + $this->initLogList(); + } else { + $this->initTaskList(); + } + } + + public function init() + { + parent::init(); + if (Tools::getValue('id_cron_task')) { + self::$currentIndex .= '&id_cron_task=' . (int) Tools::getValue('id_cron_task'); + $this->toolbar_title = $this->l('Cron Task Logs'); + $this->page_header_toolbar_btn['back-btn'] = array( + 'href' => $this->getBackButtonLink(), + 'icon' => 'process-icon-back', + 'desc' => $this->l('Back To Tasks'), + ); + } + } + + protected function getBackButtonLink() + { + $backLink = preg_replace('/([&?])id_cron_task=\d+(&)?/', '$1', self::$currentIndex); + $backLink = rtrim($backLink, '&?'); + + return $backLink . '&token=' . $this->token; + } + + protected function initTaskList() + { + $this->useTaskContext(); + $this->lang = false; + $this->_orderBy = 'id_cron_task'; + $this->_orderWay = 'ASC'; + $this->list_no_link = true; + $this->_join = 'LEFT JOIN ( + SELECT ctl.`id_cron_task`, ctl.`date_add` AS `last_run`, ctl.`status` AS `last_status` + FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` ctl + INNER JOIN ( + SELECT `id_cron_task`, MAX(`id_cron_task_log`) AS `max_id` + FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` + GROUP BY `id_cron_task` + ) latest ON ctl.`id_cron_task_log` = latest.`max_id` + ) AS llog ON (llog.`id_cron_task` = a.`id_cron_task`)'; + + $this->_select = 'llog.`last_run`, llog.`last_status`'; + + $this->addRowAction('view'); + + $this->bulk_actions = array( + 'enableSelection' => array( + 'text' => $this->l('Enable selected'), + 'icon' => 'icon-power-off text-success', + ), + 'disableSelection' => array( + 'text' => $this->l('Disable selected'), + 'icon' => 'icon-power-off text-danger', + ), + ); + + $this->fields_list = array( + 'id_cron_task' => array( + 'title' => $this->l('ID'), + 'align' => 'center', + 'class' => 'fixed-width-xs', + ), + 'module_name' => array( + 'title' => $this->l('Module'), + 'filter_key' => 'a!module_name', + ), + 'description' => array( + 'title' => $this->l('Task'), + 'filter_key' => 'a!description', + ), + 'cron_expression' => array( + 'title' => $this->l('Cron Expression'), + 'align' => 'center', + 'class' => 'fixed-width-lg', + 'search' => false, + ), + 'last_run' => array( + 'title' => $this->l('Last Run'), + 'align' => 'center', + 'filter' => false, + 'search' => false, + ), + 'last_status' => array( + 'title' => $this->l('Last Status'), + 'align' => 'center', + 'filter' => false, + 'search' => false, + 'callback' => 'getLastStatus', + ), + 'active' => array( + 'title' => $this->l('Active'), + 'active' => 'status', + 'type' => 'bool', + 'align' => 'center', + 'class' => 'fixed-width-sm', + 'filter_key' => 'a!active', + ), + ); + } + + protected function initLogList() + { + $this->table = 'qctm_cron_task_log'; + $this->className = 'QctmCronTaskLog'; + $this->identifier = 'id_cron_task_log'; + $this->lang = false; + $this->_orderBy = 'id_cron_task_log'; + $this->_orderWay = 'DESC'; + $this->list_no_link = true; + + $idCronTask = (int) Tools::getValue('id_cron_task'); + if ($idCronTask) { + $this->_where = ' AND a.`id_cron_task` = ' . $idCronTask; + } + + $this->_select = 'ct.`module_name`, ct.`description` as task_description'; + $this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'qctm_cron_task` ct + ON (a.`id_cron_task` = ct.`id_cron_task`)'; + + $cronTaskLogStatuses = QctmCronTaskLog::getStatuses(); + + $this->fields_list = array( + 'id_cron_task_log' => array( + 'title' => $this->l('ID'), + 'align' => 'center', + 'class' => 'fixed-width-xs', + ), + 'module_name' => array( + 'title' => $this->l('Module'), + 'filter_key' => 'ct!module_name', + ), + 'task_description' => array( + 'title' => $this->l('Task'), + 'filter' => false, + 'search' => false, + ), + 'status' => array( + 'title' => $this->l('Status'), + 'align' => 'center', + 'type' => 'select', + 'list' => $cronTaskLogStatuses, + 'filter_key' => 'a!status', + 'callback' => 'getStatusBadge', + ), + 'error_message' => array( + 'title' => $this->l('Error Message'), + 'maxlength' => 80, + ), + 'execution_time' => array( + 'title' => $this->l('Execution Time'), + 'align' => 'center', + 'suffix' => ' s', + 'class' => 'fixed-width-md', + ), + 'date_add' => array( + 'title' => $this->l('Date'), + 'type' => 'datetime', + 'align' => 'right', + 'filter_key' => 'a!date_add', + ), + ); + } + + public function initToolbar() + { + parent::initToolbar(); + unset($this->toolbar_btn['new']); + } + + + public function processStatus() + { + $this->showLogs = false; + $this->useTaskContext(); + + return parent::processStatus(); + } + + protected function processBulkEnableSelection() + { + $this->showLogs = false; + $this->useTaskContext(); + + return parent::processBulkEnableSelection(); + } + + protected function processBulkDisableSelection() + { + $this->showLogs = false; + $this->useTaskContext(); + + return parent::processBulkDisableSelection(); + } + + public function getLastStatus($lastStatus, $row) + { + $this->context->smarty->assign('status', $lastStatus); + return $this->createTemplate('qctm_badge_status.tpl')->fetch(); + } + + public function getStatusBadge($status, $row) + { + $this->context->smarty->assign('status', $status); + return $this->createTemplate('qctm_badge_status.tpl')->fetch(); + } + + public function displayViewlogsLink($token, $id, $name = null) + { + $logLink = self::$currentIndex.'&id_cron_task=' . (int) $id. '&token=' . $this->token; + + $this->context->smarty->assign('logLink', $logLink); + + return $this->createTemplate('qctm_view_log.tpl')->fetch(); + } + + public function setMedia() + { + parent::setMedia(); + + $this->addCSS($this->module->getPathUri() . 'views/css/admin/qlo_cron_task_manager_admin.css'); + $this->addJS($this->module->getPathUri() . 'views/js/admin/qlo_cron_task_manager_admin.js'); + } +} diff --git a/modules/qlocrontaskmanager/controllers/admin/index.php b/modules/qlocrontaskmanager/controllers/admin/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/controllers/admin/index.php @@ -0,0 +1,32 @@ +active) { + die('Module not active'); +} + +$module->dispatch(); diff --git a/modules/qlocrontaskmanager/define.php b/modules/qlocrontaskmanager/define.php new file mode 100644 index 000000000..689847e02 --- /dev/null +++ b/modules/qlocrontaskmanager/define.php @@ -0,0 +1,27 @@ +name = 'qlocrontaskmanager'; + $this->tab = 'administration'; + $this->version = '1.0.0'; + $this->author = 'Webkul'; + $this->need_instance = 0; + $this->bootstrap = true; + + parent::__construct(); + + $this->displayName = $this->l('Cron Task Manager'); + $this->description = $this->l('Centralized cron scheduling system. One server cron entry manages all module tasks.'); + $this->confirmUninstall = $this->l('Are you sure you want to uninstall? All cron task data will be lost.'); + } + + + public function getContent() + { + $output = ''; + + if (Tools::isSubmit('submitCronTaskManagerSettings')) { + $output .= $this->processSaveSettings(); + } + + if (Tools::isSubmit('regenerateToken')) { + Configuration::updateValue('QCTM_CRON_TASK_MANAGER_TOKEN', Tools::passwdGen(32)); + $output .= $this->displayConfirmation($this->l('Token has been regenerated. Update your server cron entry with the new URL.')); + } + + $this->context->controller->addCSS($this->_path . 'views/css/admin/qlo_cron_task_manager_admin.css'); + $this->context->controller->addJS($this->_path . 'views/js/admin/qlo_cron_task_manager_admin.js'); + + $output .= $this->renderCronCommandPanel(); + $output .= $this->renderSettingsForm(); + + return $output; + } + + protected function processSaveSettings() + { + $logRetention = (int) Tools::getValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); + + if ($logRetention < 1 || $logRetention > 365) { + return $this->displayError($this->l('Log retention must be between 1 and 365 days.')); + } + + Configuration::updateValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS', $logRetention); + + return $this->displayConfirmation($this->l('Settings saved successfully.')); + } + + protected function renderCronCommandPanel() + { + $token = Configuration::get('QCTM_CRON_TASK_MANAGER_TOKEN'); + $cronUrl = Tools::getShopDomainSsl(true) . __PS_BASE_URI__ + . 'modules/qlocrontaskmanager/cron.php?token=' . $token; + $cronCommand = '* * * * * curl -s "' . $cronUrl . '"'; + + $this->context->smarty->assign(array( + 'cronCommand' => $cronCommand, + )); + + return $this->display(__FILE__, 'views/templates/admin/configure.tpl'); + } + + protected function renderSettingsForm() + { + $token = Configuration::get('QCTM_CRON_TASK_MANAGER_TOKEN'); + + $helper = new HelperForm(); + $helper->module = $this; + $helper->name_controller = $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; + $helper->default_form_language = (int) Configuration::get('PS_LANG_DEFAULT'); + + $helper->fields_value = array( + 'QCTM_CRON_TASK_MANAGER_TOKEN' => $token, + 'QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS' => (int) Configuration::get('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'), + ); + + $fields_form = array( + 'form' => array( + 'legend' => array( + 'title' => $this->l('Settings'), + 'icon' => 'icon-cogs', + ), + 'input' => array( + array( + 'type' => 'text', + 'label' => $this->l('Secure Token'), + 'name' => 'QCTM_CRON_TASK_MANAGER_TOKEN', + 'readonly' => true, + 'col' => 6, + 'desc' => $this->l('This token secures the cron endpoint. Regenerating it will require updating your server cron entry.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Log Retention (days)'), + 'name' => 'QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS', + 'col' => 2, + 'required' => true, + 'desc' => $this->l('Number of days to keep execution logs.'), + ), + ), + 'submit' => array( + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right', + 'name' => 'submitCronTaskManagerSettings', + ), + 'buttons' => array( + array( + 'title' => $this->l('Regenerate Token'), + 'icon' => 'process-icon-refresh', + 'name' => 'regenerateToken', + 'type' => 'submit', + 'class' => 'btn btn-default', + ), + ), + ), + ); + + return $helper->generateForm(array($fields_form)); + } + + public function hookActionModuleInstallAfter($params) + { + if (!isset($params['object']) || !($params['object'] instanceof Module)) { + return; + } + + $module = $params['object']; + + if (method_exists($module, 'hookRegisterCronTasks')) { + $tasks = $module->hookRegisterCronTasks(); + if (!empty($tasks) && is_array($tasks)) { + $this->registerTasksForModule($module->name, $tasks); + } + } + } + + public function hookActionModuleUninstallBefore($params) + { + if (!isset($params['object']) || !($params['object'] instanceof Module)) { + return; + } + + QctmCronTask::deleteByModule($params['object']->name); + } + + protected function syncAllExistingModules() + { + $allTasks = Hook::exec('registerCronTasks', array(), null, true); + + if (!empty($allTasks) && is_array($allTasks)) { + foreach ($allTasks as $moduleName => $tasks) { + if (!empty($tasks) && is_array($tasks)) { + $this->registerTasksForModule($moduleName, $tasks); + } + } + } + } + + /** + * Register tasks for a specific module after validation + * + * @param string $moduleName + * @param array $tasks + */ + protected function registerTasksForModule($moduleName, $tasks) + { + foreach ($tasks as $task) { + if (!$this->validateTask($moduleName, $task)) { + continue; + } + + if (QctmCronTask::getByModuleAndName($moduleName, $task['name'])) { + continue; + } + + $cronTask = new QctmCronTask(); + $cronTask->module_name = $moduleName; + $cronTask->task_name = $task['name']; + $cronTask->description = $task['description']; + $cronTask->cron_expression = $task['cron']; + $cronTask->callback = $task['callback']; + $cronTask->active = 1; + if (!$cronTask->save()) { + PrestaShopLogger::addLog( + 'QloCronTaskManager: failed to register task "' . $task['name'] . '" for module "' . $moduleName . '"', + 3 + ); + } + } + } + + /** + * Validate a task definition array + * + * @param string $moduleName + * @param array $task + * @return bool + */ + protected function validateTask($moduleName, $task) + { + if (empty($task['name']) || empty($task['description']) + || empty($task['cron']) || empty($task['callback'])) { + return false; + } + + if (!\Cron\CronExpression::isValidExpression($task['cron'])) { + return false; + } + + $module = Module::getInstanceByName($moduleName); + if (!$module || !method_exists($module, $task['callback'])) { + return false; + } + + return true; + } + + public function dispatch() + { + $now = new DateTime(); + $tasks = QctmCronTask::getActiveTasks(); + + if (empty($tasks)) { + return; + } + + foreach ($tasks as $task) { + $cron = \Cron\CronExpression::factory($task['cron_expression']); + + if (!$cron->isDue($now)) { + continue; + } + + $module = Module::getInstanceByName($task['module_name']); + + if (!$module || !$module->active) { + continue; + } + + $callback = $task['callback']; + + if (!method_exists($module, $callback)) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Callback method "' . $callback . '" not found on module "' . $task['module_name'] . '"' + ); + continue; + } + + $startTime = microtime(true); + + try { + $module->{$callback}(); + $executionTime = round(microtime(true) - $startTime, 4); + + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_SUCCESS, + null, + $executionTime + ); + } catch (\Throwable $e) { + $executionTime = round(microtime(true) - $startTime, 4); + + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + $e->getMessage(), + $executionTime + ); + } + } + + $this->cleanOldLogs(); + } + + public function cleanOldLogs() + { + $lastRun = (int) Configuration::get('QCTM_LOG_CLEANUP_LAST_RUN'); + + if ($lastRun && (time() - $lastRun) < 86400) { + return; + } + + $days = (int) Configuration::get('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); + if ($days > 0) { + QctmCronTaskLog::deleteOlderThan($days); + } + + Configuration::updateValue('QCTM_LOG_CLEANUP_LAST_RUN', time()); + } + + public function install() + { + $db = new QctmCronTaskManagerDb(); + + if (!parent::install() + || !$this->registerHook('actionModuleInstallAfter') + || !$this->registerHook('actionModuleUninstallBefore') + || !$db->createTables() + || !$db->installDefaultData() + || !$this->installTabs() + ) { + return false; + } + + $this->syncAllExistingModules(); + + return true; + } + + public function uninstall() + { + $db = new QctmCronTaskManagerDb(); + + if (!$this->uninstallTabs() + || !$db->deleteConfigurations() + || !$db->dropTables() + || !parent::uninstall() + ) { + return false; + } + + return true; + } + + protected function installTabs() + { + $tab = new Tab(); + $tab->class_name = 'AdminCronTaskManager'; + $tab->id_parent = (int) Tab::getIdFromClassName('AdminTools'); + $tab->module = $this->name; + $tab->position = Tab::getNewLastPosition($tab->id_parent); + + foreach (Language::getLanguages(false) as $language) { + $tab->name[$language['id_lang']] = $this->l('Cron Task Manager'); + } + + return $tab->add(); + } + + protected function uninstallTabs() + { + $idTab = (int) Tab::getIdFromClassName('AdminCronTaskManager'); + if ($idTab) { + $tab = new Tab($idTab); + if (!$tab->delete()) { + return false; + } + } + + return true; + } +} diff --git a/modules/qlocrontaskmanager/upgrade/index.php b/modules/qlocrontaskmanager/upgrade/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/upgrade/index.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private + */ +function includeFile($file) +{ + include $file; +} diff --git a/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php b/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php new file mode 100644 index 000000000..c6b54af7b --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php @@ -0,0 +1,352 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/modules/qlocrontaskmanager/vendor/composer/LICENSE b/modules/qlocrontaskmanager/vendor/composer/LICENSE new file mode 100644 index 000000000..f27399a04 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_classmap.php b/modules/qlocrontaskmanager/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..0fb0a2c19 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_namespaces.php b/modules/qlocrontaskmanager/vendor/composer/autoload_namespaces.php new file mode 100644 index 000000000..15a2ff3ad --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/dragonmantank/cron-expression/src/Cron'), +); diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_real.php b/modules/qlocrontaskmanager/vendor/composer/autoload_real.php new file mode 100644 index 000000000..30d40765b --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_static.php b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php new file mode 100644 index 000000000..e173eaa32 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'Cron\\' => 5, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Cron\\' => + array ( + 0 => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.json b/modules/qlocrontaskmanager/vendor/composer/installed.json new file mode 100644 index 000000000..9424b69ae --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/installed.json @@ -0,0 +1,73 @@ +{ + "packages": [ + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "time": "2025-10-31T18:51:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "install-path": "../dragonmantank/cron-expression" + } + ], + "dev": false, + "dev-package-names": [] +} diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.php b/modules/qlocrontaskmanager/vendor/composer/installed.php new file mode 100644 index 000000000..0dbb6b6de --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/installed.php @@ -0,0 +1,38 @@ + array( + 'name' => 'qloapps/qlocrontaskmanager', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'dragonmantank/cron-expression' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => 'd61a8a9604ec1f8c3d150d09db6ce98b32675013', + 'type' => 'library', + 'install_path' => __DIR__ . '/../dragonmantank/cron-expression', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'mtdowling/cron-expression' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^1.0', + ), + ), + 'qloapps/qlocrontaskmanager' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/modules/qlocrontaskmanager/vendor/composer/platform_check.php b/modules/qlocrontaskmanager/vendor/composer/platform_check.php new file mode 100644 index 000000000..d32d90c6a --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/CHANGELOG.md b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/CHANGELOG.md new file mode 100644 index 000000000..17ab2ce44 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/CHANGELOG.md @@ -0,0 +1,240 @@ +# Change Log + +## [3.3.3] - 2024-08-10 + +### Added +- N/A + +### Changed +- N/A + +### Fixed +- Added fixes for making sure `?` is not passed for both DOM and DOW (#148, thank you https://github.com/LeoVie) +- Fixed bug in Next Execution Time by sorting minutes properly (#160, thank you https://github.com/imyip) + +## [3.3.2] - 2022-09-19 + +### Added +- N/A + +### Changed +- Skip some daylight savings time tests for PHP 8.1 daylight savings time weirdness (#146) + +### Fixed +- Changed string interpolations to work better with PHP 8.2 (#142) + +## [3.3.1] - 2022-01-18 + +### Added +- N/A + +### Changed +- N/A + +### Fixed +- Fixed issue when timezones had no transition, which can occur over very short timespans (#134) + +## [3.3.0] - 2022-01-13 + +### Added +- Added ability to register your own expression aliases (#132) + +### Changed +- Changed how Day of Week and Day of Month resolve when one or the other is `*` or `?` + +### Fixed +- PHPStan should no longer error out + +## [3.2.4] - 2022-01-12 + +### Added +- N/A + +### Changed +- Changed how Day of Week increment/decrement to help with DST changes (#131) + +### Fixed +- N/A + +## [3.2.3] - 2022-01-05 + +### Added +- N/A + +### Changed +- Changed how minutes and hours increment/decrement to help with DST changes (#131) + +### Fixed +- N/A + +## [3.2.2] - 2022-01-05 + +### Added +- N/A + +### Changed +- Marked some methods `@internal` (#124) + +### Fixed +- Fixed issue with small ranges and large steps that caused an error with `range()` (#88) +- Fixed issue where wraparound logic incorrectly considered high bound on range (#89) + +## [3.2.1] - 2022-01-04 + +### Added +- N/A + +### Changed +- Added PHP 8.1 to testing (#125) + +### Fixed +- Allow better mixture of ranges, steps, and lists (#122) +- Fixed return order when multiple dates are requested and inverted (#121) +- Better handling over DST (#115) +- Fixed PHPStan tests (#130) + +## [3.2.0] - 2022-01-04 + +### Added +- Added alias for `@midnight` (#117) + +### Changed +- Improved testing for instance of field in tests (#105) +- Optimization for determining multiple run dates (#75) +- `CronExpression` properties changed from private to protected (#106) + +### Fixed +- N/A + +## [3.1.0] - 2020-11-24 + +### Added +- Added `CronExpression::getParts()` method to get parts of the expression as an array (#83) + +### Changed +- Changed to Interfaces for some type hints (#97, #86) +- Dropped minimum PHP version to 7.2 +- Few syntax changes for phpstan compatibility (#93) + +### Fixed +- N/A + +### Deprecated +- Deprecated `CronExpression::factory` in favor of the constructor (#56) +- Deprecated `CronExpression::YEAR` as a formality, the functionality is already removed (#87) + +## [3.0.1] - 2020-10-12 +### Added +- Added support for PHP 8 (#92) +### Changed +- N/A +### Fixed +- N/A + +## [3.0.0] - 2020-03-25 + +**MAJOR CHANGE** - In previous versions of this library, setting both a "Day of Month" and a "Day of Week" would be interpreted as an `AND` statement, not an `OR` statement. For example: + +`30 0 1 * 1` + +would evaluate to "Run 30 minutes after the 0 hour when the Day Of Month is 1 AND a Monday" instead of "Run 30 minutes after the 0 hour on Day Of Month 1 OR a Monday", where the latter is more inline with most cron systems. This means that if your cron expression has both of these fields set, you may see your expression fire more often starting with v3.0.0. + +### Added +- Additional docblocks for IDE and documentation +- Added phpstan as a development dependency +- Added a `Cron\FieldFactoryInterface` to make migrations easier (#38) +### Changed +- Changed some DI testing during TravisCI runs +- `\Cron\CronExpression::determineTimezone()` now checks for `\DateTimeInterface` instead of just `\DateTime` +- Errors with fields now report a more human-understandable error and are 1-based instead of 0-based +- Better support for `\DateTimeImmutable` across the library by typehinting for `\DateTimeInterface` now +- Literals should now be less case-sensative across the board +- Changed logic for when both a Day of Week and a Day of Month are supplied to now be an OR statement, not an AND +### Fixed +- Fixed infinite loop when determining last day of week from literals +- Fixed bug where single number ranges were allowed (ex: `1/10`) +- Fixed nullable FieldFactory in CronExpression where no factory could be supplied +- Fixed issue where logic for dropping seconds to 0 could lead to a timezone change + +## [2.3.1] - 2020-10-12 +### Added +- Added support for PHP 8 (#92) +### Changed +- N/A +### Fixed +- N/A + +## [2.3.0] - 2019-03-30 +### Added +- Added support for DateTimeImmutable via DateTimeInterface +- Added support for PHP 7.3 +- Started listing projects that use the library +### Changed +- Errors should now report a human readable position in the cron expression, instead of starting at 0 +### Fixed +- N/A + +## [2.2.0] - 2018-06-05 +### Added +- Added support for steps larger than field ranges (#6) +## Changed +- N/A +### Fixed +- Fixed validation for numbers with leading 0s (#12) + +## [2.1.0] - 2018-04-06 +### Added +- N/A +### Changed +- Upgraded to PHPUnit 6 (#2) +### Fixed +- Refactored timezones to deal with some inconsistent behavior (#3) +- Allow ranges and lists in same expression (#5) +- Fixed regression where literals were not converted to their numerical counterpart (#) + +## [2.0.0] - 2017-10-12 +### Added +- N/A + +### Changed +- Dropped support for PHP 5.x +- Dropped support for the YEAR field, as it was not part of the cron standard + +### Fixed +- Reworked validation for all the field types +- Stepping should now work for 1-indexed fields like Month (#153) + +## [1.2.0] - 2017-01-22 +### Added +- Added IDE, CodeSniffer, and StyleCI.IO support + +### Changed +- Switched to PSR-4 Autoloading + +### Fixed +- 0 step expressions are handled better +- Fixed `DayOfMonth` validation to be more strict +- Typos + +## [1.1.0] - 2016-01-26 +### Added +- Support for non-hourly offset timezones +- Checks for valid expressions + +### Changed +- Max Iterations no longer hardcoded for `getRunDate()` +- Supports DateTimeImmutable for newer PHP verions + +### Fixed +- Fixed looping bug for PHP 7 when determining the last specified weekday of a month + +## [1.0.3] - 2013-11-23 +### Added +- Now supports expressions with any number of extra spaces, tabs, or newlines + +### Changed +- Using static instead of self in `CronExpression::factory` + +### Fixed +- Fixes issue [#28](https://github.com/mtdowling/cron-expression/issues/28) where PHP increments of ranges were failing due to PHP casting hyphens to 0 +- Only set default timezone if the given $currentTime is not a DateTime instance ([#34](https://github.com/mtdowling/cron-expression/issues/34)) diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/LICENSE b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/LICENSE new file mode 100644 index 000000000..3e38bbc88 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Michael Dowling , 2016 Chris Tankersley , and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/README.md b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/README.md new file mode 100644 index 000000000..5c3ef5d57 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/README.md @@ -0,0 +1,131 @@ +PHP Cron Expression Parser +========================== + +[![Latest Stable Version](https://poser.pugx.org/dragonmantank/cron-expression/v)](https://packagist.org/packages/dragonmantank/cron-expression) [![Total Downloads](https://poser.pugx.org/dragonmantank/cron-expression/downloads)](https://packagist.org/packages/dragonmantank/cron-expression) [![Tests](https://github.com/dragonmantank/cron-expression/actions/workflows/tests.yml/badge.svg)](https://github.com/dragonmantank/cron-expression/actions/workflows/tests.yml) [![StyleCI](https://github.styleci.io/repos/103715337/shield?branch=master)](https://github.styleci.io/repos/103715337) + +The PHP cron expression parser can parse a CRON expression, determine if it is +due to run, calculate the next run date of the expression, and calculate the previous +run date of the expression. You can calculate dates far into the future or past by +skipping **n** number of matching dates. + +The parser can handle increments of ranges (e.g. */12, 2-59/3), intervals (e.g. 0-9), +lists (e.g. 1,2,3), **W** to find the nearest weekday for a given day of the month, **L** to +find the last day of the month, **L** to find the last given weekday of a month, and hash +(#) to find the nth weekday of a given month. + +More information about this fork can be found in the blog post [here](http://ctankersley.com/2017/10/12/cron-expression-update/). tl;dr - v2.0.0 is a major breaking change, and @dragonmantank can better take care of the project in a separate fork. + +Installing +========== + +Add the dependency to your project: + +```bash +composer require dragonmantank/cron-expression +``` + +Usage +===== +```php +isDue(); +echo $cron->getNextRunDate()->format('Y-m-d H:i:s'); +echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s'); + +// Works with complex expressions +$cron = new Cron\CronExpression('3-59/15 6-12 */15 1 2-5'); +echo $cron->getNextRunDate()->format('Y-m-d H:i:s'); + +// Calculate a run date two iterations into the future +$cron = new Cron\CronExpression('@daily'); +echo $cron->getNextRunDate(null, 2)->format('Y-m-d H:i:s'); + +// Calculate a run date relative to a specific time +$cron = new Cron\CronExpression('@monthly'); +echo $cron->getNextRunDate('2010-01-12 00:00:00')->format('Y-m-d H:i:s'); +``` + +CRON Expressions +================ + +A CRON expression is a string representing the schedule for a particular command to execute. The parts of a CRON schedule are as follows: + +``` +* * * * * +- - - - - +| | | | | +| | | | | +| | | | +----- day of week (0-7) (Sunday = 0 or 7) (or SUN-SAT) +| | | +--------- month (1-12) (or JAN-DEC) +| | +------------- day of month (1-31) +| +----------------- hour (0-23) ++--------------------- minute (0-59) +``` + +Each part of expression can also use wildcard, lists, ranges and steps: + +- wildcard - match always + - `* * * * *` - At every minute. + - day of week and day of month also support `?`, an alias to `*` +- lists - match list of values, ranges and steps + - e.g. `15,30 * * * *` - At minute 15 and 30. +- ranges - match values in range + - e.g. `1-9 * * * *` - At every minute from 1 through 9. +- steps - match every nth value in range + - e.g. `*/5 * * * *` - At every 5th minute. + - e.g. `0-30/5 * * * *` - At every 5th minute from 0 through 30. +- combinations + - e.g. `0-14,30-44 * * * *` - At every minute from 0 through 14 and every minute from 30 through 44. + +You can also use macro instead of an expression: + +- `@yearly`, `@annually` - At 00:00 on 1st of January. (same as `0 0 1 1 *`) +- `@monthly` - At 00:00 on day-of-month 1. (same as `0 0 1 * *`) +- `@weekly` - At 00:00 on Sunday. (same as `0 0 * * 0`) +- `@daily`, `@midnight` - At 00:00. (same as `0 0 * * *`) +- `@hourly` - At minute 0. (same as `0 * * * *`) + +Day of month extra features: + +- nearest weekday - weekday (Monday-Friday) nearest to the given day + - e.g. `* * 15W * *` - At every minute on a weekday nearest to the 15th. + - If you were to specify `15W` as the value, the meaning is: "the nearest weekday to the 15th of the month" + So if the 15th is a Saturday, the trigger will fire on Friday the 14th. + If the 15th is a Sunday, the trigger will fire on Monday the 16th. + If the 15th is a Tuesday, then it will fire on Tuesday the 15th. + - However, if you specify `1W` as the value for day-of-month, + and the 1st is a Saturday, the trigger will fire on Monday the 3rd, + as it will not 'jump' over the boundary of a month's days. +- last day of the month + - e.g. `* * L * *` - At every minute on a last day-of-month. +- last weekday of the month + - e.g. `* * LW * *` - At every minute on a last weekday. + +Day of week extra features: + +- nth day + - e.g. `* * * * 7#4` - At every minute on 4th Sunday. + - 1-5 + - Every day of week repeats 4-5 times a month. To target the last one, use "last day" feature instead. +- last day + - e.g. `* * * * 7L` - At every minute on the last Sunday. + +Requirements +============ + +- PHP 7.2+ +- PHPUnit is required to run the unit tests +- Composer is required to run the unit tests + +Projects that Use cron-expression +================================= +* Part of the [Laravel Framework](https://github.com/laravel/framework/) +* Available as a [Symfony Bundle - setono/cron-expression-bundle](https://github.com/Setono/CronExpressionBundle) +* Framework agnostic, PHP-based job scheduler - [Crunz](https://github.com/crunzphp/crunz) +* Framework agnostic job scheduler - with locks, parallelism, per-second scheduling and more - [orisai/scheduler](https://github.com/orisai/scheduler) +* Explain expression in English (and other languages) with [orisai/cron-expression-explainer](https://github.com/orisai/cron-expression-explainer) diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/composer.json b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/composer.json new file mode 100644 index 000000000..d2e23b860 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/composer.json @@ -0,0 +1,50 @@ +{ + "name": "dragonmantank/cron-expression", + "type": "library", + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": ["cron", "schedule"], + "license": "MIT", + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "require-dev": { + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0", + "phpstan/extension-installer": "^1.4.3" + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "autoload-dev": { + "psr-4": { + "Cron\\Tests\\": "tests/Cron/" + } + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "scripts": { + "phpstan": "./vendor/bin/phpstan analyze", + "test": "phpunit" + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "config": { + "allow-plugins": { + "ocramius/package-versions": true, + "phpstan/extension-installer": true + } + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/AbstractField.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/AbstractField.php new file mode 100644 index 000000000..55c9a9de5 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/AbstractField.php @@ -0,0 +1,345 @@ + + */ + protected $fullRange = []; + + /** + * Literal values we need to convert to integers. + * + * @var array + */ + protected $literals = []; + + /** + * Start value of the full range. + * + * @var int + */ + protected $rangeStart; + + /** + * End value of the full range. + * + * @var int + */ + protected $rangeEnd; + + /** + * Constructor + */ + public function __construct() + { + $this->fullRange = range($this->rangeStart, $this->rangeEnd); + } + + /** + * Check to see if a field is satisfied by a value. + * + * @internal + * @param int $dateValue Date value to check + * @param string $value Value to test + * + * @return bool + */ + public function isSatisfied(int $dateValue, string $value): bool + { + if ($this->isIncrementsOfRanges($value)) { + return $this->isInIncrementsOfRanges($dateValue, $value); + } + + if ($this->isRange($value)) { + return $this->isInRange($dateValue, $value); + } + + return '*' === $value || $dateValue === (int) $value; + } + + /** + * Check if a value is a range. + * + * @internal + * @param string $value Value to test + * + * @return bool + */ + public function isRange(string $value): bool + { + return false !== strpos($value, '-'); + } + + /** + * Check if a value is an increments of ranges. + * + * @internal + * @param string $value Value to test + * + * @return bool + */ + public function isIncrementsOfRanges(string $value): bool + { + return false !== strpos($value, '/'); + } + + /** + * Test if a value is within a range. + * + * @internal + * @param int $dateValue Set date value + * @param string $value Value to test + * + * @return bool + */ + public function isInRange(int $dateValue, $value): bool + { + $parts = array_map( + function ($value) { + $value = trim($value); + + return $this->convertLiterals($value); + }, + explode('-', $value, 2) + ); + + return $dateValue >= $parts[0] && $dateValue <= $parts[1]; + } + + /** + * Test if a value is within an increments of ranges (offset[-to]/step size). + * + * @internal + * @param int $dateValue Set date value + * @param string $value Value to test + * + * @return bool + */ + public function isInIncrementsOfRanges(int $dateValue, string $value): bool + { + $chunks = array_map('trim', explode('/', $value, 2)); + $range = $chunks[0]; + $step = $chunks[1] ?? 0; + + // No step or 0 steps aren't cool + if (null === $step || '0' === $step || 0 === $step) { + return false; + } + + // Expand the * to a full range + if ('*' === $range) { + $range = $this->rangeStart . '-' . $this->rangeEnd; + } + + // Generate the requested small range + $rangeChunks = explode('-', $range, 2); + $rangeStart = (int) $rangeChunks[0]; + $rangeEnd = $rangeChunks[1] ?? $rangeStart; + $rangeEnd = (int) $rangeEnd; + + if ($rangeStart < $this->rangeStart || $rangeStart > $this->rangeEnd || $rangeStart > $rangeEnd) { + throw new \OutOfRangeException('Invalid range start requested'); + } + + if ($rangeEnd < $this->rangeStart || $rangeEnd > $this->rangeEnd || $rangeEnd < $rangeStart) { + throw new \OutOfRangeException('Invalid range end requested'); + } + + // Steps larger than the range need to wrap around and be handled + // slightly differently than smaller steps + + // UPDATE - This is actually false. The C implementation will allow a + // larger step as valid syntax, it never wraps around. It will stop + // once it hits the end. Unfortunately this means in future versions + // we will not wrap around. However, because the logic exists today + // per the above documentation, fixing the bug from #89 + if ($step > $this->rangeEnd) { + $thisRange = [$this->fullRange[(int) $step % \count($this->fullRange)]]; + } else { + if ($step > ($rangeEnd - $rangeStart)) { + $thisRange[$rangeStart] = (int) $rangeStart; + } else { + $thisRange = range($rangeStart, $rangeEnd, (int) $step); + } + } + + return \in_array($dateValue, $thisRange, true); + } + + /** + * Returns a range of values for the given cron expression. + * + * @param string $expression The expression to evaluate + * @param int $max Maximum offset for range + * + * @return array + */ + public function getRangeForExpression(string $expression, int $max): array + { + $values = []; + $expression = $this->convertLiterals($expression); + + if (false !== strpos($expression, ',')) { + $ranges = explode(',', $expression); + $values = []; + foreach ($ranges as $range) { + $expanded = $this->getRangeForExpression($range, $this->rangeEnd); + $values = array_merge($values, $expanded); + } + + return $values; + } + + if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) { + if (!$this->isIncrementsOfRanges($expression)) { + [$offset, $to] = explode('-', $expression); + $offset = $this->convertLiterals($offset); + $to = $this->convertLiterals($to); + $stepSize = 1; + } else { + $range = array_map('trim', explode('/', $expression, 2)); + $stepSize = $range[1] ?? 0; + $range = $range[0]; + $range = explode('-', $range, 2); + $offset = $range[0]; + $to = $range[1] ?? $max; + } + $offset = '*' === $offset ? $this->rangeStart : $offset; + if ($stepSize >= $this->rangeEnd) { + $values = [$this->fullRange[(int) $stepSize % \count($this->fullRange)]]; + } else { + for ($i = $offset; $i <= $to; $i += $stepSize) { + $values[] = (int) $i; + } + } + sort($values); + } else { + $values = [$expression]; + } + + return $values; + } + + /** + * Convert literal. + * + * @param string $value + * + * @return string + */ + protected function convertLiterals(string $value): string + { + if (\count($this->literals)) { + $key = array_search(strtoupper($value), $this->literals, true); + if (false !== $key) { + return (string) $key; + } + } + + return $value; + } + + /** + * Checks to see if a value is valid for the field. + * + * @param string $value + * + * @return bool + */ + public function validate(string $value): bool + { + $value = $this->convertLiterals($value); + + // All fields allow * as a valid value + if ('*' === $value) { + return true; + } + + // Validate each chunk of a list individually + if (false !== strpos($value, ',')) { + foreach (explode(',', $value) as $listItem) { + if (!$this->validate($listItem)) { + return false; + } + } + + return true; + } + + if (false !== strpos($value, '/')) { + [$range, $step] = explode('/', $value); + + // Don't allow numeric ranges + if (is_numeric($range)) { + return false; + } + + return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT); + } + + if (false !== strpos($value, '-')) { + if (substr_count($value, '-') > 1) { + return false; + } + + $chunks = explode('-', $value); + $chunks[0] = $this->convertLiterals($chunks[0]); + $chunks[1] = $this->convertLiterals($chunks[1]); + + if ('*' === $chunks[0] || '*' === $chunks[1]) { + return false; + } + + return $this->validate($chunks[0]) && $this->validate($chunks[1]); + } + + if (!is_numeric($value)) { + return false; + } + + if (false !== strpos($value, '.')) { + return false; + } + + // We should have a numeric by now, so coerce this into an integer + $value = (int) $value; + + return \in_array($value, $this->fullRange, true); + } + + protected function timezoneSafeModify(DateTimeInterface $dt, string $modification): DateTimeInterface + { + $timezone = $dt->getTimezone(); + $dt = $dt->setTimezone(new \DateTimeZone("UTC")); + $dt = $dt->modify($modification); + $dt = $dt->setTimezone($timezone); + return $dt; + } + + protected function setTimeHour(DateTimeInterface $date, bool $invert, int $originalTimestamp): DateTimeInterface + { + $date = $date->setTime((int)$date->format('H'), ($invert ? 59 : 0)); + + // setTime caused the offset to change, moving time in the wrong direction + $actualTimestamp = $date->format('U'); + if ((! $invert) && ($actualTimestamp <= $originalTimestamp)) { + $date = $this->timezoneSafeModify($date, "+1 hour"); + } elseif ($invert && ($actualTimestamp >= $originalTimestamp)) { + $date = $this->timezoneSafeModify($date, "-1 hour"); + } + + return $date; + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php new file mode 100644 index 000000000..33f0d6c95 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php @@ -0,0 +1,591 @@ + '0 0 1 1 *', + '@annually' => '0 0 1 1 *', + '@monthly' => '0 0 1 * *', + '@weekly' => '0 0 * * 0', + '@daily' => '0 0 * * *', + '@midnight' => '0 0 * * *', + '@hourly' => '0 * * * *', + ]; + + /** + * @var array CRON expression parts + */ + protected $cronParts; + + /** + * @var FieldFactoryInterface CRON field factory + */ + protected $fieldFactory; + + /** + * @var int Max iteration count when searching for next run date + */ + protected $maxIterationCount = 1000; + + /** + * @var array Order in which to test of cron parts + */ + protected static $order = [ + self::YEAR, + self::MONTH, + self::DAY, + self::WEEKDAY, + self::HOUR, + self::MINUTE, + ]; + + /** + * @var array + */ + private static $registeredAliases = self::MAPPINGS; + + /** + * Registered a user defined CRON Expression Alias. + * + * @throws LogicException If the expression or the alias name are invalid + * or if the alias is already registered. + */ + public static function registerAlias(string $alias, string $expression): void + { + try { + new self($expression); + } catch (InvalidArgumentException $exception) { + throw new LogicException("The expression `$expression` is invalid", 0, $exception); + } + + $shortcut = strtolower($alias); + if (1 !== preg_match('/^@\w+$/', $shortcut)) { + throw new LogicException("The alias `$alias` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_)."); + } + + if (isset(self::$registeredAliases[$shortcut])) { + throw new LogicException("The alias `$alias` is already registered."); + } + + self::$registeredAliases[$shortcut] = $expression; + } + + /** + * Unregistered a user defined CRON Expression Alias. + * + * @throws LogicException If the user tries to unregister a built-in alias + */ + public static function unregisterAlias(string $alias): bool + { + $shortcut = strtolower($alias); + if (isset(self::MAPPINGS[$shortcut])) { + throw new LogicException("The alias `$alias` is a built-in alias; it can not be unregistered."); + } + + if (!isset(self::$registeredAliases[$shortcut])) { + return false; + } + + unset(self::$registeredAliases[$shortcut]); + + return true; + } + + /** + * Tells whether a CRON Expression alias is registered. + */ + public static function supportsAlias(string $alias): bool + { + return isset(self::$registeredAliases[strtolower($alias)]); + } + + /** + * Returns all registered aliases as an associated array where the aliases are the key + * and their associated expressions are the values. + * + * @return array + */ + public static function getAliases(): array + { + return self::$registeredAliases; + } + + /** + * @deprecated since version 3.0.2, use __construct instead. + */ + public static function factory(string $expression, ?FieldFactoryInterface $fieldFactory = null): CronExpression + { + /** @phpstan-ignore-next-line */ + return new static($expression, $fieldFactory); + } + + /** + * Validate a CronExpression. + * + * @param string $expression the CRON expression to validate + * + * @return bool True if a valid CRON expression was passed. False if not. + */ + public static function isValidExpression(string $expression): bool + { + try { + new CronExpression($expression); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Parse a CRON expression. + * + * @param string $expression CRON expression (e.g. '8 * * * *') + * @param null|FieldFactoryInterface $fieldFactory Factory to create cron fields + * @throws InvalidArgumentException + */ + public function __construct(string $expression, ?FieldFactoryInterface $fieldFactory = null) + { + $shortcut = strtolower($expression); + $expression = self::$registeredAliases[$shortcut] ?? $expression; + + $this->fieldFactory = $fieldFactory ?: new FieldFactory(); + $this->setExpression($expression); + } + + /** + * Set or change the CRON expression. + * + * @param string $value CRON expression (e.g. 8 * * * *) + * + * @throws \InvalidArgumentException if not a valid CRON expression + * + * @return CronExpression + */ + public function setExpression(string $value): CronExpression + { + $split = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY); + + if (!\is_array($split)) { + throw new InvalidArgumentException( + $value . ' is not a valid CRON expression' + ); + } + + $notEnoughParts = \count($split) < 5; + + $questionMarkInInvalidPart = array_key_exists(0, $split) && $split[0] === '?' + || array_key_exists(1, $split) && $split[1] === '?' + || array_key_exists(3, $split) && $split[3] === '?'; + + $tooManyQuestionMarks = array_key_exists(2, $split) && $split[2] === '?' + && array_key_exists(4, $split) && $split[4] === '?'; + + if ($notEnoughParts || $questionMarkInInvalidPart || $tooManyQuestionMarks) { + throw new InvalidArgumentException( + $value . ' is not a valid CRON expression' + ); + } + + $this->cronParts = $split; + foreach ($this->cronParts as $position => $part) { + $this->setPart($position, $part); + } + + return $this; + } + + /** + * Set part of the CRON expression. + * + * @param int $position The position of the CRON expression to set + * @param string $value The value to set + * + * @throws \InvalidArgumentException if the value is not valid for the part + * + * @return CronExpression + */ + public function setPart(int $position, string $value): CronExpression + { + if (!$this->fieldFactory->getField($position)->validate($value)) { + throw new InvalidArgumentException( + 'Invalid CRON field value ' . $value . ' at position ' . $position + ); + } + + $this->cronParts[$position] = $value; + + return $this; + } + + /** + * Set max iteration count for searching next run dates. + * + * @param int $maxIterationCount Max iteration count when searching for next run date + * + * @return CronExpression + */ + public function setMaxIterationCount(int $maxIterationCount): CronExpression + { + $this->maxIterationCount = $maxIterationCount; + + return $this; + } + + /** + * Get a next run date relative to the current date or a specific date + * + * @param string|\DateTimeInterface $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning a + * matching next run date. 0, the default, will return the + * current date and time if the next run date falls on the + * current date and time. Setting this value to 1 will + * skip the first match and go to the second match. + * Setting this value to 2 will skip the first 2 + * matches and so on. + * @param bool $allowCurrentDate Set to TRUE to return the current date if + * it matches the cron expression. + * @param null|string $timeZone TimeZone to use instead of the system default + * + * @throws \RuntimeException on too many iterations + * @throws \Exception + * + * @return \DateTime + */ + public function getNextRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime + { + return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone); + } + + /** + * Get a previous run date relative to the current date or a specific date. + * + * @param string|\DateTimeInterface $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * @param null|string $timeZone TimeZone to use instead of the system default + * + * @throws \RuntimeException on too many iterations + * @throws \Exception + * + * @return \DateTime + * + * @see \Cron\CronExpression::getNextRunDate + */ + public function getPreviousRunDate($currentTime = 'now', int $nth = 0, bool $allowCurrentDate = false, $timeZone = null): DateTime + { + return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate, $timeZone); + } + + /** + * Get multiple run dates starting at the current date or a specific date. + * + * @param int $total Set the total number of dates to calculate + * @param string|\DateTimeInterface|null $currentTime Relative calculation date + * @param bool $invert Set to TRUE to retrieve previous dates + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * @param null|string $timeZone TimeZone to use instead of the system default + * + * @return \DateTime[] Returns an array of run dates + */ + public function getMultipleRunDates(int $total, $currentTime = 'now', bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): array + { + $timeZone = $this->determineTimeZone($currentTime, $timeZone); + + if ('now' === $currentTime) { + $currentTime = new DateTime(); + } elseif ($currentTime instanceof DateTime) { + $currentTime = clone $currentTime; + } elseif ($currentTime instanceof DateTimeImmutable) { + $currentTime = DateTime::createFromFormat('U', $currentTime->format('U')); + } elseif (\is_string($currentTime)) { + $currentTime = new DateTime($currentTime); + } + + if (!$currentTime instanceof DateTime) { + throw new InvalidArgumentException('invalid current time'); + } + + $currentTime->setTimezone(new DateTimeZone($timeZone)); + + $matches = []; + for ($i = 0; $i < $total; ++$i) { + try { + $result = $this->getRunDate($currentTime, 0, $invert, $allowCurrentDate, $timeZone); + } catch (RuntimeException $e) { + break; + } + + $allowCurrentDate = false; + $currentTime = clone $result; + $matches[] = $result; + } + + return $matches; + } + + /** + * Get all or part of the CRON expression. + * + * @param int|string|null $part specify the part to retrieve or NULL to get the full + * cron schedule string + * + * @return null|string Returns the CRON expression, a part of the + * CRON expression, or NULL if the part was specified but not found + */ + public function getExpression($part = null): ?string + { + if (null === $part) { + return implode(' ', $this->cronParts); + } + + if (array_key_exists($part, $this->cronParts)) { + return $this->cronParts[$part]; + } + + return null; + } + + /** + * Gets the parts of the cron expression as an array. + * + * @return string[] + * The array of parts that make up this expression. + */ + public function getParts() + { + return $this->cronParts; + } + + /** + * Helper method to output the full expression. + * + * @return string Full CRON expression + */ + public function __toString(): string + { + return (string) $this->getExpression(); + } + + /** + * Determine if the cron is due to run based on the current date or a + * specific date. This method assumes that the current number of + * seconds are irrelevant, and should be called once per minute. + * + * @param string|\DateTimeInterface $currentTime Relative calculation date + * @param null|string $timeZone TimeZone to use instead of the system default + * + * @return bool Returns TRUE if the cron is due to run or FALSE if not + */ + public function isDue($currentTime = 'now', $timeZone = null): bool + { + $timeZone = $this->determineTimeZone($currentTime, $timeZone); + + if ('now' === $currentTime) { + $currentTime = new DateTime(); + } elseif ($currentTime instanceof DateTime) { + $currentTime = clone $currentTime; + } elseif ($currentTime instanceof DateTimeImmutable) { + $currentTime = DateTime::createFromFormat('U', $currentTime->format('U')); + } elseif (\is_string($currentTime)) { + $currentTime = new DateTime($currentTime); + } + + if (!$currentTime instanceof DateTime) { + throw new InvalidArgumentException('invalid current time'); + } + + $currentTime->setTimezone(new DateTimeZone($timeZone)); + + // drop the seconds to 0 + $currentTime->setTime((int) $currentTime->format('H'), (int) $currentTime->format('i'), 0); + + try { + return $this->getNextRunDate($currentTime, 0, true)->getTimestamp() === $currentTime->getTimestamp(); + } catch (Exception $e) { + return false; + } + } + + /** + * Get the next or previous run date of the expression relative to a date. + * + * @param string|\DateTimeInterface|null $currentTime Relative calculation date + * @param int $nth Number of matches to skip before returning + * @param bool $invert Set to TRUE to go backwards in time + * @param bool $allowCurrentDate Set to TRUE to return the + * current date if it matches the cron expression + * @param string|null $timeZone TimeZone to use instead of the system default + * + * @throws \RuntimeException on too many iterations + * @throws Exception + * + * @return \DateTime + */ + protected function getRunDate($currentTime = null, int $nth = 0, bool $invert = false, bool $allowCurrentDate = false, $timeZone = null): DateTime + { + $timeZone = $this->determineTimeZone($currentTime, $timeZone); + + if ($currentTime instanceof DateTime) { + $currentDate = clone $currentTime; + } elseif ($currentTime instanceof DateTimeImmutable) { + $currentDate = DateTime::createFromFormat('U', $currentTime->format('U')); + } elseif (\is_string($currentTime)) { + $currentDate = new DateTime($currentTime); + } else { + $currentDate = new DateTime('now'); + } + + if (!$currentDate instanceof DateTime) { + throw new InvalidArgumentException('invalid current date'); + } + + $currentDate->setTimezone(new DateTimeZone($timeZone)); + // Workaround for setTime causing an offset change: https://bugs.php.net/bug.php?id=81074 + $currentDate = DateTime::createFromFormat("!Y-m-d H:iO", $currentDate->format("Y-m-d H:iP"), $currentDate->getTimezone()); + if ($currentDate === false) { + throw new \RuntimeException('Unable to create date from format'); + } + $currentDate->setTimezone(new DateTimeZone($timeZone)); + + $nextRun = clone $currentDate; + + // We don't have to satisfy * or null fields + $parts = []; + $fields = []; + foreach (self::$order as $position) { + $part = $this->getExpression($position); + if (null === $part || '*' === $part) { + continue; + } + $parts[$position] = $part; + $fields[$position] = $this->fieldFactory->getField($position); + } + + if (isset($parts[self::DAY]) && isset($parts[self::WEEKDAY])) { + $domExpression = sprintf('%s %s %s %s *', $this->getExpression(0), $this->getExpression(1), $this->getExpression(2), $this->getExpression(3)); + $dowExpression = sprintf('%s %s * %s %s', $this->getExpression(0), $this->getExpression(1), $this->getExpression(3), $this->getExpression(4)); + + $domExpression = new self($domExpression); + $dowExpression = new self($dowExpression); + + $domRunDates = $domExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone); + $dowRunDates = $dowExpression->getMultipleRunDates($nth + 1, $currentTime, $invert, $allowCurrentDate, $timeZone); + + if ($parts[self::DAY] === '?' || $parts[self::DAY] === '*') { + $domRunDates = []; + } + + if ($parts[self::WEEKDAY] === '?' || $parts[self::WEEKDAY] === '*') { + $dowRunDates = []; + } + + $combined = array_merge($domRunDates, $dowRunDates); + usort($combined, function ($a, $b) { + return $a->format('Y-m-d H:i:s') <=> $b->format('Y-m-d H:i:s'); + }); + if ($invert) { + $combined = array_reverse($combined); + } + + return $combined[$nth]; + } + + // Set a hard limit to bail on an impossible date + for ($i = 0; $i < $this->maxIterationCount; ++$i) { + foreach ($parts as $position => $part) { + $satisfied = false; + // Get the field object used to validate this part + $field = $fields[$position]; + // Check if this is singular or a list + if (false === strpos($part, ',')) { + $satisfied = $field->isSatisfiedBy($nextRun, $part, $invert); + } else { + foreach (array_map('trim', explode(',', $part)) as $listPart) { + if ($field->isSatisfiedBy($nextRun, $listPart, $invert)) { + $satisfied = true; + + break; + } + } + } + + // If the field is not satisfied, then start over + if (!$satisfied) { + $field->increment($nextRun, $invert, $part); + + continue 2; + } + } + + // Skip this match if needed + if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) { + $this->fieldFactory->getField(self::MINUTE)->increment($nextRun, $invert, $parts[self::MINUTE] ?? null); + continue; + } + + return $nextRun; + } + + // @codeCoverageIgnoreStart + throw new RuntimeException('Impossible CRON expression'); + // @codeCoverageIgnoreEnd + } + + /** + * Workout what timeZone should be used. + * + * @param string|\DateTimeInterface|null $currentTime Relative calculation date + * @param string|null $timeZone TimeZone to use instead of the system default + * + * @return string + */ + protected function determineTimeZone($currentTime, ?string $timeZone): string + { + if (null !== $timeZone) { + return $timeZone; + } + + if ($currentTime instanceof DateTimeInterface) { + return $currentTime->getTimezone()->getName(); + } + + return date_default_timezone_get(); + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php new file mode 100644 index 000000000..39ff59783 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php @@ -0,0 +1,164 @@ + + */ +class DayOfMonthField extends AbstractField +{ + /** + * {@inheritdoc} + */ + protected $rangeStart = 1; + + /** + * {@inheritdoc} + */ + protected $rangeEnd = 31; + + /** + * Get the nearest day of the week for a given day in a month. + * + * @param int $currentYear Current year + * @param int $currentMonth Current month + * @param int $targetDay Target day of the month + * + * @return \DateTime|null Returns the nearest date + */ + private static function getNearestWeekday(int $currentYear, int $currentMonth, int $targetDay): ?DateTime + { + $tday = str_pad((string) $targetDay, 2, '0', STR_PAD_LEFT); + $target = DateTime::createFromFormat('Y-m-d', "{$currentYear}-{$currentMonth}-{$tday}"); + + if ($target === false) { + return null; + } + + $currentWeekday = (int) $target->format('N'); + + if ($currentWeekday < 6) { + return $target; + } + + $lastDayOfMonth = $target->format('t'); + foreach ([-1, 1, -2, 2] as $i) { + $adjusted = $targetDay + $i; + if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) { + $target->setDate($currentYear, $currentMonth, $adjusted); + + if ((int) $target->format('N') < 6 && (int) $target->format('m') === $currentMonth) { + return $target; + } + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool + { + // ? states that the field value is to be skipped + if ('?' === $value) { + return true; + } + + $fieldValue = $date->format('d'); + + // Check to see if this is the last day of the month + if ('L' === $value) { + return $fieldValue === $date->format('t'); + } + + // Check to see if this is the nearest weekday to a particular value + if ($wPosition = strpos($value, 'W')) { + // Parse the target day + $targetDay = (int) substr($value, 0, $wPosition); + // Find out if the current day is the nearest day of the week + $nearest = self::getNearestWeekday( + (int) $date->format('Y'), + (int) $date->format('m'), + $targetDay + ); + if ($nearest) { + return $date->format('j') === $nearest->format('j'); + } + + throw new \RuntimeException('Unable to return nearest weekday'); + } + + return $this->isSatisfied((int) $date->format('d'), $value); + } + + /** + * @inheritDoc + * + * @param \DateTime|\DateTimeImmutable $date + */ + public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface + { + if (! $invert) { + $date = $date->add(new \DateInterval('P1D')); + $date = $date->setTime(0, 0); + } else { + $date = $date->sub(new \DateInterval('P1D')); + $date = $date->setTime(23, 59); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function validate(string $value): bool + { + $basicChecks = parent::validate($value); + + // Validate that a list don't have W or L + if (false !== strpos($value, ',') && (false !== strpos($value, 'W') || false !== strpos($value, 'L'))) { + return false; + } + + if (!$basicChecks) { + if ('?' === $value) { + return true; + } + + if ('L' === $value) { + return true; + } + + if (preg_match('/^(.*)W$/', $value, $matches)) { + return $this->validate($matches[1]); + } + + return false; + } + + return $basicChecks; + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php new file mode 100644 index 000000000..6fae740ad --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php @@ -0,0 +1,194 @@ + Weekday range + */ + protected $nthRange; + + /** + * {@inheritdoc} + */ + protected $literals = [1 => 'MON', 2 => 'TUE', 3 => 'WED', 4 => 'THU', 5 => 'FRI', 6 => 'SAT', 7 => 'SUN']; + + /** + * Constructor + */ + public function __construct() + { + $this->nthRange = range(1, 5); + parent::__construct(); + } + + /** + * @inheritDoc + */ + public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool + { + if ('?' === $value) { + return true; + } + + // Convert text day of the week values to integers + $value = $this->convertLiterals($value); + + $currentYear = (int) $date->format('Y'); + $currentMonth = (int) $date->format('m'); + $lastDayOfMonth = (int) $date->format('t'); + + // Find out if this is the last specific weekday of the month + if ($lPosition = strpos($value, 'L')) { + $weekday = (int) $this->convertLiterals(substr($value, 0, $lPosition)); + $weekday %= 7; + + $daysInMonth = (int) $date->format('t'); + $remainingDaysInMonth = $daysInMonth - (int) $date->format('d'); + return (($weekday === (int) $date->format('w')) && ($remainingDaysInMonth < 7)); + } + + // Handle # hash tokens + if (strpos($value, '#')) { + [$weekday, $nth] = explode('#', $value); + + if (!is_numeric($nth)) { + throw new InvalidArgumentException("Hashed weekdays must be numeric, {$nth} given"); + } else { + $nth = (int) $nth; + } + + // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601 + if ('0' === $weekday) { + $weekday = 7; + } + + $weekday = (int) $this->convertLiterals((string) $weekday); + + // Validate the hash fields + if ($weekday < 0 || $weekday > 7) { + throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given"); + } + + if (!\in_array($nth, $this->nthRange, true)) { + throw new InvalidArgumentException("There are never more than 5 or less than 1 of a given weekday in a month, {$nth} given"); + } + + // The current weekday must match the targeted weekday to proceed + if ((int) $date->format('N') !== $weekday) { + return false; + } + + $tdate = clone $date; + $tdate = $tdate->setDate($currentYear, $currentMonth, 1); + $dayCount = 0; + $currentDay = 1; + while ($currentDay < $lastDayOfMonth + 1) { + if ((int) $tdate->format('N') === $weekday) { + if (++$dayCount >= $nth) { + break; + } + } + $tdate = $tdate->setDate($currentYear, $currentMonth, ++$currentDay); + } + + return (int) $date->format('j') === $currentDay; + } + + // Handle day of the week values + if (false !== strpos($value, '-')) { + $parts = explode('-', $value); + if ('7' === $parts[0]) { + $parts[0] = 0; + } elseif ('0' === $parts[1]) { + $parts[1] = 7; + } + $value = implode('-', $parts); + } + + // Test to see which Sunday to use -- 0 == 7 == Sunday + $format = \in_array(7, array_map(function ($value) { + return (int) $value; + }, str_split($value)), true) ? 'N' : 'w'; + $fieldValue = (int) $date->format($format); + + return $this->isSatisfied($fieldValue, $value); + } + + /** + * @inheritDoc + */ + public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface + { + if (! $invert) { + $date = $date->add(new \DateInterval('P1D')); + $date = $date->setTime(0, 0); + } else { + $date = $date->sub(new \DateInterval('P1D')); + $date = $date->setTime(23, 59); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function validate(string $value): bool + { + $basicChecks = parent::validate($value); + + if (!$basicChecks) { + if ('?' === $value) { + return true; + } + + // Handle the # value + if (false !== strpos($value, '#')) { + $chunks = explode('#', $value); + $chunks[0] = $this->convertLiterals($chunks[0]); + + if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && \in_array((int) $chunks[1], $this->nthRange, true)) { + return true; + } + } + + if (preg_match('/^(.*)L$/', $value, $matches)) { + return $this->validate($matches[1]); + } + + return false; + } + + return $basicChecks; + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactory.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactory.php new file mode 100644 index 000000000..4d3a764b1 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactory.php @@ -0,0 +1,52 @@ + Cache of instantiated fields + */ + private $fields = []; + + /** + * Get an instance of a field object for a cron expression position. + * + * @param int $position CRON expression position value to retrieve + * + * @throws InvalidArgumentException if a position is not valid + */ + public function getField(int $position): FieldInterface + { + return $this->fields[$position] ?? $this->fields[$position] = $this->instantiateField($position); + } + + private function instantiateField(int $position): FieldInterface + { + switch ($position) { + case CronExpression::MINUTE: + return new MinutesField(); + case CronExpression::HOUR: + return new HoursField(); + case CronExpression::DAY: + return new DayOfMonthField(); + case CronExpression::MONTH: + return new MonthField(); + case CronExpression::WEEKDAY: + return new DayOfWeekField(); + } + + throw new InvalidArgumentException( + ($position + 1) . ' is not a valid position' + ); + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php new file mode 100644 index 000000000..8bd3c6581 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/FieldFactoryInterface.php @@ -0,0 +1,8 @@ +>|null Transitions returned by DateTimeZone::getTransitions() + */ + protected $transitions = []; + + /** + * @var int|null Timestamp of the start of the transitions range + */ + protected $transitionsStart = null; + + /** + * @var int|null Timestamp of the end of the transitions range + */ + protected $transitionsEnd = null; + + /** + * {@inheritdoc} + */ + public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool + { + $checkValue = (int) $date->format('H'); + $retval = $this->isSatisfied($checkValue, $value); + if ($retval) { + return $retval; + } + + // Are we on the edge of a transition + $lastTransition = $this->getPastTransition($date); + if (($lastTransition !== null) && ($lastTransition["ts"] > ((int) $date->format('U') - 3600))) { + $dtLastOffset = clone $date; + $this->timezoneSafeModify($dtLastOffset, "-1 hour"); + $lastOffset = $dtLastOffset->getOffset(); + + $dtNextOffset = clone $date; + $this->timezoneSafeModify($dtNextOffset, "+1 hour"); + $nextOffset = $dtNextOffset->getOffset(); + + $offsetChange = $nextOffset - $lastOffset; + if ($offsetChange >= 3600) { + $checkValue -= 1; + return $this->isSatisfied($checkValue, $value); + } + if ((! $invert) && ($offsetChange <= -3600)) { + $checkValue += 1; + return $this->isSatisfied($checkValue, $value); + } + } + + return $retval; + } + + /** + * @return non-empty-array|null + */ + public function getPastTransition(DateTimeInterface $date): ?array + { + $currentTimestamp = (int) $date->format('U'); + if ( + ($this->transitions === null) + || ($this->transitionsStart < ($currentTimestamp + 86400)) + || ($this->transitionsEnd > ($currentTimestamp - 86400)) + ) { + // We start a day before current time so we can differentiate between the first transition entry + // and a change that happens now + $dtLimitStart = clone $date; + $dtLimitStart = $dtLimitStart->modify("-12 months"); + $dtLimitEnd = clone $date; + $dtLimitEnd = $dtLimitEnd->modify('+12 months'); + + $this->transitions = $date->getTimezone()->getTransitions( + $dtLimitStart->getTimestamp(), + $dtLimitEnd->getTimestamp() + ); + if (empty($this->transitions)) { + return null; + } + $this->transitionsStart = $dtLimitStart->getTimestamp(); + $this->transitionsEnd = $dtLimitEnd->getTimestamp(); + } + + $nextTransition = null; + foreach ($this->transitions as $transition) { + if ($transition["ts"] > $currentTimestamp) { + continue; + } + + if (($nextTransition !== null) && ($transition["ts"] < $nextTransition["ts"])) { + continue; + } + + $nextTransition = $transition; + } + + return ($nextTransition ?? null); + } + + /** + * {@inheritdoc} + * + * @param string|null $parts + */ + public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface + { + $originalTimestamp = (int) $date->format('U'); + + // Change timezone to UTC temporarily. This will + // allow us to go back or forwards and hour even + // if DST will be changed between the hours. + if (null === $parts || '*' === $parts) { + if ($invert) { + $date = $date->sub(new \DateInterval('PT1H')); + } else { + $date = $date->add(new \DateInterval('PT1H')); + } + + $date = $this->setTimeHour($date, $invert, $originalTimestamp); + return $this; + } + + $parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts]; + $hours = []; + foreach ($parts as $part) { + $hours = array_merge($hours, $this->getRangeForExpression($part, 23)); + } + + $current_hour = (int) $date->format('H'); + $position = $invert ? \count($hours) - 1 : 0; + $countHours = \count($hours); + if ($countHours > 1) { + for ($i = 0; $i < $countHours - 1; ++$i) { + if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) || + ($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) { + $position = $invert ? $i : $i + 1; + + break; + } + } + } + + $target = (int) $hours[$position]; + $originalHour = (int)$date->format('H'); + $originalDst = (int)$date->format('I'); + + $originalDay = (int)$date->format('d'); + $previousOffset = $date->getOffset(); + + if (! $invert) { + if ($originalHour >= $target) { + $distance = 24 - $originalHour; + $date = $this->timezoneSafeModify($date, "+{$distance} hours"); + + $actualDay = (int)$date->format('d'); + $actualHour = (int)$date->format('H'); + if (($actualDay !== ($originalDay + 1)) && ($actualHour !== 0)) { + $offsetChange = ($previousOffset - $date->getOffset()); + $date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds"); + } + + $originalHour = (int)$date->format('H'); + } + + $distance = $target - $originalHour; + $date = $this->timezoneSafeModify($date, "+{$distance} hours"); + } else { + if ($originalHour <= $target) { + $distance = ($originalHour + 1); + $date = $this->timezoneSafeModify($date, "-" . $distance . " hours"); + + $actualDay = (int)$date->format('d'); + $actualHour = (int)$date->format('H'); + if (($actualDay !== ($originalDay - 1)) && ($actualHour !== 23)) { + $offsetChange = ($previousOffset - $date->getOffset()); + $date = $this->timezoneSafeModify($date, "+{$offsetChange} seconds"); + } + + $originalHour = (int)$date->format('H'); + } + + $distance = $originalHour - $target; + $date = $this->timezoneSafeModify($date, "-{$distance} hours"); + } + + $actualDst = (int)$date->format('I'); + if ($originalDst < $actualDst) { + $date = $this->timezoneSafeModify($date, "-1 hours"); + } + + $date = $this->setTimeHour($date, $invert, $originalTimestamp); + + $actualHour = (int)$date->format('H'); + if ($invert && ($actualHour === ($target - 1) || (($actualHour === 23) && ($target === 0)))) { + $date = $this->timezoneSafeModify($date, "+1 hour"); + } + + return $this; + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MinutesField.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MinutesField.php new file mode 100644 index 000000000..f077e6ec5 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MinutesField.php @@ -0,0 +1,97 @@ +isSatisfied((int)$date->format('i'), $value); + } + + /** + * {@inheritdoc} + * {@inheritDoc} + * + * @param string|null $parts + */ + public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface + { + if (is_null($parts)) { + $date = $this->timezoneSafeModify($date, ($invert ? "-" : "+") ."1 minute"); + return $this; + } + + $current_minute = (int) $date->format('i'); + + $parts = false !== strpos($parts, ',') ? explode(',', $parts) : [$parts]; + sort($parts); + $minutes = []; + foreach ($parts as $part) { + $minutes = array_merge($minutes, $this->getRangeForExpression($part, 59)); + } + + $position = $invert ? \count($minutes) - 1 : 0; + if (\count($minutes) > 1) { + for ($i = 0; $i < \count($minutes) - 1; ++$i) { + if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) || + ($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) { + $position = $invert ? $i : $i + 1; + + break; + } + } + } + + $target = (int) $minutes[$position]; + $originalMinute = (int) $date->format("i"); + + if (! $invert) { + if ($originalMinute >= $target) { + $distance = 60 - $originalMinute; + $date = $this->timezoneSafeModify($date, "+{$distance} minutes"); + + $originalMinute = (int) $date->format("i"); + } + + $distance = $target - $originalMinute; + $date = $this->timezoneSafeModify($date, "+{$distance} minutes"); + } else { + if ($originalMinute <= $target) { + $distance = ($originalMinute + 1); + $date = $this->timezoneSafeModify($date, "-{$distance} minutes"); + + $originalMinute = (int) $date->format("i"); + } + + $distance = $originalMinute - $target; + $date = $this->timezoneSafeModify($date, "-{$distance} minutes"); + } + + return $this; + } +} diff --git a/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MonthField.php b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MonthField.php new file mode 100644 index 000000000..5a15fbb8b --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/dragonmantank/cron-expression/src/Cron/MonthField.php @@ -0,0 +1,61 @@ + 'JAN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAY', 6 => 'JUN', 7 => 'JUL', + 8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC', ]; + + /** + * {@inheritdoc} + */ + public function isSatisfiedBy(DateTimeInterface $date, $value, bool $invert): bool + { + if ($value === '?') { + return true; + } + + $value = $this->convertLiterals($value); + + return $this->isSatisfied((int) $date->format('m'), $value); + } + + /** + * @inheritDoc + * + * @param \DateTime|\DateTimeImmutable $date + */ + public function increment(DateTimeInterface &$date, $invert = false, $parts = null): FieldInterface + { + if (! $invert) { + $date = $date->modify('first day of next month'); + $date = $date->setTime(0, 0); + } else { + $date = $date->modify('last day of previous month'); + $date = $date->setTime(23, 59); + } + + return $this; + } +} diff --git a/modules/qlocrontaskmanager/views/css/admin/index.php b/modules/qlocrontaskmanager/views/css/admin/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/views/css/admin/index.php @@ -0,0 +1,32 @@ + ' + copiedText); + + setTimeout(function () { + $button.html(originalHtml); + }, 2000); + }, + + getRegenerateConfirmText: function () { + return $(this.selectors.textContainer).data('regenerate-confirm') || ''; + }, +}; + +$(document).ready(function () { + QctmAdmin.init(); +}); diff --git a/modules/qlocrontaskmanager/views/js/index.php b/modules/qlocrontaskmanager/views/js/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/views/js/index.php @@ -0,0 +1,32 @@ + +
+ {l s='Cron Command' mod='qlocrontaskmanager'} +
+
+
+ {l s='Add the following cron entry to your server. This single command will manage all module cron tasks automatically.' mod='qlocrontaskmanager'} +
+
+ +
+
+ + + + +
+

+ {l s='This command runs every minute and dispatches all registered module tasks based on their cron expressions.' mod='qlocrontaskmanager'} +

+
+
+
+ + + diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/index.php b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/index.php @@ -0,0 +1,32 @@ +{l s='Success' mod='qlocrontaskmanager'} +{elseif $status == QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR} + {l s='Error' mod='qlocrontaskmanager'} +{else} + {l s='N/A' mod='qlocrontaskmanager'} +{/if} + diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl new file mode 100644 index 000000000..93adea4ff --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl @@ -0,0 +1,23 @@ +{* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 +*} + +{l s='Logs' mod='qlocrontaskmanager'} diff --git a/modules/qlocrontaskmanager/views/templates/admin/index.php b/modules/qlocrontaskmanager/views/templates/admin/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/index.php @@ -0,0 +1,32 @@ + Date: Fri, 8 May 2026 16:07:34 +0530 Subject: [PATCH 02/11] Add cron log controller, cron validation, human-readable schedule, next-run column, system log-cleanup task, and preserve logs on module uninstall --- modules/qlocrontaskmanager/README.md | 44 +- .../classes/QctmCronTask.php | 67 +-- .../classes/QctmCronTaskManagerDb.php | 7 +- .../classes/QctmRequiredClasses.php | 27 ++ modules/qlocrontaskmanager/composer.json | 3 +- .../admin/AdminCronTaskLogsController.php | 164 +++++++ .../admin/AdminCronTaskManagerController.php | 452 ++++++++++++------ modules/qlocrontaskmanager/logo.gif | Bin 0 -> 615 bytes modules/qlocrontaskmanager/logo.png | Bin 0 -> 1543 bytes .../qlocrontaskmanager/qlocrontaskmanager.php | 190 +++++--- .../qlocrontaskmanager/vendor/autoload.php | 14 +- .../vendor/composer/ClassLoader.php | 137 +++--- .../vendor/composer/InstalledVersions.php | 58 ++- .../vendor/composer/autoload_psr4.php | 1 + .../vendor/composer/autoload_static.php | 8 + .../vendor/composer/installed.json | 61 ++- .../vendor/composer/installed.php | 23 +- .../lorisleiva/cron-translator/.gitignore | 5 + .../vendor/lorisleiva/cron-translator/LICENSE | 21 + .../lorisleiva/cron-translator/README.md | 51 ++ .../lorisleiva/cron-translator/composer.json | 34 ++ .../cron-translator/src/CronExpression.php | 110 +++++ .../src/CronParsingException.php | 13 + .../cron-translator/src/CronTranslator.php | 76 +++ .../cron-translator/src/CronType.php | 91 ++++ .../cron-translator/src/DaysOfMonthField.php | 68 +++ .../cron-translator/src/DaysOfWeekField.php | 56 +++ .../lorisleiva/cron-translator/src/Field.php | 63 +++ .../cron-translator/src/HoursField.php | 85 ++++ .../cron-translator/src/MinutesField.php | 39 ++ .../cron-translator/src/MonthsField.php | 66 +++ .../src/TranslationFileMissingException.php | 13 + .../cron-translator/src/lang/ar/days.php | 11 + .../cron-translator/src/lang/ar/fields.php | 48 ++ .../cron-translator/src/lang/ar/months.php | 16 + .../cron-translator/src/lang/ar/ordinals.php | 12 + .../cron-translator/src/lang/ar/times.php | 7 + .../cron-translator/src/lang/de/days.php | 11 + .../cron-translator/src/lang/de/fields.php | 50 ++ .../cron-translator/src/lang/de/months.php | 16 + .../cron-translator/src/lang/de/ordinals.php | 13 + .../cron-translator/src/lang/de/times.php | 7 + .../cron-translator/src/lang/en/days.php | 11 + .../cron-translator/src/lang/en/fields.php | 48 ++ .../cron-translator/src/lang/en/months.php | 16 + .../cron-translator/src/lang/en/ordinals.php | 12 + .../cron-translator/src/lang/en/times.php | 7 + .../cron-translator/src/lang/es/days.php | 11 + .../cron-translator/src/lang/es/fields.php | 48 ++ .../cron-translator/src/lang/es/months.php | 16 + .../cron-translator/src/lang/es/ordinals.php | 5 + .../cron-translator/src/lang/es/times.php | 6 + .../cron-translator/src/lang/fr/days.php | 11 + .../cron-translator/src/lang/fr/fields.php | 48 ++ .../cron-translator/src/lang/fr/months.php | 16 + .../cron-translator/src/lang/fr/ordinals.php | 6 + .../cron-translator/src/lang/fr/times.php | 7 + .../cron-translator/src/lang/lv/days.php | 11 + .../cron-translator/src/lang/lv/fields.php | 48 ++ .../cron-translator/src/lang/lv/months.php | 16 + .../cron-translator/src/lang/lv/ordinals.php | 5 + .../cron-translator/src/lang/lv/times.php | 7 + .../cron-translator/src/lang/nl/days.php | 11 + .../cron-translator/src/lang/nl/fields.php | 48 ++ .../cron-translator/src/lang/nl/months.php | 16 + .../cron-translator/src/lang/nl/ordinals.php | 5 + .../cron-translator/src/lang/nl/times.php | 5 + .../cron-translator/src/lang/pt/days.php | 11 + .../cron-translator/src/lang/pt/fields.php | 48 ++ .../cron-translator/src/lang/pt/months.php | 16 + .../cron-translator/src/lang/pt/ordinals.php | 5 + .../cron-translator/src/lang/pt/times.php | 6 + .../cron-translator/src/lang/sk/days.php | 11 + .../cron-translator/src/lang/sk/fields.php | 48 ++ .../cron-translator/src/lang/sk/months.php | 16 + .../cron-translator/src/lang/sk/ordinals.php | 12 + .../cron-translator/src/lang/sk/times.php | 7 + .../cron-translator/src/lang/zh/days.php | 11 + .../cron-translator/src/lang/zh/fields.php | 48 ++ .../cron-translator/src/lang/zh/months.php | 16 + .../cron-translator/src/lang/zh/ordinals.php | 5 + .../cron-translator/src/lang/zh/times.php | 5 + .../css/admin/qlo_cron_task_manager_admin.css | 23 + .../js/admin/qlo_cron_task_manager_admin.js | 53 ++ .../views/templates/admin/configure.tpl | 4 +- .../templates/admin/cron_task_logs/index.php} | 13 +- .../cron_task_logs/qctm_badge_status.tpl | 31 ++ .../qctm_cron_expression_helper.tpl | 48 ++ .../qctm_reset_module_crons_link.tpl | 26 + .../admin/cron_task_manager/qctm_view_log.tpl | 6 +- .../cron_task_manager/qctm_view_logs_link.tpl | 27 ++ 91 files changed, 2826 insertions(+), 348 deletions(-) create mode 100644 modules/qlocrontaskmanager/classes/QctmRequiredClasses.php create mode 100644 modules/qlocrontaskmanager/controllers/admin/AdminCronTaskLogsController.php create mode 100644 modules/qlocrontaskmanager/logo.gif create mode 100644 modules/qlocrontaskmanager/logo.png create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronTranslator.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php create mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php rename modules/qlocrontaskmanager/{define.php => views/templates/admin/cron_task_logs/index.php} (74%) create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_logs/qctm_badge_status.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_cron_expression_helper.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl create mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_logs_link.tpl diff --git a/modules/qlocrontaskmanager/README.md b/modules/qlocrontaskmanager/README.md index c27efc9fe..0954dbf58 100644 --- a/modules/qlocrontaskmanager/README.md +++ b/modules/qlocrontaskmanager/README.md @@ -1,20 +1,42 @@ +## Overview + +The **QloApps Cron Task Manager** module enables centralized management and execution of scheduled (cron) tasks within QloApps. It allows developers and administrators to define, register, and execute automated jobs efficiently. + +**QloApps Cron Task Manager 1.0.0** + +- Current version: 1.0.0 + +- Module V1.0.0 compatible with QloApps version 1.7.0 and V1.8.x. + + + +## Usage (QloApps version 1.7.0) + +To register a cron task, add a new hook inside your module's main class file. + +### Step 1: Register Cron Tasks Hook + +```php +public function hookRegisterCronTasks() +{ + return array( + array( + 'name' => 'cron_success_probe', + 'description' => $this->l('Sample cron task for scheduler verification.'), + 'cron' => '* * * * *', + 'callback' => 'cronSuccessProbe', + ) + ); +} + +``` ### Support Policy: https://store.webkul.com/support.html -### Store link: - - -### Explore Modules: - ### Explore Addons: https://qloapps.com/addons/ - -**QloApps Cron Task Manager 1.0.0** - -- Module V1.0.0 compatible with QloApps version V1.8.x. - ### Refund Policy: -https://store.webkul.com/refund-policy.html/ +https://store.webkul.com/refund-policy.html/ \ No newline at end of file diff --git a/modules/qlocrontaskmanager/classes/QctmCronTask.php b/modules/qlocrontaskmanager/classes/QctmCronTask.php index 36f538606..586b2ba0e 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronTask.php +++ b/modules/qlocrontaskmanager/classes/QctmCronTask.php @@ -28,12 +28,13 @@ if (!defined('_PS_VERSION_')) { class QctmCronTask extends ObjectModel { public $id_cron_task; - public $module_name; + public $id_module; public $task_name; public $description; public $cron_expression; public $callback; public $active; + public $is_system; public $date_add; public $date_upd; @@ -41,22 +42,22 @@ class QctmCronTask extends ObjectModel 'table' => 'qctm_cron_task', 'primary' => 'id_cron_task', 'fields' => array( - 'module_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName','size' => 64,'required' => true), - 'task_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName','size' => 128,'required' => true), + 'id_module' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), + 'task_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 128, 'required' => true), 'description' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 255, 'required' => true), 'cron_expression' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 64, 'required' => true), - 'callback' => array('type' => self::TYPE_STRING,'validate' => 'isGenericName','size' => 128, 'required' => true), - 'active' => array('type' => self::TYPE_BOOL,'validate' => 'isBool'), - 'date_add' => array('type' => self::TYPE_DATE,'validate' => 'isDate'), + 'callback' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 128, 'required' => true), + 'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), + 'is_system' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), + 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), ), ); - public static function getActiveTasks() { return Db::getInstance()->executeS( - 'SELECT `id_cron_task`, `module_name`, `task_name`, `description`, + 'SELECT `id_cron_task`, `id_module`, `task_name`, `description`, `cron_expression`, `callback`, `active` FROM `' . _DB_PREFIX_ . 'qctm_cron_task` WHERE `active` = 1' @@ -64,62 +65,32 @@ class QctmCronTask extends ObjectModel } /** - * Find a task by module name and task name - * - * @param string $moduleName + * @param int $idModule * @param string $taskName * @return int|false */ - public static function getByModuleAndName($moduleName, $taskName) + public static function getByModuleAndName($idModule, $taskName) { return Db::getInstance()->getValue( 'SELECT `id_cron_task` FROM `' . _DB_PREFIX_ . 'qctm_cron_task` - WHERE `module_name` = \'' . pSQL($moduleName) . '\' + WHERE `id_module` = ' . (int) $idModule . ' AND `task_name` = \'' . pSQL($taskName) . '\'' ); } /** - * Delete all tasks belonging to a module + * Delete all tasks belonging to a module. + * Logs are intentionally preserved for history tracking. * - * @param string $moduleName + * @param int $idModule * @return bool */ - public static function deleteByModule($moduleName) + public static function deleteByModule($idModule) { - $taskIds = Db::getInstance()->executeS( - 'SELECT `id_cron_task` - FROM `' . _DB_PREFIX_ . 'qctm_cron_task` - WHERE `module_name` = \'' . pSQL($moduleName) . '\'' + return Db::getInstance()->execute( + 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task` + WHERE `id_module` = ' . (int) $idModule ); - - $db = Db::getInstance(); - $db->execute('START TRANSACTION'); - - try { - if ($taskIds) { - $ids = array_column($taskIds, 'id_cron_task'); - $idsString = implode(',', array_map('intval', $ids)); - $db->execute( - 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` - WHERE `id_cron_task` IN (' . $idsString . ')' - ); - } - - $result = $db->execute( - 'DELETE FROM `' . _DB_PREFIX_ . 'qctm_cron_task` - WHERE `module_name` = \'' . pSQL($moduleName) . '\'' - ); - - $db->execute('COMMIT'); - - return $result; - } catch (Exception $e) { - $db->execute('ROLLBACK'); - - return false; - } } - } diff --git a/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php b/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php index 078eadb01..f08ae839e 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php +++ b/modules/qlocrontaskmanager/classes/QctmCronTaskManagerDb.php @@ -33,16 +33,17 @@ class QctmCronTaskManagerDb $sql = array( 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'qctm_cron_task` ( `id_cron_task` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `module_name` VARCHAR(64) NOT NULL, + `id_module` INT(11) UNSIGNED NOT NULL, `task_name` VARCHAR(128) NOT NULL, `description` VARCHAR(255) NOT NULL, `cron_expression` VARCHAR(64) NOT NULL, `callback` VARCHAR(128) NOT NULL, `active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + `is_system` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, `date_add` DATETIME NOT NULL, `date_upd` DATETIME NOT NULL, PRIMARY KEY (`id_cron_task`), - UNIQUE KEY `module_task` (`module_name`, `task_name`) + UNIQUE KEY `module_task` (`id_module`, `task_name`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;', 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'qctm_cron_task_log` ( @@ -87,7 +88,6 @@ class QctmCronTaskManagerDb { Configuration::updateValue('QCTM_CRON_TASK_MANAGER_TOKEN', Tools::passwdGen(32)); Configuration::updateValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS', 30); - Configuration::updateValue('QCTM_LOG_CLEANUP_LAST_RUN', 0); return true; } @@ -96,7 +96,6 @@ class QctmCronTaskManagerDb { Configuration::deleteByName('QCTM_CRON_TASK_MANAGER_TOKEN'); Configuration::deleteByName('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); - Configuration::deleteByName('QCTM_LOG_CLEANUP_LAST_RUN'); return true; } diff --git a/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php b/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php new file mode 100644 index 000000000..e8ec7e004 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php @@ -0,0 +1,27 @@ +bootstrap = true; + $this->list_no_link = true; + $this->table = 'qctm_cron_task_log'; + $this->className = 'QctmCronTaskLog'; + $this->identifier = 'id_cron_task_log'; + + parent::__construct(); + + $this->initLogList(); + } + + public function init() + { + parent::init(); + + $idCronTask = (int) Tools::getValue('id_cron_task'); + $prefix = $this->getCookieFilterPrefix(); + $cookieKey = $prefix . $this->table . 'Filter_ct!task_name'; + + if ($idCronTask > 0) { + $cronTask = new QctmCronTask($idCronTask); + if (Validate::isLoadedObject($cronTask)) { + $this->_where .= ' AND a.`id_cron_task` = ' . $idCronTask; + $this->context->cookie->{$cookieKey} = $cronTask->task_name; + $this->context->cookie->{'submitFilter' . $this->table} = 1; + $this->toolbar_title = $this->l('Logs for: ') . $cronTask->task_name; + } else { + $this->toolbar_title = $this->l('Cron Task Logs'); + } + } else { + unset($this->context->cookie->{$cookieKey}); + $this->context->cookie->{'submitFilter' . $this->table} = 0; + $this->toolbar_title = $this->l('All Cron Task Logs'); + } + + $this->page_header_toolbar_btn['back_to_tasks'] = array( + 'href' => $this->context->link->getAdminLink('AdminCronTaskManager'), + 'icon' => 'process-icon-back', + 'desc' => $this->l('Back to Tasks'), + ); + } + + protected function initLogList() + { + $this->lang = false; + $this->_orderBy = 'id_cron_task_log'; + $this->_orderWay = 'DESC'; + + $this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'qctm_cron_task` ct + ON (a.`id_cron_task` = ct.`id_cron_task`) + LEFT JOIN `' . _DB_PREFIX_ . 'module` m + ON (m.`id_module` = ct.`id_module`)'; + + $this->_select = 'm.`name` AS `module_name`, ct.`task_name`, ct.`description` AS task_description'; + + $cronTaskLogStatuses = QctmCronTaskLog::getStatuses(); + + $this->fields_list = array( + 'id_cron_task_log' => array( + 'title' => $this->l('ID'), + 'align' => 'center', + 'class' => 'fixed-width-xs', + ), + 'module_name' => array( + 'title' => $this->l('Module'), + 'filter_key' => 'm!name', + 'order_key' => 'm!name', + 'callback' => 'displayModuleName', + ), + 'task_name' => array( + 'title' => $this->l('Task Name'), + 'filter_key' => 'ct!task_name', + 'search' => true, + ), + 'status' => array( + 'title' => $this->l('Status'), + 'align' => 'center', + 'type' => 'select', + 'list' => $cronTaskLogStatuses, + 'filter_key' => 'a!status', + 'callback' => 'getStatusBadge', + ), + 'error_message' => array( + 'title' => $this->l('Error Message'), + 'maxlength' => 80, + ), + 'execution_time' => array( + 'title' => $this->l('Execution Time'), + 'align' => 'center', + 'suffix' => ' s', + 'class' => 'fixed-width-md', + ), + 'date_add' => array( + 'title' => $this->l('Date'), + 'type' => 'datetime', + 'align' => 'right', + 'filter_key' => 'a!date_add', + ), + ); + } + + public function initToolbar() + { + parent::initToolbar(); + unset($this->toolbar_btn['new']); + } + + public function getStatusBadge($status, $row) + { + $this->context->smarty->assign('status', $status); + return $this->createTemplate('qctm_badge_status.tpl')->fetch(); + } + + public function displayModuleName($moduleName, $row) + { + $cacheKey = 'displayModuleName_' . $moduleName; + + if (!Cache::isStored($cacheKey)) { + $module = Module::getInstanceByName($moduleName); + $displayName = ($module && $module->displayName) ? $module->displayName : $moduleName; + Cache::store($cacheKey, $displayName); + } + + return Cache::retrieve($cacheKey); + } + + public function setMedia() + { + parent::setMedia(); + + $this->addCSS($this->module->getPathUri() . 'views/css/admin/qlo_cron_task_manager_admin.css'); + } +} diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 56155d9ec..e37d8c18a 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -27,58 +27,22 @@ if (!defined('_PS_VERSION_')) { class AdminCronTaskManagerController extends ModuleAdminController { - protected $showLogs = false; - - protected function useTaskContext() - { - $this->table = 'qctm_cron_task'; - $this->className = 'QctmCronTask'; - $this->identifier = 'id_cron_task'; - } - public function __construct() { $this->bootstrap = true; - - parent::__construct(); - - if (Tools::getValue('id_cron_task')) { - $this->showLogs = true; - $this->initLogList(); - } else { - $this->initTaskList(); - } - } - - public function init() - { - parent::init(); - if (Tools::getValue('id_cron_task')) { - self::$currentIndex .= '&id_cron_task=' . (int) Tools::getValue('id_cron_task'); - $this->toolbar_title = $this->l('Cron Task Logs'); - $this->page_header_toolbar_btn['back-btn'] = array( - 'href' => $this->getBackButtonLink(), - 'icon' => 'process-icon-back', - 'desc' => $this->l('Back To Tasks'), - ); - } - } - - protected function getBackButtonLink() - { - $backLink = preg_replace('/([&?])id_cron_task=\d+(&)?/', '$1', self::$currentIndex); - $backLink = rtrim($backLink, '&?'); - - return $backLink . '&token=' . $this->token; - } - - protected function initTaskList() - { - $this->useTaskContext(); - $this->lang = false; - $this->_orderBy = 'id_cron_task'; - $this->_orderWay = 'ASC'; $this->list_no_link = true; + $this->table = 'qctm_cron_task'; + $this->className = 'QctmCronTask'; + $this->identifier = 'id_cron_task'; + $this->_orderBy = 'id_cron_task'; + + $this->lang = false; + $this->_orderWay = 'ASC'; + + parent::__construct(); + // Exclude hidden system tasks from the listing + $this->_where = ' AND a.`is_system` = 0'; + $this->_join = 'LEFT JOIN ( SELECT ctl.`id_cron_task`, ctl.`date_add` AS `last_run`, ctl.`status` AS `last_status` FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` ctl @@ -87,11 +51,14 @@ class AdminCronTaskManagerController extends ModuleAdminController FROM `' . _DB_PREFIX_ . 'qctm_cron_task_log` GROUP BY `id_cron_task` ) latest ON ctl.`id_cron_task_log` = latest.`max_id` - ) AS llog ON (llog.`id_cron_task` = a.`id_cron_task`)'; + ) AS llog ON (llog.`id_cron_task` = a.`id_cron_task`) + LEFT JOIN `' . _DB_PREFIX_ . 'module` m ON (m.`id_module` = a.`id_module`)'; - $this->_select = 'llog.`last_run`, llog.`last_status`'; + $this->_select = 'm.`name` as `module_name`, llog.`last_run`, llog.`last_status`, a.`cron_expression` AS `next_run_expression`'; - $this->addRowAction('view'); + $this->addRowAction('viewLogs'); + $this->addRowAction('edit'); + $this->addRowAction('resetModuleCrons'); $this->bulk_actions = array( 'enableSelection' => array( @@ -111,18 +78,21 @@ class AdminCronTaskManagerController extends ModuleAdminController 'class' => 'fixed-width-xs', ), 'module_name' => array( - 'title' => $this->l('Module'), - 'filter_key' => 'a!module_name', + 'title' => $this->l('Module Name'), + 'filter_key' => 'm!name', + 'order_key' => 'm!name', + 'callback' => 'displayModuleName', ), - 'description' => array( - 'title' => $this->l('Task'), - 'filter_key' => 'a!description', + 'task_name' => array( + 'title' => $this->l('Task Name'), + 'filter_key' => 'a!task_name', ), 'cron_expression' => array( 'title' => $this->l('Cron Expression'), 'align' => 'center', 'class' => 'fixed-width-lg', 'search' => false, + 'callback' => 'getCronWithReadable', ), 'last_run' => array( 'title' => $this->l('Last Run'), @@ -137,6 +107,19 @@ class AdminCronTaskManagerController extends ModuleAdminController 'search' => false, 'callback' => 'getLastStatus', ), + 'next_run_expression' => array( + 'title' => $this->l('Next Run'), + 'align' => 'center', + 'filter' => false, + 'search' => false, + 'callback' => 'getNextRun', + ), + 'date_add' => array( + 'title' => $this->l('Added Time'), + 'align' => 'center', + 'filter' => false, + 'search' => false, + ), 'active' => array( 'title' => $this->l('Active'), 'active' => 'status', @@ -146,127 +129,322 @@ class AdminCronTaskManagerController extends ModuleAdminController 'filter_key' => 'a!active', ), ); + } - protected function initLogList() + public function init() { - $this->table = 'qctm_cron_task_log'; - $this->className = 'QctmCronTaskLog'; - $this->identifier = 'id_cron_task_log'; - $this->lang = false; - $this->_orderBy = 'id_cron_task_log'; - $this->_orderWay = 'DESC'; - $this->list_no_link = true; + parent::init(); - $idCronTask = (int) Tools::getValue('id_cron_task'); - if ($idCronTask) { - $this->_where = ' AND a.`id_cron_task` = ' . $idCronTask; + if(!Validate::isUnsignedInt($idCronTask = Tools::getValue('id_cron_task')) && $this->display == 'edit') { + $this->errors = array($this->l('Invalid Cron Task ID')); + $this->display = 'list'; + }else{ + if (Validate::isUnsignedInt($idCronTask = Tools::getValue('id_cron_task')) && Validate::isLoadedObject($objCronTask = new QctmCronTask($idCronTask)) && $this->display == 'edit' ) { + $this->toolbar_title = $this->l('Edit Cron Task: ') . $objCronTask->task_name; + } } - $this->_select = 'ct.`module_name`, ct.`description` as task_description'; - $this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'qctm_cron_task` ct - ON (a.`id_cron_task` = ct.`id_cron_task`)'; - - $cronTaskLogStatuses = QctmCronTaskLog::getStatuses(); - - $this->fields_list = array( - 'id_cron_task_log' => array( - 'title' => $this->l('ID'), - 'align' => 'center', - 'class' => 'fixed-width-xs', - ), - 'module_name' => array( - 'title' => $this->l('Module'), - 'filter_key' => 'ct!module_name', - ), - 'task_description' => array( - 'title' => $this->l('Task'), - 'filter' => false, - 'search' => false, - ), - 'status' => array( - 'title' => $this->l('Status'), - 'align' => 'center', - 'type' => 'select', - 'list' => $cronTaskLogStatuses, - 'filter_key' => 'a!status', - 'callback' => 'getStatusBadge', - ), - 'error_message' => array( - 'title' => $this->l('Error Message'), - 'maxlength' => 80, - ), - 'execution_time' => array( - 'title' => $this->l('Execution Time'), - 'align' => 'center', - 'suffix' => ' s', - 'class' => 'fixed-width-md', - ), - 'date_add' => array( - 'title' => $this->l('Date'), - 'type' => 'datetime', - 'align' => 'right', - 'filter_key' => 'a!date_add', - ), - ); } public function initToolbar() { parent::initToolbar(); - unset($this->toolbar_btn['new']); + + $this->page_header_toolbar_btn['view_all_logs'] = array( + 'href' => $this->context->link->getAdminLink('AdminCronTaskLogs'), + 'desc' => $this->l('View All Logs'), + 'icon' => 'process-icon-list', + ); } + + + public function renderForm() + { + $cronExpression = '* * * * *'; + if(Validate::isLoadedObject($this->object)){ + $cronExpression = $this->object->cron_expression; + } + + $isValidExpression = \Cron\CronExpression::isValidExpression($cronExpression); + if ($isValidExpression) { + try { + $readable = $this->l('Runs ') . \Lorisleiva\CronTranslator\CronTranslator::translate($cronExpression); + } catch (\Exception $e) { + $readable = ''; + } + } else { + $readable = $this->l('Invalid cron expression.'); + } + + $moduleDisplayName = ''; + if (Validate::isLoadedObject($this->object) && $this->object->id_module) { + $module = Module::getInstanceById($this->object->id_module); + $moduleDisplayName = ($module && $module->displayName) ? $module->displayName : '#' . (int) $this->object->id_module; + } + $this->fields_value['module_display_name'] = $moduleDisplayName; + + $this->context->smarty->assign(array( + 'qctmCronExpression' => $cronExpression, + 'qctmCronReadable' => $readable, + 'qctmAjaxUrl' => $this->context->link->getAdminLink($this->controller_name) . '&ajax=1', + )); + + $this->fields_form = array( + 'legend' => array( + 'title' => $this->l('Cron Task'), + 'icon' => 'icon-time', + ), + 'input' => array( + array( + 'type' => 'text', + 'label' => $this->l('Module Name'), + 'name' => 'module_display_name', + 'readonly' => true, + 'col' => 5, + 'desc' => $this->l('The module that owns this task.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Task Name'), + 'name' => 'task_name', + 'readonly' => true, + 'required' => true, + 'col' => 5, + 'hint' => $this->l('e.g. send_daily_report'), + 'desc' => $this->l('A unique snake_case identifier for this task within the module. Used to detect duplicate registrations.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Callback Method'), + 'name' => 'callback', + 'required' => true, + 'readonly' => true, + 'col' => 5, + 'hint' => $this->l('e.g. runDailyReport'), + 'desc' => $this->l('The public method name on the module class that will be called when this task fires. Must exist on the module at runtime.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Description'), + 'name' => 'description', + 'required' => true, + 'col' => 7, + 'hint' => $this->l('e.g. Sends the daily booking summary report'), + 'desc' => $this->l('A short human-readable description of what this task does. Shown in the task list.'), + ), + array( + 'type' => 'text', + 'label' => $this->l('Cron Expression'), + 'name' => 'cron_expression', + 'required' => true, + 'col' => 4, + 'class' => 'qctm-cron-expression', + 'hint' => $this->l('e.g. 0 0 * * *'), + 'desc' => $readable, + ), + array( + 'type' => 'html', + 'name' => 'qctm_cron_helper', + 'html_content' => $this->createTemplate('qctm_cron_expression_helper.tpl')->fetch(), + ), + array( + 'type' => 'switch', + 'label' => $this->l('Active'), + 'name' => 'active', + 'required' => false, + 'desc' => $this->l('Disabled tasks are skipped by the cron dispatcher even if their schedule is due.'), + 'values' => array( + array('id' => 'active_on', 'value' => 1, 'label' => $this->l('Enabled')), + array('id' => 'active_off', 'value' => 0, 'label' => $this->l('Disabled')), + ), + ), + ), + 'submit' => array( + 'title' => $this->l('Save'), + ), + ); + return parent::renderForm(); + } + + public function ajaxProcessGetCronReadable() + { + $expression = Tools::getValue('cron_expression'); + $result = array('readable' => $this->l('Invalid cron expression'), 'valid' => false); + + if (\Cron\CronExpression::isValidExpression($expression)) { + $result['valid'] = true; + try { + $result['readable'] = $this->l('Runs ') . \Lorisleiva\CronTranslator\CronTranslator::translate($expression); + } catch (\Exception $e) { + $result['readable'] = $this->l('Invalid cron expression'); + } + } + + die(Tools::jsonEncode($result)); + } + + public function processSave() + { + if (!$this->validateCronExpression()) { + $this->display = 'edit'; + return false; + } + + return parent::processSave(); + } + + protected function validateCronExpression() + { + $cronExpression = Tools::getValue('cron_expression'); + + if (!\Cron\CronExpression::isValidExpression($cronExpression)) { + $this->errors[] = $this->l('Invalid cron expression. Must have exactly 5 parts: minute hour day month weekday. Example: * * * * *'); + return false; + } + + return true; + } public function processStatus() { - $this->showLogs = false; - $this->useTaskContext(); - return parent::processStatus(); } protected function processBulkEnableSelection() { - $this->showLogs = false; - $this->useTaskContext(); - return parent::processBulkEnableSelection(); } protected function processBulkDisableSelection() { - $this->showLogs = false; - $this->useTaskContext(); - return parent::processBulkDisableSelection(); } + public function displayViewLogsLink($token = null, $id = null, $row = array()) + { + $this->context->smarty->assign( + 'viewLogsLink', + $this->context->link->getAdminLink('AdminCronTaskLogs') . '&id_cron_task=' . (int) $id + ); + return $this->createTemplate('qctm_view_logs_link.tpl')->fetch(); + } + + public function displayResetModuleCronsLink($token = null, $id = null, $row = array()) + { + $this->context->smarty->assign( + 'resetLink', + $this->context->link->getAdminLink('AdminCronTaskManager') + . '&action=resetModuleCrons&' . $this->identifier . '=' . (int) $id + ); + return $this->createTemplate('qctm_reset_module_crons_link.tpl')->fetch(); + } + + public function processResetModuleCrons() + { + $idCronTask = (int) Tools::getValue($this->identifier); + $cronTask = new QctmCronTask($idCronTask); + + if (!Validate::isLoadedObject($cronTask)) { + $this->errors[] = $this->l('Cron task not found.'); + return false; + } + + $module = Module::getInstanceById($cronTask->id_module); + + if (!$module) { + $this->errors[] = $this->l('Module not found.'); + return false; + } + + if (!method_exists($module, 'hookRegisterCronTasks')) { + $this->errors[] = $this->l('Module does not define any cron tasks.'); + return false; + } + + $tasks = $module->hookRegisterCronTasks(); + + if (empty($tasks) || !is_array($tasks)) { + $this->errors[] = $this->l('No tasks returned by module.'); + return false; + } + + foreach ($tasks as $task) { + if ($task['name'] === $cronTask->task_name) { + $cronTask->cron_expression = $task['cron']; + $cronTask->description = $task['description']; + $cronTask->callback = $task['callback']; + + if (!$cronTask->save()) { + $this->errors[] = $this->l('Failed to reset cron task.'); + return false; + } + + $this->confirmations[] = $this->l('Cron task reset to module defaults successfully.'); + return true; + } + } + + $this->errors[] = $this->l('Task definition not found in module.'); + return false; + } + + public function getCronWithReadable($cronExpression, $row) + { + try { + $readable = \Lorisleiva\CronTranslator\CronTranslator::translate($cronExpression); + } catch (\Exception $e) { + $readable = $cronExpression; + } + + return '' . htmlspecialchars($cronExpression) . '' + . '
' . htmlspecialchars($readable) . ''; + } + + public function displayModuleName($moduleName, $row) + { + $cacheKey = 'displayModuleName_' . $moduleName; + + if (!Cache::isStored($cacheKey)) { + $module = Module::getInstanceByName($moduleName); + $displayName = ($module && $module->displayName) ? $module->displayName : $moduleName; + Cache::store($cacheKey, $displayName); + } + + return Cache::retrieve($cacheKey); + } + + public function getNextRun($cronExpression, $row) + { + if (!$cronExpression || !\Cron\CronExpression::isValidExpression($cronExpression)) { + return '-'; + } + + try { + $next = \Cron\CronExpression::factory($cronExpression)->getNextRunDate(); + return $next->format('Y-m-d H:i:s'); + } catch (\Exception $e) { + return '-'; + } + } + public function getLastStatus($lastStatus, $row) { $this->context->smarty->assign('status', $lastStatus); return $this->createTemplate('qctm_badge_status.tpl')->fetch(); } - public function getStatusBadge($status, $row) - { - $this->context->smarty->assign('status', $status); - return $this->createTemplate('qctm_badge_status.tpl')->fetch(); - } - - public function displayViewlogsLink($token, $id, $name = null) - { - $logLink = self::$currentIndex.'&id_cron_task=' . (int) $id. '&token=' . $this->token; - - $this->context->smarty->assign('logLink', $logLink); - - return $this->createTemplate('qctm_view_log.tpl')->fetch(); - } public function setMedia() { parent::setMedia(); + Media::addJsDef( + array( + 'INVALID_CRON_EXPRESSION' => $this->l('Invalid cron expression.'), + ) + ); $this->addCSS($this->module->getPathUri() . 'views/css/admin/qlo_cron_task_manager_admin.css'); $this->addJS($this->module->getPathUri() . 'views/js/admin/qlo_cron_task_manager_admin.js'); } diff --git a/modules/qlocrontaskmanager/logo.gif b/modules/qlocrontaskmanager/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c7f7e0dbfd300c2751243be368e3ad7bba5d51f GIT binary patch literal 615 zcmZ?wbhEHb6krfw_~yX?1Rrw(KIR5|$_eCjF;mFUYVn3Wr{Cqm$>zRa)XR`lZ&HHq|?Ej5It*nRI*^hOy9&2YmDd}Ciab;8E zq@{o7HolxU>F@G}@5}rDFYo)mdG7PQTOaM;`}uOim+ReMZ%_SpZ^8TH`+gtX`1|zU z-#52?f3)hyv#md0?SFat_@7%B{yx6_`{R}0AFlrSa{un5H~)To{r~?z$ZvxlDE?$& z1UXXiKeuOYVtT4?VtQtBHg9@jZf>Hjfu4z}iK!`r4oC$k!Wh_>H!w6cX=-RTcXX(! zX?Cl1HmfS@Xic4}EhE#ZsyK7r{0@1!ney}Jbx6sW+gZrFtaq`S)x6Sdlf3CpQG+&h8U4-9_or&Q@%fZQPEc|6pW;i-4FtBk6sRV3LP-0;hijgSb$P8%c K=VNAMum%7;I@&z| literal 0 HcmV?d00001 diff --git a/modules/qlocrontaskmanager/logo.png b/modules/qlocrontaskmanager/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..908f03fc7939e1b57947b7515ec02fc8f7329cc2 GIT binary patch literal 1543 zcmV+i2Kf1jP)2fe=w04|ML%{(LcaMv5AR<#9)jX9}pjjuWEcHhC*s2qClzzYAH5-T-&?e z-tFGr&Nws2T{(J3@3Gf5$9MSdkqe3KBDnx}%cD@7^^hITjCwvqRaVd8?GGG0F6y{=ee@Ev!Cz1gN)q|Vwq@9| z-T}W`hL0t`N5Mw}J8<1>opY>zbJv_YS#^rUg>Pz{M8`u(9~_>bw{zguI2Cy*lqB-K z7aE1Y6vVPHKq9!_kAiu4gN~y+=9>L0?-RV!Lr@Aui(Io(%@D{*Bn%wg`GrU$T3x=E zg11J8#}Uig*0x4MsT3{pZh;&<8cvs2xu;+UJ~v9H=*58FlXh?8fUt$&@rF z2}y|D&@~vb2+DzPF-4lDf$-F5&UQ(4gC;bTiXFqXEQ11E)8UMDH} zg@qX7uoGKFx>}!xe)9~Ltv(F7wh{5MpVQH-UG76!J7bkujCrfILp zCDjEp*vAB)MKKV4iAK_#>`O@cN#vE6tQ;VUAy%U$iDtnSL^me&7T+Fcdb+Pu!UqW{`@gKQJYMYvjzAa>(j zI9r~qOm|g%ZNgbqU;F*xu2yvLp6)ttr(zz|#v7p+PMDD}UfL5!&kY^D|HV-Zw!3R; zm=!apOeLK_@52g>({x}auaF}mF;Pv(d_Ke90o0u0H*RH#SyYj>_M!wF^XtWS; zEh&)14Un|~sH-_Sq(q8xD_N z%3bdkn9E%qnjdbgLp(f$X1>NY_Y0?A6= zZ@DQT^7w%#5Nzp$G82Tpb{j~o3-a=&jB9*9io7UEB;U^GFSRsbvZ%0mXl3zpS%xI5 zLa-z!pM(5H@Z&|93AE~_p-v>fk(j^sKyuf^92|tX_i-4jHi!tyY)8;}@u%6}W~ZMS zUWiCqtb` trur9`N}(#UQmBfo6sjUCg{sI(;eR8#cHgLO3X=c;002ovPDHLkV1jhf-Esf` literal 0 HcmV?d00001 diff --git a/modules/qlocrontaskmanager/qlocrontaskmanager.php b/modules/qlocrontaskmanager/qlocrontaskmanager.php index 4da681801..9675cedc8 100644 --- a/modules/qlocrontaskmanager/qlocrontaskmanager.php +++ b/modules/qlocrontaskmanager/qlocrontaskmanager.php @@ -25,7 +25,7 @@ if (!defined('_PS_VERSION_')) { exit; } -include_once 'define.php'; +require_once dirname(__FILE__).'/classes/QctmRequiredClasses.php'; class QloCronTaskManager extends Module { @@ -41,8 +41,8 @@ class QloCronTaskManager extends Module parent::__construct(); $this->displayName = $this->l('Cron Task Manager'); - $this->description = $this->l('Centralized cron scheduling system. One server cron entry manages all module tasks.'); - $this->confirmUninstall = $this->l('Are you sure you want to uninstall? All cron task data will be lost.'); + $this->description = $this->l('A centralized cron scheduling system where a single server cron entry manages all module tasks.'); + $this->confirmUninstall = $this->l('Are you sure you want to uninstall this module? All cron task data will be lost.'); } @@ -59,8 +59,10 @@ class QloCronTaskManager extends Module $output .= $this->displayConfirmation($this->l('Token has been regenerated. Update your server cron entry with the new URL.')); } - $this->context->controller->addCSS($this->_path . 'views/css/admin/qlo_cron_task_manager_admin.css'); - $this->context->controller->addJS($this->_path . 'views/js/admin/qlo_cron_task_manager_admin.js'); + if(Tools::getValue('module_name') === $this->name) { + $this->context->controller->addCSS($this->_path . 'views/css/admin/qlo_cron_task_manager_admin.css'); + $this->context->controller->addJS($this->_path . 'views/js/admin/qlo_cron_task_manager_admin.js'); + } $output .= $this->renderCronCommandPanel(); $output .= $this->renderSettingsForm(); @@ -70,7 +72,11 @@ class QloCronTaskManager extends Module protected function processSaveSettings() { - $logRetention = (int) Tools::getValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); + $logRetention = Tools::getValue('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); + + if(!Validate::isUnsignedInt($logRetention)) { + return $this->displayError($this->l('Log retention must be an integer value.')); + } if ($logRetention < 1 || $logRetention > 365) { return $this->displayError($this->l('Log retention must be between 1 and 365 days.')); @@ -124,7 +130,7 @@ class QloCronTaskManager extends Module 'name' => 'QCTM_CRON_TASK_MANAGER_TOKEN', 'readonly' => true, 'col' => 6, - 'desc' => $this->l('This token secures the cron endpoint. Regenerating it will require updating your server cron entry.'), + 'desc' => $this->l('This token secures the cron endpoint. After regenerating it, you will need to update your server cron entry.'), ), array( 'type' => 'text', @@ -132,6 +138,8 @@ class QloCronTaskManager extends Module 'name' => 'QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS', 'col' => 2, 'required' => true, + 'validation' => 'isUnsignedInt', + 'suffix' => $this->l('days'), 'desc' => $this->l('Number of days to keep execution logs.'), ), ), @@ -166,18 +174,18 @@ class QloCronTaskManager extends Module if (method_exists($module, 'hookRegisterCronTasks')) { $tasks = $module->hookRegisterCronTasks(); if (!empty($tasks) && is_array($tasks)) { - $this->registerTasksForModule($module->name, $tasks); + $this->registerTasksForModule($module->id, $tasks); } } } public function hookActionModuleUninstallBefore($params) { - if (!isset($params['object']) || !($params['object'] instanceof Module)) { + if (!isset($params['object']) || !($params['object'] instanceof Module) || $params['object']->id == $this->id) { return; } - QctmCronTask::deleteByModule($params['object']->name); + QctmCronTask::deleteByModule($params['object']->id); } protected function syncAllExistingModules() @@ -187,7 +195,10 @@ class QloCronTaskManager extends Module if (!empty($allTasks) && is_array($allTasks)) { foreach ($allTasks as $moduleName => $tasks) { if (!empty($tasks) && is_array($tasks)) { - $this->registerTasksForModule($moduleName, $tasks); + $module = Module::getInstanceByName($moduleName); + if($module){ + $this->registerTasksForModule($module->id, $tasks); + } } } } @@ -199,27 +210,34 @@ class QloCronTaskManager extends Module * @param string $moduleName * @param array $tasks */ - protected function registerTasksForModule($moduleName, $tasks) + protected function registerTasksForModule($idModule, $tasks) { + $module = Module::getInstanceById($idModule); + if (!$module) { + return; + } + + foreach ($tasks as $task) { - if (!$this->validateTask($moduleName, $task)) { + if (!$this->validateTask($module, $task)) { continue; } - if (QctmCronTask::getByModuleAndName($moduleName, $task['name'])) { + if (QctmCronTask::getByModuleAndName($idModule, $task['name'])) { continue; } $cronTask = new QctmCronTask(); - $cronTask->module_name = $moduleName; + $cronTask->id_module = $idModule; $cronTask->task_name = $task['name']; $cronTask->description = $task['description']; $cronTask->cron_expression = $task['cron']; $cronTask->callback = $task['callback']; $cronTask->active = 1; + $cronTask->is_system = 0; if (!$cronTask->save()) { PrestaShopLogger::addLog( - 'QloCronTaskManager: failed to register task "' . $task['name'] . '" for module "' . $moduleName . '"', + 'QloCronTaskManager: failed to register task "' . $task['name'] . '" for module "' . $module->name . '"', 3 ); } @@ -233,10 +251,10 @@ class QloCronTaskManager extends Module * @param array $task * @return bool */ - protected function validateTask($moduleName, $task) + protected function validateTask(Module $module, $task) { - if (empty($task['name']) || empty($task['description']) - || empty($task['cron']) || empty($task['callback'])) { + if (!Validate::isGenericName($task['name']) || !Validate::isGenericName($task['description']) + || !Validate::isString($task['cron']) || !Validate::isString($task['callback'])) { return false; } @@ -244,8 +262,7 @@ class QloCronTaskManager extends Module return false; } - $module = Module::getInstanceByName($moduleName); - if (!$module || !method_exists($module, $task['callback'])) { + if (!method_exists($module, $task['callback'])) { return false; } @@ -254,23 +271,45 @@ class QloCronTaskManager extends Module public function dispatch() { - $now = new DateTime(); - $tasks = QctmCronTask::getActiveTasks(); + if (!$this->active) { + return; + } + $tasks = QctmCronTask::getActiveTasks(); if (empty($tasks)) { return; } + $now = new DateTime(); + foreach ($tasks as $task) { + if (!\Cron\CronExpression::isValidExpression($task['cron_expression'])) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Invalid cron expression "' . $task['cron_expression'] . '"' + ); + continue; + } + $cron = \Cron\CronExpression::factory($task['cron_expression']); if (!$cron->isDue($now)) { continue; } - $module = Module::getInstanceByName($task['module_name']); + $module = Module::getInstanceById($task['id_module']); - if (!$module || !$module->active) { + if (!$module) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Module with ID "' . (int) $task['id_module'] . '" could not be loaded' + ); + continue; + } + + if (!$module->active) { continue; } @@ -280,7 +319,7 @@ class QloCronTaskManager extends Module QctmCronTaskLog::addLog( $task['id_cron_task'], QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, - 'Callback method "' . $callback . '" not found on module "' . $task['module_name'] . '"' + 'Callback method "' . $callback . '" not found on module "' . $module->name . '"' ); continue; } @@ -289,43 +328,34 @@ class QloCronTaskManager extends Module try { $module->{$callback}(); - $executionTime = round(microtime(true) - $startTime, 4); QctmCronTaskLog::addLog( $task['id_cron_task'], QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_SUCCESS, null, - $executionTime + round(microtime(true) - $startTime, 4) ); } catch (\Throwable $e) { - $executionTime = round(microtime(true) - $startTime, 4); - QctmCronTaskLog::addLog( $task['id_cron_task'], QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, $e->getMessage(), - $executionTime + round(microtime(true) - $startTime, 4) ); } } - - $this->cleanOldLogs(); } - public function cleanOldLogs() + /** + * Called by the system log-cleanup cron task (daily at midnight). + */ + public function runLogCleanup() { - $lastRun = (int) Configuration::get('QCTM_LOG_CLEANUP_LAST_RUN'); - - if ($lastRun && (time() - $lastRun) < 86400) { - return; - } - $days = (int) Configuration::get('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); if ($days > 0) { QctmCronTaskLog::deleteOlderThan($days); } - Configuration::updateValue('QCTM_LOG_CLEANUP_LAST_RUN', time()); } public function install() @@ -333,11 +363,11 @@ class QloCronTaskManager extends Module $db = new QctmCronTaskManagerDb(); if (!parent::install() - || !$this->registerHook('actionModuleInstallAfter') - || !$this->registerHook('actionModuleUninstallBefore') + || !$this->registerHooks() || !$db->createTables() || !$db->installDefaultData() || !$this->installTabs() + || !$this->installDefaultTasks() ) { return false; } @@ -347,6 +377,16 @@ class QloCronTaskManager extends Module return true; } + public function registerHooks() + { + return $this->registerHook( + array( + 'actionModuleInstallAfter', + 'actionModuleUninstallBefore', + ) + ); + } + public function uninstall() { $db = new QctmCronTaskManagerDb(); @@ -362,28 +402,62 @@ class QloCronTaskManager extends Module return true; } - protected function installTabs() + protected function installDefaultTasks() { - $tab = new Tab(); - $tab->class_name = 'AdminCronTaskManager'; - $tab->id_parent = (int) Tab::getIdFromClassName('AdminTools'); - $tab->module = $this->name; - $tab->position = Tab::getNewLastPosition($tab->id_parent); - - foreach (Language::getLanguages(false) as $language) { - $tab->name[$language['id_lang']] = $this->l('Cron Task Manager'); + if (QctmCronTask::getByModuleAndName($this->id, 'log_cleanup')) { + return true; } - return $tab->add(); + $task = new QctmCronTask(); + $task->id_module = (int) $this->id; + $task->task_name = 'log_cleanup'; + $task->description = 'Automatic log cleanup'; + $task->cron_expression = '0 0 * * *'; + $task->callback = 'runLogCleanup'; + $task->active = 1; + $task->is_system = 1; + + return $task->save(); + } + + protected function installTabs() + { + $tabCronTasks = new Tab(); + $tabCronTasks->class_name = 'AdminCronTaskManager'; + $tabCronTasks->id_parent = (int) Tab::getIdFromClassName('AdminTools'); + $tabCronTasks->module = $this->name; + $tabCronTasks->position = Tab::getNewLastPosition($tabCronTasks->id_parent); + + foreach (Language::getLanguages(false) as $language) { + $tabCronTasks->name[$language['id_lang']] = $this->l('Cron Task Manager'); + } + + if (!$tabCronTasks->add()) { + return false; + } + + // Hidden tab — accessible via URL but not shown in navigation menu + $tabLogs = new Tab(); + $tabLogs->class_name = 'AdminCronTaskLogs'; + $tabLogs->id_parent = -1; + $tabLogs->module = $this->name; + + foreach (Language::getLanguages(false) as $language) { + $tabLogs->name[$language['id_lang']] = $this->l('Cron Task Logs'); + } + + return $tabLogs->add(); } protected function uninstallTabs() { - $idTab = (int) Tab::getIdFromClassName('AdminCronTaskManager'); - if ($idTab) { - $tab = new Tab($idTab); - if (!$tab->delete()) { - return false; + foreach (array('AdminCronTaskManager', 'AdminCronTaskLogs') as $className) { + $idTab = (int) Tab::getIdFromClassName($className); + if ($idTab) { + $tab = new Tab($idTab); + if (!$tab->delete()) { + return false; + } } } diff --git a/modules/qlocrontaskmanager/vendor/autoload.php b/modules/qlocrontaskmanager/vendor/autoload.php index 0f90a20bd..947105c29 100644 --- a/modules/qlocrontaskmanager/vendor/autoload.php +++ b/modules/qlocrontaskmanager/vendor/autoload.php @@ -3,8 +3,18 @@ // autoload.php @generated by Composer if (PHP_VERSION_ID < 50600) { - echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - exit(1); + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/modules/qlocrontaskmanager/vendor/composer/ClassLoader.php b/modules/qlocrontaskmanager/vendor/composer/ClassLoader.php index afef3fa2a..7824d8f7e 100644 --- a/modules/qlocrontaskmanager/vendor/composer/ClassLoader.php +++ b/modules/qlocrontaskmanager/vendor/composer/ClassLoader.php @@ -42,35 +42,37 @@ namespace Composer\Autoload; */ class ClassLoader { - /** @var ?string */ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ private $vendorDir; // PSR-4 /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixLengthsPsr4 = array(); /** - * @var array[] - * @psalm-var array> + * @var array> */ private $prefixDirsPsr4 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** - * @var array[] - * @psalm-var array> + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> */ private $prefixesPsr0 = array(); /** - * @var array[] - * @psalm-var array + * @var list */ private $fallbackDirsPsr0 = array(); @@ -78,8 +80,7 @@ class ClassLoader private $useIncludePath = false; /** - * @var string[] - * @psalm-var array + * @var array */ private $classMap = array(); @@ -87,29 +88,29 @@ class ClassLoader private $classMapAuthoritative = false; /** - * @var bool[] - * @psalm-var array + * @var array */ private $missingClasses = array(); - /** @var ?string */ + /** @var string|null */ private $apcuPrefix; /** - * @var self[] + * @var array */ private static $registeredLoaders = array(); /** - * @param ?string $vendorDir + * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); } /** - * @return string[] + * @return array> */ public function getPrefixes() { @@ -121,8 +122,7 @@ class ClassLoader } /** - * @return array[] - * @psalm-return array> + * @return array> */ public function getPrefixesPsr4() { @@ -130,8 +130,7 @@ class ClassLoader } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirs() { @@ -139,8 +138,7 @@ class ClassLoader } /** - * @return array[] - * @psalm-return array + * @return list */ public function getFallbackDirsPsr4() { @@ -148,8 +146,7 @@ class ClassLoader } /** - * @return string[] Array of classname => path - * @psalm-return array + * @return array Array of classname => path */ public function getClassMap() { @@ -157,8 +154,7 @@ class ClassLoader } /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap + * @param array $classMap Class to filename map * * @return void */ @@ -175,24 +171,25 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, - (array) $paths + $paths ); } @@ -201,19 +198,19 @@ class ClassLoader $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; + $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], - (array) $paths + $paths ); } } @@ -222,9 +219,9 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * @@ -232,17 +229,18 @@ class ClassLoader */ public function addPsr4($prefix, $paths, $prepend = false) { + $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( - (array) $paths, + $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, - (array) $paths + $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { @@ -252,18 +250,18 @@ class ClassLoader throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; + $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, + $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], - (array) $paths + $paths ); } } @@ -272,8 +270,8 @@ class ClassLoader * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories * * @return void */ @@ -290,8 +288,8 @@ class ClassLoader * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * @@ -425,7 +423,8 @@ class ClassLoader public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -476,9 +475,9 @@ class ClassLoader } /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. + * Returns the currently registered loaders keyed by their corresponding vendor directories. * - * @return self[] + * @return array */ public static function getRegisteredLoaders() { @@ -555,18 +554,26 @@ class ClassLoader return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php b/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php index c6b54af7b..2052022fd 100644 --- a/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php +++ b/modules/qlocrontaskmanager/vendor/composer/InstalledVersions.php @@ -26,12 +26,23 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** * @var bool|null */ @@ -98,7 +109,7 @@ class InstalledVersions { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } @@ -119,7 +130,7 @@ class InstalledVersions */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { - $constraint = $parser->parseConstraints($constraint); + $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); @@ -309,6 +320,24 @@ class InstalledVersions { self::$installed = $data; self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; } /** @@ -322,17 +351,27 @@ class InstalledVersions } $installed = array(); + $copiedLocalDir = false; if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; } } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } } } @@ -340,12 +379,17 @@ class InstalledVersions // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; } else { self::$installed = array(); } } - $installed[] = self::$installed; + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } return $installed; } diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php b/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php index e12811ddf..bc4d7c736 100644 --- a/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php @@ -6,5 +6,6 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( + 'Lorisleiva\\CronTranslator\\' => array($vendorDir . '/lorisleiva/cron-translator/src'), 'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'), ); diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_static.php b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php index e173eaa32..f60e1ecf0 100644 --- a/modules/qlocrontaskmanager/vendor/composer/autoload_static.php +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php @@ -7,6 +7,10 @@ namespace Composer\Autoload; class ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3 { public static $prefixLengthsPsr4 = array ( + 'L' => + array ( + 'Lorisleiva\\CronTranslator\\' => 26, + ), 'C' => array ( 'Cron\\' => 5, @@ -14,6 +18,10 @@ class ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3 ); public static $prefixDirsPsr4 = array ( + 'Lorisleiva\\CronTranslator\\' => + array ( + 0 => __DIR__ . '/..' . '/lorisleiva/cron-translator/src', + ), 'Cron\\' => array ( 0 => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron', diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.json b/modules/qlocrontaskmanager/vendor/composer/installed.json index 9424b69ae..f88a76e51 100644 --- a/modules/qlocrontaskmanager/vendor/composer/installed.json +++ b/modules/qlocrontaskmanager/vendor/composer/installed.json @@ -66,8 +66,67 @@ } ], "install-path": "../dragonmantank/cron-expression" + }, + { + "name": "lorisleiva/cron-translator", + "version": "v0.3.2", + "version_normalized": "0.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/lorisleiva/cron-translator.git", + "reference": "d49dd7732197c2e967a66968c9323c81826c86e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lorisleiva/cron-translator/zipball/d49dd7732197c2e967a66968c9323c81826c86e5", + "reference": "d49dd7732197c2e967a66968c9323c81826c86e5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.21 || ^9.5" + }, + "time": "2022-06-16T07:37:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Lorisleiva\\CronTranslator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Loris LEIVA", + "email": "loris.leiva@gmail.com", + "homepage": "https://lorisleiva.com" + } + ], + "description": "Makes CRON expressions human-readable", + "homepage": "https://github.com/lorisleiva/cron-translator", + "keywords": [ + "cron", + "expression", + "human" + ], + "support": { + "issues": "https://github.com/lorisleiva/cron-translator/issues", + "source": "https://github.com/lorisleiva/cron-translator/tree/v0.3.2" + }, + "funding": [ + { + "url": "https://github.com/lorisleiva", + "type": "github" + } + ], + "install-path": "../lorisleiva/cron-translator" } ], - "dev": false, + "dev": true, "dev-package-names": [] } diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.php b/modules/qlocrontaskmanager/vendor/composer/installed.php index 0dbb6b6de..0833755c0 100644 --- a/modules/qlocrontaskmanager/vendor/composer/installed.php +++ b/modules/qlocrontaskmanager/vendor/composer/installed.php @@ -1,13 +1,13 @@ array( 'name' => 'qloapps/qlocrontaskmanager', - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => NULL, + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'reference' => 'e05c7ad488aaf4098ed194c7ddf0024160987974', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'dev' => false, + 'dev' => true, ), 'versions' => array( 'dragonmantank/cron-expression' => array( @@ -19,6 +19,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'lorisleiva/cron-translator' => array( + 'pretty_version' => 'v0.3.2', + 'version' => '0.3.2.0', + 'reference' => 'd49dd7732197c2e967a66968c9323c81826c86e5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../lorisleiva/cron-translator', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'mtdowling/cron-expression' => array( 'dev_requirement' => false, 'replaced' => array( @@ -26,9 +35,9 @@ ), ), 'qloapps/qlocrontaskmanager' => array( - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => NULL, + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', + 'reference' => 'e05c7ad488aaf4098ed194c7ddf0024160987974', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore new file mode 100644 index 000000000..2825f8dca --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore @@ -0,0 +1,5 @@ +vendor +composer.lock +.phpunit.result.cache +build +.idea \ No newline at end of file diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE new file mode 100644 index 000000000..fdf460a8f --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Loris Leiva + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md new file mode 100644 index 000000000..8fe69121c --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md @@ -0,0 +1,51 @@ +# CRON Translator +⏰️ Makes CRON expressions human-readable + +![intro-rounded](https://user-images.githubusercontent.com/3642397/60768671-7d6c7100-a0be-11e9-8cee-8a8d2780d76f.png) + +## Installation + +```sh +composer require lorisleiva/cron-translator +``` + +## Usage + +```php +use Lorisleiva\CronTranslator\CronTranslator; + +CronTranslator::translate('* * * * *'); // => Every minute +CronTranslator::translate('30 22 * * *'); // => Every day at 10:30pm +CronTranslator::translate('0 16 * * 1'); // => Every Monday at 4:00pm +CronTranslator::translate('0 0 1 1 *'); // => Every year on January the 1st at 12:00am +CronTranslator::translate('0 0 1 * *'); // => The 1st of every month at 12:00am +CronTranslator::translate('0 * * * 1'); // => Once an hour on Mondays +CronTranslator::translate('* 1-20 * * *'); // => Every minute 20 hours a day +CronTranslator::translate('0,30 * * * *'); // => Twice an hour +CronTranslator::translate('0 1-5 * * *'); // => 5 times a day +CronTranslator::translate('0 1 1-5 * *'); // => 5 days a month at 1:00am +CronTranslator::translate('*/2 * * * *'); // => Every 2 minutes +CronTranslator::translate('* 1/3 2 * *'); // => Every minute of every 3 hours on the 2nd of every month +CronTranslator::translate('1-3/5 * * * *'); // => 3 times every 5 minutes +CronTranslator::translate('1,2 0 */2 1,2 *'); // => Twice an hour every 2 days 2 months a year at 12am +``` + +You may also provide a locale as a second argument and whether to format the time using the 24-hour format as a third argument. + + +```php +CronTranslator::translate('30 18 * * *', 'fr'); // => Chaque jour à 6:30pm +CronTranslator::translate('30 18 * * *', 'fr', true); // => Chaque jour à 18:30 +``` + +The following locales are currently supported. Feel free to PR more locales if you need them. 🙂 +- `ar` — Arabic +- `de` — German +- `en` — English +- `es` — Spanish +- `fr` — French +- `lv` — Latvian +- `nl` — Dutch +- `pt` — Portuguese +- `zh` — Chinese +- `sk` - Slovak diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json new file mode 100644 index 000000000..1853c3dca --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json @@ -0,0 +1,34 @@ +{ + "name": "lorisleiva/cron-translator", + "description": "Makes CRON expressions human-readable", + "keywords": [ + "cron", + "expression", + "human" + ], + "homepage": "https://github.com/lorisleiva/cron-translator", + "license": "MIT", + "authors": [ + { + "name": "Loris LEIVA", + "email": "loris.leiva@gmail.com", + "homepage": "https://lorisleiva.com" + } + ], + "require": { + "php" : "^7.4 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit" : "^8.5.21 || ^9.5" + }, + "autoload": { + "psr-4": { + "Lorisleiva\\CronTranslator\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Lorisleiva\\CronTranslator\\Tests\\": "tests" + } + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php new file mode 100644 index 000000000..f129e1116 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php @@ -0,0 +1,110 @@ +raw = $cron; + $fields = explode(' ', $cron); + $this->minute = new MinutesField($this, $fields[0]); + $this->hour = new HoursField($this, $fields[1]); + $this->day = new DaysOfMonthField($this, $fields[2]); + $this->month = new MonthsField($this, $fields[3]); + $this->weekday = new DaysOfWeekField($this, $fields[4]); + $this->locale = $locale; + $this->timeFormat24hours = $timeFormat24hours; + $this->ensureLocaleExists(); + $this->loadTranslations(); + } + + public function getFields(): array + { + return [ + $this->minute, + $this->hour, + $this->day, + $this->month, + $this->weekday, + ]; + } + + public function langCountable(string $type, int $number) + { + $array = $this->translations[$type]; + + $value = $array[$number] ?? ($array['default'] ?: ''); + + return str_replace(':number', $number, $value); + } + + public function lang(string $key, array $replacements = []) + { + $translation = $this->getArrayDot($this->translations['fields'], $key); + + foreach ($replacements as $key => $value) { + $translation = str_replace(':' . $key, $value, $translation); + } + + return $translation; + } + + protected function ensureLocaleExists(string $fallbackLocale = 'en') + { + if (! is_dir($this->getTranslationDirectory())) { + $this->locale = $fallbackLocale; + } + } + + /** + * @throws TranslationFileMissingException + */ + protected function loadTranslations() + { + $this->translations = [ + 'days' => $this->loadTranslationFile('days'), + 'fields' => $this->loadTranslationFile('fields'), + 'months' => $this->loadTranslationFile('months'), + 'ordinals' => $this->loadTranslationFile('ordinals'), + 'times' => $this->loadTranslationFile('times'), + ]; + } + + protected function loadTranslationFile(string $file) + { + $filename = sprintf('%s/%s.php', $this->getTranslationDirectory(), $file); + + if (! is_file($filename)) { + throw new TranslationFileMissingException($this->locale, $file); + } + + return include $filename; + } + + protected function getTranslationDirectory(): string + { + return __DIR__ . '/lang/' . $this->locale; + } + + protected function getArrayDot(array $array, string $key) + { + $keys = explode('.', $key); + + foreach ($keys as $key) { + $array = $array[$key]; + } + + return $array; + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php new file mode 100644 index 000000000..06f8c7335 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php @@ -0,0 +1,13 @@ + '0 0 1 1 *', + '@annually' => '0 0 1 1 *', + '@monthly' => '0 0 1 * *', + '@weekly' => '0 0 * * 0', + '@daily' => '0 0 * * *', + '@hourly' => '0 * * * *' + ]; + + public static function translate(string $cron, string $locale = 'en', bool $timeFormat24hours = false): string + { + if (isset(self::$extendedMap[$cron])) { + $cron = self::$extendedMap[$cron]; + } + + try { + $expression = new CronExpression($cron, $locale, $timeFormat24hours); + $orderedFields = static::orderFields($expression->getFields()); + + $translations = array_map(function (Field $field) { + return $field->translate(); + }, $orderedFields); + + return ucfirst(implode(' ', array_filter($translations))); + } catch (Throwable $th) { + throw new CronParsingException($cron); + } + } + + protected static function orderFields(array $fields) + { + // Group fields by CRON types. + $onces = static::filterType($fields, 'Once'); + $everys = static::filterType($fields, 'Every'); + $incrementsAndMultiples = static::filterType($fields, 'Increment', 'Multiple'); + + // Decide whether to keep one or zero CRON type "Every". + $firstEvery = reset($everys)->position ?? PHP_INT_MIN; + $firstIncrementOrMultiple = reset($incrementsAndMultiples)->position ?? PHP_INT_MAX; + $numberOfEverysKept = $firstIncrementOrMultiple < $firstEvery ? 0 : 1; + + // Mark fields that will not be displayed as dropped. + // This allows other fields to check whether some + // information is missing and adapt their translation. + /** @var Field $field */ + foreach (array_slice($everys, $numberOfEverysKept) as $field) { + $field->dropped = true; + } + + return array_merge( + // Place one or zero "Every" field at the beginning. + array_slice($everys, 0, $numberOfEverysKept), + + // Place all "Increment" and "Multiple" fields in the middle. + $incrementsAndMultiples, + + // Finish with the "Once" fields reversed (i.e. from months to minutes). + array_reverse($onces) + ); + } + + protected static function filterType(array $fields, ...$types): array + { + return array_filter($fields, function (Field $field) use ($types) { + return $field->hasType(...$types); + }); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php new file mode 100644 index 000000000..3c4629bed --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php @@ -0,0 +1,91 @@ +type = $type; + $this->value = $value; + $this->count = $count; + $this->increment = $increment; + } + + public static function every(): CronType + { + return new static('Every'); + } + + public static function increment(int $increment, int $count = 1): CronType + { + return new static('Increment', null, $count, $increment); + } + + public static function multiple(int $count): CronType + { + return new static('Multiple', null, $count); + } + + public static function once(int $value): CronType + { + return new static('Once', $value); + } + + /** + * @throws CronParsingException + */ + public static function parse(string $expression): CronType + { + // Parse "*". + if ($expression === '*') { + return static::every(); + } + + // Parse fixed values like "1". + if (preg_match("/^[0-9]+$/", $expression)) { + return static::once((int) $expression); + } + + // Parse multiple selected values like "1,2,5". + if (preg_match("/^[0-9]+(,[0-9]+)+$/", $expression)) { + return static::multiple(count(explode(',', $expression))); + } + + // Parse ranges of selected values like "1-5". + if (preg_match("/^([0-9]+)\-([0-9]+)$/", $expression, $matches)) { + $count = $matches[2] - $matches[1] + 1; + return $count > 1 + ? static::multiple($count) + : static::once((int) $matches[1]); + } + + // Parse incremental expressions like "*/2", "1-4/10" or "1,3/4". + if (preg_match("/(.+)\/([0-9]+)$/", $expression, $matches)) { + $range = static::parse($matches[1]); + if ($range->hasType('Once', 'Every')) { + return static::Increment($matches[2]); + } + if ($range->hasType('Multiple')) { + return static::Increment($matches[2], $range->count); + } + } + + // Unsupported expressions throw exceptions. + throw new CronParsingException($expression); + } + + public function hasType(): bool + { + return in_array($this->type, func_get_args()); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php new file mode 100644 index 000000000..16143f204 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php @@ -0,0 +1,68 @@ +expression->weekday->hasType('Once')) { + return $this->lang('days_of_week.every', [ + 'weekday' => $this->expression->weekday->format(), + ]); + } + + return $this->lang('days_of_month.every'); + } + + public function translateIncrement() + { + if ($this->getCount() > 1) { + return $this->lang('days_of_month.multiple_per_increment', [ + 'count' => $this->getCount(), + 'increment' => $this->getIncrement(), + ]); + } + + return $this->lang('days_of_month.increment', [ + 'increment' => $this->getIncrement(), + ]); + } + + public function translateMultiple() + { + return $this->lang('days_of_month.multiple_per_month', [ + 'count' => $this->getCount(), + ]); + } + + public function translateOnce(): ?string + { + $month = $this->expression->month; + + if ($month->hasType('Once')) { + return null; // MonthsField adapts to "On January the 1st". + } + + if ($month->hasType('Every') && ! $month->dropped) { + return null; // MonthsField adapts to "The 1st of every month". + } + + if ($month->hasType('Every') && $month->dropped) { + return $this->lang('days_of_month.every_on_day', [ + 'day' => $this->format() + ]); + } + + return $this->lang('days_of_month.once_on_day', [ + 'day' => $this->format() + ]); + } + + public function format() + { + return $this->langCountable('ordinals', $this->getValue()); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php new file mode 100644 index 000000000..60a615a37 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php @@ -0,0 +1,56 @@ +lang('years.every'); + } + + public function translateIncrement() + { + if ($this->getCount() > 1) { + return $this->lang('days_of_week.multiple_per_increment', [ + 'count' => $this->getCount(), + 'increment' => $this->getIncrement(), + ]); + } + + return $this->lang('days_of_week.increment', [ + 'increment' => $this->getIncrement(), + ]); + } + + public function translateMultiple() + { + return $this->lang('days_of_week.multiple_days_a_week', [ + 'count' => $this->getCount(), + ]); + } + + public function translateOnce() + { + if ($this->expression->day->hasType('Every') && ! $this->expression->day->dropped) { + return; // DaysOfMonthField adapts to "Every Sunday". + } + + return $this->lang('days_of_week.once_on_day', [ + 'day' => $this->format() + ]); + } + + public function format(): string + { + $weekday = $this->getValue() === 0 ? 7 : $this->getValue(); + + if ($weekday < 1 || $weekday > 7) { + throw new CronParsingException($this->expression->raw); + } + + return $this->langCountable('days', $weekday); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php new file mode 100644 index 000000000..83470b471 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php @@ -0,0 +1,63 @@ +expression = $expression; + $this->rawField = $rawField; + $this->type = CronType::parse($rawField); + } + + public function translate() + { + $method = 'translate' . $this->type->type; + + if (method_exists($this, $method)) { + return $this->{$method}(); + } + } + + public function hasType(): bool + { + return $this->type->hasType(...func_get_args()); + } + + public function getValue(): ?int + { + return $this->type->value; + } + + public function getCount(): ?int + { + return $this->type->count; + } + + public function getIncrement(): ?int + { + return $this->type->increment; + } + + public function getTimes() + { + return $this->langCountable('times', $this->getCount()); + } + + protected function langCountable(string $key, int $value) + { + return $this->expression->langCountable($key, $value); + } + + protected function lang(string $key, array $replacements = []) + { + return $this->expression->lang($key, $replacements); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php new file mode 100644 index 000000000..7bb91c39e --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php @@ -0,0 +1,85 @@ +expression->minute->hasType('Once')) { + return $this->lang('hours.once_an_hour'); + } + + return $this->lang('hours.every'); + } + + public function translateIncrement() + { + if ($this->expression->minute->hasType('Once')) { + return $this->lang('hours.times_per_increment', [ + 'times' => $this->getTimes(), + 'increment' => $this->getIncrement(), + ]); + } + + if ($this->getCount() > 1) { + return $this->lang('hours.multiple_per_increment', [ + 'count' => $this->getCount(), + 'increment' => $this->getIncrement(), + ]); + } + + if ($this->expression->minute->hasType('Every')) { + return $this->lang('hours.increment_chained', [ + 'increment' => $this->getIncrement(), + ]); + } + + return $this->lang('hours.increment', [ + 'increment' => $this->getIncrement(), + ]); + } + + public function translateMultiple() + { + if ($this->expression->minute->hasType('Once')) { + return $this->lang('hours.times_per_day', [ + 'times' => $this->getTimes(), + ]); + } + + return $this->lang('hours.multiple_per_day', [ + 'count' => $this->getCount(), + ]); + } + + public function translateOnce() + { + $minute = $this->expression->minute->hasType('Once') + ? $this->expression->minute + : null; + + return $this->lang('hours.once_at_time', [ + 'time' => $this->format($minute) + ]); + } + + public function format(?MinutesField $minute = null): string + { + if ($this->expression->timeFormat24hours) { + $hour = $this->getValue(); + + return $minute ? "{$hour}:{$minute->format()}" : "{$hour}:00"; + } + + $amOrPm = $this->getValue() < 12 ? 'am' : 'pm'; + $hour = $this->getValue() % 12; + $hour = $hour === 0 ? 12 : $hour; + + return $minute + ? "{$hour}:{$minute->format()}{$amOrPm}" + : "{$hour}{$amOrPm}"; + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php new file mode 100644 index 000000000..926efb1be --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php @@ -0,0 +1,39 @@ +lang('minutes.every'); + } + + public function translateIncrement() + { + if ($this->getCount() > 1) { + return $this->lang('minutes.times_per_increment', [ + 'times' => $this->getTimes(), + 'increment' => $this->getIncrement(), + ]); + } + + return $this->lang('minutes.increment', [ + 'increment' => $this->getIncrement(), + ]); + } + + public function translateMultiple() + { + return $this->lang('minutes.multiple', [ + 'times' => $this->getTimes(), + ]); + } + + public function format(): string + { + return ($this->getValue() < 10 ? '0' : '') . $this->getValue(); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php new file mode 100644 index 000000000..71b54a2b4 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php @@ -0,0 +1,66 @@ +expression->day->hasType('Once')) { + return $this->lang('months.every_on_day', [ + 'day' => $this->expression->day->format(), + ]); + } + + return $this->lang('months.every'); + } + + public function translateIncrement() + { + if ($this->getCount() > 1) { + return $this->lang('months.multiple_per_increment', [ + 'count' => $this->getCount(), + 'increment' => $this->getCount(), + ]); + } + + return $this->lang('months.increment', [ + 'increment' => $this->getIncrement(), + ]); + } + + public function translateMultiple() + { + return $this->lang('months.multiple_per_year', [ + 'count' => $this->getCount(), + ]); + } + + public function translateOnce() + { + if ($this->expression->day->hasType('Once')) { + return $this->lang('months.once_on_day', [ + 'month' => $this->format(), + 'day' => $this->expression->day->format(), + ]); + } + + return $this->lang('months.once_on_month', [ + 'month' => $this->format() + ]); + } + + /** + * @throws CronParsingException + */ + public function format() + { + if ($this->getValue() < 1 || $this->getValue() > 12) { + throw new CronParsingException($this->expression->raw); + } + + return $this->langCountable('months', $this->getValue()); + } +} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php new file mode 100644 index 000000000..541946731 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php @@ -0,0 +1,13 @@ + 'الإثنين', + 2 => 'الثلاثاء', + 3 => 'الأربعاء', + 4 => 'الخميس', + 5 => 'الجمعة', + 6 => 'السبت', + 7 => 'الأحد', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php new file mode 100644 index 000000000..9bf2551bd --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'كل دقيقة', + 'increment' => 'كل :increment دقائق', + 'times_per_increment' => ':times كل :increment دقائق', + 'multiple' => ':times ساعات', + ], + 'hours' => [ + 'every' => 'كل ساعة', + 'once_an_hour' => 'مرة في كل ساعة', + 'increment' => 'كل :increment ساعات', + 'multiple_per_increment' => ':count ساعات من :increment', + 'times_per_increment' => ':times كل :increment ساعة', + 'increment_chained' => 'كل :increment ساعة', + 'multiple_per_day' => ':count ساعة كل يوم', + 'times_per_day' => ':times في اليوم', + 'once_at_time' => 'في :time', + ], + 'days_of_month' => [ + 'every' => 'كل يوم', + 'increment' => 'كل :increment يوم', + 'multiple_per_increment' => ':count أيام من :increment', + 'multiple_per_month' => ':count أيام في الشهر', + 'once_on_day' => 'في :day', + 'every_on_day' => 'كل :day من كل شهر', + ], + 'months' => [ + 'every' => 'كل شهر', + 'every_on_day' => ':day من كل شهر', + 'increment' => 'كل :increment شهر', + 'multiple_per_increment' => ':count أشهر من :increment', + 'multiple_per_year' => ':count أشهر في العام', + 'once_on_month' => 'في :month', + 'once_on_day' => 'في :month ال :day', + ], + 'days_of_week' => [ + 'every' => 'كل :weekday', + 'increment' => 'كل :increment أيام في الأسبوع', + 'multiple_per_increment' => ':count أيام الأسبوع من :increment', + 'multiple_days_a_week' => ':count أيام في الأسبوع', + 'once_on_day' => 'في :days', + ], + 'years' => [ + 'every' => 'كل عام', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php new file mode 100644 index 000000000..d2e39e0b8 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php @@ -0,0 +1,16 @@ + 'يناير', + 2 => 'فبراير', + 3 => 'مارس', + 4 => 'ابريل', + 5 => 'مايو', + 6 => 'يونيو', + 7 => 'يوليو', + 8 => 'أغسطس', + 9 => 'سبتمبر', + 10 => 'أكتوبر', + 11 => 'نوفمبر', + 12 => 'ديسمبر', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php new file mode 100644 index 000000000..5196797a6 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php @@ -0,0 +1,12 @@ + ':numberth', + 1 => 'الأول', + 2 => 'الثاني', + 3 => 'الثالث', + 21 => 'الواحد و العشرون', + 22 => 'الثاني و العشرون', + 23 => 'الثالث و العشرون', + 31 => 'الواحد و الثلاثون', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php new file mode 100644 index 000000000..97814a23a --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php @@ -0,0 +1,7 @@ + ':number أوقات', + 1 => 'مرة', + 2 => 'مرتان', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php new file mode 100644 index 000000000..6fe567556 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php @@ -0,0 +1,11 @@ + 'Montag', + 2 => 'Dienstag', + 3 => 'Mittwoch', + 4 => 'Donnerstag', + 5 => 'Freitag', + 6 => 'Samstag', + 7 => 'Sonntag', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php new file mode 100644 index 000000000..f82786f81 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php @@ -0,0 +1,50 @@ + [ + 'every' => 'jede Minute', + 'increment' => 'jede :increment Minute', + 'times_per_increment' => ':times mal alle :increment Minuten', + 'multiple' => ':times mal pro Stunde', + ], + 'hours' => [ + 'every' => 'jede Stunde', + 'once_an_hour' => 'ein mal pro Stunde', + 'increment' => 'jede :increment Stunde', + 'multiple_per_increment' => ':count Stunden aus :increment', + 'times_per_increment' => ':times mal alle :increment Stunden', + 'increment_chained' => 'alle :increment Stunden', + 'multiple_per_day' => ':count Stunden pro Tag', + 'times_per_day' => ':times mal am Tag', + 'once_at_time' => 'um :time', + ], + 'days_of_month' => [ + 'every' => 'jeden Tag', + 'increment' => 'alle :increment Tage', + 'multiple_per_increment' => ':count Tage von :increment', + 'multiple_per_month' => ':count Tage im Monat', + 'once_on_day' => 'am :day', + 'every_on_day' => 'am :day eines jeden Monats', + ], + 'months' => [ + 'every' => 'eines jeden Monat', + 'every_on_day' => 'jeden :day eines jeden Monats', + 'increment' => 'alle :increment Monate', + 'multiple_per_increment' => ':count Monate von :increment', + 'multiple_per_year' => ':count Monate im Jahr', + 'once_on_month' => 'im :month', + 'once_on_day' => 'im :month den :day', + ], + 'days_of_week' => [ + 'every' => 'jeden :weekday', + 'increment' => 'jeden :increment Tag der Woche', + 'multiple_per_increment' => ':count Tage in der Woche aus :increment', + 'multiple_days_a_week' => ':count Tage in der Woche', + #'once_on_day' => 'jeden :days', + #'once_on_day' => 'am :days', + 'once_on_day' => 'des :days', + ], + 'years' => [ + 'every' => 'jedes Jahr', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php new file mode 100644 index 000000000..c27991a1a --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php @@ -0,0 +1,16 @@ + 'Januar', + 2 => 'Februar', + 3 => 'März', + 4 => 'April', + 5 => 'Mai', + 6 => 'Juni', + 7 => 'Juli', + 8 => 'August', + 9 => 'September', + 10 => 'Oktober', + 11 => 'November', + 12 => 'Dezember', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php new file mode 100644 index 000000000..05b089b00 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php @@ -0,0 +1,13 @@ + ':number.', + #'default' => str_pad(':number', 2, 0, STR_PAD_LEFT). '.', + 1 => '1.', + 2 => '2.', + 3 => '3.', + 21 => '21.', + 22 => '22.', + 23 => '23.', + 31 => '31.', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php new file mode 100644 index 000000000..27be24004 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php @@ -0,0 +1,7 @@ + ':number', + 1 => 'Ein', + 2 => 'Zwei', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php new file mode 100644 index 000000000..7df6a9b9f --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php @@ -0,0 +1,11 @@ + 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + 7 => 'Sunday', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php new file mode 100644 index 000000000..28da50251 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'every minute', + 'increment' => 'every :increment minutes', + 'times_per_increment' => ':times every :increment minutes', + 'multiple' => ':times an hour', + ], + 'hours' => [ + 'every' => 'every hour', + 'once_an_hour' => 'once an hour', + 'increment' => 'every :increment hours', + 'multiple_per_increment' => ':count hours out of :increment', + 'times_per_increment' => ':times every :increment hours', + 'increment_chained' => 'of every :increment hours', + 'multiple_per_day' => ':count hours a day', + 'times_per_day' => ':times a day', + 'once_at_time' => 'at :time', + ], + 'days_of_month' => [ + 'every' => 'every day', + 'increment' => 'every :increment days', + 'multiple_per_increment' => ':count days out of :increment', + 'multiple_per_month' => ':count days a month', + 'once_on_day' => 'on the :day', + 'every_on_day' => 'on the :day of every month', + ], + 'months' => [ + 'every' => 'every month', + 'every_on_day' => 'the :day of every month', + 'increment' => 'every :increment months', + 'multiple_per_increment' => ':count months out of :increment', + 'multiple_per_year' => ':count months a year', + 'once_on_month' => 'on :month', + 'once_on_day' => 'on :month the :day', + ], + 'days_of_week' => [ + 'every' => 'every :weekday', + 'increment' => 'every :increment days of the week', + 'multiple_per_increment' => ':count days of the week out of :increment', + 'multiple_days_a_week' => ':count days a week', + 'once_on_day' => 'on :days', + ], + 'years' => [ + 'every' => 'every year', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php new file mode 100644 index 000000000..159ba1891 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php @@ -0,0 +1,16 @@ + 'January', + 2 => 'February', + 3 => 'March', + 4 => 'April', + 5 => 'May', + 6 => 'June', + 7 => 'July', + 8 => 'August', + 9 => 'September', + 10 => 'October', + 11 => 'November', + 12 => 'December', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php new file mode 100644 index 000000000..47d3f593c --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php @@ -0,0 +1,12 @@ + ':numberth', + 1 => '1st', + 2 => '2nd', + 3 => '3rd', + 21 => '21st', + 22 => '22nd', + 23 => '23rd', + 31 => '31st', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php new file mode 100644 index 000000000..d651e4cba --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php @@ -0,0 +1,7 @@ + ':number times', + 1 => 'once', + 2 => 'twice', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php new file mode 100644 index 000000000..77dd55118 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php @@ -0,0 +1,11 @@ + 'lunes', + 2 => 'martes', + 3 => 'miércoles', + 4 => 'jueves', + 5 => 'viernes', + 6 => 'sábado', + 7 => 'domingo', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php new file mode 100644 index 000000000..d4bbca445 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'cada minuto', + 'increment' => 'cada :increment minutos', + 'times_per_increment' => ':times cada :increment minutos', + 'multiple' => ':times cada hora', + ], + 'hours' => [ + 'every' => 'cada hora', + 'once_an_hour' => 'una vez cada hora', + 'increment' => 'cada :increment horas', + 'multiple_per_increment' => ':count horas de cada :increment', + 'times_per_increment' => ':times cada :increment horas', + 'increment_chained' => 'de cada :increment horas', + 'multiple_per_day' => ':count horas al día', + 'times_per_day' => ':times al día', + 'once_at_time' => 'a las :time', + ], + 'days_of_month' => [ + 'every' => 'diario', + 'increment' => 'cada :increment días', + 'multiple_per_increment' => ':count días de :increment', + 'multiple_per_month' => ':count días al mes', + 'once_on_day' => 'el :day', + 'every_on_day' => 'el :day de cada mes', + ], + 'months' => [ + 'every' => 'cada mes', + 'every_on_day' => 'el :day de cada mes', + 'increment' => 'cada :increment meses', + 'multiple_per_increment' => ':count meses de :increment', + 'multiple_per_year' => ':count meses al año', + 'once_on_month' => 'de :month', + 'once_on_day' => 'en :month el :day', + ], + 'days_of_week' => [ + 'every' => 'cada :weekday', + 'increment' => 'cada :increment días de la semana', + 'multiple_per_increment' => ':count días a la semana de :increment', + 'multiple_days_a_week' => ':count días a la semana', + 'once_on_day' => 'los :days', + ], + 'years' => [ + 'every' => 'cada año', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php new file mode 100644 index 000000000..2e988a449 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php @@ -0,0 +1,16 @@ + 'enero', + 2 => 'febrero', + 3 => 'marzo', + 4 => 'abril', + 5 => 'mayo', + 6 => 'junio', + 7 => 'julio', + 8 => 'agosto', + 9 => 'septiembre', + 10 => 'octubre', + 11 => 'noviembre', + 12 => 'diciembre', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php new file mode 100644 index 000000000..1f79960dc --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php @@ -0,0 +1,5 @@ + ':numberº', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php new file mode 100644 index 000000000..868156e00 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php @@ -0,0 +1,6 @@ + ':number veces', + 1 => 'una vez', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php new file mode 100644 index 000000000..2af7be296 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php @@ -0,0 +1,11 @@ + 'lundi', + 2 => 'mardi', + 3 => 'mercredi', + 4 => 'jeudi', + 5 => 'vendredi', + 6 => 'samedi', + 7 => 'dimanche', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php new file mode 100644 index 000000000..a339c9715 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'chaque minute', + 'increment' => 'toutes les :increment minutes', + 'times_per_increment' => ':times toutes les :increment minutes', + 'multiple' => ':times par heure', + ], + 'hours' => [ + 'every' => 'chaque heure', + 'once_an_hour' => 'une fois par heure', + 'increment' => 'toutes les :increment heures', + 'multiple_per_increment' => ':count heures au-delà de :increment', + 'times_per_increment' => ':times toutes les :increment heures', + 'increment_chained' => 'toutes les :increment heures', + 'multiple_per_day' => ':count heures par jour', + 'times_per_day' => ':times par jour', + 'once_at_time' => 'à :time', + ], + 'days_of_month' => [ + 'every' => 'chaque jour', + 'increment' => 'tous les :increment jours', + 'multiple_per_increment' => ':count jours sur :increment', + 'multiple_per_month' => ':count jours par mois', + 'once_on_day' => 'le :day', + 'every_on_day' => 'le :day de chaque mois', + ], + 'months' => [ + 'every' => 'chaque mois', + 'every_on_day' => 'le :day de chaque mois', + 'increment' => 'tous les :increment mois', + 'multiple_per_increment' => ':count mois sur :increment', + 'multiple_per_year' => ':count mois par an', + 'once_on_month' => 'en :month', + 'once_on_day' => 'le :day :month', + ], + 'days_of_week' => [ + 'every' => 'chaque :weekday', + 'increment' => 'tous les :increment jours de la semaine', + 'multiple_per_increment' => ':count jours de la semaine sur :increment', + 'multiple_days_a_week' => ':count jours par semaine', + 'once_on_day' => 'les :days', + ], + 'years' => [ + 'every' => 'chaque année', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php new file mode 100644 index 000000000..9b02c93fd --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php @@ -0,0 +1,16 @@ + 'janvier', + 2 => 'février', + 3 => 'mars', + 4 => 'avril', + 5 => 'mai', + 6 => 'juin', + 7 => 'juillet', + 8 => 'août', + 9 => 'septembre', + 10 => 'octobre', + 11 => 'novembre', + 12 => 'décembre', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php new file mode 100644 index 000000000..10a111e78 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php @@ -0,0 +1,6 @@ + ':number', + 1 => '1er', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php new file mode 100644 index 000000000..1d29659b3 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php @@ -0,0 +1,7 @@ + ':number fois', + 1 => 'une fois', + 2 => 'deux fois', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php new file mode 100644 index 000000000..ee858deff --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php @@ -0,0 +1,11 @@ + 'pirmdien', + 2 => 'otrdien', + 3 => 'trešdien', + 4 => 'ceturtdien', + 5 => 'piektdien', + 6 => 'sestdien', + 7 => 'svētdien', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php new file mode 100644 index 000000000..2af59e309 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'katru minūti', + 'increment' => 'ik pēc :increment minūtēm', + 'times_per_increment' => ':times ik pēc :increment minūtēm', + 'multiple' => ':times stundā', + ], + 'hours' => [ + 'every' => 'katru stundu', + 'once_an_hour' => 'vienreiz stundā', + 'increment' => 'katras :increment stundas', + 'multiple_per_increment' => ':count no :increment stundām', + 'times_per_increment' => ':times katras :increment stundas', + 'increment_chained' => 'ik pēc :increment stundām', + 'multiple_per_day' => ':count stundas dienā', + 'times_per_day' => ':times dienā', + 'once_at_time' => 'plkst. :time', + ], + 'days_of_month' => [ + 'every' => 'katru dienu', + 'increment' => 'ik pēc :increment dienām', + 'multiple_per_increment' => ':count dienas no :increment', + 'multiple_per_month' => ':count dienas mēnesī', + 'once_on_day' => ':day. datumā', + 'every_on_day' => 'katra mēneša :day. datumā', + ], + 'months' => [ + 'every' => 'katru mēnesi', + 'every_on_day' => 'katra mēneša :day. datumā', + 'increment' => 'katrus :increment mēnešus', + 'multiple_per_increment' => ':count mēnešus no :increment', + 'multiple_per_year' => ':count mēnešus gadā', + 'once_on_month' => ':month', + 'once_on_day' => ':day. :month', + ], + 'days_of_week' => [ + 'every' => 'katru :weekdayu', + 'increment' => 'ik pēc :increment nedēļas dienām', + 'multiple_per_increment' => ':count nedēļas dienas no :increment', + 'multiple_days_a_week' => ':count dienas nedēļā', + 'once_on_day' => ':dayās', + ], + 'years' => [ + 'every' => 'katru gadu', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php new file mode 100644 index 000000000..23c210f6d --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php @@ -0,0 +1,16 @@ + 'janvārī', + 2 => 'februārī', + 3 => 'martā', + 4 => 'aprīlī', + 5 => 'maijā', + 6 => 'jūnijā', + 7 => 'jūlijā', + 8 => 'augustā', + 9 => 'septembrī', + 10 => 'oktobrī', + 11 => 'novembrī', + 12 => 'decembrī', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php new file mode 100644 index 000000000..e160d8016 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php @@ -0,0 +1,5 @@ + ':number', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php new file mode 100644 index 000000000..9ada98fe3 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php @@ -0,0 +1,7 @@ + ':number reizes', + 1 => 'vienreiz', + 2 => 'divreiz', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php new file mode 100644 index 000000000..95fa5b769 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php @@ -0,0 +1,11 @@ + 'Maandag', + 2 => 'Dinsdag', + 3 => 'Woensdag', + 4 => 'Donderdag', + 5 => 'Vrijdag', + 6 => 'Zaterdag', + 7 => 'Zondag', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php new file mode 100644 index 000000000..5a17ce00a --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'elke minuut', + 'increment' => 'elke :increment minuten', + 'times_per_increment' => ':times elke :increment minuten', + 'multiple' => ':times per uur', + ], + 'hours' => [ + 'every' => 'elk uur', + 'once_an_hour' => 'eens per uur', + 'increment' => 'elke :increment uur', + 'multiple_per_increment' => ':count uur van de :increment', + 'times_per_increment' => ':times elke :increment uur', + 'increment_chained' => 'van elke :increment uur', + 'multiple_per_day' => ':count uur per dag', + 'times_per_day' => ':times per dag', + 'once_at_time' => 'om :time', + ], + 'days_of_month' => [ + 'every' => 'elke dag', + 'increment' => 'elke :increment dagen', + 'multiple_per_increment' => ':count dagen van de :increment', + 'multiple_per_month' => ':count dagen per maand', + 'once_on_day' => 'op de :day', + 'every_on_day' => 'op de :day van elke maand', + ], + 'months' => [ + 'every' => 'elke maand', + 'every_on_day' => 'de :day van elke maand', + 'increment' => 'elke :increment maanden', + 'multiple_per_increment' => ':count maanden van de :increment', + 'multiple_per_year' => ':count maanden per jaar', + 'once_on_month' => 'in :month', + 'once_on_day' => 'in :month de :day', + ], + 'days_of_week' => [ + 'every' => 'elke :weekday', + 'increment' => 'elke :increment dag van de week', + 'multiple_per_increment' => ':count dagen van de week op :increment', + 'multiple_days_a_week' => ':count dagen per week ', + 'once_on_day' => 'op :days', + ], + 'years' => [ + 'every' => 'elk jaar', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php new file mode 100644 index 000000000..0214c8d9f --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php @@ -0,0 +1,16 @@ + 'januari', + 2 => 'februari', + 3 => 'maart', + 4 => 'april', + 5 => 'mei', + 6 => 'juni', + 7 => 'juli', + 8 => 'augustus', + 9 => 'september', + 10 => 'oktober', + 11 => 'november', + 12 => 'december', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php new file mode 100644 index 000000000..89fbe94ef --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php @@ -0,0 +1,5 @@ + ':numbere', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php new file mode 100644 index 000000000..b550e5134 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php @@ -0,0 +1,5 @@ + ':number keer', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php new file mode 100644 index 000000000..e4a1ca963 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php @@ -0,0 +1,11 @@ + 'Segunda-feira', + 2 => 'Terça-feira', + 3 => 'Quarta-feira', + 4 => 'Quinta-feira', + 5 => 'Sexta-feira', + 6 => 'Sábado', + 7 => 'Domingo', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php new file mode 100644 index 000000000..d126f4eb3 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'todos os minutos', + 'increment' => 'cada :increment minutos', + 'times_per_increment' => ':times cada :increment minutos', + 'multiple' => ':times por hora', + ], + 'hours' => [ + 'every' => 'todas as horas', + 'once_an_hour' => 'uma vez por hora', + 'increment' => 'cada :increment horas', + 'multiple_per_increment' => ':count horas cada :increment', + 'times_per_increment' => ':times cada :increment horas', + 'increment_chained' => 'de cada :increment horas', + 'multiple_per_day' => ':count horas por dia', + 'times_per_day' => ':times por dia', + 'once_at_time' => 'às :time', + ], + 'days_of_month' => [ + 'every' => 'todos os dias', + 'increment' => 'cada :increment dias', + 'multiple_per_increment' => ':count dias cada :increment', + 'multiple_per_month' => ':count dias por mês', + 'once_on_day' => 'no :day dia', + 'every_on_day' => 'no :day dia de cada mês', + ], + 'months' => [ + 'every' => 'todos os meses', + 'every_on_day' => 'no :day dia de cada mês', + 'increment' => 'cada :increment meses', + 'multiple_per_increment' => ':count meses cada :increment', + 'multiple_per_year' => ':count meses por ano', + 'once_on_month' => 'em :month', + 'once_on_day' => 'no :day de :month', + ], + 'days_of_week' => [ + 'every' => 'cada :weekday', + 'increment' => 'cada :increment dias da semana', + 'multiple_per_increment' => ':count dias da semana cada :increment', + 'multiple_days_a_week' => ':count dias da semana', + 'once_on_day' => 'nas/nos :days', + ], + 'years' => [ + 'every' => 'todos os anos', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php new file mode 100644 index 000000000..b49d24e61 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php @@ -0,0 +1,16 @@ + 'Janeiro', + 2 => 'Fevereiro', + 3 => 'Março', + 4 => 'Abril', + 5 => 'Maio', + 6 => 'Junho', + 7 => 'Julho', + 8 => 'Agosto', + 9 => 'Setembro', + 10 => 'Outubro', + 11 => 'Novembro', + 12 => 'Dezembro', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php new file mode 100644 index 000000000..1f79960dc --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php @@ -0,0 +1,5 @@ + ':numberº', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php new file mode 100644 index 000000000..e56f6099e --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php @@ -0,0 +1,6 @@ + ':number vezes', + 1 => 'uma vez', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php new file mode 100644 index 000000000..cb0927418 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php @@ -0,0 +1,11 @@ + 'pondelok', + 2 => 'utorok', + 3 => 'streda', + 4 => 'štvrtok', + 5 => 'piatok', + 6 => 'sobota', + 7 => 'nedeľa', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php new file mode 100644 index 000000000..6aa909629 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => 'každú minútu', + 'increment' => 'každých :increment minút', + 'times_per_increment' => ':times každých :increment minút', + 'multiple' => ':times za hodinu', + ], + 'hours' => [ + 'every' => 'každú hodinu', + 'once_an_hour' => 'raz za hodinu', + 'increment' => 'každých :increment hodín', + 'multiple_per_increment' => ':count hodín z :increment', + 'times_per_increment' => ':times každých :increment hodín', + 'increment_chained' => 'každých :increment hodín', + 'multiple_per_day' => ':count hodín za deň', + 'times_per_day' => ':times za deň', + 'once_at_time' => 'o :time', + ], + 'days_of_month' => [ + 'every' => 'každý deň', + 'increment' => 'každých :increment dní', + 'multiple_per_increment' => ':count dní z :increment', + 'multiple_per_month' => ':count dní v mesiaci', + 'once_on_day' => ':day dňa v mesiaci', + 'every_on_day' => 'na každý :day deň v mesiaci', + ], + 'months' => [ + 'every' => 'každý mesiac', + 'every_on_day' => 'každý :day deň v mesiaci', + 'increment' => 'každých :increment mesiacov', + 'multiple_per_increment' => ':count mesiacov z :increment', + 'multiple_per_year' => ':count mesiacov ročne', + 'once_on_month' => 'v mesiaci :month', + 'once_on_day' => 'na :day deň v mesiaci :month', + ], + 'days_of_week' => [ + 'every' => 'každá/ý :weekday', + 'increment' => 'každých :increment dní v týždni', + 'multiple_per_increment' => ':count dní v týždni z :increment', + 'multiple_days_a_week' => ':count dní v týždni', + 'once_on_day' => 'v :day', + ], + 'years' => [ + 'every' => 'každý rok', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php new file mode 100644 index 000000000..d3be7f015 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php @@ -0,0 +1,16 @@ + 'január', + 2 => 'február', + 3 => 'marec', + 4 => 'apríl', + 5 => 'máj', + 6 => 'jún', + 7 => 'júl', + 8 => 'august', + 9 => 'september', + 10 => 'október', + 11 => 'november', + 12 => 'december', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php new file mode 100644 index 000000000..7194b57a7 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php @@ -0,0 +1,12 @@ + ':number.', + 1 => '1.', + 2 => '2.', + 3 => '3.', + 21 => '21.', + 22 => '22.', + 23 => '23.', + 31 => '31.', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php new file mode 100644 index 000000000..a51eee42a --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php @@ -0,0 +1,7 @@ + ':number-krát', + 1 => 'raz', + 2 => 'dva razy', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php new file mode 100644 index 000000000..62eb6813f --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php @@ -0,0 +1,11 @@ + '周一', + 2 => '周二', + 3 => '周三', + 4 => '周四', + 5 => '周五', + 6 => '周六', + 7 => '周日', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php new file mode 100644 index 000000000..630ed4769 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php @@ -0,0 +1,48 @@ + [ + 'every' => '每分钟', + 'increment' => '每:increment分钟', + 'times_per_increment' => '每:increment分钟:times', + 'multiple' => '一小时:times', + ], + 'hours' => [ + 'every' => '每小时', + 'once_an_hour' => '每整点', + 'increment' => '每:increment小时', + 'multiple_per_increment' => '每:increment小时中有:count小时', + 'times_per_increment' => '每:increment小时:times', + 'increment_chained' => '每:increment小时', + 'multiple_per_day' => '每天有:count小时', + 'times_per_day' => '每天:times', + 'once_at_time' => '在:time', + ], + 'days_of_month' => [ + 'every' => '每天', + 'increment' => '每:increment天', + 'multiple_per_increment' => '每:increment天中有:count天', + 'multiple_per_month' => '每月:count天', + 'once_on_day' => '在:day号', + 'every_on_day' => '在每月:day号', + ], + 'months' => [ + 'every' => '每月', + 'every_on_day' => '每月:day号', + 'increment' => '每:increment个月', + 'multiple_per_increment' => '每:increment月中有:count月', + 'multiple_per_year' => '每年:count个月', + 'once_on_month' => '在:month', + 'once_on_day' => '在:month:day号', + ], + 'days_of_week' => [ + 'every' => '每周:weekday', + 'increment' => '每周:increment天', + 'multiple_per_increment' => '每:increment周中有:count天', + 'multiple_days_a_week' => '一周:count天', + 'once_on_day' => '在:day', + ], + 'years' => [ + 'every' => '每年', + ], +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php new file mode 100644 index 000000000..8451ff176 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php @@ -0,0 +1,16 @@ + '一月', + 2 => '二月', + 3 => '三月', + 4 => '四月', + 5 => '五月', + 6 => '六月', + 7 => '七月', + 8 => '八月', + 9 => '九月', + 10 => '十月', + 11 => '十一月', + 12 => '十二月', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php new file mode 100644 index 000000000..e160d8016 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php @@ -0,0 +1,5 @@ + ':number', +]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php new file mode 100644 index 000000000..339427672 --- /dev/null +++ b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php @@ -0,0 +1,5 @@ + ':number次', +]; diff --git a/modules/qlocrontaskmanager/views/css/admin/qlo_cron_task_manager_admin.css b/modules/qlocrontaskmanager/views/css/admin/qlo_cron_task_manager_admin.css index 59148e892..c52809b7c 100644 --- a/modules/qlocrontaskmanager/views/css/admin/qlo_cron_task_manager_admin.css +++ b/modules/qlocrontaskmanager/views/css/admin/qlo_cron_task_manager_admin.css @@ -31,3 +31,26 @@ .label-default { background-color: #999; } + + +.qctm-cron-fields-table thead th { + text-align: center; + font-size: 14px; + font-weight: bold; + border: 1px solid #ddd!important; + padding: 8px; +} + +.qctm-cron-fields-table tbody td { + text-align: center; + font-size: 12px; + color: #666; + border: 1px solid #e5e5e5; + padding: 6px; +} +.cron-expression-invalid{ + color: #c33333!important; +} +.cron-expression-valid{ + color: #409bbd !important +} diff --git a/modules/qlocrontaskmanager/views/js/admin/qlo_cron_task_manager_admin.js b/modules/qlocrontaskmanager/views/js/admin/qlo_cron_task_manager_admin.js index c361251e7..05b821077 100644 --- a/modules/qlocrontaskmanager/views/js/admin/qlo_cron_task_manager_admin.js +++ b/modules/qlocrontaskmanager/views/js/admin/qlo_cron_task_manager_admin.js @@ -111,6 +111,59 @@ var QctmAdmin = { }, }; +var QctmCronHelper = { + $input: null, + cronRegex: /^(\*|[\d\/*,\-]+)\s+(\*|[\d\/*,\-]+)\s+(\*|[\d\/*,\-]+)\s+(\*|[\d\/*,\-]+)\s+(\*|[\d\/*,\-]+)$/, + + init: function () { + this.$input = $('.qctm-cron-expression'); + + if (!this.$input.length) { + return; + } + this.$input.on('input keyup', this.onInput.bind(this)); + }, + + onInput: function (e) { + var $input = $(e.currentTarget); + var val = $input.val(); + val = val.replace(/[^0-9*\/,\-\s]/g, ''); + $input.val(val); + var $readable = $input.closest('.col-lg-4').find('.help-block'); + if (this.cronRegex.test(val)) { + this.fetchReadable(val, $readable); + } else { + $readable.removeClass('cron-expression-valid').addClass('cron-expression-invalid').text(INVALID_CRON_EXPRESSION); + } + }, + + fetchReadable: function (val, $readable) { + var ajaxUrl = $('#qctm-ajax-url').val(); + + if (!ajaxUrl || !val) { + return; + } + + $.ajax({ + url: ajaxUrl, + type: 'GET', + data: { + action: 'getCronReadable', + cron_expression: val + }, + dataType: 'json', + success: function (response) { + if (response && response.valid) { + $readable.removeClass('cron-expression-invalid').addClass('cron-expression-valid').text(response.readable ); + } else { + $readable.removeClass('cron-expression-valid').addClass('cron-expression-invalid').text(response.readable); + } + } + }); + } +}; + $(document).ready(function () { QctmAdmin.init(); + QctmCronHelper.init(); }); diff --git a/modules/qlocrontaskmanager/views/templates/admin/configure.tpl b/modules/qlocrontaskmanager/views/templates/admin/configure.tpl index bb2171db3..ad651a980 100644 --- a/modules/qlocrontaskmanager/views/templates/admin/configure.tpl +++ b/modules/qlocrontaskmanager/views/templates/admin/configure.tpl @@ -24,12 +24,12 @@
{l s='Cron Command' mod='qlocrontaskmanager'}
-
+
{l s='Add the following cron entry to your server. This single command will manage all module cron tasks automatically.' mod='qlocrontaskmanager'}
- +
{l s='Success' mod='qlocrontaskmanager'} +{elseif $status == QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR} + {l s='Error' mod='qlocrontaskmanager'} +{else} + {l s='N/A' mod='qlocrontaskmanager'} +{/if} + diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_cron_expression_helper.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_cron_expression_helper.tpl new file mode 100644 index 000000000..71fa155cf --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_cron_expression_helper.tpl @@ -0,0 +1,48 @@ +{* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 +*} + +
+
+

The cron expression is made of five fields. Each field can have the following values.

+ + + + + + + + + + + + + + + + + + + +
*****
{l s='minute (0-59)' mod='qlocrontaskmanager'}{l s='hour (0 - 23)' mod='qlocrontaskmanager'}{l s='day of the month (1 - 31)' mod='qlocrontaskmanager'}{l s='month (1 - 12)' mod='qlocrontaskmanager'}{l s='day of the week (0 - 6)' mod='qlocrontaskmanager'}
+
+
+ \ No newline at end of file diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl new file mode 100644 index 000000000..ec588fc96 --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl @@ -0,0 +1,26 @@ +{* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 +*} + + + {l s='Reset' mod='qlocrontaskmanager'} + diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl index 93adea4ff..25390af48 100644 --- a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl @@ -20,4 +20,8 @@ * @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 *} -{l s='Logs' mod='qlocrontaskmanager'} + diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_logs_link.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_logs_link.tpl new file mode 100644 index 000000000..c81c1bc50 --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_logs_link.tpl @@ -0,0 +1,27 @@ +{* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 +*} + + + + {l s='View Logs' mod='qlocrontaskmanager'} + + From 75ba1c0d1507a48d775a31802e4c37d1637ca1e6 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Wed, 13 May 2026 16:18:48 +0530 Subject: [PATCH 03/11] Fixed: update review changes, remove callback from render form , add icon on view logs button --- .../admin/AdminCronTaskLogsController.php | 1 - .../admin/AdminCronTaskManagerController.php | 24 ++++++------------- .../qctm_reset_module_crons_link.tpl | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskLogsController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskLogsController.php index 4d67df5dc..52df2c45a 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskLogsController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskLogsController.php @@ -59,7 +59,6 @@ class AdminCronTaskLogsController extends ModuleAdminController $this->toolbar_title = $this->l('Cron Task Logs'); } } else { - unset($this->context->cookie->{$cookieKey}); $this->context->cookie->{'submitFilter' . $this->table} = 0; $this->toolbar_title = $this->l('All Cron Task Logs'); } diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index e37d8c18a..558584dbd 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -150,11 +150,11 @@ class AdminCronTaskManagerController extends ModuleAdminController public function initToolbar() { parent::initToolbar(); - + unset($this->toolbar_btn['new']); $this->page_header_toolbar_btn['view_all_logs'] = array( 'href' => $this->context->link->getAdminLink('AdminCronTaskLogs'), 'desc' => $this->l('View All Logs'), - 'icon' => 'process-icon-list', + 'icon' => 'icon-history', ); } @@ -201,7 +201,7 @@ class AdminCronTaskManagerController extends ModuleAdminController 'type' => 'text', 'label' => $this->l('Module Name'), 'name' => 'module_display_name', - 'readonly' => true, + 'disabled' => true, 'col' => 5, 'desc' => $this->l('The module that owns this task.'), ), @@ -209,21 +209,11 @@ class AdminCronTaskManagerController extends ModuleAdminController 'type' => 'text', 'label' => $this->l('Task Name'), 'name' => 'task_name', - 'readonly' => true, + 'disabled' => true, 'required' => true, 'col' => 5, 'hint' => $this->l('e.g. send_daily_report'), - 'desc' => $this->l('A unique snake_case identifier for this task within the module. Used to detect duplicate registrations.'), - ), - array( - 'type' => 'text', - 'label' => $this->l('Callback Method'), - 'name' => 'callback', - 'required' => true, - 'readonly' => true, - 'col' => 5, - 'hint' => $this->l('e.g. runDailyReport'), - 'desc' => $this->l('The public method name on the module class that will be called when this task fires. Must exist on the module at runtime.'), + 'desc' => $this->l('A unique identifier for this task within the module.'), ), array( 'type' => 'text', @@ -232,7 +222,7 @@ class AdminCronTaskManagerController extends ModuleAdminController 'required' => true, 'col' => 7, 'hint' => $this->l('e.g. Sends the daily booking summary report'), - 'desc' => $this->l('A short human-readable description of what this task does. Shown in the task list.'), + 'desc' => $this->l('A short human-readable description of what this task does.'), ), array( 'type' => 'text', @@ -254,7 +244,7 @@ class AdminCronTaskManagerController extends ModuleAdminController 'label' => $this->l('Active'), 'name' => 'active', 'required' => false, - 'desc' => $this->l('Disabled tasks are skipped by the cron dispatcher even if their schedule is due.'), + 'desc' => $this->l('Disabled tasks are skipped.'), 'values' => array( array('id' => 'active_on', 'value' => 1, 'label' => $this->l('Enabled')), array('id' => 'active_off', 'value' => 0, 'label' => $this->l('Disabled')), diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl index ec588fc96..a0d3a6221 100644 --- a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl @@ -21,6 +21,6 @@ *} + onclick="return confirm('{l s='Are you sure? This will revert the task settings to the module\'s original values.' mod='qlocrontaskmanager' js=1}');"> {l s='Reset' mod='qlocrontaskmanager'} From 3f848d66fda594f5d55ca7ca344313f0699ca43c Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Wed, 20 May 2026 16:17:50 +0530 Subject: [PATCH 04/11] disabled task description and added this lang object to convert string to langguage by --- .../controllers/admin/AdminCronTaskManagerController.php | 1 + modules/qlocrontaskmanager/qlocrontaskmanager.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 558584dbd..2df44bc3d 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -220,6 +220,7 @@ class AdminCronTaskManagerController extends ModuleAdminController 'label' => $this->l('Description'), 'name' => 'description', 'required' => true, + 'disabled' => true, 'col' => 7, 'hint' => $this->l('e.g. Sends the daily booking summary report'), 'desc' => $this->l('A short human-readable description of what this task does.'), diff --git a/modules/qlocrontaskmanager/qlocrontaskmanager.php b/modules/qlocrontaskmanager/qlocrontaskmanager.php index 9675cedc8..111aa77f1 100644 --- a/modules/qlocrontaskmanager/qlocrontaskmanager.php +++ b/modules/qlocrontaskmanager/qlocrontaskmanager.php @@ -230,7 +230,7 @@ class QloCronTaskManager extends Module $cronTask = new QctmCronTask(); $cronTask->id_module = $idModule; $cronTask->task_name = $task['name']; - $cronTask->description = $task['description']; + $cronTask->description = $this->l($task['description']); $cronTask->cron_expression = $task['cron']; $cronTask->callback = $task['callback']; $cronTask->active = 1; From ffa1aa038ab817ad52e132ce44237bf9d36ff9da Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Fri, 22 May 2026 15:10:16 +0530 Subject: [PATCH 05/11] Fixed: fix review changes --- .../admin/AdminCronTaskManagerController.php | 19 ++++++++++++++----- .../qlocrontaskmanager/qlocrontaskmanager.php | 2 +- .../qctm_reset_module_crons_link.tpl | 4 ++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 2df44bc3d..2fabf5043 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -151,11 +151,20 @@ class AdminCronTaskManagerController extends ModuleAdminController { parent::initToolbar(); unset($this->toolbar_btn['new']); - $this->page_header_toolbar_btn['view_all_logs'] = array( - 'href' => $this->context->link->getAdminLink('AdminCronTaskLogs'), - 'desc' => $this->l('View All Logs'), - 'icon' => 'icon-history', - ); + if($this->display && $this->display == 'list') { + $this->page_header_toolbar_btn['view_all_logs'] = array( + 'href' => $this->context->link->getAdminLink('AdminCronTaskLogs'), + 'desc' => $this->l('View All Logs'), + 'icon' => 'icon-history', + ); + }else if($this->display && $this->display == 'edit') { + $idCronTask = Tools::getValue('id_cron_task'); + $this->page_header_toolbar_btn['view_logs'] = array( + 'href' => $this->context->link->getAdminLink('AdminCronTaskLogs') . '&id_cron_task=' . (int) $idCronTask, + 'desc' => $this->l('View Logs'), + 'icon' => 'icon-history', + ); + } } diff --git a/modules/qlocrontaskmanager/qlocrontaskmanager.php b/modules/qlocrontaskmanager/qlocrontaskmanager.php index 111aa77f1..9675cedc8 100644 --- a/modules/qlocrontaskmanager/qlocrontaskmanager.php +++ b/modules/qlocrontaskmanager/qlocrontaskmanager.php @@ -230,7 +230,7 @@ class QloCronTaskManager extends Module $cronTask = new QctmCronTask(); $cronTask->id_module = $idModule; $cronTask->task_name = $task['name']; - $cronTask->description = $this->l($task['description']); + $cronTask->description = $task['description']; $cronTask->cron_expression = $task['cron']; $cronTask->callback = $task['callback']; $cronTask->active = 1; diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl index a0d3a6221..d73f43e90 100644 --- a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl +++ b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_reset_module_crons_link.tpl @@ -20,7 +20,7 @@ * @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 *} - - {l s='Reset' mod='qlocrontaskmanager'} + {l s='Reset Task' mod='qlocrontaskmanager'} From f7d1d629ca72fc5ffd3349dea2877013367cea81 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Fri, 5 Jun 2026 11:26:00 +0530 Subject: [PATCH 06/11] Fix: Move unused functions from main module file to class files and resolve critical and major issues --- .../classes/QctmCronTask.php | 162 ++++++++++++++++- .../admin/AdminCronTaskManagerController.php | 5 + modules/qlocrontaskmanager/cron.php | 6 +- .../qlocrontaskmanager/qlocrontaskmanager.php | 164 +----------------- .../admin/cron_task_manager/qctm_view_log.tpl | 27 --- 5 files changed, 174 insertions(+), 190 deletions(-) delete mode 100644 modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl diff --git a/modules/qlocrontaskmanager/classes/QctmCronTask.php b/modules/qlocrontaskmanager/classes/QctmCronTask.php index 586b2ba0e..b7b157366 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronTask.php +++ b/modules/qlocrontaskmanager/classes/QctmCronTask.php @@ -45,7 +45,7 @@ class QctmCronTask extends ObjectModel 'id_module' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), 'task_name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 128, 'required' => true), 'description' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 255, 'required' => true), - 'cron_expression' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 64, 'required' => true), + 'cron_expression' => array('type' => self::TYPE_STRING, 'validate' => 'isString', 'size' => 64, 'required' => true), 'callback' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 128, 'required' => true), 'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), 'is_system' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), @@ -64,6 +64,166 @@ class QctmCronTask extends ObjectModel ); } + public function dispatch() + { + $tasks = self::getActiveTasks(); + if (empty($tasks)) { + return; + } + + $now = new DateTime('now', new DateTimeZone(Configuration::get('PS_TIMEZONE') ?: date_default_timezone_get())); + $moduleCache = []; + foreach ($tasks as $task) { + if (!\Cron\CronExpression::isValidExpression($task['cron_expression'])) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Invalid cron expression "' . $task['cron_expression'] . '"' + ); + continue; + } + + $cron = \Cron\CronExpression::factory($task['cron_expression']); + + if (!$cron->isDue($now)) { + continue; + } + + $idModule = (int) $task['id_module']; + if (!array_key_exists($idModule, $moduleCache)) { + $moduleCache[$idModule] = Module::getInstanceById($idModule); + } + $module = $moduleCache[$idModule]; + + if (!$module) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Module with ID "' . (int) $task['id_module'] . '" could not be loaded' + ); + continue; + } + + if (!$module->active) { + continue; + } + + $callback = $task['callback']; + + if (!method_exists($module, $callback)) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + 'Callback method "' . $callback . '" not found on module "' . $module->name . '"' + ); + continue; + } + + $startTime = microtime(true); + + try { + $module->{$callback}(); + + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_SUCCESS, + null, + round(microtime(true) - $startTime, 4) + ); + } catch (\Throwable $e) { + QctmCronTaskLog::addLog( + $task['id_cron_task'], + QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, + $e->getMessage(), + round(microtime(true) - $startTime, 4) + ); + } + } + + } + + + public function syncAllExistingModules() + { + $allTasks = Hook::exec('registerCronTasks', array(), null, true); + + if (!empty($allTasks) && is_array($allTasks)) { + foreach ($allTasks as $moduleName => $tasks) { + if (!empty($tasks) && is_array($tasks)) { + $module = Module::getInstanceByName($moduleName); + if($module){ + $this->registerTasksForModule($module->id, $tasks); + } + } + } + } + } + + /** + * Register tasks for a specific module after validation + * + * @param int $idModule + * @param array $tasks + */ + public function registerTasksForModule($idModule, $tasks) + { + $module = Module::getInstanceById($idModule); + if (!$module) { + return; + } + + + foreach ($tasks as $task) { + if (!$this->validateTask($module, $task)) { + continue; + } + + if (QctmCronTask::getByModuleAndName($idModule, $task['name'])) { + continue; + } + + $cronTask = new QctmCronTask(); + $cronTask->id_module = $idModule; + $cronTask->task_name = $task['name']; + $cronTask->description = $task['description']; + $cronTask->cron_expression = $task['cron']; + $cronTask->callback = $task['callback']; + $cronTask->active = 1; + $cronTask->is_system = 0; + if (!$cronTask->save()) { + PrestaShopLogger::addLog( + 'QloCronTaskManager: failed to register task "' . $task['name'] . '" for module "' . $module->name . '"', + 3 + ); + } + } + } + + /** + * Validate a task definition array + * + * @param object $module + * @param array $task + * @return bool + */ + public function validateTask(Module $module, $task) + { + if (!Validate::isGenericName($task['name']) || !Validate::isGenericName($task['description']) + || !Validate::isString($task['cron']) || !Validate::isString($task['callback'])) { + return false; + } + + if (!\Cron\CronExpression::isValidExpression($task['cron'])) { + return false; + } + + if (!method_exists($module, $task['callback'])) { + return false; + } + + return true; + } + /** * @param int $idModule * @param string $taskName diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 2fabf5043..10a29b905 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -372,6 +372,11 @@ class AdminCronTaskManagerController extends ModuleAdminController foreach ($tasks as $task) { if ($task['name'] === $cronTask->task_name) { + if (!$cronTask->validateTask($module, $task)) { + $this->errors[] = $this->l('Module returned an invalid task definition (bad cron expression or missing callback).'); + return false; + } + $cronTask->cron_expression = $task['cron']; $cronTask->description = $task['description']; $cronTask->callback = $task['callback']; diff --git a/modules/qlocrontaskmanager/cron.php b/modules/qlocrontaskmanager/cron.php index d37c9e035..48bf45cae 100644 --- a/modules/qlocrontaskmanager/cron.php +++ b/modules/qlocrontaskmanager/cron.php @@ -35,7 +35,9 @@ if (!$token || $token !== $expectedToken) { $module = Module::getInstanceByName('qlocrontaskmanager'); if (!$module || !$module->active) { - die('Module not active'); + die('Cron module not active'); } -$module->dispatch(); +require_once dirname(__FILE__) . '/classes/QctmCronTask.php'; +$objCronTask = new QctmCronTask(); +$objCronTask->dispatch(); diff --git a/modules/qlocrontaskmanager/qlocrontaskmanager.php b/modules/qlocrontaskmanager/qlocrontaskmanager.php index 9675cedc8..2df0f0237 100644 --- a/modules/qlocrontaskmanager/qlocrontaskmanager.php +++ b/modules/qlocrontaskmanager/qlocrontaskmanager.php @@ -174,7 +174,8 @@ class QloCronTaskManager extends Module if (method_exists($module, 'hookRegisterCronTasks')) { $tasks = $module->hookRegisterCronTasks(); if (!empty($tasks) && is_array($tasks)) { - $this->registerTasksForModule($module->id, $tasks); + $objCronTask = new QctmCronTask(); + $objCronTask->registerTasksForModule($module->id, $tasks); } } } @@ -188,164 +189,6 @@ class QloCronTaskManager extends Module QctmCronTask::deleteByModule($params['object']->id); } - protected function syncAllExistingModules() - { - $allTasks = Hook::exec('registerCronTasks', array(), null, true); - - if (!empty($allTasks) && is_array($allTasks)) { - foreach ($allTasks as $moduleName => $tasks) { - if (!empty($tasks) && is_array($tasks)) { - $module = Module::getInstanceByName($moduleName); - if($module){ - $this->registerTasksForModule($module->id, $tasks); - } - } - } - } - } - - /** - * Register tasks for a specific module after validation - * - * @param string $moduleName - * @param array $tasks - */ - protected function registerTasksForModule($idModule, $tasks) - { - $module = Module::getInstanceById($idModule); - if (!$module) { - return; - } - - - foreach ($tasks as $task) { - if (!$this->validateTask($module, $task)) { - continue; - } - - if (QctmCronTask::getByModuleAndName($idModule, $task['name'])) { - continue; - } - - $cronTask = new QctmCronTask(); - $cronTask->id_module = $idModule; - $cronTask->task_name = $task['name']; - $cronTask->description = $task['description']; - $cronTask->cron_expression = $task['cron']; - $cronTask->callback = $task['callback']; - $cronTask->active = 1; - $cronTask->is_system = 0; - if (!$cronTask->save()) { - PrestaShopLogger::addLog( - 'QloCronTaskManager: failed to register task "' . $task['name'] . '" for module "' . $module->name . '"', - 3 - ); - } - } - } - - /** - * Validate a task definition array - * - * @param string $moduleName - * @param array $task - * @return bool - */ - protected function validateTask(Module $module, $task) - { - if (!Validate::isGenericName($task['name']) || !Validate::isGenericName($task['description']) - || !Validate::isString($task['cron']) || !Validate::isString($task['callback'])) { - return false; - } - - if (!\Cron\CronExpression::isValidExpression($task['cron'])) { - return false; - } - - if (!method_exists($module, $task['callback'])) { - return false; - } - - return true; - } - - public function dispatch() - { - if (!$this->active) { - return; - } - - $tasks = QctmCronTask::getActiveTasks(); - if (empty($tasks)) { - return; - } - - $now = new DateTime(); - - foreach ($tasks as $task) { - if (!\Cron\CronExpression::isValidExpression($task['cron_expression'])) { - QctmCronTaskLog::addLog( - $task['id_cron_task'], - QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, - 'Invalid cron expression "' . $task['cron_expression'] . '"' - ); - continue; - } - - $cron = \Cron\CronExpression::factory($task['cron_expression']); - - if (!$cron->isDue($now)) { - continue; - } - - $module = Module::getInstanceById($task['id_module']); - - if (!$module) { - QctmCronTaskLog::addLog( - $task['id_cron_task'], - QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, - 'Module with ID "' . (int) $task['id_module'] . '" could not be loaded' - ); - continue; - } - - if (!$module->active) { - continue; - } - - $callback = $task['callback']; - - if (!method_exists($module, $callback)) { - QctmCronTaskLog::addLog( - $task['id_cron_task'], - QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, - 'Callback method "' . $callback . '" not found on module "' . $module->name . '"' - ); - continue; - } - - $startTime = microtime(true); - - try { - $module->{$callback}(); - - QctmCronTaskLog::addLog( - $task['id_cron_task'], - QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_SUCCESS, - null, - round(microtime(true) - $startTime, 4) - ); - } catch (\Throwable $e) { - QctmCronTaskLog::addLog( - $task['id_cron_task'], - QctmCronTaskLog::QCTM_CRON_TASK_LOG_STATUS_ERROR, - $e->getMessage(), - round(microtime(true) - $startTime, 4) - ); - } - } - } - /** * Called by the system log-cleanup cron task (daily at midnight). */ @@ -361,6 +204,7 @@ class QloCronTaskManager extends Module public function install() { $db = new QctmCronTaskManagerDb(); + $objCronTask = new QctmCronTask(); if (!parent::install() || !$this->registerHooks() @@ -372,7 +216,7 @@ class QloCronTaskManager extends Module return false; } - $this->syncAllExistingModules(); + $objCronTask->syncAllExistingModules(); return true; } diff --git a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl b/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl deleted file mode 100644 index 25390af48..000000000 --- a/modules/qlocrontaskmanager/views/templates/admin/cron_task_manager/qctm_view_log.tpl +++ /dev/null @@ -1,27 +0,0 @@ -{* -* NOTICE OF LICENSE -* -* This source file is subject to the Academic Free License (AFL 3.0) -* that is bundled with this package in the file LICENSE.md -* It is also available through the world-wide-web at this URL: -* https://opensource.org/licenses/afl-3.0.php -* If you did not receive a copy of the license and are unable to -* obtain it through the world-wide-web, please send an email -* to support@qloapps.com so we can send you a copy immediately. -* -* DISCLAIMER -* -* Do not edit or add to this file if you wish to upgrade this module to a newer -* versions in the future. If you wish to customize this module for your needs -* please refer to https://store.webkul.com/customisation-guidelines for more information. -* -* @author Webkul IN -* @copyright Since 2010 Webkul -* @license https://opensource.org/licenses/afl-3.0.php Academic Free License 3.0 -*} - - From 689613a3182f1230aae79c7454483e8b62c69be6 Mon Sep 17 00:00:00 2001 From: amit sharma Date: Mon, 6 Jul 2026 12:42:18 +0530 Subject: [PATCH 07/11] Fixed: Removed the unnecessary library and created a custom function to convert cron expressions into a human-readable format. --- .../classes/QctmCronExpressionTranslator.php | 355 ++++++++++++++++++ .../classes/QctmRequiredClasses.php | 1 + modules/qlocrontaskmanager/composer.json | 3 +- .../admin/AdminCronTaskManagerController.php | 22 +- .../vendor/composer/autoload_psr4.php | 1 - .../vendor/composer/autoload_static.php | 8 - .../vendor/composer/installed.json | 59 --- .../vendor/composer/installed.php | 13 +- .../lorisleiva/cron-translator/.gitignore | 5 - .../vendor/lorisleiva/cron-translator/LICENSE | 21 -- .../lorisleiva/cron-translator/README.md | 51 --- .../lorisleiva/cron-translator/composer.json | 34 -- .../cron-translator/src/CronExpression.php | 110 ------ .../src/CronParsingException.php | 13 - .../cron-translator/src/CronTranslator.php | 76 ---- .../cron-translator/src/CronType.php | 91 ----- .../cron-translator/src/DaysOfMonthField.php | 68 ---- .../cron-translator/src/DaysOfWeekField.php | 56 --- .../lorisleiva/cron-translator/src/Field.php | 63 ---- .../cron-translator/src/HoursField.php | 85 ----- .../cron-translator/src/MinutesField.php | 39 -- .../cron-translator/src/MonthsField.php | 66 ---- .../src/TranslationFileMissingException.php | 13 - .../cron-translator/src/lang/ar/days.php | 11 - .../cron-translator/src/lang/ar/fields.php | 48 --- .../cron-translator/src/lang/ar/months.php | 16 - .../cron-translator/src/lang/ar/ordinals.php | 12 - .../cron-translator/src/lang/ar/times.php | 7 - .../cron-translator/src/lang/de/days.php | 11 - .../cron-translator/src/lang/de/fields.php | 50 --- .../cron-translator/src/lang/de/months.php | 16 - .../cron-translator/src/lang/de/ordinals.php | 13 - .../cron-translator/src/lang/de/times.php | 7 - .../cron-translator/src/lang/en/days.php | 11 - .../cron-translator/src/lang/en/fields.php | 48 --- .../cron-translator/src/lang/en/months.php | 16 - .../cron-translator/src/lang/en/ordinals.php | 12 - .../cron-translator/src/lang/en/times.php | 7 - .../cron-translator/src/lang/es/days.php | 11 - .../cron-translator/src/lang/es/fields.php | 48 --- .../cron-translator/src/lang/es/months.php | 16 - .../cron-translator/src/lang/es/ordinals.php | 5 - .../cron-translator/src/lang/es/times.php | 6 - .../cron-translator/src/lang/fr/days.php | 11 - .../cron-translator/src/lang/fr/fields.php | 48 --- .../cron-translator/src/lang/fr/months.php | 16 - .../cron-translator/src/lang/fr/ordinals.php | 6 - .../cron-translator/src/lang/fr/times.php | 7 - .../cron-translator/src/lang/lv/days.php | 11 - .../cron-translator/src/lang/lv/fields.php | 48 --- .../cron-translator/src/lang/lv/months.php | 16 - .../cron-translator/src/lang/lv/ordinals.php | 5 - .../cron-translator/src/lang/lv/times.php | 7 - .../cron-translator/src/lang/nl/days.php | 11 - .../cron-translator/src/lang/nl/fields.php | 48 --- .../cron-translator/src/lang/nl/months.php | 16 - .../cron-translator/src/lang/nl/ordinals.php | 5 - .../cron-translator/src/lang/nl/times.php | 5 - .../cron-translator/src/lang/pt/days.php | 11 - .../cron-translator/src/lang/pt/fields.php | 48 --- .../cron-translator/src/lang/pt/months.php | 16 - .../cron-translator/src/lang/pt/ordinals.php | 5 - .../cron-translator/src/lang/pt/times.php | 6 - .../cron-translator/src/lang/sk/days.php | 11 - .../cron-translator/src/lang/sk/fields.php | 48 --- .../cron-translator/src/lang/sk/months.php | 16 - .../cron-translator/src/lang/sk/ordinals.php | 12 - .../cron-translator/src/lang/sk/times.php | 7 - .../cron-translator/src/lang/zh/days.php | 11 - .../cron-translator/src/lang/zh/fields.php | 48 --- .../cron-translator/src/lang/zh/months.php | 16 - .../cron-translator/src/lang/zh/ordinals.php | 5 - .../cron-translator/src/lang/zh/times.php | 5 - .../views/templates/admin/configure.tpl | 4 +- 74 files changed, 368 insertions(+), 1785 deletions(-) create mode 100644 modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronTranslator.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php delete mode 100644 modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php diff --git a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php new file mode 100644 index 000000000..9f26af8a1 --- /dev/null +++ b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php @@ -0,0 +1,355 @@ + 'Sunday', + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + 7 => 'Sunday', + ); + + const MONTHS = array( + 1 => 'January', + 2 => 'February', + 3 => 'March', + 4 => 'April', + 5 => 'May', + 6 => 'June', + 7 => 'July', + 8 => 'August', + 9 => 'September', + 10 => 'October', + 11 => 'November', + 12 => 'December', + ); + + public static function translate($expression) + { + $parts = preg_split('/\s+/', trim($expression)); + + if (count($parts) !== 5) { + return self::trans('Invalid cron expression'); + } + + list($minute, $hour, $day, $month, $weekday) = $parts; + + if ($expression === '* * * * *') { + return self::trans('Every minute'); + } + + if (preg_match('/^\*\/(\d+)$/', $minute, $m) + && $hour == '*' && $day == '*' && $month == '*' && $weekday == '*') { + return self::trans('Every %d minutes', (int) $m[1]); + } + + if ($minute == '0' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '*') { + return self::trans('Every hour'); + } + + if ($minute == '0' + && preg_match('/^\*\/(\d+)$/', $hour, $m) + && $day == '*' && $month == '*' && $weekday == '*') { + return self::trans('Every %d hours', (int) $m[1]); + } + + if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '*') { + return self::trans('Every day at %s', self::hourLabel($hour, $minute)); + } + + if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $weekday == '*' + && preg_match('/^\*\/(\d+)$/', $month, $mm)) { + return self::trans( + 'Every day every %1$d months at %2$s', + array((int) $mm[1], self::hourLabel($hour, $minute)) + ); + } + + if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '1-5') { + return self::trans('Every weekday at %s', self::hourLabel($hour, $minute)); + } + + if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '0,6') { + return self::trans('Every weekend at %s', self::hourLabel($hour, $minute)); + } + + if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && self::isNumeric($weekday)) { + return self::trans( + 'Every %1$s at %2$s', + array(self::weekdayName($weekday), self::hourLabel($hour, $minute)) + ); + } + + if (strpos($weekday, ',') !== false + && self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*') { + return self::trans( + 'Every %1$s at %2$s', + array(self::joinNames($weekday, 'weekdayName'), self::hourLabel($hour, $minute)) + ); + } + + if (self::isNumeric($day) && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { + return self::trans( + 'On the %1$s of every month at %2$s', + array(self::ordinal($day), self::hourLabel($hour, $minute)) + ); + } + + if (preg_match('/^(\d+)-(\d+)$/', $day, $dm) + && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { + return self::trans( + 'Every day from the %1$s to the %2$s of each month at %3$s', + array(self::ordinal($dm[1]), self::ordinal($dm[2]), self::hourLabel($hour, $minute)) + ); + } + + if (strpos($day, ',') !== false + && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { + return self::trans( + 'On the %1$s of each month at %2$s', + array(self::joinOrdinals($day), self::hourLabel($hour, $minute)) + ); + } + + if (self::isNumeric($day) && self::isNumeric($month) && self::isNumeric($hour) && self::isNumeric($minute) && $weekday == '*') { + return self::trans( + 'Every %1$s %2$s at %3$s', + array(self::monthName($month), self::ordinal($day), self::hourLabel($hour, $minute)) + ); + } + + if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && self::isNumeric($weekday)) { + return self::trans('Every minute on %s', self::weekdayName($weekday)); + } + + if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '1-5') { + return self::trans('Every minute on weekdays'); + } + + if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '0,6') { + return self::trans('Every minute on weekends'); + } + + if (self::isNumeric($minute) && self::isNumeric($hour)) { + $when = self::trans('At %s', self::hourLabel($hour, $minute)); + } else { + $minuteFreq = null; + if ($minute === '*') { + $minuteFreq = self::trans('Every minute'); + } elseif (preg_match('/^\*\/(\d+)$/', $minute, $m)) { + $minuteFreq = self::trans('Every %d minutes', (int) $m[1]); + } + + if (preg_match('/^(\d+)-(\d+)\/(\d+)$/', $hour, $hs) && self::isNumeric($minute)) { + $when = self::trans( + 'Every %1$d hours between %2$s and %3$s', + array((int) $hs[3], self::hourLabel($hs[1], $minute), self::hourLabel($hs[2], $minute)) + ); + } elseif ($minuteFreq !== null) { + if ($hour === '*') { + $when = $minuteFreq; + } elseif (self::isNumeric($hour)) { + $when = self::trans('%1$s, during hour %2$s', array($minuteFreq, $hour)); + } elseif (preg_match('/^(\d+)-(\d+)$/', $hour, $hm)) { + $when = self::trans( + '%1$s between %2$s and %3$s', + array($minuteFreq, self::hourLabel($hm[1], 0), self::hourLabel($hm[2], 59)) + ); + } else { + $when = self::trans('%1$s, during hour %2$s', array($minuteFreq, $hour)); + } + } elseif (self::isNumeric($minute) && $hour === '*') { + $when = self::trans('Every hour, at minute %s', $minute); + } elseif (self::isNumeric($minute) && preg_match('/^(\d+)-(\d+)$/', $hour, $hm)) { + $when = self::trans( + 'At minute %1$s, between %2$s and %3$s', + array($minute, self::hourLabel($hm[1], $minute), self::hourLabel($hm[2], $minute)) + ); + } else { + $when = self::trans('At minute %1$s, hour %2$s', array($minute, $hour)); + } + } + + $conditions = array(); + + if ($day !== '*') { + if (preg_match('/^(\d+)-(\d+)$/', $day, $dm)) { + $conditions[] = self::trans( + 'from the %1$s to the %2$s of the month', + array(self::ordinal($dm[1]), self::ordinal($dm[2])) + ); + } elseif (strpos($day, ',') !== false) { + $conditions[] = self::trans('on the %s of the month', self::joinOrdinals($day)); + } elseif (self::isNumeric($day)) { + $conditions[] = self::trans('on the %s of the month', self::ordinal($day)); + } else { + $conditions[] = self::trans('on day %s of the month', $day); + } + } + + if ($month !== '*') { + if (preg_match('/^\*\/(\d+)$/', $month, $mf)) { + $conditions[] = self::trans('every %d months', (int) $mf[1]); + } else { + if (preg_match('/^(\d+)-(\d+)$/', $month, $mm)) { + $monthDesc = self::trans('%1$s through %2$s', array(self::monthName($mm[1]), self::monthName($mm[2]))); + } elseif (strpos($month, ',') !== false) { + $monthDesc = self::joinNames($month, 'monthName'); + } else { + $monthDesc = self::monthName($month); + } + $conditions[] = self::trans('in %s', $monthDesc); + } + } + + if ($weekday !== '*') { + if ($weekday === '1-5') { + $weekdayDesc = self::trans('weekdays'); + } elseif ($weekday === '0,6' || $weekday === '6,0') { + $weekdayDesc = self::trans('weekends'); + } elseif (preg_match('/^(\d+)-(\d+)$/', $weekday, $wm)) { + $weekdayDesc = self::trans('%1$s through %2$s', array(self::weekdayName($wm[1]), self::weekdayName($wm[2]))); + } elseif (strpos($weekday, ',') !== false) { + $weekdayDesc = self::joinNames($weekday, 'weekdayName'); + } else { + $weekdayDesc = self::weekdayName($weekday); + } + $conditions[] = self::trans('on %s', $weekdayDesc); + } + + if (empty($conditions)) { + return $when; + } + + return $when . ' ' . implode(', ', $conditions); + } + + public static function trans($string, $sprintf = null) + { + return Translate::getModuleTranslation(self::MODULE_NAME, $string, 'QctmCronExpressionTranslator', $sprintf); + } + + public static function isNumeric($value) + { + return preg_match('/^\d+$/', $value) === 1; + } + + public static function weekdayName($weekday) + { + return (self::isNumeric($weekday) && isset(self::WEEKDAYS[(int) $weekday])) + ? self::trans(self::WEEKDAYS[(int) $weekday]) + : $weekday; + } + + public static function monthName($month) + { + return (self::isNumeric($month) && isset(self::MONTHS[(int) $month])) + ? self::trans(self::MONTHS[(int) $month]) + : $month; + } + + public static function hourLabel($hour, $minute = null) + { + $h = (int) $hour; + $suffix = $h >= 12 ? self::trans('PM') : self::trans('AM'); + $h12 = $h % 12; + + if ($h12 === 0) { + $h12 = 12; + } + + return $minute === null + ? sprintf('%d %s', $h12, $suffix) + : sprintf('%d:%02d %s', $h12, (int) $minute, $suffix); + } + + public static function joinNames($csv, $nameMethod) + { + $names = array(); + + foreach (explode(',', $csv) as $token) { + $names[] = self::$nameMethod($token); + } + + if (count($names) === 1) { + return $names[0]; + } + + $last = array_pop($names); + + return implode(', ', $names) . ' ' . self::trans('and') . ' ' . $last; + } + + public static function ordinal($number) + { + $number = (int) $number; + + if ($number % 100 >= 11 && $number % 100 <= 13) { + $suffix = 'th'; + } else { + switch ($number % 10) { + case 1: + $suffix = 'st'; + break; + case 2: + $suffix = 'nd'; + break; + case 3: + $suffix = 'rd'; + break; + default: + $suffix = 'th'; + } + } + + return $number . $suffix; + } + + public static function joinOrdinals($csv) + { + $ordinals = array(); + + foreach (explode(',', $csv) as $token) { + $ordinals[] = self::isNumeric($token) ? self::ordinal($token) : $token; + } + + if (count($ordinals) === 1) { + return $ordinals[0]; + } + + $last = array_pop($ordinals); + + return implode(', ', $ordinals) . ' ' . self::trans('and') . ' ' . $last; + } +} diff --git a/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php b/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php index e8ec7e004..9f2e9b4f4 100644 --- a/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php +++ b/modules/qlocrontaskmanager/classes/QctmRequiredClasses.php @@ -24,4 +24,5 @@ require_once _PS_MODULE_DIR_.'qlocrontaskmanager/classes/QctmCronTaskManagerDb.php'; require_once _PS_MODULE_DIR_.'qlocrontaskmanager/classes/QctmCronTask.php'; require_once _PS_MODULE_DIR_.'qlocrontaskmanager/classes/QctmCronTaskLog.php'; +require_once _PS_MODULE_DIR_.'qlocrontaskmanager/classes/QctmCronExpressionTranslator.php'; require_once _PS_MODULE_DIR_.'qlocrontaskmanager/vendor/autoload.php'; diff --git a/modules/qlocrontaskmanager/composer.json b/modules/qlocrontaskmanager/composer.json index 33e328f69..28ab8bb00 100644 --- a/modules/qlocrontaskmanager/composer.json +++ b/modules/qlocrontaskmanager/composer.json @@ -2,7 +2,6 @@ "name": "qloapps/qlocrontaskmanager", "description": "Centralized cron task manager for QloApps modules", "require": { - "dragonmantank/cron-expression": "^3.0", - "lorisleiva/cron-translator": "^0.3" + "dragonmantank/cron-expression": "^3.0" } } diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 10a29b905..228a055f7 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -178,11 +178,7 @@ class AdminCronTaskManagerController extends ModuleAdminController $isValidExpression = \Cron\CronExpression::isValidExpression($cronExpression); if ($isValidExpression) { - try { - $readable = $this->l('Runs ') . \Lorisleiva\CronTranslator\CronTranslator::translate($cronExpression); - } catch (\Exception $e) { - $readable = ''; - } + $readable = $this->l('Runs ') . QctmCronExpressionTranslator::translate($cronExpression); } else { $readable = $this->l('Invalid cron expression.'); } @@ -275,12 +271,8 @@ class AdminCronTaskManagerController extends ModuleAdminController if (\Cron\CronExpression::isValidExpression($expression)) { $result['valid'] = true; - try { - $result['readable'] = $this->l('Runs ') . \Lorisleiva\CronTranslator\CronTranslator::translate($expression); - } catch (\Exception $e) { - $result['readable'] = $this->l('Invalid cron expression'); - } - } + $result['readable'] = $this->l('Runs ') . QctmCronExpressionTranslator::translate($expression); + } die(Tools::jsonEncode($result)); } @@ -397,11 +389,9 @@ class AdminCronTaskManagerController extends ModuleAdminController public function getCronWithReadable($cronExpression, $row) { - try { - $readable = \Lorisleiva\CronTranslator\CronTranslator::translate($cronExpression); - } catch (\Exception $e) { - $readable = $cronExpression; - } + $readable = \Cron\CronExpression::isValidExpression($cronExpression) + ? QctmCronExpressionTranslator::translate($cronExpression) + : $cronExpression; return '' . htmlspecialchars($cronExpression) . '' . '
' . htmlspecialchars($readable) . ''; diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php b/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php index bc4d7c736..e12811ddf 100644 --- a/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_psr4.php @@ -6,6 +6,5 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( - 'Lorisleiva\\CronTranslator\\' => array($vendorDir . '/lorisleiva/cron-translator/src'), 'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'), ); diff --git a/modules/qlocrontaskmanager/vendor/composer/autoload_static.php b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php index f60e1ecf0..e173eaa32 100644 --- a/modules/qlocrontaskmanager/vendor/composer/autoload_static.php +++ b/modules/qlocrontaskmanager/vendor/composer/autoload_static.php @@ -7,10 +7,6 @@ namespace Composer\Autoload; class ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3 { public static $prefixLengthsPsr4 = array ( - 'L' => - array ( - 'Lorisleiva\\CronTranslator\\' => 26, - ), 'C' => array ( 'Cron\\' => 5, @@ -18,10 +14,6 @@ class ComposerStaticInitc2f08a32af23ee5d749fb28358c620b3 ); public static $prefixDirsPsr4 = array ( - 'Lorisleiva\\CronTranslator\\' => - array ( - 0 => __DIR__ . '/..' . '/lorisleiva/cron-translator/src', - ), 'Cron\\' => array ( 0 => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron', diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.json b/modules/qlocrontaskmanager/vendor/composer/installed.json index f88a76e51..0228ed9d2 100644 --- a/modules/qlocrontaskmanager/vendor/composer/installed.json +++ b/modules/qlocrontaskmanager/vendor/composer/installed.json @@ -66,65 +66,6 @@ } ], "install-path": "../dragonmantank/cron-expression" - }, - { - "name": "lorisleiva/cron-translator", - "version": "v0.3.2", - "version_normalized": "0.3.2.0", - "source": { - "type": "git", - "url": "https://github.com/lorisleiva/cron-translator.git", - "reference": "d49dd7732197c2e967a66968c9323c81826c86e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lorisleiva/cron-translator/zipball/d49dd7732197c2e967a66968c9323c81826c86e5", - "reference": "d49dd7732197c2e967a66968c9323c81826c86e5", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.21 || ^9.5" - }, - "time": "2022-06-16T07:37:33+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Lorisleiva\\CronTranslator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Loris LEIVA", - "email": "loris.leiva@gmail.com", - "homepage": "https://lorisleiva.com" - } - ], - "description": "Makes CRON expressions human-readable", - "homepage": "https://github.com/lorisleiva/cron-translator", - "keywords": [ - "cron", - "expression", - "human" - ], - "support": { - "issues": "https://github.com/lorisleiva/cron-translator/issues", - "source": "https://github.com/lorisleiva/cron-translator/tree/v0.3.2" - }, - "funding": [ - { - "url": "https://github.com/lorisleiva", - "type": "github" - } - ], - "install-path": "../lorisleiva/cron-translator" } ], "dev": true, diff --git a/modules/qlocrontaskmanager/vendor/composer/installed.php b/modules/qlocrontaskmanager/vendor/composer/installed.php index 0833755c0..65c12da9b 100644 --- a/modules/qlocrontaskmanager/vendor/composer/installed.php +++ b/modules/qlocrontaskmanager/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'qloapps/qlocrontaskmanager', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => 'e05c7ad488aaf4098ed194c7ddf0024160987974', + 'reference' => 'f7d1d629ca72fc5ffd3349dea2877013367cea81', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -19,15 +19,6 @@ 'aliases' => array(), 'dev_requirement' => false, ), - 'lorisleiva/cron-translator' => array( - 'pretty_version' => 'v0.3.2', - 'version' => '0.3.2.0', - 'reference' => 'd49dd7732197c2e967a66968c9323c81826c86e5', - 'type' => 'library', - 'install_path' => __DIR__ . '/../lorisleiva/cron-translator', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'mtdowling/cron-expression' => array( 'dev_requirement' => false, 'replaced' => array( @@ -37,7 +28,7 @@ 'qloapps/qlocrontaskmanager' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => 'e05c7ad488aaf4098ed194c7ddf0024160987974', + 'reference' => 'f7d1d629ca72fc5ffd3349dea2877013367cea81', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore deleted file mode 100644 index 2825f8dca..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -vendor -composer.lock -.phpunit.result.cache -build -.idea \ No newline at end of file diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE deleted file mode 100644 index fdf460a8f..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Loris Leiva - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md deleted file mode 100644 index 8fe69121c..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# CRON Translator -⏰️ Makes CRON expressions human-readable - -![intro-rounded](https://user-images.githubusercontent.com/3642397/60768671-7d6c7100-a0be-11e9-8cee-8a8d2780d76f.png) - -## Installation - -```sh -composer require lorisleiva/cron-translator -``` - -## Usage - -```php -use Lorisleiva\CronTranslator\CronTranslator; - -CronTranslator::translate('* * * * *'); // => Every minute -CronTranslator::translate('30 22 * * *'); // => Every day at 10:30pm -CronTranslator::translate('0 16 * * 1'); // => Every Monday at 4:00pm -CronTranslator::translate('0 0 1 1 *'); // => Every year on January the 1st at 12:00am -CronTranslator::translate('0 0 1 * *'); // => The 1st of every month at 12:00am -CronTranslator::translate('0 * * * 1'); // => Once an hour on Mondays -CronTranslator::translate('* 1-20 * * *'); // => Every minute 20 hours a day -CronTranslator::translate('0,30 * * * *'); // => Twice an hour -CronTranslator::translate('0 1-5 * * *'); // => 5 times a day -CronTranslator::translate('0 1 1-5 * *'); // => 5 days a month at 1:00am -CronTranslator::translate('*/2 * * * *'); // => Every 2 minutes -CronTranslator::translate('* 1/3 2 * *'); // => Every minute of every 3 hours on the 2nd of every month -CronTranslator::translate('1-3/5 * * * *'); // => 3 times every 5 minutes -CronTranslator::translate('1,2 0 */2 1,2 *'); // => Twice an hour every 2 days 2 months a year at 12am -``` - -You may also provide a locale as a second argument and whether to format the time using the 24-hour format as a third argument. - - -```php -CronTranslator::translate('30 18 * * *', 'fr'); // => Chaque jour à 6:30pm -CronTranslator::translate('30 18 * * *', 'fr', true); // => Chaque jour à 18:30 -``` - -The following locales are currently supported. Feel free to PR more locales if you need them. 🙂 -- `ar` — Arabic -- `de` — German -- `en` — English -- `es` — Spanish -- `fr` — French -- `lv` — Latvian -- `nl` — Dutch -- `pt` — Portuguese -- `zh` — Chinese -- `sk` - Slovak diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json deleted file mode 100644 index 1853c3dca..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "lorisleiva/cron-translator", - "description": "Makes CRON expressions human-readable", - "keywords": [ - "cron", - "expression", - "human" - ], - "homepage": "https://github.com/lorisleiva/cron-translator", - "license": "MIT", - "authors": [ - { - "name": "Loris LEIVA", - "email": "loris.leiva@gmail.com", - "homepage": "https://lorisleiva.com" - } - ], - "require": { - "php" : "^7.4 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit" : "^8.5.21 || ^9.5" - }, - "autoload": { - "psr-4": { - "Lorisleiva\\CronTranslator\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "Lorisleiva\\CronTranslator\\Tests\\": "tests" - } - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php deleted file mode 100644 index f129e1116..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronExpression.php +++ /dev/null @@ -1,110 +0,0 @@ -raw = $cron; - $fields = explode(' ', $cron); - $this->minute = new MinutesField($this, $fields[0]); - $this->hour = new HoursField($this, $fields[1]); - $this->day = new DaysOfMonthField($this, $fields[2]); - $this->month = new MonthsField($this, $fields[3]); - $this->weekday = new DaysOfWeekField($this, $fields[4]); - $this->locale = $locale; - $this->timeFormat24hours = $timeFormat24hours; - $this->ensureLocaleExists(); - $this->loadTranslations(); - } - - public function getFields(): array - { - return [ - $this->minute, - $this->hour, - $this->day, - $this->month, - $this->weekday, - ]; - } - - public function langCountable(string $type, int $number) - { - $array = $this->translations[$type]; - - $value = $array[$number] ?? ($array['default'] ?: ''); - - return str_replace(':number', $number, $value); - } - - public function lang(string $key, array $replacements = []) - { - $translation = $this->getArrayDot($this->translations['fields'], $key); - - foreach ($replacements as $key => $value) { - $translation = str_replace(':' . $key, $value, $translation); - } - - return $translation; - } - - protected function ensureLocaleExists(string $fallbackLocale = 'en') - { - if (! is_dir($this->getTranslationDirectory())) { - $this->locale = $fallbackLocale; - } - } - - /** - * @throws TranslationFileMissingException - */ - protected function loadTranslations() - { - $this->translations = [ - 'days' => $this->loadTranslationFile('days'), - 'fields' => $this->loadTranslationFile('fields'), - 'months' => $this->loadTranslationFile('months'), - 'ordinals' => $this->loadTranslationFile('ordinals'), - 'times' => $this->loadTranslationFile('times'), - ]; - } - - protected function loadTranslationFile(string $file) - { - $filename = sprintf('%s/%s.php', $this->getTranslationDirectory(), $file); - - if (! is_file($filename)) { - throw new TranslationFileMissingException($this->locale, $file); - } - - return include $filename; - } - - protected function getTranslationDirectory(): string - { - return __DIR__ . '/lang/' . $this->locale; - } - - protected function getArrayDot(array $array, string $key) - { - $keys = explode('.', $key); - - foreach ($keys as $key) { - $array = $array[$key]; - } - - return $array; - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php deleted file mode 100644 index 06f8c7335..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronParsingException.php +++ /dev/null @@ -1,13 +0,0 @@ - '0 0 1 1 *', - '@annually' => '0 0 1 1 *', - '@monthly' => '0 0 1 * *', - '@weekly' => '0 0 * * 0', - '@daily' => '0 0 * * *', - '@hourly' => '0 * * * *' - ]; - - public static function translate(string $cron, string $locale = 'en', bool $timeFormat24hours = false): string - { - if (isset(self::$extendedMap[$cron])) { - $cron = self::$extendedMap[$cron]; - } - - try { - $expression = new CronExpression($cron, $locale, $timeFormat24hours); - $orderedFields = static::orderFields($expression->getFields()); - - $translations = array_map(function (Field $field) { - return $field->translate(); - }, $orderedFields); - - return ucfirst(implode(' ', array_filter($translations))); - } catch (Throwable $th) { - throw new CronParsingException($cron); - } - } - - protected static function orderFields(array $fields) - { - // Group fields by CRON types. - $onces = static::filterType($fields, 'Once'); - $everys = static::filterType($fields, 'Every'); - $incrementsAndMultiples = static::filterType($fields, 'Increment', 'Multiple'); - - // Decide whether to keep one or zero CRON type "Every". - $firstEvery = reset($everys)->position ?? PHP_INT_MIN; - $firstIncrementOrMultiple = reset($incrementsAndMultiples)->position ?? PHP_INT_MAX; - $numberOfEverysKept = $firstIncrementOrMultiple < $firstEvery ? 0 : 1; - - // Mark fields that will not be displayed as dropped. - // This allows other fields to check whether some - // information is missing and adapt their translation. - /** @var Field $field */ - foreach (array_slice($everys, $numberOfEverysKept) as $field) { - $field->dropped = true; - } - - return array_merge( - // Place one or zero "Every" field at the beginning. - array_slice($everys, 0, $numberOfEverysKept), - - // Place all "Increment" and "Multiple" fields in the middle. - $incrementsAndMultiples, - - // Finish with the "Once" fields reversed (i.e. from months to minutes). - array_reverse($onces) - ); - } - - protected static function filterType(array $fields, ...$types): array - { - return array_filter($fields, function (Field $field) use ($types) { - return $field->hasType(...$types); - }); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php deleted file mode 100644 index 3c4629bed..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/CronType.php +++ /dev/null @@ -1,91 +0,0 @@ -type = $type; - $this->value = $value; - $this->count = $count; - $this->increment = $increment; - } - - public static function every(): CronType - { - return new static('Every'); - } - - public static function increment(int $increment, int $count = 1): CronType - { - return new static('Increment', null, $count, $increment); - } - - public static function multiple(int $count): CronType - { - return new static('Multiple', null, $count); - } - - public static function once(int $value): CronType - { - return new static('Once', $value); - } - - /** - * @throws CronParsingException - */ - public static function parse(string $expression): CronType - { - // Parse "*". - if ($expression === '*') { - return static::every(); - } - - // Parse fixed values like "1". - if (preg_match("/^[0-9]+$/", $expression)) { - return static::once((int) $expression); - } - - // Parse multiple selected values like "1,2,5". - if (preg_match("/^[0-9]+(,[0-9]+)+$/", $expression)) { - return static::multiple(count(explode(',', $expression))); - } - - // Parse ranges of selected values like "1-5". - if (preg_match("/^([0-9]+)\-([0-9]+)$/", $expression, $matches)) { - $count = $matches[2] - $matches[1] + 1; - return $count > 1 - ? static::multiple($count) - : static::once((int) $matches[1]); - } - - // Parse incremental expressions like "*/2", "1-4/10" or "1,3/4". - if (preg_match("/(.+)\/([0-9]+)$/", $expression, $matches)) { - $range = static::parse($matches[1]); - if ($range->hasType('Once', 'Every')) { - return static::Increment($matches[2]); - } - if ($range->hasType('Multiple')) { - return static::Increment($matches[2], $range->count); - } - } - - // Unsupported expressions throw exceptions. - throw new CronParsingException($expression); - } - - public function hasType(): bool - { - return in_array($this->type, func_get_args()); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php deleted file mode 100644 index 16143f204..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfMonthField.php +++ /dev/null @@ -1,68 +0,0 @@ -expression->weekday->hasType('Once')) { - return $this->lang('days_of_week.every', [ - 'weekday' => $this->expression->weekday->format(), - ]); - } - - return $this->lang('days_of_month.every'); - } - - public function translateIncrement() - { - if ($this->getCount() > 1) { - return $this->lang('days_of_month.multiple_per_increment', [ - 'count' => $this->getCount(), - 'increment' => $this->getIncrement(), - ]); - } - - return $this->lang('days_of_month.increment', [ - 'increment' => $this->getIncrement(), - ]); - } - - public function translateMultiple() - { - return $this->lang('days_of_month.multiple_per_month', [ - 'count' => $this->getCount(), - ]); - } - - public function translateOnce(): ?string - { - $month = $this->expression->month; - - if ($month->hasType('Once')) { - return null; // MonthsField adapts to "On January the 1st". - } - - if ($month->hasType('Every') && ! $month->dropped) { - return null; // MonthsField adapts to "The 1st of every month". - } - - if ($month->hasType('Every') && $month->dropped) { - return $this->lang('days_of_month.every_on_day', [ - 'day' => $this->format() - ]); - } - - return $this->lang('days_of_month.once_on_day', [ - 'day' => $this->format() - ]); - } - - public function format() - { - return $this->langCountable('ordinals', $this->getValue()); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php deleted file mode 100644 index 60a615a37..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/DaysOfWeekField.php +++ /dev/null @@ -1,56 +0,0 @@ -lang('years.every'); - } - - public function translateIncrement() - { - if ($this->getCount() > 1) { - return $this->lang('days_of_week.multiple_per_increment', [ - 'count' => $this->getCount(), - 'increment' => $this->getIncrement(), - ]); - } - - return $this->lang('days_of_week.increment', [ - 'increment' => $this->getIncrement(), - ]); - } - - public function translateMultiple() - { - return $this->lang('days_of_week.multiple_days_a_week', [ - 'count' => $this->getCount(), - ]); - } - - public function translateOnce() - { - if ($this->expression->day->hasType('Every') && ! $this->expression->day->dropped) { - return; // DaysOfMonthField adapts to "Every Sunday". - } - - return $this->lang('days_of_week.once_on_day', [ - 'day' => $this->format() - ]); - } - - public function format(): string - { - $weekday = $this->getValue() === 0 ? 7 : $this->getValue(); - - if ($weekday < 1 || $weekday > 7) { - throw new CronParsingException($this->expression->raw); - } - - return $this->langCountable('days', $weekday); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php deleted file mode 100644 index 83470b471..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/Field.php +++ /dev/null @@ -1,63 +0,0 @@ -expression = $expression; - $this->rawField = $rawField; - $this->type = CronType::parse($rawField); - } - - public function translate() - { - $method = 'translate' . $this->type->type; - - if (method_exists($this, $method)) { - return $this->{$method}(); - } - } - - public function hasType(): bool - { - return $this->type->hasType(...func_get_args()); - } - - public function getValue(): ?int - { - return $this->type->value; - } - - public function getCount(): ?int - { - return $this->type->count; - } - - public function getIncrement(): ?int - { - return $this->type->increment; - } - - public function getTimes() - { - return $this->langCountable('times', $this->getCount()); - } - - protected function langCountable(string $key, int $value) - { - return $this->expression->langCountable($key, $value); - } - - protected function lang(string $key, array $replacements = []) - { - return $this->expression->lang($key, $replacements); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php deleted file mode 100644 index 7bb91c39e..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/HoursField.php +++ /dev/null @@ -1,85 +0,0 @@ -expression->minute->hasType('Once')) { - return $this->lang('hours.once_an_hour'); - } - - return $this->lang('hours.every'); - } - - public function translateIncrement() - { - if ($this->expression->minute->hasType('Once')) { - return $this->lang('hours.times_per_increment', [ - 'times' => $this->getTimes(), - 'increment' => $this->getIncrement(), - ]); - } - - if ($this->getCount() > 1) { - return $this->lang('hours.multiple_per_increment', [ - 'count' => $this->getCount(), - 'increment' => $this->getIncrement(), - ]); - } - - if ($this->expression->minute->hasType('Every')) { - return $this->lang('hours.increment_chained', [ - 'increment' => $this->getIncrement(), - ]); - } - - return $this->lang('hours.increment', [ - 'increment' => $this->getIncrement(), - ]); - } - - public function translateMultiple() - { - if ($this->expression->minute->hasType('Once')) { - return $this->lang('hours.times_per_day', [ - 'times' => $this->getTimes(), - ]); - } - - return $this->lang('hours.multiple_per_day', [ - 'count' => $this->getCount(), - ]); - } - - public function translateOnce() - { - $minute = $this->expression->minute->hasType('Once') - ? $this->expression->minute - : null; - - return $this->lang('hours.once_at_time', [ - 'time' => $this->format($minute) - ]); - } - - public function format(?MinutesField $minute = null): string - { - if ($this->expression->timeFormat24hours) { - $hour = $this->getValue(); - - return $minute ? "{$hour}:{$minute->format()}" : "{$hour}:00"; - } - - $amOrPm = $this->getValue() < 12 ? 'am' : 'pm'; - $hour = $this->getValue() % 12; - $hour = $hour === 0 ? 12 : $hour; - - return $minute - ? "{$hour}:{$minute->format()}{$amOrPm}" - : "{$hour}{$amOrPm}"; - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php deleted file mode 100644 index 926efb1be..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MinutesField.php +++ /dev/null @@ -1,39 +0,0 @@ -lang('minutes.every'); - } - - public function translateIncrement() - { - if ($this->getCount() > 1) { - return $this->lang('minutes.times_per_increment', [ - 'times' => $this->getTimes(), - 'increment' => $this->getIncrement(), - ]); - } - - return $this->lang('minutes.increment', [ - 'increment' => $this->getIncrement(), - ]); - } - - public function translateMultiple() - { - return $this->lang('minutes.multiple', [ - 'times' => $this->getTimes(), - ]); - } - - public function format(): string - { - return ($this->getValue() < 10 ? '0' : '') . $this->getValue(); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php deleted file mode 100644 index 71b54a2b4..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/MonthsField.php +++ /dev/null @@ -1,66 +0,0 @@ -expression->day->hasType('Once')) { - return $this->lang('months.every_on_day', [ - 'day' => $this->expression->day->format(), - ]); - } - - return $this->lang('months.every'); - } - - public function translateIncrement() - { - if ($this->getCount() > 1) { - return $this->lang('months.multiple_per_increment', [ - 'count' => $this->getCount(), - 'increment' => $this->getCount(), - ]); - } - - return $this->lang('months.increment', [ - 'increment' => $this->getIncrement(), - ]); - } - - public function translateMultiple() - { - return $this->lang('months.multiple_per_year', [ - 'count' => $this->getCount(), - ]); - } - - public function translateOnce() - { - if ($this->expression->day->hasType('Once')) { - return $this->lang('months.once_on_day', [ - 'month' => $this->format(), - 'day' => $this->expression->day->format(), - ]); - } - - return $this->lang('months.once_on_month', [ - 'month' => $this->format() - ]); - } - - /** - * @throws CronParsingException - */ - public function format() - { - if ($this->getValue() < 1 || $this->getValue() > 12) { - throw new CronParsingException($this->expression->raw); - } - - return $this->langCountable('months', $this->getValue()); - } -} diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php deleted file mode 100644 index 541946731..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/TranslationFileMissingException.php +++ /dev/null @@ -1,13 +0,0 @@ - 'الإثنين', - 2 => 'الثلاثاء', - 3 => 'الأربعاء', - 4 => 'الخميس', - 5 => 'الجمعة', - 6 => 'السبت', - 7 => 'الأحد', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php deleted file mode 100644 index 9bf2551bd..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'كل دقيقة', - 'increment' => 'كل :increment دقائق', - 'times_per_increment' => ':times كل :increment دقائق', - 'multiple' => ':times ساعات', - ], - 'hours' => [ - 'every' => 'كل ساعة', - 'once_an_hour' => 'مرة في كل ساعة', - 'increment' => 'كل :increment ساعات', - 'multiple_per_increment' => ':count ساعات من :increment', - 'times_per_increment' => ':times كل :increment ساعة', - 'increment_chained' => 'كل :increment ساعة', - 'multiple_per_day' => ':count ساعة كل يوم', - 'times_per_day' => ':times في اليوم', - 'once_at_time' => 'في :time', - ], - 'days_of_month' => [ - 'every' => 'كل يوم', - 'increment' => 'كل :increment يوم', - 'multiple_per_increment' => ':count أيام من :increment', - 'multiple_per_month' => ':count أيام في الشهر', - 'once_on_day' => 'في :day', - 'every_on_day' => 'كل :day من كل شهر', - ], - 'months' => [ - 'every' => 'كل شهر', - 'every_on_day' => ':day من كل شهر', - 'increment' => 'كل :increment شهر', - 'multiple_per_increment' => ':count أشهر من :increment', - 'multiple_per_year' => ':count أشهر في العام', - 'once_on_month' => 'في :month', - 'once_on_day' => 'في :month ال :day', - ], - 'days_of_week' => [ - 'every' => 'كل :weekday', - 'increment' => 'كل :increment أيام في الأسبوع', - 'multiple_per_increment' => ':count أيام الأسبوع من :increment', - 'multiple_days_a_week' => ':count أيام في الأسبوع', - 'once_on_day' => 'في :days', - ], - 'years' => [ - 'every' => 'كل عام', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php deleted file mode 100644 index d2e39e0b8..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'يناير', - 2 => 'فبراير', - 3 => 'مارس', - 4 => 'ابريل', - 5 => 'مايو', - 6 => 'يونيو', - 7 => 'يوليو', - 8 => 'أغسطس', - 9 => 'سبتمبر', - 10 => 'أكتوبر', - 11 => 'نوفمبر', - 12 => 'ديسمبر', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php deleted file mode 100644 index 5196797a6..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/ordinals.php +++ /dev/null @@ -1,12 +0,0 @@ - ':numberth', - 1 => 'الأول', - 2 => 'الثاني', - 3 => 'الثالث', - 21 => 'الواحد و العشرون', - 22 => 'الثاني و العشرون', - 23 => 'الثالث و العشرون', - 31 => 'الواحد و الثلاثون', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php deleted file mode 100644 index 97814a23a..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/ar/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number أوقات', - 1 => 'مرة', - 2 => 'مرتان', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php deleted file mode 100644 index 6fe567556..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Montag', - 2 => 'Dienstag', - 3 => 'Mittwoch', - 4 => 'Donnerstag', - 5 => 'Freitag', - 6 => 'Samstag', - 7 => 'Sonntag', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php deleted file mode 100644 index f82786f81..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/fields.php +++ /dev/null @@ -1,50 +0,0 @@ - [ - 'every' => 'jede Minute', - 'increment' => 'jede :increment Minute', - 'times_per_increment' => ':times mal alle :increment Minuten', - 'multiple' => ':times mal pro Stunde', - ], - 'hours' => [ - 'every' => 'jede Stunde', - 'once_an_hour' => 'ein mal pro Stunde', - 'increment' => 'jede :increment Stunde', - 'multiple_per_increment' => ':count Stunden aus :increment', - 'times_per_increment' => ':times mal alle :increment Stunden', - 'increment_chained' => 'alle :increment Stunden', - 'multiple_per_day' => ':count Stunden pro Tag', - 'times_per_day' => ':times mal am Tag', - 'once_at_time' => 'um :time', - ], - 'days_of_month' => [ - 'every' => 'jeden Tag', - 'increment' => 'alle :increment Tage', - 'multiple_per_increment' => ':count Tage von :increment', - 'multiple_per_month' => ':count Tage im Monat', - 'once_on_day' => 'am :day', - 'every_on_day' => 'am :day eines jeden Monats', - ], - 'months' => [ - 'every' => 'eines jeden Monat', - 'every_on_day' => 'jeden :day eines jeden Monats', - 'increment' => 'alle :increment Monate', - 'multiple_per_increment' => ':count Monate von :increment', - 'multiple_per_year' => ':count Monate im Jahr', - 'once_on_month' => 'im :month', - 'once_on_day' => 'im :month den :day', - ], - 'days_of_week' => [ - 'every' => 'jeden :weekday', - 'increment' => 'jeden :increment Tag der Woche', - 'multiple_per_increment' => ':count Tage in der Woche aus :increment', - 'multiple_days_a_week' => ':count Tage in der Woche', - #'once_on_day' => 'jeden :days', - #'once_on_day' => 'am :days', - 'once_on_day' => 'des :days', - ], - 'years' => [ - 'every' => 'jedes Jahr', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php deleted file mode 100644 index c27991a1a..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'Januar', - 2 => 'Februar', - 3 => 'März', - 4 => 'April', - 5 => 'Mai', - 6 => 'Juni', - 7 => 'Juli', - 8 => 'August', - 9 => 'September', - 10 => 'Oktober', - 11 => 'November', - 12 => 'Dezember', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php deleted file mode 100644 index 05b089b00..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/ordinals.php +++ /dev/null @@ -1,13 +0,0 @@ - ':number.', - #'default' => str_pad(':number', 2, 0, STR_PAD_LEFT). '.', - 1 => '1.', - 2 => '2.', - 3 => '3.', - 21 => '21.', - 22 => '22.', - 23 => '23.', - 31 => '31.', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php deleted file mode 100644 index 27be24004..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/de/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number', - 1 => 'Ein', - 2 => 'Zwei', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php deleted file mode 100644 index 7df6a9b9f..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Monday', - 2 => 'Tuesday', - 3 => 'Wednesday', - 4 => 'Thursday', - 5 => 'Friday', - 6 => 'Saturday', - 7 => 'Sunday', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php deleted file mode 100644 index 28da50251..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'every minute', - 'increment' => 'every :increment minutes', - 'times_per_increment' => ':times every :increment minutes', - 'multiple' => ':times an hour', - ], - 'hours' => [ - 'every' => 'every hour', - 'once_an_hour' => 'once an hour', - 'increment' => 'every :increment hours', - 'multiple_per_increment' => ':count hours out of :increment', - 'times_per_increment' => ':times every :increment hours', - 'increment_chained' => 'of every :increment hours', - 'multiple_per_day' => ':count hours a day', - 'times_per_day' => ':times a day', - 'once_at_time' => 'at :time', - ], - 'days_of_month' => [ - 'every' => 'every day', - 'increment' => 'every :increment days', - 'multiple_per_increment' => ':count days out of :increment', - 'multiple_per_month' => ':count days a month', - 'once_on_day' => 'on the :day', - 'every_on_day' => 'on the :day of every month', - ], - 'months' => [ - 'every' => 'every month', - 'every_on_day' => 'the :day of every month', - 'increment' => 'every :increment months', - 'multiple_per_increment' => ':count months out of :increment', - 'multiple_per_year' => ':count months a year', - 'once_on_month' => 'on :month', - 'once_on_day' => 'on :month the :day', - ], - 'days_of_week' => [ - 'every' => 'every :weekday', - 'increment' => 'every :increment days of the week', - 'multiple_per_increment' => ':count days of the week out of :increment', - 'multiple_days_a_week' => ':count days a week', - 'once_on_day' => 'on :days', - ], - 'years' => [ - 'every' => 'every year', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php deleted file mode 100644 index 159ba1891..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'January', - 2 => 'February', - 3 => 'March', - 4 => 'April', - 5 => 'May', - 6 => 'June', - 7 => 'July', - 8 => 'August', - 9 => 'September', - 10 => 'October', - 11 => 'November', - 12 => 'December', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php deleted file mode 100644 index 47d3f593c..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/ordinals.php +++ /dev/null @@ -1,12 +0,0 @@ - ':numberth', - 1 => '1st', - 2 => '2nd', - 3 => '3rd', - 21 => '21st', - 22 => '22nd', - 23 => '23rd', - 31 => '31st', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php deleted file mode 100644 index d651e4cba..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/en/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number times', - 1 => 'once', - 2 => 'twice', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php deleted file mode 100644 index 77dd55118..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'lunes', - 2 => 'martes', - 3 => 'miércoles', - 4 => 'jueves', - 5 => 'viernes', - 6 => 'sábado', - 7 => 'domingo', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php deleted file mode 100644 index d4bbca445..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'cada minuto', - 'increment' => 'cada :increment minutos', - 'times_per_increment' => ':times cada :increment minutos', - 'multiple' => ':times cada hora', - ], - 'hours' => [ - 'every' => 'cada hora', - 'once_an_hour' => 'una vez cada hora', - 'increment' => 'cada :increment horas', - 'multiple_per_increment' => ':count horas de cada :increment', - 'times_per_increment' => ':times cada :increment horas', - 'increment_chained' => 'de cada :increment horas', - 'multiple_per_day' => ':count horas al día', - 'times_per_day' => ':times al día', - 'once_at_time' => 'a las :time', - ], - 'days_of_month' => [ - 'every' => 'diario', - 'increment' => 'cada :increment días', - 'multiple_per_increment' => ':count días de :increment', - 'multiple_per_month' => ':count días al mes', - 'once_on_day' => 'el :day', - 'every_on_day' => 'el :day de cada mes', - ], - 'months' => [ - 'every' => 'cada mes', - 'every_on_day' => 'el :day de cada mes', - 'increment' => 'cada :increment meses', - 'multiple_per_increment' => ':count meses de :increment', - 'multiple_per_year' => ':count meses al año', - 'once_on_month' => 'de :month', - 'once_on_day' => 'en :month el :day', - ], - 'days_of_week' => [ - 'every' => 'cada :weekday', - 'increment' => 'cada :increment días de la semana', - 'multiple_per_increment' => ':count días a la semana de :increment', - 'multiple_days_a_week' => ':count días a la semana', - 'once_on_day' => 'los :days', - ], - 'years' => [ - 'every' => 'cada año', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php deleted file mode 100644 index 2e988a449..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'enero', - 2 => 'febrero', - 3 => 'marzo', - 4 => 'abril', - 5 => 'mayo', - 6 => 'junio', - 7 => 'julio', - 8 => 'agosto', - 9 => 'septiembre', - 10 => 'octubre', - 11 => 'noviembre', - 12 => 'diciembre', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php deleted file mode 100644 index 1f79960dc..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/ordinals.php +++ /dev/null @@ -1,5 +0,0 @@ - ':numberº', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php deleted file mode 100644 index 868156e00..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/es/times.php +++ /dev/null @@ -1,6 +0,0 @@ - ':number veces', - 1 => 'una vez', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php deleted file mode 100644 index 2af7be296..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'lundi', - 2 => 'mardi', - 3 => 'mercredi', - 4 => 'jeudi', - 5 => 'vendredi', - 6 => 'samedi', - 7 => 'dimanche', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php deleted file mode 100644 index a339c9715..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'chaque minute', - 'increment' => 'toutes les :increment minutes', - 'times_per_increment' => ':times toutes les :increment minutes', - 'multiple' => ':times par heure', - ], - 'hours' => [ - 'every' => 'chaque heure', - 'once_an_hour' => 'une fois par heure', - 'increment' => 'toutes les :increment heures', - 'multiple_per_increment' => ':count heures au-delà de :increment', - 'times_per_increment' => ':times toutes les :increment heures', - 'increment_chained' => 'toutes les :increment heures', - 'multiple_per_day' => ':count heures par jour', - 'times_per_day' => ':times par jour', - 'once_at_time' => 'à :time', - ], - 'days_of_month' => [ - 'every' => 'chaque jour', - 'increment' => 'tous les :increment jours', - 'multiple_per_increment' => ':count jours sur :increment', - 'multiple_per_month' => ':count jours par mois', - 'once_on_day' => 'le :day', - 'every_on_day' => 'le :day de chaque mois', - ], - 'months' => [ - 'every' => 'chaque mois', - 'every_on_day' => 'le :day de chaque mois', - 'increment' => 'tous les :increment mois', - 'multiple_per_increment' => ':count mois sur :increment', - 'multiple_per_year' => ':count mois par an', - 'once_on_month' => 'en :month', - 'once_on_day' => 'le :day :month', - ], - 'days_of_week' => [ - 'every' => 'chaque :weekday', - 'increment' => 'tous les :increment jours de la semaine', - 'multiple_per_increment' => ':count jours de la semaine sur :increment', - 'multiple_days_a_week' => ':count jours par semaine', - 'once_on_day' => 'les :days', - ], - 'years' => [ - 'every' => 'chaque année', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php deleted file mode 100644 index 9b02c93fd..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'janvier', - 2 => 'février', - 3 => 'mars', - 4 => 'avril', - 5 => 'mai', - 6 => 'juin', - 7 => 'juillet', - 8 => 'août', - 9 => 'septembre', - 10 => 'octobre', - 11 => 'novembre', - 12 => 'décembre', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php deleted file mode 100644 index 10a111e78..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/ordinals.php +++ /dev/null @@ -1,6 +0,0 @@ - ':number', - 1 => '1er', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php deleted file mode 100644 index 1d29659b3..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/fr/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number fois', - 1 => 'une fois', - 2 => 'deux fois', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php deleted file mode 100644 index ee858deff..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'pirmdien', - 2 => 'otrdien', - 3 => 'trešdien', - 4 => 'ceturtdien', - 5 => 'piektdien', - 6 => 'sestdien', - 7 => 'svētdien', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php deleted file mode 100644 index 2af59e309..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'katru minūti', - 'increment' => 'ik pēc :increment minūtēm', - 'times_per_increment' => ':times ik pēc :increment minūtēm', - 'multiple' => ':times stundā', - ], - 'hours' => [ - 'every' => 'katru stundu', - 'once_an_hour' => 'vienreiz stundā', - 'increment' => 'katras :increment stundas', - 'multiple_per_increment' => ':count no :increment stundām', - 'times_per_increment' => ':times katras :increment stundas', - 'increment_chained' => 'ik pēc :increment stundām', - 'multiple_per_day' => ':count stundas dienā', - 'times_per_day' => ':times dienā', - 'once_at_time' => 'plkst. :time', - ], - 'days_of_month' => [ - 'every' => 'katru dienu', - 'increment' => 'ik pēc :increment dienām', - 'multiple_per_increment' => ':count dienas no :increment', - 'multiple_per_month' => ':count dienas mēnesī', - 'once_on_day' => ':day. datumā', - 'every_on_day' => 'katra mēneša :day. datumā', - ], - 'months' => [ - 'every' => 'katru mēnesi', - 'every_on_day' => 'katra mēneša :day. datumā', - 'increment' => 'katrus :increment mēnešus', - 'multiple_per_increment' => ':count mēnešus no :increment', - 'multiple_per_year' => ':count mēnešus gadā', - 'once_on_month' => ':month', - 'once_on_day' => ':day. :month', - ], - 'days_of_week' => [ - 'every' => 'katru :weekdayu', - 'increment' => 'ik pēc :increment nedēļas dienām', - 'multiple_per_increment' => ':count nedēļas dienas no :increment', - 'multiple_days_a_week' => ':count dienas nedēļā', - 'once_on_day' => ':dayās', - ], - 'years' => [ - 'every' => 'katru gadu', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php deleted file mode 100644 index 23c210f6d..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'janvārī', - 2 => 'februārī', - 3 => 'martā', - 4 => 'aprīlī', - 5 => 'maijā', - 6 => 'jūnijā', - 7 => 'jūlijā', - 8 => 'augustā', - 9 => 'septembrī', - 10 => 'oktobrī', - 11 => 'novembrī', - 12 => 'decembrī', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php deleted file mode 100644 index e160d8016..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/ordinals.php +++ /dev/null @@ -1,5 +0,0 @@ - ':number', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php deleted file mode 100644 index 9ada98fe3..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/lv/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number reizes', - 1 => 'vienreiz', - 2 => 'divreiz', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php deleted file mode 100644 index 95fa5b769..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Maandag', - 2 => 'Dinsdag', - 3 => 'Woensdag', - 4 => 'Donderdag', - 5 => 'Vrijdag', - 6 => 'Zaterdag', - 7 => 'Zondag', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php deleted file mode 100644 index 5a17ce00a..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'elke minuut', - 'increment' => 'elke :increment minuten', - 'times_per_increment' => ':times elke :increment minuten', - 'multiple' => ':times per uur', - ], - 'hours' => [ - 'every' => 'elk uur', - 'once_an_hour' => 'eens per uur', - 'increment' => 'elke :increment uur', - 'multiple_per_increment' => ':count uur van de :increment', - 'times_per_increment' => ':times elke :increment uur', - 'increment_chained' => 'van elke :increment uur', - 'multiple_per_day' => ':count uur per dag', - 'times_per_day' => ':times per dag', - 'once_at_time' => 'om :time', - ], - 'days_of_month' => [ - 'every' => 'elke dag', - 'increment' => 'elke :increment dagen', - 'multiple_per_increment' => ':count dagen van de :increment', - 'multiple_per_month' => ':count dagen per maand', - 'once_on_day' => 'op de :day', - 'every_on_day' => 'op de :day van elke maand', - ], - 'months' => [ - 'every' => 'elke maand', - 'every_on_day' => 'de :day van elke maand', - 'increment' => 'elke :increment maanden', - 'multiple_per_increment' => ':count maanden van de :increment', - 'multiple_per_year' => ':count maanden per jaar', - 'once_on_month' => 'in :month', - 'once_on_day' => 'in :month de :day', - ], - 'days_of_week' => [ - 'every' => 'elke :weekday', - 'increment' => 'elke :increment dag van de week', - 'multiple_per_increment' => ':count dagen van de week op :increment', - 'multiple_days_a_week' => ':count dagen per week ', - 'once_on_day' => 'op :days', - ], - 'years' => [ - 'every' => 'elk jaar', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php deleted file mode 100644 index 0214c8d9f..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'januari', - 2 => 'februari', - 3 => 'maart', - 4 => 'april', - 5 => 'mei', - 6 => 'juni', - 7 => 'juli', - 8 => 'augustus', - 9 => 'september', - 10 => 'oktober', - 11 => 'november', - 12 => 'december', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php deleted file mode 100644 index 89fbe94ef..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/ordinals.php +++ /dev/null @@ -1,5 +0,0 @@ - ':numbere', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php deleted file mode 100644 index b550e5134..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/nl/times.php +++ /dev/null @@ -1,5 +0,0 @@ - ':number keer', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php deleted file mode 100644 index e4a1ca963..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Segunda-feira', - 2 => 'Terça-feira', - 3 => 'Quarta-feira', - 4 => 'Quinta-feira', - 5 => 'Sexta-feira', - 6 => 'Sábado', - 7 => 'Domingo', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php deleted file mode 100644 index d126f4eb3..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'todos os minutos', - 'increment' => 'cada :increment minutos', - 'times_per_increment' => ':times cada :increment minutos', - 'multiple' => ':times por hora', - ], - 'hours' => [ - 'every' => 'todas as horas', - 'once_an_hour' => 'uma vez por hora', - 'increment' => 'cada :increment horas', - 'multiple_per_increment' => ':count horas cada :increment', - 'times_per_increment' => ':times cada :increment horas', - 'increment_chained' => 'de cada :increment horas', - 'multiple_per_day' => ':count horas por dia', - 'times_per_day' => ':times por dia', - 'once_at_time' => 'às :time', - ], - 'days_of_month' => [ - 'every' => 'todos os dias', - 'increment' => 'cada :increment dias', - 'multiple_per_increment' => ':count dias cada :increment', - 'multiple_per_month' => ':count dias por mês', - 'once_on_day' => 'no :day dia', - 'every_on_day' => 'no :day dia de cada mês', - ], - 'months' => [ - 'every' => 'todos os meses', - 'every_on_day' => 'no :day dia de cada mês', - 'increment' => 'cada :increment meses', - 'multiple_per_increment' => ':count meses cada :increment', - 'multiple_per_year' => ':count meses por ano', - 'once_on_month' => 'em :month', - 'once_on_day' => 'no :day de :month', - ], - 'days_of_week' => [ - 'every' => 'cada :weekday', - 'increment' => 'cada :increment dias da semana', - 'multiple_per_increment' => ':count dias da semana cada :increment', - 'multiple_days_a_week' => ':count dias da semana', - 'once_on_day' => 'nas/nos :days', - ], - 'years' => [ - 'every' => 'todos os anos', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php deleted file mode 100644 index b49d24e61..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'Janeiro', - 2 => 'Fevereiro', - 3 => 'Março', - 4 => 'Abril', - 5 => 'Maio', - 6 => 'Junho', - 7 => 'Julho', - 8 => 'Agosto', - 9 => 'Setembro', - 10 => 'Outubro', - 11 => 'Novembro', - 12 => 'Dezembro', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php deleted file mode 100644 index 1f79960dc..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/ordinals.php +++ /dev/null @@ -1,5 +0,0 @@ - ':numberº', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php deleted file mode 100644 index e56f6099e..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/pt/times.php +++ /dev/null @@ -1,6 +0,0 @@ - ':number vezes', - 1 => 'uma vez', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php deleted file mode 100644 index cb0927418..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/days.php +++ /dev/null @@ -1,11 +0,0 @@ - 'pondelok', - 2 => 'utorok', - 3 => 'streda', - 4 => 'štvrtok', - 5 => 'piatok', - 6 => 'sobota', - 7 => 'nedeľa', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php deleted file mode 100644 index 6aa909629..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => 'každú minútu', - 'increment' => 'každých :increment minút', - 'times_per_increment' => ':times každých :increment minút', - 'multiple' => ':times za hodinu', - ], - 'hours' => [ - 'every' => 'každú hodinu', - 'once_an_hour' => 'raz za hodinu', - 'increment' => 'každých :increment hodín', - 'multiple_per_increment' => ':count hodín z :increment', - 'times_per_increment' => ':times každých :increment hodín', - 'increment_chained' => 'každých :increment hodín', - 'multiple_per_day' => ':count hodín za deň', - 'times_per_day' => ':times za deň', - 'once_at_time' => 'o :time', - ], - 'days_of_month' => [ - 'every' => 'každý deň', - 'increment' => 'každých :increment dní', - 'multiple_per_increment' => ':count dní z :increment', - 'multiple_per_month' => ':count dní v mesiaci', - 'once_on_day' => ':day dňa v mesiaci', - 'every_on_day' => 'na každý :day deň v mesiaci', - ], - 'months' => [ - 'every' => 'každý mesiac', - 'every_on_day' => 'každý :day deň v mesiaci', - 'increment' => 'každých :increment mesiacov', - 'multiple_per_increment' => ':count mesiacov z :increment', - 'multiple_per_year' => ':count mesiacov ročne', - 'once_on_month' => 'v mesiaci :month', - 'once_on_day' => 'na :day deň v mesiaci :month', - ], - 'days_of_week' => [ - 'every' => 'každá/ý :weekday', - 'increment' => 'každých :increment dní v týždni', - 'multiple_per_increment' => ':count dní v týždni z :increment', - 'multiple_days_a_week' => ':count dní v týždni', - 'once_on_day' => 'v :day', - ], - 'years' => [ - 'every' => 'každý rok', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php deleted file mode 100644 index d3be7f015..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/months.php +++ /dev/null @@ -1,16 +0,0 @@ - 'január', - 2 => 'február', - 3 => 'marec', - 4 => 'apríl', - 5 => 'máj', - 6 => 'jún', - 7 => 'júl', - 8 => 'august', - 9 => 'september', - 10 => 'október', - 11 => 'november', - 12 => 'december', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php deleted file mode 100644 index 7194b57a7..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/ordinals.php +++ /dev/null @@ -1,12 +0,0 @@ - ':number.', - 1 => '1.', - 2 => '2.', - 3 => '3.', - 21 => '21.', - 22 => '22.', - 23 => '23.', - 31 => '31.', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php deleted file mode 100644 index a51eee42a..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/sk/times.php +++ /dev/null @@ -1,7 +0,0 @@ - ':number-krát', - 1 => 'raz', - 2 => 'dva razy', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php deleted file mode 100644 index 62eb6813f..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/days.php +++ /dev/null @@ -1,11 +0,0 @@ - '周一', - 2 => '周二', - 3 => '周三', - 4 => '周四', - 5 => '周五', - 6 => '周六', - 7 => '周日', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php deleted file mode 100644 index 630ed4769..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/fields.php +++ /dev/null @@ -1,48 +0,0 @@ - [ - 'every' => '每分钟', - 'increment' => '每:increment分钟', - 'times_per_increment' => '每:increment分钟:times', - 'multiple' => '一小时:times', - ], - 'hours' => [ - 'every' => '每小时', - 'once_an_hour' => '每整点', - 'increment' => '每:increment小时', - 'multiple_per_increment' => '每:increment小时中有:count小时', - 'times_per_increment' => '每:increment小时:times', - 'increment_chained' => '每:increment小时', - 'multiple_per_day' => '每天有:count小时', - 'times_per_day' => '每天:times', - 'once_at_time' => '在:time', - ], - 'days_of_month' => [ - 'every' => '每天', - 'increment' => '每:increment天', - 'multiple_per_increment' => '每:increment天中有:count天', - 'multiple_per_month' => '每月:count天', - 'once_on_day' => '在:day号', - 'every_on_day' => '在每月:day号', - ], - 'months' => [ - 'every' => '每月', - 'every_on_day' => '每月:day号', - 'increment' => '每:increment个月', - 'multiple_per_increment' => '每:increment月中有:count月', - 'multiple_per_year' => '每年:count个月', - 'once_on_month' => '在:month', - 'once_on_day' => '在:month:day号', - ], - 'days_of_week' => [ - 'every' => '每周:weekday', - 'increment' => '每周:increment天', - 'multiple_per_increment' => '每:increment周中有:count天', - 'multiple_days_a_week' => '一周:count天', - 'once_on_day' => '在:day', - ], - 'years' => [ - 'every' => '每年', - ], -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php deleted file mode 100644 index 8451ff176..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/months.php +++ /dev/null @@ -1,16 +0,0 @@ - '一月', - 2 => '二月', - 3 => '三月', - 4 => '四月', - 5 => '五月', - 6 => '六月', - 7 => '七月', - 8 => '八月', - 9 => '九月', - 10 => '十月', - 11 => '十一月', - 12 => '十二月', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php deleted file mode 100644 index e160d8016..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/ordinals.php +++ /dev/null @@ -1,5 +0,0 @@ - ':number', -]; diff --git a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php b/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php deleted file mode 100644 index 339427672..000000000 --- a/modules/qlocrontaskmanager/vendor/lorisleiva/cron-translator/src/lang/zh/times.php +++ /dev/null @@ -1,5 +0,0 @@ - ':number次', -]; diff --git a/modules/qlocrontaskmanager/views/templates/admin/configure.tpl b/modules/qlocrontaskmanager/views/templates/admin/configure.tpl index ad651a980..9ba2c5c27 100644 --- a/modules/qlocrontaskmanager/views/templates/admin/configure.tpl +++ b/modules/qlocrontaskmanager/views/templates/admin/configure.tpl @@ -29,7 +29,9 @@ {l s='Add the following cron entry to your server. This single command will manage all module cron tasks automatically.' mod='qlocrontaskmanager'}
- +
Date: Mon, 6 Jul 2026 13:15:35 +0530 Subject: [PATCH 08/11] Fixed: crontab is created on server or not and show error for the Cron setup --- .../qlocrontaskmanager/qlocrontaskmanager.php | 30 +++++++++++++++++ .../views/templates/admin/dashboard/index.php | 32 +++++++++++++++++++ .../dashboard/qctm_cron_not_configured.tpl | 30 +++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 modules/qlocrontaskmanager/views/templates/admin/dashboard/index.php create mode 100644 modules/qlocrontaskmanager/views/templates/admin/dashboard/qctm_cron_not_configured.tpl diff --git a/modules/qlocrontaskmanager/qlocrontaskmanager.php b/modules/qlocrontaskmanager/qlocrontaskmanager.php index 2df0f0237..3cc49da79 100644 --- a/modules/qlocrontaskmanager/qlocrontaskmanager.php +++ b/modules/qlocrontaskmanager/qlocrontaskmanager.php @@ -180,6 +180,35 @@ class QloCronTaskManager extends Module } } + public function hookDashboardZoneThree($params) + { + if ($this->isCrontabConfigured() !== false) { + return ''; + } + + $this->context->smarty->assign(array( + 'qctmConfigureLink' => $this->context->link->getAdminLink('AdminModules') . '&configure=' . $this->name, + )); + + return $this->display(__FILE__, 'views/templates/admin/dashboard/qctm_cron_not_configured.tpl'); + } + + public function isCrontabConfigured() + { + if (!function_exists('shell_exec')) { + return null; + } + + $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); + if (in_array('shell_exec', $disabled, true)) { + return null; + } + + $output = @shell_exec('crontab -l 2>/dev/null'); + + return strpos((string) $output, 'qlocrontaskmanager/cron.php') !== false; + } + public function hookActionModuleUninstallBefore($params) { if (!isset($params['object']) || !($params['object'] instanceof Module) || $params['object']->id == $this->id) { @@ -227,6 +256,7 @@ class QloCronTaskManager extends Module array( 'actionModuleInstallAfter', 'actionModuleUninstallBefore', + 'dashboardZoneThree', ) ); } diff --git a/modules/qlocrontaskmanager/views/templates/admin/dashboard/index.php b/modules/qlocrontaskmanager/views/templates/admin/dashboard/index.php new file mode 100644 index 000000000..409280877 --- /dev/null +++ b/modules/qlocrontaskmanager/views/templates/admin/dashboard/index.php @@ -0,0 +1,32 @@ + + +
From e55012fd2ff8e6acd133dfca183788d1e33b6dcc Mon Sep 17 00:00:00 2001 From: amit sharma Date: Tue, 7 Jul 2026 18:50:43 +0530 Subject: [PATCH 09/11] Fixed: fix translation issue and change function name --- .../classes/QctmCronExpressionTranslator.php | 240 +++++++++--------- .../admin/AdminCronTaskManagerController.php | 6 +- 2 files changed, 125 insertions(+), 121 deletions(-) diff --git a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php index 9f26af8a1..4cfe1d5bf 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php +++ b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php @@ -27,8 +27,6 @@ if (!defined('_PS_VERSION_')) { class QctmCronExpressionTranslator { - const MODULE_NAME = 'qlocrontaskmanager'; - const WEEKDAYS = array( 0 => 'Sunday', 1 => 'Monday', @@ -55,149 +53,154 @@ class QctmCronExpressionTranslator 12 => 'December', ); - public static function translate($expression) + protected $module; + + public function __construct() + { + $this->module = Module::getInstanceByName('qlocrontaskmanager'); + } + + public function getCronExpressionTranslation($expression) { $parts = preg_split('/\s+/', trim($expression)); if (count($parts) !== 5) { - return self::trans('Invalid cron expression'); + return $this->module->l('Invalid cron expression'); } list($minute, $hour, $day, $month, $weekday) = $parts; + $hasFixedTime = $this->isNumeric($hour) && $this->isNumeric($minute); + $dayMonthWildcard = ($day == '*' && $month == '*'); + $monthWeekdayWildcard = ($month == '*' && $weekday == '*'); + $everyMinuteWildcard = ($minute == '*' && $hour == '*'); + if ($expression === '* * * * *') { - return self::trans('Every minute'); + return $this->module->l('Every minute'); } - if (preg_match('/^\*\/(\d+)$/', $minute, $m) - && $hour == '*' && $day == '*' && $month == '*' && $weekday == '*') { - return self::trans('Every %d minutes', (int) $m[1]); + if (preg_match('/^\*\/(\d+)$/', $minute, $m) && $hour == '*' && $dayMonthWildcard && $weekday == '*') { + return vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); } - if ($minute == '0' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '*') { - return self::trans('Every hour'); + if ($minute == '0' && $hour == '*' && $dayMonthWildcard && $weekday == '*') { + return $this->module->l('Every hour'); } - if ($minute == '0' - && preg_match('/^\*\/(\d+)$/', $hour, $m) - && $day == '*' && $month == '*' && $weekday == '*') { - return self::trans('Every %d hours', (int) $m[1]); + if ($minute == '0' && preg_match('/^\*\/(\d+)$/', $hour, $m) && $dayMonthWildcard && $weekday == '*') { + return vsprintf($this->module->l('Every %d hours'), array((int) $m[1])); } - if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '*') { - return self::trans('Every day at %s', self::hourLabel($hour, $minute)); + if ($hasFixedTime && $dayMonthWildcard && $weekday == '*') { + return vsprintf($this->module->l('Every day at %s'), array($this->hourLabel($hour, $minute))); } - if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $weekday == '*' - && preg_match('/^\*\/(\d+)$/', $month, $mm)) { - return self::trans( - 'Every day every %1$d months at %2$s', - array((int) $mm[1], self::hourLabel($hour, $minute)) + if ($hasFixedTime && $day == '*' && $weekday == '*' && preg_match('/^\*\/(\d+)$/', $month, $mm)) { + return vsprintf( + $this->module->l('Every day every %1$d months at %2$s'), + array((int) $mm[1], $this->hourLabel($hour, $minute)) ); } - if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '1-5') { - return self::trans('Every weekday at %s', self::hourLabel($hour, $minute)); + if ($hasFixedTime && $dayMonthWildcard && $weekday == '1-5') { + return vsprintf($this->module->l('Every weekday at %s'), array($this->hourLabel($hour, $minute))); } - if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && $weekday == '0,6') { - return self::trans('Every weekend at %s', self::hourLabel($hour, $minute)); + if ($hasFixedTime && $dayMonthWildcard && $weekday == '0,6') { + return vsprintf($this->module->l('Every weekend at %s'), array($this->hourLabel($hour, $minute))); } - if (self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*' && self::isNumeric($weekday)) { - return self::trans( - 'Every %1$s at %2$s', - array(self::weekdayName($weekday), self::hourLabel($hour, $minute)) + if ($hasFixedTime && $dayMonthWildcard && $this->isNumeric($weekday)) { + return vsprintf( + $this->module->l('Every %1$s at %2$s'), + array($this->weekdayName($weekday), $this->hourLabel($hour, $minute)) ); } - if (strpos($weekday, ',') !== false - && self::isNumeric($hour) && self::isNumeric($minute) && $day == '*' && $month == '*') { - return self::trans( - 'Every %1$s at %2$s', - array(self::joinNames($weekday, 'weekdayName'), self::hourLabel($hour, $minute)) + if (strpos($weekday, ',') !== false && $hasFixedTime && $dayMonthWildcard) { + return vsprintf( + $this->module->l('Every %1$s at %2$s'), + array($this->joinNames($weekday, 'weekdayName'), $this->hourLabel($hour, $minute)) ); } - if (self::isNumeric($day) && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { - return self::trans( - 'On the %1$s of every month at %2$s', - array(self::ordinal($day), self::hourLabel($hour, $minute)) + if ($this->isNumeric($day) && $hasFixedTime && $monthWeekdayWildcard) { + return vsprintf( + $this->module->l('On the %1$s of every month at %2$s'), + array($this->ordinal($day), $this->hourLabel($hour, $minute)) ); } - if (preg_match('/^(\d+)-(\d+)$/', $day, $dm) - && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { - return self::trans( - 'Every day from the %1$s to the %2$s of each month at %3$s', - array(self::ordinal($dm[1]), self::ordinal($dm[2]), self::hourLabel($hour, $minute)) + if (preg_match('/^(\d+)-(\d+)$/', $day, $dm) && $hasFixedTime && $monthWeekdayWildcard) { + return vsprintf( + $this->module->l('Every day from the %1$s to the %2$s of each month at %3$s'), + array($this->ordinal($dm[1]), $this->ordinal($dm[2]), $this->hourLabel($hour, $minute)) ); } - if (strpos($day, ',') !== false - && self::isNumeric($hour) && self::isNumeric($minute) && $month == '*' && $weekday == '*') { - return self::trans( - 'On the %1$s of each month at %2$s', - array(self::joinOrdinals($day), self::hourLabel($hour, $minute)) + if (strpos($day, ',') !== false && $hasFixedTime && $monthWeekdayWildcard) { + return vsprintf( + $this->module->l('On the %1$s of each month at %2$s'), + array($this->joinOrdinals($day), $this->hourLabel($hour, $minute)) ); } - if (self::isNumeric($day) && self::isNumeric($month) && self::isNumeric($hour) && self::isNumeric($minute) && $weekday == '*') { - return self::trans( - 'Every %1$s %2$s at %3$s', - array(self::monthName($month), self::ordinal($day), self::hourLabel($hour, $minute)) + if ($this->isNumeric($day) && $this->isNumeric($month) && $hasFixedTime && $weekday == '*') { + return vsprintf( + $this->module->l('Every %1$s %2$s at %3$s'), + array($this->monthName($month), $this->ordinal($day), $this->hourLabel($hour, $minute)) ); } - if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && self::isNumeric($weekday)) { - return self::trans('Every minute on %s', self::weekdayName($weekday)); + if ($everyMinuteWildcard && $dayMonthWildcard && $this->isNumeric($weekday)) { + return vsprintf($this->module->l('Every minute on %s'), array($this->weekdayName($weekday))); } - if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '1-5') { - return self::trans('Every minute on weekdays'); + if ($everyMinuteWildcard && $dayMonthWildcard && $weekday == '1-5') { + return $this->module->l('Every minute on weekdays'); } - if ($minute == '*' && $hour == '*' && $day == '*' && $month == '*' && $weekday == '0,6') { - return self::trans('Every minute on weekends'); + if ($everyMinuteWildcard && $dayMonthWildcard && $weekday == '0,6') { + return $this->module->l('Every minute on weekends'); } - if (self::isNumeric($minute) && self::isNumeric($hour)) { - $when = self::trans('At %s', self::hourLabel($hour, $minute)); + if ($hasFixedTime) { + $when = vsprintf($this->module->l('At %s'), array($this->hourLabel($hour, $minute))); } else { $minuteFreq = null; if ($minute === '*') { - $minuteFreq = self::trans('Every minute'); + $minuteFreq = $this->module->l('Every minute'); } elseif (preg_match('/^\*\/(\d+)$/', $minute, $m)) { - $minuteFreq = self::trans('Every %d minutes', (int) $m[1]); + $minuteFreq = vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); } - if (preg_match('/^(\d+)-(\d+)\/(\d+)$/', $hour, $hs) && self::isNumeric($minute)) { - $when = self::trans( - 'Every %1$d hours between %2$s and %3$s', - array((int) $hs[3], self::hourLabel($hs[1], $minute), self::hourLabel($hs[2], $minute)) + if (preg_match('/^(\d+)-(\d+)\/(\d+)$/', $hour, $hs) && $this->isNumeric($minute)) { + $when = vsprintf( + $this->module->l('Every %1$d hours between %2$s and %3$s'), + array((int) $hs[3], $this->hourLabel($hs[1], $minute), $this->hourLabel($hs[2], $minute)) ); } elseif ($minuteFreq !== null) { if ($hour === '*') { $when = $minuteFreq; - } elseif (self::isNumeric($hour)) { - $when = self::trans('%1$s, during hour %2$s', array($minuteFreq, $hour)); + } elseif ($this->isNumeric($hour)) { + $when = vsprintf($this->module->l('%1$s, during hour %2$s'), array($minuteFreq, $hour)); } elseif (preg_match('/^(\d+)-(\d+)$/', $hour, $hm)) { - $when = self::trans( - '%1$s between %2$s and %3$s', - array($minuteFreq, self::hourLabel($hm[1], 0), self::hourLabel($hm[2], 59)) + $when = vsprintf( + $this->module->l('%1$s between %2$s and %3$s'), + array($minuteFreq, $this->hourLabel($hm[1], 0), $this->hourLabel($hm[2], 59)) ); } else { - $when = self::trans('%1$s, during hour %2$s', array($minuteFreq, $hour)); + $when = vsprintf($this->module->l('%1$s, during hour %2$s'), array($minuteFreq, $hour)); } - } elseif (self::isNumeric($minute) && $hour === '*') { - $when = self::trans('Every hour, at minute %s', $minute); - } elseif (self::isNumeric($minute) && preg_match('/^(\d+)-(\d+)$/', $hour, $hm)) { - $when = self::trans( - 'At minute %1$s, between %2$s and %3$s', - array($minute, self::hourLabel($hm[1], $minute), self::hourLabel($hm[2], $minute)) + } elseif ($this->isNumeric($minute) && $hour === '*') { + $when = vsprintf($this->module->l('Every hour, at minute %s'), array($minute)); + } elseif ($this->isNumeric($minute) && preg_match('/^(\d+)-(\d+)$/', $hour, $hm)) { + $when = vsprintf( + $this->module->l('At minute %1$s, between %2$s and %3$s'), + array($minute, $this->hourLabel($hm[1], $minute), $this->hourLabel($hm[2], $minute)) ); } else { - $when = self::trans('At minute %1$s, hour %2$s', array($minute, $hour)); + $when = vsprintf($this->module->l('At minute %1$s, hour %2$s'), array($minute, $hour)); } } @@ -205,47 +208,53 @@ class QctmCronExpressionTranslator if ($day !== '*') { if (preg_match('/^(\d+)-(\d+)$/', $day, $dm)) { - $conditions[] = self::trans( - 'from the %1$s to the %2$s of the month', - array(self::ordinal($dm[1]), self::ordinal($dm[2])) + $conditions[] = vsprintf( + $this->module->l('from the %1$s to the %2$s of the month'), + array($this->ordinal($dm[1]), $this->ordinal($dm[2])) ); } elseif (strpos($day, ',') !== false) { - $conditions[] = self::trans('on the %s of the month', self::joinOrdinals($day)); - } elseif (self::isNumeric($day)) { - $conditions[] = self::trans('on the %s of the month', self::ordinal($day)); + $conditions[] = vsprintf($this->module->l('on the %s of the month'), array($this->joinOrdinals($day))); + } elseif ($this->isNumeric($day)) { + $conditions[] = vsprintf($this->module->l('on the %s of the month'), array($this->ordinal($day))); } else { - $conditions[] = self::trans('on day %s of the month', $day); + $conditions[] = vsprintf($this->module->l('on day %s of the month'), array($day)); } } if ($month !== '*') { if (preg_match('/^\*\/(\d+)$/', $month, $mf)) { - $conditions[] = self::trans('every %d months', (int) $mf[1]); + $conditions[] = vsprintf($this->module->l('every %d months'), array((int) $mf[1])); } else { if (preg_match('/^(\d+)-(\d+)$/', $month, $mm)) { - $monthDesc = self::trans('%1$s through %2$s', array(self::monthName($mm[1]), self::monthName($mm[2]))); + $monthDesc = vsprintf( + $this->module->l('%1$s through %2$s'), + array($this->monthName($mm[1]), $this->monthName($mm[2])) + ); } elseif (strpos($month, ',') !== false) { - $monthDesc = self::joinNames($month, 'monthName'); + $monthDesc = $this->joinNames($month, 'monthName'); } else { - $monthDesc = self::monthName($month); + $monthDesc = $this->monthName($month); } - $conditions[] = self::trans('in %s', $monthDesc); + $conditions[] = vsprintf($this->module->l('in %s'), array($monthDesc)); } } if ($weekday !== '*') { if ($weekday === '1-5') { - $weekdayDesc = self::trans('weekdays'); + $weekdayDesc = $this->module->l('weekdays'); } elseif ($weekday === '0,6' || $weekday === '6,0') { - $weekdayDesc = self::trans('weekends'); + $weekdayDesc = $this->module->l('weekends'); } elseif (preg_match('/^(\d+)-(\d+)$/', $weekday, $wm)) { - $weekdayDesc = self::trans('%1$s through %2$s', array(self::weekdayName($wm[1]), self::weekdayName($wm[2]))); + $weekdayDesc = vsprintf( + $this->module->l('%1$s through %2$s'), + array($this->weekdayName($wm[1]), $this->weekdayName($wm[2])) + ); } elseif (strpos($weekday, ',') !== false) { - $weekdayDesc = self::joinNames($weekday, 'weekdayName'); + $weekdayDesc = $this->joinNames($weekday, 'weekdayName'); } else { - $weekdayDesc = self::weekdayName($weekday); + $weekdayDesc = $this->weekdayName($weekday); } - $conditions[] = self::trans('on %s', $weekdayDesc); + $conditions[] = vsprintf($this->module->l('on %s'), array($weekdayDesc)); } if (empty($conditions)) { @@ -255,34 +264,29 @@ class QctmCronExpressionTranslator return $when . ' ' . implode(', ', $conditions); } - public static function trans($string, $sprintf = null) - { - return Translate::getModuleTranslation(self::MODULE_NAME, $string, 'QctmCronExpressionTranslator', $sprintf); - } - - public static function isNumeric($value) + public function isNumeric($value) { return preg_match('/^\d+$/', $value) === 1; } - public static function weekdayName($weekday) + public function weekdayName($weekday) { - return (self::isNumeric($weekday) && isset(self::WEEKDAYS[(int) $weekday])) - ? self::trans(self::WEEKDAYS[(int) $weekday]) + return ($this->isNumeric($weekday) && isset(self::WEEKDAYS[(int) $weekday])) + ? $this->module->l(self::WEEKDAYS[(int) $weekday]) : $weekday; } - public static function monthName($month) + public function monthName($month) { - return (self::isNumeric($month) && isset(self::MONTHS[(int) $month])) - ? self::trans(self::MONTHS[(int) $month]) + return ($this->isNumeric($month) && isset(self::MONTHS[(int) $month])) + ? $this->module->l(self::MONTHS[(int) $month]) : $month; } - public static function hourLabel($hour, $minute = null) + public function hourLabel($hour, $minute = null) { $h = (int) $hour; - $suffix = $h >= 12 ? self::trans('PM') : self::trans('AM'); + $suffix = $h >= 12 ? $this->module->l('PM') : $this->module->l('AM'); $h12 = $h % 12; if ($h12 === 0) { @@ -294,12 +298,12 @@ class QctmCronExpressionTranslator : sprintf('%d:%02d %s', $h12, (int) $minute, $suffix); } - public static function joinNames($csv, $nameMethod) + public function joinNames($csv, $nameMethod) { $names = array(); foreach (explode(',', $csv) as $token) { - $names[] = self::$nameMethod($token); + $names[] = $this->$nameMethod($token); } if (count($names) === 1) { @@ -308,10 +312,10 @@ class QctmCronExpressionTranslator $last = array_pop($names); - return implode(', ', $names) . ' ' . self::trans('and') . ' ' . $last; + return implode(', ', $names) . ' ' . $this->module->l('and') . ' ' . $last; } - public static function ordinal($number) + public function ordinal($number) { $number = (int) $number; @@ -336,12 +340,12 @@ class QctmCronExpressionTranslator return $number . $suffix; } - public static function joinOrdinals($csv) + public function joinOrdinals($csv) { $ordinals = array(); foreach (explode(',', $csv) as $token) { - $ordinals[] = self::isNumeric($token) ? self::ordinal($token) : $token; + $ordinals[] = $this->isNumeric($token) ? $this->ordinal($token) : $token; } if (count($ordinals) === 1) { @@ -350,6 +354,6 @@ class QctmCronExpressionTranslator $last = array_pop($ordinals); - return implode(', ', $ordinals) . ' ' . self::trans('and') . ' ' . $last; + return implode(', ', $ordinals) . ' ' . $this->module->l('and') . ' ' . $last; } } diff --git a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php index 228a055f7..30b4ee5b6 100644 --- a/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php +++ b/modules/qlocrontaskmanager/controllers/admin/AdminCronTaskManagerController.php @@ -178,7 +178,7 @@ class AdminCronTaskManagerController extends ModuleAdminController $isValidExpression = \Cron\CronExpression::isValidExpression($cronExpression); if ($isValidExpression) { - $readable = $this->l('Runs ') . QctmCronExpressionTranslator::translate($cronExpression); + $readable = $this->l('Runs ') . (new QctmCronExpressionTranslator())->getCronExpressionTranslation($cronExpression); } else { $readable = $this->l('Invalid cron expression.'); } @@ -271,7 +271,7 @@ class AdminCronTaskManagerController extends ModuleAdminController if (\Cron\CronExpression::isValidExpression($expression)) { $result['valid'] = true; - $result['readable'] = $this->l('Runs ') . QctmCronExpressionTranslator::translate($expression); + $result['readable'] = $this->l('Runs ') . (new QctmCronExpressionTranslator())->getCronExpressionTranslation($expression); } die(Tools::jsonEncode($result)); @@ -390,7 +390,7 @@ class AdminCronTaskManagerController extends ModuleAdminController public function getCronWithReadable($cronExpression, $row) { $readable = \Cron\CronExpression::isValidExpression($cronExpression) - ? QctmCronExpressionTranslator::translate($cronExpression) + ? (new QctmCronExpressionTranslator())->getCronExpressionTranslation($cronExpression) : $cronExpression; return '' . htmlspecialchars($cronExpression) . '' From 971f448c42ad81145e8b27b8de3f6a7fcf873e66 Mon Sep 17 00:00:00 2001 From: amit sharma Date: Thu, 9 Jul 2026 15:45:30 +0530 Subject: [PATCH 10/11] Fixed: Resolved language translation issues. --- .../classes/QctmCronExpressionTranslator.php | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php index 4cfe1d5bf..23ba5a362 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php +++ b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php @@ -27,32 +27,6 @@ if (!defined('_PS_VERSION_')) { class QctmCronExpressionTranslator { - const WEEKDAYS = array( - 0 => 'Sunday', - 1 => 'Monday', - 2 => 'Tuesday', - 3 => 'Wednesday', - 4 => 'Thursday', - 5 => 'Friday', - 6 => 'Saturday', - 7 => 'Sunday', - ); - - const MONTHS = array( - 1 => 'January', - 2 => 'February', - 3 => 'March', - 4 => 'April', - 5 => 'May', - 6 => 'June', - 7 => 'July', - 8 => 'August', - 9 => 'September', - 10 => 'October', - 11 => 'November', - 12 => 'December', - ); - protected $module; public function __construct() @@ -271,16 +245,38 @@ class QctmCronExpressionTranslator public function weekdayName($weekday) { - return ($this->isNumeric($weekday) && isset(self::WEEKDAYS[(int) $weekday])) - ? $this->module->l(self::WEEKDAYS[(int) $weekday]) - : $weekday; + $weekdays = array( + 0 => $this->module->l('Sunday'), + 1 => $this->module->l('Monday'), + 2 => $this->module->l('Tuesday'), + 3 => $this->module->l('Wednesday'), + 4 => $this->module->l('Thursday'), + 5 => $this->module->l('Friday'), + 6 => $this->module->l('Saturday'), + 7 => $this->module->l('Sunday'), + ); + + return ($this->isNumeric($weekday) && isset($weekdays[(int) $weekday])) ? $weekdays[(int) $weekday] : $weekday; } public function monthName($month) { - return ($this->isNumeric($month) && isset(self::MONTHS[(int) $month])) - ? $this->module->l(self::MONTHS[(int) $month]) - : $month; + $months = array( + 1 => $this->module->l('January'), + 2 => $this->module->l('February'), + 3 => $this->module->l('March'), + 4 => $this->module->l('April'), + 5 => $this->module->l('May'), + 6 => $this->module->l('June'), + 7 => $this->module->l('July'), + 8 => $this->module->l('August'), + 9 => $this->module->l('September'), + 10 => $this->module->l('October'), + 11 => $this->module->l('November'), + 12 => $this->module->l('December'), + ); + + return ($this->isNumeric($month) && isset($months[(int) $month])) ? $months[(int) $month] : $month; } public function hourLabel($hour, $minute = null) From e767df340dc0565351d27c782689b3431631496c Mon Sep 17 00:00:00 2001 From: amit sharma Date: Fri, 17 Jul 2026 17:24:23 +0530 Subject: [PATCH 11/11] Fixed: fix cron expression validate issue Co-authored-by: Name --- .../classes/QctmCronExpressionTranslator.php | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php index 23ba5a362..68e04abf4 100644 --- a/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php +++ b/modules/qlocrontaskmanager/classes/QctmCronExpressionTranslator.php @@ -54,7 +54,9 @@ class QctmCronExpressionTranslator } if (preg_match('/^\*\/(\d+)$/', $minute, $m) && $hour == '*' && $dayMonthWildcard && $weekday == '*') { - return vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); + return ((int) $m[1] === 1) + ? $this->module->l('Every minute') + : vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); } if ($minute == '0' && $hour == '*' && $dayMonthWildcard && $weekday == '*') { @@ -62,7 +64,9 @@ class QctmCronExpressionTranslator } if ($minute == '0' && preg_match('/^\*\/(\d+)$/', $hour, $m) && $dayMonthWildcard && $weekday == '*') { - return vsprintf($this->module->l('Every %d hours'), array((int) $m[1])); + return ((int) $m[1] === 1) + ? $this->module->l('Every hour') + : vsprintf($this->module->l('Every %d hours'), array((int) $m[1])); } if ($hasFixedTime && $dayMonthWildcard && $weekday == '*') { @@ -70,10 +74,12 @@ class QctmCronExpressionTranslator } if ($hasFixedTime && $day == '*' && $weekday == '*' && preg_match('/^\*\/(\d+)$/', $month, $mm)) { - return vsprintf( - $this->module->l('Every day every %1$d months at %2$s'), - array((int) $mm[1], $this->hourLabel($hour, $minute)) - ); + return ((int) $mm[1] === 1) + ? vsprintf($this->module->l('Every day every month at %s'), array($this->hourLabel($hour, $minute))) + : vsprintf( + $this->module->l('Every day every %1$d months at %2$s'), + array((int) $mm[1], $this->hourLabel($hour, $minute)) + ); } if ($hasFixedTime && $dayMonthWildcard && $weekday == '1-5') { @@ -145,14 +151,21 @@ class QctmCronExpressionTranslator if ($minute === '*') { $minuteFreq = $this->module->l('Every minute'); } elseif (preg_match('/^\*\/(\d+)$/', $minute, $m)) { - $minuteFreq = vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); + $minuteFreq = ((int) $m[1] === 1) + ? $this->module->l('Every minute') + : vsprintf($this->module->l('Every %d minutes'), array((int) $m[1])); } if (preg_match('/^(\d+)-(\d+)\/(\d+)$/', $hour, $hs) && $this->isNumeric($minute)) { - $when = vsprintf( - $this->module->l('Every %1$d hours between %2$s and %3$s'), - array((int) $hs[3], $this->hourLabel($hs[1], $minute), $this->hourLabel($hs[2], $minute)) - ); + $when = ((int) $hs[3] === 1) + ? vsprintf( + $this->module->l('Every hour between %1$s and %2$s'), + array($this->hourLabel($hs[1], $minute), $this->hourLabel($hs[2], $minute)) + ) + : vsprintf( + $this->module->l('Every %1$d hours between %2$s and %3$s'), + array((int) $hs[3], $this->hourLabel($hs[1], $minute), $this->hourLabel($hs[2], $minute)) + ); } elseif ($minuteFreq !== null) { if ($hour === '*') { $when = $minuteFreq; @@ -197,7 +210,9 @@ class QctmCronExpressionTranslator if ($month !== '*') { if (preg_match('/^\*\/(\d+)$/', $month, $mf)) { - $conditions[] = vsprintf($this->module->l('every %d months'), array((int) $mf[1])); + $conditions[] = ((int) $mf[1] === 1) + ? $this->module->l('every month') + : vsprintf($this->module->l('every %d months'), array((int) $mf[1])); } else { if (preg_match('/^(\d+)-(\d+)$/', $month, $mm)) { $monthDesc = vsprintf(