grthtrhthjhtyjytjytkergtrhtrjytjerhrfh4:24 29/09/2026. -------------------------------------------------------------------------- */ /** @file * @brief */ // Needed for signal handler declare(ticks = 1); if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } /** * CronTask class */ class CronTask extends CommonDBTM{ // From CommonDBTM public $dohistory = true; // Specific ones static private $lockname = ''; private $timer = 0.0; private $startlog = 0; private $volume = 0; static $rightname = 'config'; // Class constant const STATE_DISABLE = 0; const STATE_WAITING = 1; const STATE_RUNNING = 2; const MODE_INTERNAL = 1; const MODE_EXTERNAL = 2; /** * @see CommonGLPI::getForbiddenActionsForMenu() * * @since version 0.85 **/ static function getForbiddenActionsForMenu() { return array('add'); } function getForbiddenStandardMassiveAction() { $forbidden = parent::getForbiddenStandardMassiveAction(); $forbidden[] = 'delete'; $forbidden[] = 'purge'; $forbidden[] = 'restore'; return $forbidden; } static function getTypeName($nb=0) { return _n('Automatic action', 'Automatic actions', $nb); } function defineTabs($options=array()) { $ong = array(); $this->addDefaultFormTab($ong); $this->addStandardTab('CronTaskLog', $ong, $options); $this->addStandardTab('Log', $ong, $options); return $ong; } static function canDelete() { return false; } function cleanDBonPurge() { global $DB; $query = "DELETE FROM `glpi_crontasklogs` WHERE `crontasks_id` = '".$this->fields['id']."'"; $result = $DB->query($query); } /** * Read a Crontask by its name * * Used by plugins to load its crontasks * * @param $itemtype itemtype of the crontask * @param $name name of the task * * @return true if succeed else false **/ function getFromDBbyName($itemtype, $name) { return $this->getFromDBByQuery("WHERE `".$this->getTable()."`.`name` = '$name' AND `".$this->getTable()."`.`itemtype` = '$itemtype'"); } /** * Give a task state * * @return integer 0 : task is enabled * if disable : 1: by config, 2: by system lock, 3: by plugin **/ function isDisabled() { if ($this->fields['state'] == self::STATE_DISABLE) { return 1; } if (is_file(GLPI_CRON_DIR. '/all.lock') || is_file(GLPI_CRON_DIR. '/'.$this->fields['name'].'.lock')) { // Global lock return 2; } if (!($tab = isPluginItemType($this->fields['itemtype']))) { return 0; } // Plugin case $plug = new Plugin(); if (!$plug->isActivated($tab["plugin"])) { return 3; } return 0; } /** * Get all itemtypes used * * @return array of itemtypes **/ static function getUsedItemtypes() { global $DB; $types= array(); foreach ($DB->request("SELECT DISTINCT(`itemtype`) FROM `glpi_crontasks`") as $data) { $types[] = $data['itemtype']; } return $types; } /** * Signal handler callback * * @since 9.1 */ function signal($signo) { if ($signo == SIGTERM) { pcntl_signal(SIGTERM, SIG_DFL); // End of this task $this->end(NULL); // End of this cron $_SESSION["glpicronuserrunning"]=''; self::release_lock(); Toolbox::logInFile('cron', __('Action aborted')."\n"); exit; } } /** * Start a task, timer, stat, log, ... * * @return bool : true if ok (not start by another) **/ function start() { global $DB; if (!isset($this->fields['id']) || ($DB->isSlave())) { return false; } if (isCommandLine() && function_exists('pcntl_signal')) { pcntl_signal(SIGTERM, [$this, 'signal']); } $query = "UPDATE `".$this->getTable()."` SET `state` = '".self::STATE_RUNNING."', `lastrun` = DATE_FORMAT(NOW(),'%Y-%m-%d %H:%i:00') WHERE `id` = '".$this->fields['id']."' AND `state` != '".self::STATE_RUNNING."'"; $result = $DB->query($query); if ($DB->affected_rows($result)>0) { $this->timer = microtime(true); $this->volume = 0; $log = new CronTaskLog(); // No gettext for log $txt = sprintf('%1$s: %2$s', 'Run mode', $this->getModeName(isCommandLine() ? self::MODE_EXTERNAL : self::MODE_INTERNAL)); $this->startlog = $log->add(array('crontasks_id' => $this->fields['id'], 'date' => $_SESSION['glpi_currenttime'], 'content' => addslashes($txt), 'crontasklogs_id' => 0, 'state' => CronTaskLog::STATE_START, 'volume' => 0, 'elapsed' => 0)); return true; } return false; } /** * Set the currently proccessed volume of a running task * * @param $volume **/ function setVolume($volume) { $this->volume = $volume; } /** * Increase the currently proccessed volume of a running task * * @param $volume **/ function addVolume($volume) { $this->volume += $volume; } /** * Start a task, timer, stat, log, ... * * @param $retcode : <0 : need to run again, 0:nothing to do, >0:ok * * @return bool : true if ok (not start by another) **/ function end($retcode) { global $DB; if (!isset($this->fields['id'])) { return false; } $query = "UPDATE `".$this->getTable()."` SET `state` = '".$this->fields['state']."' WHERE `id` = '".$this->fields['id']."' AND `state` = '".self::STATE_RUNNING."'"; $result = $DB->query($query); if ($DB->affected_rows($result) > 0) { // No gettext for log but add gettext line to be parsed for pot generation // order is important for insertion in english in the database if (is_null($retcode)) { $content = __('Action aborted'); $content = 'Action aborted'; } else if ($retcode < 0) { $content = __('Action completed, partially processed'); $content = 'Action completed, partially processed'; } else if ($retcode > 0) { $content = __('Action completed, fully processed'); $content = 'Action completed, fully processed'; } else { $content = __('Action completed, no processing required'); $content = 'Action completed, no processing required'; } $log = new CronTaskLog(); $log->add(array('crontasks_id' => $this->fields['id'], 'date' => $_SESSION['glpi_currenttime'], 'content' => $content, 'crontasklogs_id' => $this->startlog, 'state' => CronTaskLog::STATE_STOP, 'volume' => $this->volume, 'elapsed' => (microtime(true)-$this->timer))); return true; } return false; } /** * Add a log message for a running task * * @param $content **/ function log($content) { if (!isset($this->fields['id'])) { return false; } $log = new CronTaskLog(); $content = Toolbox::substr($content, 0, 200); return $log->add(array('crontasks_id' => $this->fields['id'], 'date' => $_SESSION['glpi_currenttime'], 'content' => addslashes($content), 'crontasklogs_id' => $this->startlog, 'state' => CronTaskLog::STATE_RUN, 'volume' => $this->volume, 'elapsed' => (microtime(true)-$this->timer))); } /** * read the first task which need to be run by cron * * @param $mode >0 retrieve task configured for this mode * <0 retrieve task allowed for this mode (force, no time check) * (default 0) * @param $name one specify action (default '') * * @return false if no task to run **/ function getNeedToRun($mode=0, $name='') { global $DB; $hour = date('H'); // First core ones $query = "SELECT *, LOCATE('Plugin',itemtype) AS ISPLUGIN FROM `".$this->getTable()."` WHERE (`itemtype` NOT LIKE 'Plugin%'"; if (count($_SESSION['glpi_plugins'])) { // Only activated plugins foreach ($_SESSION['glpi_plugins'] as $plug) { $query .= " OR `itemtype` LIKE 'Plugin$plug%'"; } } $query .= ')'; if ($name) { $query .= " AND `name` = '".addslashes($name)."' "; } // In force mode if ($mode < 0) { $query .= " AND `state` != '".self::STATE_RUNNING."' AND (`allowmode` & ".(-intval($mode)).") "; } else { $query .= " AND `state` = '".self::STATE_WAITING."'"; if ($mode > 0) { $query .= " AND `mode` = '$mode' "; } // Get system lock if (is_file(GLPI_CRON_DIR. '/all.lock')) { // Global lock return false; } $locks = array(); foreach (glob(GLPI_CRON_DIR. '/*.lock') as $lock) { if (preg_match('!.*/(.*).lock$!', $lock, $reg)) { $locks[] = $reg[1]; } } if (count($locks)) { $lock = "AND `name` NOT IN ('".implode("','",$locks)."')"; } else { $lock = ''; } // Build query for frequency and allowed hour $query .= " AND ((`hourmin` < `hourmax` AND '$hour' >= `hourmin` AND '$hour' < `hourmax`) OR (`hourmin` > `hourmax` AND ('$hour' >= `hourmin` OR '$hour' < `hourmax`))) AND (`lastrun` IS NULL OR unix_timestamp(`lastrun`) + `frequency` <= unix_timestamp(now())) $lock "; } // Core task before plugins $query .= "ORDER BY ISPLUGIN, unix_timestamp(`lastrun`)+`frequency`"; if ($result = $DB->query($query)) { if ($DB->numrows($result)>0) { $this->fields = $DB->fetch_assoc($result); return true; } } return false; } /** * Print the contact form * * @param $ID integer ID of the item * @param $options array * - target filename : where to go when done. * - withtemplate boolean : template or basic item * * @return Nothing (display) **/ function showForm($ID, $options=array()) { global $CFG_GLPI; if (!Config::canView() || !$this->getFromDB($ID)) { return false; } $options['candel'] = false; $this->showFormHeader($options); echo ""; echo "".__('Name').""; echo ""; $name = $this->fields["name"]; if ($isplug = isPluginItemType($this->fields["itemtype"])) { $name = sprintf(__('%1$s - %2$s'), $isplug["plugin"], $name); } echo $name.""; echo "".__('Comments').""; echo ""; echo ""; echo ""; echo "".__('Description').""; echo $this->getDescription($ID); echo ""; echo "".__('Run frequency').""; $this->dropdownFrequency('frequency', $this->fields["frequency"]); echo ""; $tmpstate = $this->fields["state"]; echo "".__('Status').""; if (is_file(GLPI_CRON_DIR. '/'.$this->fields["name"].'.lock') || is_file(GLPI_CRON_DIR. '/all.lock')) { echo "" . __('System lock')."
"; $tmpstate = self::STATE_DISABLE; } if ($isplug) { $plug = new Plugin(); if (!$plug->isActivated($isplug["plugin"])) { echo "" . __('Disabled plugin')."
"; $tmpstate = self::STATE_DISABLE; } } if ($this->fields["state"] == self::STATE_RUNNING) { echo "" . $this->getStateName(self::STATE_RUNNING).""; } else { self::dropdownState('state', $this->fields["state"]); } echo ""; echo "".__('Run mode').""; $modes = array(); if ($this->fields['allowmode']&self::MODE_INTERNAL) { $modes[self::MODE_INTERNAL] = self::getModeName(self::MODE_INTERNAL); } if ($this->fields['allowmode']&self::MODE_EXTERNAL) { $modes[self::MODE_EXTERNAL] = self::getModeName(self::MODE_EXTERNAL); } Dropdown::showFromArray('mode', $modes, array('value' => $this->fields['mode'])); echo ""; echo "".__('Run period').""; Dropdown::showNumber('hourmin', array('value' => $this->fields['hourmin'], 'min' => 0, 'max' => 24)); echo " -> "; Dropdown::showNumber('hourmax', array('value' => $this->fields['hourmax'], 'min' => 0, 'max' => 24)); echo ""; echo "".__('Number of days this action logs are stored').""; Dropdown::showNumber('logs_lifetime', array('value' => $this->fields['logs_lifetime'], 'min' => 10, 'max' => 360, 'step' => 10, 'toadd' => array(0 => __('Infinite')))); echo "".__('Last run').""; if (empty($this->fields['lastrun'])) { _e('Never'); } else { echo Html::convDateTime($this->fields['lastrun']); echo " "; Html::showSimpleForm(static::getFormURL(), 'resetdate', __('Blank'), array('id' => $ID), $CFG_GLPI['root_doc']."/pics/reset.png"); } echo ""; $label = $this->getParameterDescription(); echo ""; if (empty($label)) { echo "  "; } else { echo $label." "; Dropdown::showNumber('param', array('value' => $this->fields['param'], 'min' => 0, 'max' => 400)); } echo "".__('Next run').""; if ($tmpstate == self::STATE_RUNNING) { $launch = false; } else { $launch = $this->fields['allowmode']&self::MODE_INTERNAL; } if ($tmpstate != self::STATE_WAITING) { echo $this->getStateName($tmpstate); } else if (empty($this->fields['lastrun'])) { _e('As soon as possible'); } else { $next = strtotime($this->fields['lastrun'])+$this->fields['frequency']; $h = date('H',$next); $deb = ($this->fields['hourmin'] < 10 ? "0".$this->fields['hourmin'] : $this->fields['hourmin']); $fin = ($this->fields['hourmax'] < 10 ? "0".$this->fields['hourmax'] : $this->fields['hourmax']); if (($deb < $fin) && ($h < $deb)) { $disp = date('Y-m-d', $next). " $deb:00:00"; $next = strtotime($disp); } else if (($deb < $fin) && ($h >= $this->fields['hourmax'])) { $disp = date('Y-m-d', $next+DAY_TIMESTAMP). " $deb:00:00"; $next = strtotime($disp); } if (($deb > $fin) && ($h < $deb) && ($h >= $fin)) { $disp = date('Y-m-d', $next). " $deb:00:00"; $next = strtotime($disp); } else { $disp = date("Y-m-d H:i:s", $next); } if ($next < time()) { echo __('As soon as possible').'
('.Html::convDateTime($disp).') '; } else { echo Html::convDateTime($disp); } } if ($launch) { echo " "; Html::showSimpleForm(static::getFormURL(), array('execute' => $this->fields['name']), __('Execute')); } if ($tmpstate == self::STATE_RUNNING) { Html::showSimpleForm(static::getFormURL(), 'resetstate', __('Blank'), array('id' => $ID), $CFG_GLPI['root_doc']."/pics/reset.png"); } echo ""; $this->showFormButtons($options); return true; } /** * reset the next launch date => for a launch as soon as possible **/ function resetDate () { global $DB; if (!isset($this->fields['id'])) { return false; } return $this->update(array('id' => $this->fields['id'], 'lastrun' => 'NULL')); } /** * reset the current state **/ function resetState () { global $DB; if (!isset($this->fields['id'])) { return false; } return $this->update(array('id' => $this->fields['id'], 'state' => self::STATE_WAITING)); } /** * Translate task description * * @param $id integer ID of the crontask * * @return string **/ public function getDescription($id) { if (!isset($this->fields['id']) || ($this->fields['id'] != $id)) { $this->getFromDB($id); } $hook = array($this->fields['itemtype'], 'cronInfo'); if (is_callable($hook)) { $info = call_user_func($hook, $this->fields['name']); } else { $info = false; } if (isset($info['description'])) { return $info['description']; } return $this->fields['name']; } /** * Translate task parameter description * * @return string **/ public function getParameterDescription() { $hook = array($this->fields['itemtype'], 'cronInfo'); if (is_callable($hook)) { $info = call_user_func($hook, $this->fields['name']); } else { $info = false; } if (isset($info['parameter'])) { return $info['parameter']; } return ''; } /** * Translate state to string * * @param $state integer * * @return string **/ static public function getStateName($state) { switch ($state) { case self::STATE_RUNNING : return __('Running'); case self::STATE_WAITING : return __('Scheduled'); case self::STATE_DISABLE : return __('Disabled'); } return '???'; } /** * Dropdown of state * * @param $name select name * @param $value default value (default 0) * @param $display display or get string (true by default) * * @return nothing (display) **/ static function dropdownState($name, $value=0, $display=true) { return Dropdown::showFromArray($name, array(self::STATE_DISABLE => __('Disabled'), self::STATE_WAITING => __('Scheduled')), array('value' => $value, 'display' => $display)); } /** * Translate Mode to string * * @param $mode integer * * @return string **/ static public function getModeName($mode) { switch ($mode) { case self::MODE_INTERNAL : return __('GLPI'); case self::MODE_EXTERNAL : return __('CLI'); } return '???'; } /** * Get a global database lock for cron * * @return Boolean **/ static private function get_lock() { global $DB; // Changer de nom toutes les heures en cas de blocage MySQL (ca arrive) $nom = "glpicron." . intval(time()/HOUR_TIMESTAMP-340000); if ($DB->getLock($nom)) { self::$lockname = $nom; return true; } return false; } /** * Release the global database lock **/ static private function release_lock() { global $DB; if (self::$lockname) { $DB->releaseLock(self::$lockname); self::$lockname = ''; } } /** * Launch the need cron tasks * * @param $mode (internal/external, <0 to force) * @param $max number of task to launch (default 1) * @param $name of task to run (default '') * * @return the name of last task launched **/ static public function launch($mode, $max=1, $name='') { global $CFG_GLPI; // No cron in maintenance mode if (isset($CFG_GLPI['maintenance_mode']) && $CFG_GLPI['maintenance_mode']) { return false; } $crontask = new self(); $taskname = ''; if (abs($mode) == self::MODE_EXTERNAL) { // If cron is launched in command line, and if memory is insufficient, // display a warning in the logs if (Toolbox::checkMemoryLimit() == 2) { Toolbox::logInFile('cron', __('A minimum of 64 Mio is commonly required for GLPI.')."\n"); } // If no task in CLI mode, call cron.php from command line is not really usefull ;) if (!countElementsInTable($crontask->getTable(), "`mode` = '".abs($mode)."'")) { Toolbox::logInFile('cron', __('No task with Run mode = CLI, fix your tasks configuration')."\n"); } } if (self::get_lock()) { for ($i=1 ; $i<=$max ; $i++) { $prefix = (abs($mode) == self::MODE_EXTERNAL ? __('External') : __('Internal')); if ($crontask->getNeedToRun($mode, $name)) { $_SESSION["glpicronuserrunning"] = "cron_".$crontask->fields['name']; if ($plug = isPluginItemType($crontask->fields['itemtype'])) { Plugin::load($plug['plugin'], true); } $fonction = array($crontask->fields['itemtype'], 'cron' . $crontask->fields['name']); if (is_callable($fonction)) { if ($crontask->start()) { // Lock in DB + log start $taskname = $crontask->fields['name']; //TRANS: %1$s is mode (external or internal), %2$s is an order number, $msgcron = sprintf(__('%1$s #%2$s'), $prefix, $i); $msgcron = sprintf(__('%1$s: %2$s'), $msgcron, sprintf(__('%1$s %2$s')."\n", __('Launch'), $crontask->fields['name'])); Toolbox::logInFile('cron', $msgcron); $retcode = call_user_func($fonction, $crontask); $crontask->end($retcode); // Unlock in DB + log end } else { $msgcron = sprintf(__('%1$s #%2$s'), $prefix, $i); $msgcron = sprintf(__('%1$s: %2$s'), $msgcron, sprintf(__('%1$s %2$s')."\n", __("Can't start"), $crontask->fields['name'])); Toolbox::logInFile('cron', $msgcron); } } else { if (is_array($fonction)) { $fonction = implode('::',$fonction); } Toolbox::logInFile('php-errors', sprintf(__('Undefined function %s (for cron)')."\n", $fonction)); $msgcron = sprintf(__('%1$s #%2$s'), $prefix, $i); $msgcron = sprintf(__('%1$s: %2$s'), $msgcron, sprintf(__('%1$s %2$s')."\n", __("Can't start"), $crontask->fields['name'])); Toolbox::logInFile('cron', $msgcron ."\n". sprintf(__('Undefined function %s (for cron)')."\n", $fonction)); } } else if ($i==1) { $msgcron = sprintf(__('%1$s #%2$s'), $prefix, $i); $msgcron = sprintf(__('%1$s: %2$s'), $msgcron, __('Nothing to launch')); Toolbox::logInFile('cron', $msgcron."\n"); } } // end for $_SESSION["glpicronuserrunning"]=''; self::release_lock(); } else { Toolbox::logInFile('cron', __("Can't get DB lock")."\n"); } return $taskname; } /** * Register new task for plugin (called by plugin during install) * * @param $itemtype itemtype of the plugin object * @param $name of the task * @param $frequency of execution * @param $options array of optional options * (state, mode, allowmode, hourmin, hourmax, logs_lifetime, param, comment) * * @return bool for success **/ static public function Register($itemtype, $name, $frequency, $options=array()) { // Check that hook exists if (!isPluginItemType($itemtype)) { return false; } $temp = new self(); // Avoid duplicate entry if ($temp->getFromDBbyName($itemtype, $name)) { return false; } $input = array('itemtype' => $itemtype, 'name' => $name, 'allowmode' => self::MODE_INTERNAL | self::MODE_EXTERNAL, 'frequency' => $frequency); foreach (array('allowmode', 'comment', 'hourmax', 'hourmin', 'logs_lifetime', 'mode', 'param', 'state') as $key) { if (isset($options[$key])) { $input[$key] = $options[$key]; } } if (defined('GLPI_SYSTEM_CRON') && ($input['allowmode'] & self::MODE_EXTERNAL) && !isset($input['mode'])) { // Downstream packages may provide a good system cron $input['mode'] = self::MODE_EXTERNAL; } return $temp->add($input); } /** * Unregister tasks for a plugin (call by glpi after uninstall) * * @param $plugin : name of the plugin * * @return bool for success **/ static public function Unregister($plugin) { global $DB; if (empty($plugin)) { return false; } $temp = new CronTask(); $ret = true; $query = "SELECT * FROM `glpi_crontasks` WHERE `itemtype` LIKE 'Plugin$plugin%'"; $result = $DB->query($query); if ($DB->numrows($result)) { while ($data = $DB->fetch_assoc($result)) { if (!$temp->delete($data)) { $ret = false; } } } return $ret; } /** * Display statistics of a task * * @return nothing **/ function showStatistics() { global $DB, $CFG_GLPI; echo "
"; echo ""; echo "\n"; $nbstart = countElementsInTable('glpi_crontasklogs', "`crontasks_id` = '".$this->fields['id']."' AND `state` = '".CronTaskLog::STATE_START."'"); $nbstop = countElementsInTable('glpi_crontasklogs', "`crontasks_id` = '".$this->fields['id']."' AND `state` = '".CronTaskLog::STATE_STOP."'"); echo ""; if ($nbstop) { $query = "SELECT MIN(`date`) AS datemin, MIN(`elapsed`) AS elapsedmin, MAX(`elapsed`) AS elapsedmax, AVG(`elapsed`) AS elapsedavg, SUM(`elapsed`) AS elapsedtot, MIN(`volume`) AS volmin, MAX(`volume`) AS volmax, AVG(`volume`) AS volavg, SUM(`volume`) AS voltot FROM `glpi_crontasklogs` WHERE `crontasks_id` = '".$this->fields['id']."' AND `state` = '".CronTaskLog::STATE_STOP."'"; $result = $DB->query($query); if ($data = $DB->fetch_assoc($result)) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } if ($data && ($data['voltot'] > 0)) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } echo "
 ".__('Statistics')."
