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('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.'); } 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.')); } 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(); return $output; } protected function processSaveSettings() { $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.')); } 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. After regenerating it, you will need to update 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, 'validation' => 'isUnsignedInt', 'suffix' => $this->l('days'), '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)) { $objCronTask = new QctmCronTask(); $objCronTask->registerTasksForModule($module->id, $tasks); } } } 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) { return; } QctmCronTask::deleteByModule($params['object']->id); } /** * Called by the system log-cleanup cron task (daily at midnight). */ public function runLogCleanup() { $days = (int) Configuration::get('QCTM_CRON_TASK_MANAGER_LOG_RETENTION_DAYS'); if ($days > 0) { QctmCronTaskLog::deleteOlderThan($days); } } public function install() { $db = new QctmCronTaskManagerDb(); $objCronTask = new QctmCronTask(); if (!parent::install() || !$this->registerHooks() || !$db->createTables() || !$db->installDefaultData() || !$this->installTabs() || !$this->installDefaultTasks() ) { return false; } $objCronTask->syncAllExistingModules(); return true; } public function registerHooks() { return $this->registerHook( array( 'actionModuleInstallAfter', 'actionModuleUninstallBefore', 'dashboardZoneThree', ) ); } public function uninstall() { $db = new QctmCronTaskManagerDb(); if (!$this->uninstallTabs() || !$db->deleteConfigurations() || !$db->dropTables() || !parent::uninstall() ) { return false; } return true; } protected function installDefaultTasks() { if (QctmCronTask::getByModuleAndName($this->id, 'log_cleanup')) { return true; } $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() { foreach (array('AdminCronTaskManager', 'AdminCronTaskLogs') as $className) { $idTab = (int) Tab::getIdFromClassName($className); if ($idTab) { $tab = new Tab($idTab); if (!$tab->delete()) { return false; } } } return true; } }