".__('Run count').""; if ($nbstart == $nbstop) { echo $nbstart; } else { // This should not appen => task crash ? //TRANS: %s is the number of starts printf(_n('%s start', '%s starts', $nbstart), $nbstart); echo "
"; //TRANS: %s is the number of stops printf(_n('%s stop', '%s stops', $nbstop), $nbstop); } echo "
".__('Start date')."".Html::convDateTime($data['datemin'])."
".__('Minimal time')."".sprintf(_n('%s second', '%s seconds', $data['elapsedmin']), number_format($data['elapsedmin'], 2)); echo "
".__('Maximal time')."".sprintf(_n('%s second', '%s seconds', $data['elapsedmax']), number_format($data['elapsedmax'], 2)); echo "
".__('Average time')."".sprintf(_n('%s second', '%s seconds', $data['elapsedavg']), number_format($data['elapsedavg'],2)); echo "
".__('Total duration')."".sprintf(_n('%s second', '%s seconds', $data['elapsedtot']), number_format($data['elapsedtot'],2)); echo "
".__('Minimal count')."".sprintf(_n('%s item', '%s items', $data['volmin']), $data['volmin'])."
".__('Maximal count')."".sprintf(_n('%s item', '%s items', $data['volmax']), $data['volmax'])."
".__('Average count')."".sprintf(_n('%s item', '%s items', $data['volavg']), number_format($data['volavg'],2)). "
".__('Total count')."". sprintf(_n('%s item', '%s items',$data['voltot']), $data['voltot'])."
".__('Average speed')."".sprintf(__('%s items/sec'), number_format($data['voltot']/$data['elapsedtot'],2)); echo "
"; } /** * Display list of a runned tasks * * @return nothing **/ function showHistory() { global $DB, $CFG_GLPI; if (isset($_GET["crontasklogs_id"]) && $_GET["crontasklogs_id"]) { return $this->showHistoryDetail($_GET["crontasklogs_id"]); } if (isset($_GET["start"])) { $start = $_GET["start"]; } else { $start = 0; } // Total Number of events $number = countElementsInTable('glpi_crontasklogs', "`crontasks_id` = '".$this->fields['id']."' AND `state` = '".CronTaskLog::STATE_STOP."'"); echo "
"; if ($number < 1) { echo ""; echo ""; echo "
".__('No item found')."
"; echo "
"; return; } // Display the pager Html::printAjaxPager(__('Last run list'), $start, $number); $query = "SELECT * FROM `glpi_crontasklogs` WHERE `crontasks_id` = '".$this->fields['id']."' AND `state` = '".CronTaskLog::STATE_STOP."' ORDER BY `id` DESC LIMIT ".intval($start)."," . intval($_SESSION['glpilist_limit']); if ($result = $DB->query($query)) { if ($data = $DB->fetch_assoc($result)) { echo ""; $header = ""; $header .= ""; $header .= ""; $header .= ""; $header .= ""; $header .= "\n"; echo $header; do { echo ""; echo ""; echo ""; echo ""; // Use gettext to display echo ""; echo "\n"; } while ($data = $DB->fetch_assoc($result)); echo $header; echo "
".__('Date')."".__('Total duration').""._x('quantity', 'Number')."".__('Description')."
".Html::convDateTime($data['date']). "".sprintf(_n('%s second','%s seconds', intval($data['elapsed'])), number_format($data['elapsed'], 3)). "   ".$data['volume']."".__($data['content'])."
"; } else { // Not found _e('No item found'); } } // Query Html::printAjaxPager(__('Last run list'), $start, $number); echo ""; } /** * Display detail of a runned task * * @param $logid : crontasklogs_id * * @return nothing **/ function showHistoryDetail($logid) { global $DB, $CFG_GLPI; echo "
"; echo "

".__('Last run list')."". "

"; $query = "SELECT * FROM `glpi_crontasklogs` WHERE `id` = '$logid' OR `crontasklogs_id` = '$logid' ORDER BY `id` ASC"; if ($result = $DB->query($query)) { if ($data = $DB->fetch_assoc($result)) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo "\n"; $first = true; do { echo ""; echo ""; $content = $data['content']; switch ($data['state']) { case CronTaskLog::STATE_START : echo ""; // Pass content to gettext // implode (Run mode: XXX) $list = explode(':',$data['content']); if (count($list)==2) { $content = sprintf('%1$s: %2$s', __($list[0]), $list[1]); } break; case CronTaskLog::STATE_STOP : echo ""; // Pass content to gettext $content = __($data['content']); break; default : echo ""; // Pass content to gettext $content = __($data['content']); } echo ""; echo ""; echo ""; echo "\n"; $first = false; } while ($data = $DB->fetch_assoc($result)); echo "
".__('Date')."".__('Status')."". __('Duration').""._x('quantity', 'Number')."".__('Description')."
".($first ? Html::convDateTime($data['date']) : " ")."".__('Start')."".__('End')."".__('Running')."".sprintf(_n('%s second', '%s seconds', intval($data['elapsed'])), number_format($data['elapsed'], 3)). "  ".$data['volume']."".$content."
"; } else { // Not found _e('No item found'); } } // Query echo "
"; } /** * @since version 0.84 * * @param $field * @param $name (default '') * @param $values (default '') * @param $options array **/ static function getSpecificValueToSelect($field, $name='', $values='', array $options=array()) { if (!is_array($values)) { $values = array($field => $values); } $options['display'] = 0; switch ($field) { case 'mode': $options['value'] = $values[$field]; $tab[self::MODE_INTERNAL] = self::getModeName(self::MODE_INTERNAL); $tab[self::MODE_EXTERNAL] = self::getModeName(self::MODE_EXTERNAL); return Dropdown::showFromArray($name, $tab, $options); case 'state' : return CronTask::dropdownState($name, $values[$field], false); } return parent::getSpecificValueToSelect($field, $name, $values, $options); } static function getSpecificValueToDisplay($field, $values, array $options=array()) { if (!is_array($values)) { $values = array($field => $values); } switch ($field) { case 'mode': return self::getModeName($values[$field]); case 'state': return self::getStateName($values[$field]); } return parent::getSpecificValueToDisplay($field, $values, $options); } /** * @see CommonDBTM::getSpecificMassiveActions() **/ function getSpecificMassiveActions($checkitem=NULL) { $isadmin = static::canUpdate(); $actions = parent::getSpecificMassiveActions($checkitem); if ($isadmin) { $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'reset'] = __('Reset last run'); } return $actions; } /** * @since version 0.85 * * @see CommonDBTM::processMassiveActionsForOneItemtype() **/ static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids) { switch ($ma->getAction()) { case 'reset' : if (Config::canUpdate()) { foreach ($ids as $key) { if ($item->getFromDB($key)) { if ($item->resetDate()) { $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_OK); } else { $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO); $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION)); } } else { $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_KO); $ma->addMessage($item->getErrorMessage(ERROR_NOT_FOUND)); } } } else { $ma->itemDone($item->getType(), $key, MassiveAction::ACTION_NORIGHT); $ma->addMessage($item->getErrorMessage(ERROR_RIGHT)); } return; } parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } function getSearchOptions() { $tab = array(); $tab['common'] = __('Characteristics'); $tab[1]['table'] = $this->getTable(); $tab[1]['field'] = 'name'; $tab[1]['name'] = __('Name'); $tab[1]['datatype'] = 'itemlink'; $tab[1]['massiveaction'] = false; $tab[2]['table'] = $this->getTable(); $tab[2]['field'] = 'id'; $tab[2]['name'] = __('ID'); $tab[2]['massiveaction'] = false; $tab[2]['datatype'] = 'number'; $tab[3]['table'] = $this->getTable(); $tab[3]['field'] = 'description'; $tab[3]['name'] = __('Description'); $tab[3]['nosearch'] = true; $tab[3]['nosort'] = true; $tab[3]['massiveaction'] = false; $tab[3]['datatype'] = 'text'; $tab[3]['computation'] = "TABLE.`id`"; // Virtual data $tab[4]['table'] = $this->getTable(); $tab[4]['field'] = 'state'; $tab[4]['name'] = __('Status'); $tab[4]['searchtype'] = array('equals', 'notequals'); $tab[4]['massiveaction'] = false; $tab[4]['datatype'] = 'specific'; $tab[5]['table'] = $this->getTable(); $tab[5]['field'] = 'mode'; $tab[5]['name'] = __('Run mode'); $tab[5]['datatype'] = 'specific'; $tab[5]['searchtype'] = array('equals', 'notequals'); $tab[6]['table'] = $this->getTable(); $tab[6]['field'] = 'frequency'; $tab[6]['name'] = __('Run frequency'); $tab[6]['datatype'] = 'timestamp'; $tab[6]['massiveaction'] = false; $tab[7]['table'] = $this->getTable(); $tab[7]['field'] = 'lastrun'; $tab[7]['name'] = __('Last run'); $tab[7]['datatype'] = 'datetime'; $tab[7]['massiveaction'] = false; $tab[8]['table'] = $this->getTable(); $tab[8]['field'] = 'itemtype'; $tab[8]['name'] = __('Item type'); $tab[8]['massiveaction'] = false; $tab[8]['datatype'] = 'itemtypename'; $tab[8]['types'] = self::getUsedItemtypes(); $tab[16]['table'] = $this->getTable(); $tab[16]['field'] = 'comment'; $tab[16]['name'] = __('Comments'); $tab[16]['datatype'] = 'text'; $tab[17]['table'] = $this->getTable(); $tab[17]['field'] = 'hourmin'; $tab[17]['name'] = __('Begin hour of run period'); $tab[17]['datatype'] = 'integer'; $tab[17]['min'] = 0; $tab[17]['max'] = 24; $tab[18]['table'] = $this->getTable(); $tab[18]['field'] = 'hourmax'; $tab[18]['name'] = __('End hour of run period'); $tab[18]['datatype'] = 'integer'; $tab[18]['min'] = 0; $tab[18]['max'] = 24; $tab[19]['table'] = $this->getTable(); $tab[19]['field'] = 'logs_lifetime'; $tab[19]['name'] = __('Number of days this action logs are stored'); $tab[19]['datatype'] = 'integer'; $tab[19]['min'] = 10; $tab[19]['max'] = 360; $tab[19]['step'] = 10; $tab[19]['toadd'] = array(0 => __('Infinite')); $tab[20]['table'] = $this->getTable(); $tab[20]['field'] = 'date_mod'; $tab[20]['name'] = __('Last update'); $tab[20]['datatype'] = 'datetime'; $tab[20]['massiveaction'] = false; $tab[121]['table'] = $this->getTable(); $tab[121]['field'] = 'date_creation'; $tab[121]['name'] = __('Creation date'); $tab[121]['datatype'] = 'datetime'; $tab[121]['massiveaction'] = false; return $tab; } /** * Garbage collector for expired file session * * @param $task for log **/ static function cronSession($task) { // max time to keep the file session $maxlifetime = ini_get('session.gc_maxlifetime'); if ($maxlifetime == 0) { $maxlifetime == WEEK_TIMESTAMP; } $nb = 0; foreach (glob(GLPI_SESSION_DIR."/sess_*") as $filename) { if ((filemtime($filename) + $maxlifetime) < time()) { // Delete session file if not delete before if (@unlink($filename)) { $nb++; } } } $task->setVolume($nb); if ($nb) { //TRANS: % %1$d is a number, %2$s is a number of seconds $task->log(sprintf(_n('Clean %1$d session file created since more than %2$s seconds', 'Clean %1$d session files created since more than %2$s seconds', $nb)."\n", $nb, $maxlifetime)); return 1; } return 0; } /** * Circular logs * * @since version 0.85 * * @param $task for log **/ static function cronCircularlogs($task) { $actionCode = 0; // by default $error = false ; $task->setVolume(0); // start with zero // compute date in the past for the archived log to be deleted $firstdate = date("Ymd", time() - ($task->fields['param'] * DAY_TIMESTAMP)); // compute current date - param as days and format it like YYYYMMDD // first look for bak to delete $dir = GLPI_LOG_DIR."/*.bak" ; $findfiles = glob( $dir ) ; foreach ($findfiles as $file) { $shortfile = str_replace(GLPI_LOG_DIR.'/','',$file); // now depending on the format of the name we delete the file (for aging archives) or rename it (will add Ymd.log to the end of the file) $match = null; if (preg_match('/.+[.]log[.](\\d{8})[.]bak$/', $file, $match) > 0) { if ($match[1] < $firstdate ) { $task->addVolume(1); if (unlink($file)) { $task->log(sprintf(__('Deletion of archived log file: %s'), $shortfile)); $actionCode = 1 ; } else { $task->log(sprintf(__('Unable to delete archived log file: %s'), $shortfile)); $error = true ; } } } } // second look for log to archive $dir = GLPI_LOG_DIR."/*.log" ; $findfiles = glob( $dir ) ; foreach ($findfiles as $file) { $shortfile = str_replace(GLPI_LOG_DIR.'/','',$file); // rename the file $newfilename = $file.".".date("Ymd", time()).".bak"; // will add to filename a string with format YYYYMMDD (= current date) $shortnewfile = str_replace(GLPI_LOG_DIR.'/','',$newfilename); $task->addVolume(1); if (!file_exists($newfilename) && rename($file, $newfilename)) { $task->log(sprintf(__('Archiving log file: %1$s to %2$s'), $shortfile, $shortnewfile)); $actionCode = 1 ; } else { $task->log(sprintf(__('Unable to archive log file: %1$s. %2$s already exists. Wait till next day.'), $shortfile, $shortnewfile)) ; $error = true ; } } if ($error) { return -1 ; } return $actionCode; } /** * Garbage collector for cleaning graph files * * @param $task for log **/ static function cronGraph($task) { global $CFG_GLPI; // max time to keep the file session $maxlifetime = HOUR_TIMESTAMP; $nb = 0; foreach (glob(GLPI_GRAPH_DIR."/*") as $filename) { if (basename($filename) == "remove.txt" && is_dir(GLPI_ROOT.'/.git')) { continue; } if ((filemtime($filename) + $maxlifetime) < time()) { if (@unlink($filename)) { $nb++; } } } $task->setVolume($nb); if ($nb) { $task->log(sprintf(_n('Clean %1$d graph file created since more than %2$s seconds', 'Clean %1$d graph files created since more than %2$s seconds', $nb)."\n", $nb, $maxlifetime)); return 1; } return 0; } /** * Garbage collector for cleaning tmp files * * @param $task for log **/ static function cronTemp($task) { global $CFG_GLPI; // max time to keep the file session $maxlifetime = HOUR_TIMESTAMP; $nb = 0; foreach (glob(GLPI_TMP_DIR."/*") as $filename) { if (basename($filename) == "remove.txt" && is_dir(GLPI_ROOT.'/.git')) { continue; } if ((filemtime($filename) + $maxlifetime) < time()) { if (@unlink($filename)) { $nb++; } } } $task->setVolume($nb); if ($nb) { $task->log(sprintf(_n('Clean %1$d temporary file created since more than %2$s seconds', 'Clean %1$d temporary files created since more than %2$s seconds', $nb)."\n", $nb, $maxlifetime)); return 1; } return 0; } /** * Clean log cron function * * @param $task instance of CronTask **/ static function cronLogs($task) { global $DB; $vol = 0; // Expire Event Log if ($task->fields['param'] > 0) { $vol += Event::cleanOld($task->fields['param']); } foreach ($DB->request('glpi_crontasks') as $data) { if ($data['logs_lifetime']>0) { $vol += CronTaskLog::cleanOld($data['id'], $data['logs_lifetime']); } } $task->setVolume($vol); return ($vol > 0 ? 1 : 0); } /** * Cron job to check if a new version is available * * @param $task for log **/ static function cronCheckUpdate($task) { $result = Toolbox::checkNewVersionAvailable(1); $task->log($result); return 1; } /** * Clean log cron function * * @param $task for log **/ static function cronOptimize($task) { $nb = DBmysql::optimize_tables(NULL, true); $task->setVolume($nb); return 1; } /** * Check zombie crontask * * @param $task for log **/ static function cronWatcher($task) { global $CFG_GLPI, $DB; $cron_status = 0; // Crontasks running for more than 1 hour or 2 frequency $query = "SELECT * FROM `glpi_crontasks` WHERE `state` = '".self::STATE_RUNNING."' AND ((unix_timestamp(`lastrun`) + 2 * `frequency` < unix_timestamp(now())) OR (unix_timestamp(`lastrun`) + 2*".HOUR_TIMESTAMP." < unix_timestamp(now())))"; $crontasks = array(); foreach ($DB->request($query) as $data) { $crontasks[$data['id']] = $data; } if (count($crontasks)) { $task = new self(); $task->getFromDBByQuery("WHERE `itemtype` = 'Crontask' AND `name` = 'watcher'"); if (NotificationEvent::raiseEvent("alert", $task, array('items' => $crontasks))) { $cron_status = 1; $task->addVolume(1); } QueuedMail::forceSendFor($task->getType(), $task->fields['id']); } return 1; } /** * get Cron description parameter for this class * * @param $name string name of the task * * @return array of string **/ static function cronInfo($name) { switch ($name) { case 'checkupdate' : return array('description' => __('Check for new updates')); case 'logs' : return array('description' => __('Clean old logs'), 'parameter' => __('System logs retention period (in days, 0 for infinite)')); case 'optimize' : return array('description' => __('Database optimization')); case 'session' : return array('description' => __('Clean expired sessions')); case 'graph' : return array('description' => __('Clean generated graphics')); case 'temp' : return array('description' => __('Clean temporary files')); case 'watcher' : return array('description' => __('Monitoring of automatic actions')); case 'circularlogs' : return array('description' => __("Archives log files and deletes aging ones"), 'parameter' => __("Number of days to keep archived logs")); } } /** * Dropdown for frequency (interval between 2 actions) * * @param $name select name * @param $value default value (default 0) **/ function dropdownFrequency($name, $value=0) { $tab = array(); $tab[MINUTE_TIMESTAMP] = sprintf(_n('%d minute','%d minutes',1),1); // Minutes for ($i=5 ; $i<60 ; $i+=5) { $tab[$i*MINUTE_TIMESTAMP] = sprintf(_n('%d minute','%d minutes',$i), $i); } // Heures for ($i=1 ; $i<24 ; $i++) { $tab[$i*HOUR_TIMESTAMP] = sprintf(_n('%d hour','%d hours',$i), $i); } // Jours $tab[DAY_TIMESTAMP] = __('Each day'); for ($i=2 ; $i<7 ; $i++) { $tab[$i*DAY_TIMESTAMP] = sprintf(_n('%d day','%d days',$i),$i); } $tab[WEEK_TIMESTAMP] = __('Each week'); $tab[MONTH_TIMESTAMP] = __('Each month'); Dropdown::showFromArray($name, $tab, array('value' => $value)); } /** * Call cron without time check * * @return boolean : true if launched **/ static function callCronForce() { global $CFG_GLPI; $path = $CFG_GLPI['root_doc']."/front/cron.php"; echo "
"; return true; } /** * Call cron if time since last launch elapsed * * @return nothing **/ static function callCron() { if (isset($_SESSION["glpicrontimer"])) { // call static function callcron() every 5min if ((time() - $_SESSION["glpicrontimer"]) > 300) { if (self::callCronForce()) { // Restart timer $_SESSION["glpicrontimer"] = time(); } } } else { // Start timer $_SESSION["glpicrontimer"] = time(); } } } ?>