' . file_get_contents(dirname(__FILE__) . '/README.txt') . ''; case 'admin/build/cron': return '

' . t('Here you can see the crontab settings for each job available') . '

'; case 'admin/build/cron/settings': return '

' . t('Here you can change the crontab settings for each job available') . '

'; } } /** * Implements hook_menu(). */ function ultimate_cron_menu() { $items = array(); $items['admin/config/system/cron/settings'] = array( 'title' => 'Settings', 'description' => 'Cron settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('ultimate_cron_settings_form'), 'access arguments' => array('administer ultimate cron'), 'type' => MENU_LOCAL_TASK, 'file' => 'ultimate_cron.admin.inc', ); $items['admin/config/system/cron/settings/%'] = array( 'title' => 'Settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('ultimate_cron_function_settings_form', 5), 'access arguments' => array('administer ultimate cron'), 'weight' => 0, 'file' => 'ultimate_cron.admin.inc', ); $weight = 0; foreach (array( 'error' => 'Errors', 'warning' => 'Warnings', 'info' => 'Info', 'success' => 'Success', 'running' => 'Running', ) as $status => $title) { $items['admin/config/system/cron/overview/' . $status] = array( 'title' => $title, 'description' => 'View and manage cron table', 'page callback' => 'ultimate_cron_view_page', 'page arguments' => array(5), 'access arguments' => array('administer ultimate cron'), 'module' => 'ultimate_cron', 'file' => 'ultimate_cron.admin.inc', 'weight' => $weight++, 'type' => MENU_LOCAL_TASK, ); } $items['admin/config/system/cron/overview/all'] = array( 'title' => 'All', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => $weight++, ); $items['admin/reports/cron'] = array( 'title' => 'Cron logs', 'description' => 'View logs for all cron jobs.', 'page callback' => 'ultimate_cron_view_page', 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); $items['admin/reports/cron/%'] = array( 'title' => 'Cron log', 'description' => 'View log for specific function.', 'page callback' => 'ultimate_cron_function_log_page', 'page arguments' => array(3), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); $items['admin/ultimate-cron/service/start/%'] = array( 'type' => MENU_CALLBACK, 'title' => 'Run cron job', 'description' => 'Run cron job', 'page callback' => 'ultimate_cron_service_start', 'page arguments' => array(4), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); $items['admin/ultimate-cron/service/enable/%'] = array( 'type' => MENU_CALLBACK, 'title' => 'Enable cron job', 'description' => 'Enable cron job', 'page callback' => 'ultimate_cron_service_enable', 'page arguments' => array(4, TRUE), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); $items['admin/ultimate-cron/service/disable/%'] = array( 'type' => MENU_CALLBACK, 'title' => 'Disable cron job', 'description' => 'Disable cron job', 'page callback' => 'ultimate_cron_service_enable', 'page arguments' => array(4, FALSE), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); $items['admin/ultimate-cron/service/process-status'] = array( 'type' => MENU_CALLBACK, 'title' => 'Cron job process status', 'description' => 'Cron job process status', 'page callback' => 'ultimate_cron_service_process_status', 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', ); return $items; } /** * Implements hook_cron_queue_info(). * Used for code injection in order to hijack cron runs. */ function ultimate_cron_cron_queue_info() { static $processed = FALSE; if (!$processed) { $processed = TRUE; if (basename($_SERVER['PHP_SELF']) == 'cron.php') { ultimate_cron_cron_run(FALSE); exit; } } return array(); } /** * Implements hook_permission(). */ function ultimate_cron_permission() { return array( 'administer ultimate cron' => array( 'title' => t('Administer Ultimate Cron'), 'description' => t('Lets you configure everything in Ultimate Cron') ) ); } /** * The cron handler takes over the normal Drupal cron handler * and runs the normal hook_cron() plus the hook_cronapi(). * * @param boolean $return * return to caller if TRUE, otherwise exit(). */ function ultimate_cron_cron_run($return = FALSE) { if (variable_get('install_task', FALSE) != 'done') { return; } // Be other cron module friendly if (_ultimate_cron_incompatible_modules()) { return; } // If run from core cron through CLI then don't do anything (drush core-cron) if (!$return && drupal_is_cli()) { return; } // Acquire lock if (!lock_acquire('cron', 240.0)) { drupal_set_message(t('Ultimate Cron launcher already running'), 'error'); return; } $msc = variable_get('ultimate_cron_simultaneous_connections', ULTIMATE_CRON_SIMULTANEOUS_CONNECTIONS); // Get list of cron hooks. $hooks = ultimate_cron_get_hooks(); // Get schedule. $schedule = ultimate_cron_get_schedule($hooks); drupal_set_message(t('%jobs jobs scheduled for launch', array('%jobs' => count($schedule)))); // Start the jobs. Keep launching jobs until X seconds into the request. @set_time_limit(120); $time = time(); $expire = $time + variable_get('ultimate_cron_launch_window', ULTIMATE_CRON_LAUNCH_WINDOW); $running = array(); $launched = 0; $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); // Try to launch jobs within the given time frame while (!empty($schedule) && time() < $expire) { $running_processes = db_select('background_process', 'bp') ->fields('bp', array('handle')) ->condition('bp.handle', $handle_prefix . '%', 'LIKE') ->countQuery() ->execute() ->fetchField(); // Launch jobs. reset($schedule); while ((list($name, $hook) = each($schedule)) && time() < $expire) { // Congestion protection if (empty($hook['override_congestion_protection']) && $running_processes >= $msc) { continue; } if (empty($hook['force_run']) && !ultimate_cron_hook_should_run($hook)) { unset($schedule[$name]); continue; } $result = ultimate_cron_run_hook($name, $hook); // Handle errors. if ($result) { $handle = $handle_prefix . $name; $running[$handle] = $result; unset($schedule[$name]); $launched++; $running_processes++; } else { if ($result === FALSE) { // Could not get lock, skip job. unset($schedule[$name]); } else { // Failed to start, retry next time. watchdog('ultimate_cron', "Error starting $name", array(), WATCHDOG_WARNING); } } } // Jobs running ... check for start if ($running) { $result = db_query("SELECT p.name FROM {progress} p WHERE p.name IN (:running)", array(':running' => array_keys($running))); while ($handle = $result->fetchObject()) { fclose($running[$handle->name]); unset($running[$handle->name]); } } sleep(1); } // Close all jobs left if ($running) { foreach (array_keys($running) as $handle) { fclose($running[$handle]); unset($running[$handle]); } } // Update drupals cron timestamp, but don't clear the cache for all variables! $name = 'cron_last'; $value = time(); global $conf; db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute(); $conf[$name] = $value; drupal_set_message(t('%jobs jobs launched', array('%jobs' => $launched))); if (count($schedule)) { drupal_set_message(t('%jobs jobs failed to launch within %seconds seconds', array( '%jobs' => count($schedule), '%seconds' => variable_get('ultimate_cron_launch_window', ULTIMATE_CRON_LAUNCH_WINDOW) )), empty($schedule) ? 'status' : 'error'); watchdog('ultimate_cron', '%jobs jobs failed to launch within %seconds seconds', array( '%jobs' => count($schedule), '%seconds' => variable_get('ultimate_cron_launch_window', ULTIMATE_CRON_LAUNCH_WINDOW) ), WATCHDOG_WARNING); } // Release the lock lock_release('cron'); // And we're done ... if ($return) { return empty($schedule); } else { exit; } } /** * Implements hook_cronapi(). */ function ultimate_cron_cronapi($op, $job = NULL, $hook = NULL) { // Grab the defined cron queues. static $queues = NULL; if (!isset($queues)) { $queues = module_invoke_all('cron_queue_info'); drupal_alter('cron_queue_info', $queues); } switch ($op) { case 'list': $jobs['ultimate_cron_cleanup_log'] = t('Cleanup log entries'); foreach ($queues as $queue_name => $info) { $jobs['ultimate_cron_queue_' . $queue_name] = t('Queue: %name', array('%name' => $queue_name)); } return $jobs; case 'rule': $queue_name = preg_replace('/^ultimate_cron_queue_/', '', $job); if ($queue_name === $job) { return; } return '* * * * *'; case 'execute': $queue_name = preg_replace('/^ultimate_cron_queue_/', '', $job); if ($queue_name === $job) { return; } $polling_latency = variable_get('ultimate_cron_queue_polling_latency', ULTIMATE_CRON_QUEUE_POLLING_LATENCY); $info = $queues[$queue_name]; $function = $info['worker callback']; $end = time() + (isset($info['time']) ? $info['time'] : 15); $queue = DrupalQueue::get($queue_name); $items = 0; while (time() < $end) { $lease_time = isset($hook['settings']['queue_lease_time']) ? $hook['settings']['queue_lease_time'] : variable_get('ultimate_cron_queue_lease_time', ULTIMATE_CRON_QUEUE_LEASE_TIME); $item = $queue->claimItem($lease_time); if (!$item) { if (is_numeric($polling_latency)) { usleep($polling_latency * 1000); continue; } else { break; } } try { $function($item->data); $queue->deleteItem($item); $items++; } catch (Exception $e) { // Just continue ... watchdog('ultimate_cron', "Queue item %item_id failed with message %message", array( '%item_id' => $item->item_id, '%message' => $e->getMessage() ), WATCHDOG_ERROR); } } drupal_set_message(t('Processed @items items', array('@items' => $items))); if (is_numeric($polling_latency)) { $settings = ultimate_cron_get_settings($job); $hook['settings'] = $settings + $hook['settings']; if (ultimate_cron_hook_should_run($hook)) { background_process_keepalive(); } } return; } } /** * Implements hook_progress_message_alter(). */ function ultimate_cron_progress_message_alter(&$message) { $pseudo_process = new stdClass(); $pseudo_process->handle = $message->data->progress->name; $pseudo_process->start_stamp = $message->data->progress->start_stamp; $pseudo_process->progress = $message->data->progress->progress; $pseudo_process->exec_status = 2; $message->data->background_process = $pseudo_process; return ultimate_cron_background_process_message_alter($message); } /** * Implements hook_background_process_message_alter(). */ function ultimate_cron_background_process_message_alter(&$message) { $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); // Only alter Ultimate Cron background process messages if (substr($message->data->background_process->handle, 0, 3) !== $handle_prefix) { return; } $message->data->ultimate_cron['start_stamp'] = format_date((int)$message->data->background_process->start_stamp, 'custom', 'Y-m-d H:i:s'); if ($message->data->background_process->progress >= 0) { $message->data->ultimate_cron['progress'] = gmdate('H:i:s', (int)(microtime(TRUE) - $message->data->background_process->start_stamp)) . sprintf(" (%3d%%)", $message->data->background_process->progress * 100); } $message->data->ultimate_cron['unlockURL'] = url('background-process/unlock/' . $message->data->background_process->handle, array('query' => array('destination' => 'admin/config/system/cron'))); } /** * Implements hook_theme_registry_alter(). */ function ultimate_cron_theme_registry_alter(&$theme_registry) { $theme_registry['page']['theme paths'][] = drupal_get_path('module', 'ultimate_cron') . '/templates'; } /** * Implements hook_watchdog(). */ function ultimate_cron_watchdog($log = array()) { $record = &drupal_static('ultimate_cron_record', FALSE); if ($record) { $log['variables'] = is_array($log['variables']) ? $log['variables'] : array(); ultimate_cron_record_log(t($log['message'], $log['variables']), FALSE, $log['severity']); } } /** * Implements hook_init(). */ function ultimate_cron_init() { // No need for hocus pocus and poorman until site is installed. if (variable_get('install_task') != 'done') { return; } $name = 'cron_last'; if ($value = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => $name))->fetchField()) { $value = unserialize($value); } global $conf; $conf['cron_last'] = $value; ultimate_cron_trigger_poorman(); } /** * Launch poorman cron if it's time to do so. */ function ultimate_cron_trigger_poorman() { // Launch poormans cron if applicable if (variable_get('ultimate_cron_poorman', ULTIMATE_CRON_POORMAN) && !_ultimate_cron_incompatible_modules()) { $last = variable_get('cron_last', 0); $last = floor($last / 60) * 60; $time = time(); $time = floor($time / 60) * 60; // Don't attempt, if already run within last minute if ($last < $time) { ultimate_cron_launch_poorman(); } } } /** * Launch the poormans cron background process. */ function ultimate_cron_launch_poorman() { $handle = 'ultimate_cron_poorman'; if ($process = background_process_get_process($handle)) { if ($process->start + 120 < time()) { // Must have timed out or something ... remove it! if (background_process_remove_process($handle, $process->start)) { $process = NULL; } } } if (!$process) { $process = new BackgroundProcess($handle); // Because anyone can launch poormans cron, run it as anonymous $process->uid = 0; $process->service_host = 'ultimate_cron_poorman'; $result = $process->start('_ultimate_cron_poorman'); } } /** * Implements hook_background_process_shutdown(). * * Shutdown handler for cronjobs. */ function ultimate_cron_background_process_shutdown($process, $shutdown_msg = NULL) { $args = func_get_args(); $record = drupal_static('ultimate_cron_record', FALSE); $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); $name = preg_replace('/^' . $handle_prefix . '/', '', $process->handle); if (!empty($name) && $name != $process->handle) { static $has_run = array(); if (!empty($has_run[$name])) { return; } $has_run[$name] = TRUE; // Record end time $end = microtime(TRUE); if ($record) { // Get drupal messages $messages = drupal_get_messages(NULL, TRUE); $messages['status'] = empty($messages['status']) ? array() : $messages['status']; $messages['warning'] = empty($messages['warning']) ? array() : $messages['warning']; $messages['error'] = empty($messages['error']) ? array() : $messages['error']; foreach ($messages['status'] as $message) { ultimate_cron_record_log($message); } foreach ($messages['warning'] as $message) { ultimate_cron_record_log($message, FALSE, WATCHDOG_WARNING); } foreach ($messages['error'] as $message) { ultimate_cron_record_log($message, FALSE, WATCHDOG_ERROR); } // Get error messages $error = error_get_last(); if ($error) { $message = $error['message'] . ' (line ' . $error['line'] . ' of ' . $error['file'] . ').' . "\n"; $severity = WATCHDOG_INFO; if ($error['type'] && (E_NOTICE || E_USER_NOTICE || E_USER_WARNING)) { $severity = WATCHDOG_NOTICE; } if ($error['type'] && (E_WARNING || E_CORE_WARNING || E_USER_WARNING)) { $severity = WATCHDOG_WARNING; } if ($error['type'] && (E_ERROR || E_CORE_ERROR || E_USER_ERROR || E_RECOVERABLE_ERROR)) { $severity = WATCHDOG_ERROR; } ultimate_cron_record_log($message, FALSE, $severity); } } if ($shutdown_msg) { ultimate_cron_record_log($shutdown_msg, FALSE, WATCHDOG_ERROR); } $log = drupal_static('ultimate_cron_record_log', array('msg' => '', 'severity' => -1)); $severity = $log['severity']; $msg = $log['msg']; $result = $severity < 0 || $severity >= WATCHDOG_INFO ? 1: 0; // log results here ... $object = (object)array( 'name' => $name, 'start_stamp' => $process->start, 'end_stamp' => $end, 'service_host' => $process->service_host, 'exec_status' => $result, 'severity' => $severity, 'msg' => trim($msg), ); $old_db = db_set_active('background_process'); drupal_write_record('ultimate_cron_log', $object); db_set_active($old_db); $object->formatted['start_stamp'] = format_date((int)$object->start_stamp, 'custom', 'Y-m-d H:i:s'); $object->formatted['end_stamp'] = format_date((int)$object->end_stamp, 'custom', 'Y-m-d H:i:s'); $object->formatted['duration'] = gmdate('H:i:s', (int)($object->end_stamp - $object->start_stamp)); $object->formatted['severity'] = $object->severity < 0 ? 'success' : ($object->severity >= WATCHDOG_NOTICE ? 'info' : ($object->severity >= WATCHDOG_WARNING ? 'warning' : 'error')); $object->formatted['executeURL'] = url('admin/ultimate-cron/service/start/' . $object->name, array('query' => array('destination' => 'admin/config/system/cron'))); $object->formatted['msg'] = strip_tags(html_entity_decode($object->msg, ENT_QUOTES)); if (module_exists('nodejs')) { $message = (object) array( 'channel' => 'ultimate_cron', 'data' => (object) array( 'action' => 'log', 'log' => $object, ), 'callback' => 'nodejsUltimateCron', ); nodejs_send_content_channel_message($message); } } } // ---------- FIXUPS FOR CORE ---------- /** * Implements hook_menu_alter(). * * Steal the run-cron, so when you "run cron manually" from the status-reports * page the ultimate_cron cron handler is run. */ function ultimate_cron_menu_alter(&$items) { if (_ultimate_cron_incompatible_modules()) { return; } $items['admin/config/system/cron'] = array( 'title' => 'Cron', 'description' => 'View and manage cron table', 'page callback' => 'ultimate_cron_view_page', 'access arguments' => array('administer ultimate cron'), 'module' => 'ultimate_cron', 'file' => 'ultimate_cron.admin.inc', ); $items['admin/config/system/cron/overview'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $steal = &$items['admin/reports/status/run-cron']; $steal['page callback'] = 'ultimate_cron_run_cron'; $steal['page arguments'] = array(); $steal['module'] = 'ultimate_cron'; $steal['file'] = 'ultimate_cron.admin.inc'; } /** * Implements hook_cron_alter(). * Add better description to core modules. */ function ultimate_cron_cron_alter(&$hooks) { $update['dblog_cron']['description'] = t('Remove expired log messages and flood control events'); $update['field_cron']['description'] = t('Purges deleted Field API data'); $update['filter_cron']['description'] = t('Expire outdated filter cache entries'); $update['node_cron']['description'] = t('Mark old nodes as read'); $update['search_cron']['description'] = t('Update indexes'); $update['system_cron']['description'] = t('Cleanup (batch, flood, temp-files, etc.)'); $update['aggregator_cron']['description'] = t('Refresh feeds'); $update['openid_cron']['description'] = t('Remove expired nonces from the database'); $update['ping_cron']['description'] = t('Notify remote sites'); $update['poll_cron']['description'] = t('Close expired polls'); $update['statistics_cron']['description'] = t('Reset counts and clean up'); $update['trigger_cron']['description'] = t('Run actions for cron triggers'); $update['tracker_cron']['description'] = t('Update tracker index'); $update['update_cron']['description'] = t('Check system for updates'); $update['ultimate_cron_cleanup_log']['configure'] = 'admin/config/system/cron/settings'; $update['dblog_cron']['configure'] = 'admin/config/development/logging'; foreach ($update as $name => $data) { if (isset($hooks[$name])) { foreach ($data as $key => $value) { $hooks[$name][$key] = $value; } } } } // ---------- HELPER FUNCTIONS ---------- /** * Set current hook being processed * * @param $hook * @return * Current hook */ function ultimate_cron_set_current_hook($hook = NULL) { static $current_hook; if ($hook) { $current_hook = $hook; } return $current_hook; } /** * Get current hook being processed * * @return * Current hook */ function ultimate_cron_get_current_hook() { return ultimate_cron_set_current_hook(); } /** * The actual poorman function * @return type */ function _ultimate_cron_poorman() { if (!variable_get('ultimate_cron_poorman', ULTIMATE_CRON_POORMAN) || _ultimate_cron_incompatible_modules()) { return; } // Restart when done background_process_keepalive(); // Derive current minute $time = time(); $time = floor($time / 60) * 60; // Run the cron ultimate_cron_cron_run(TRUE); // Wait until end of "current" minute $wait = $time + 60 - time(); if ($wait > 0 && $wait <= 60) { sleep($wait); } } /** * Clean up log entries. */ function ultimate_cron_cleanup_log() { do { $result = db_query_range("SELECT lid FROM {ultimate_cron_log} WHERE start_stamp < :start", 0, 1000, array(':start' => time() - variable_get('ultimate_cron_cleanup_log', ULTIMATE_CRON_CLEANUP_LOG))); $lids = array(); while ($row = $result->fetchObject()) { $lids[] = $row->lid; } if (!empty($lids)) { db_query("DELETE FROM {ultimate_cron_log} WHERE lid IN (:lids)", array(':lids' => $lids)); } } while (!empty($lids)); } /** * Run a cron hook. * Launches the cron job in a background process * * @param $name * @param $hook * @return mixed * Connections file handle on success. */ function ultimate_cron_run_hook($name, $hook) { // Run the job in background $result = NULL; $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); $handle = $handle_prefix . $name; $process = new BackgroundProcess($handle); // Always run cron job as anonymous user $process->uid = 0; // Determine service group $process->service_group = empty($hook['settings']['service_group']) ? variable_get('ultimate_cron_service_group', ULTIMATE_CRON_SERVICE_GROUP) : $hook['settings']['service_group']; $hook['timestamp'] = time(); unset($hook['log']['msg']); $result = $process->start('_ultimate_cron_run_hook', array($name, $hook)); return $result ? $process->connection : $result; } function ultimate_cron_run_hook_cli($name, $hook) { // Run the job in background $result = NULL; $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); $handle = $handle_prefix . $name; $process = new BackgroundProcess($handle); // Always run cron job as anonymous user $process->uid = 0; $hook['timestamp'] = time(); if ($process->lock()) { try { $process_obj = background_process_get_process($handle); $process->start = $process_obj->start; if (function_exists('background_process_update_status')) { background_process_update_status($handle, BACKGROUND_PROCESS_STATUS_RUNNING); } else { db_update('background_process') ->fields(array( 'exec_status' => BACKGROUND_PROCESS_STATUS_RUNNING, )) ->condition('handle', $handle) ->execute(); } $old_handle = background_process_current_handle(); background_process_current_handle($handle); _ultimate_cron_run_hook($name, $hook); module_invoke_all('background_process_shutdown', $process); background_process_current_handle($old_handle); background_process_remove_process($handle, $process->start); return TRUE; } catch (Exception $e) { module_invoke_all('background_process_shutdown', $process, (string)$e); return NULL; } } return FALSE; } /** * This is the function that is launched into a background process. * It runs the cron job and does housekeeping, pre/post execute hooks, etc. * * @param $module * Module containing function. * @param $name * Function to call. * @return boolean * TRUE on success, FALSE on failure. */ function _ultimate_cron_run_hook($name, $hook) { @set_time_limit(variable_get('ultimate_cron_max_execution_time', ULTIMATE_CRON_MAX_EXECUTION_TIME)); drupal_save_session(FALSE); // Load current process $process = background_process_get_process(background_process_current_handle()); $record = &drupal_static('ultimate_cron_record', FALSE); $record = TRUE; ultimate_cron_record_log(NULL, TRUE); // Load log if not present if (!isset($hook['log'])) { $hook['log'] = ultimate_cron_get_log($name); } $time = time(); if (empty($hook['skip_catch_up']) && !ultimate_cron_hook_should_run($hook)) { // Hook started too late! watchdog('ultimate_cron', '%function skipped. Invoked at %invoke, but did not start until %start', array( '%function' => $name, '%invoke' => format_date($hook['timestamp'], 'custom', 'Y-m-d H:i:s'), '%start' => format_date($time, 'custom', 'Y-m-d H:i:s'), ), WATCHDOG_ERROR); ultimate_cron_background_process_shutdown($process, NULL); return FALSE; } // Let other modules do stuff before execution, if they need to. module_invoke_all('cron_pre_execute', $name, $hook); module_invoke_all('cron_pre_execute_' . $name, $hook); if (!empty($hook['file'])) { include_once $hook['file']; } $callback = $hook['callback']; ultimate_cron_set_current_hook($hook); if (is_callable($callback)) { call_user_func($callback); } else { module_invoke($hook['module'], 'cronapi', 'execute', $name, $hook); } ultimate_cron_background_process_shutdown($process, NULL); // Let other modules do stuff before execution, if they need to. module_invoke_all('cron_post_execute', $name, $hook); module_invoke_all('cron_post_execute_' . $name, $hook); return TRUE; } /** * Get a list of functions that should be run now. * * @param $hooks * Array of cron hooks to check. * @return array * Functions to run now. */ function ultimate_cron_get_schedule($hooks) { // Create list of scheduled functions $schedule = array(); foreach ($hooks as $name => &$hook) { ultimate_cron_load_hook_data($hook); // Store last run in hook for sorting purposes $last_run = isset($hook['log']['start']) ? $hook['log']['start'] : 0; $hook['last_run'] = $last_run; if (ultimate_cron_hook_should_run($hook)) { $schedule[$name] = $hook; } } // Sort by last run time uasort($schedule, '_ultimate_cron_sort_schedule'); // Allow other to manipulate the schedule drupal_alter('cron_schedule', $schedule); return $schedule; } /** * Populate hook array with settings and log data * * @param type $hook */ function ultimate_cron_load_hook_data(&$hook) { // Get settings $hook['settings'] = ultimate_cron_get_settings($hook['function']) + $hook['settings']; // Get log, used for checking last start time $hook['log'] = ultimate_cron_get_log($hook['function']); } /** * Check if a hook should be run now. * * @param array $hook * @return boolean */ function ultimate_cron_hook_should_run($hook) { // Is it enabled? if (empty($hook['settings']['enabled'])) { return FALSE; } $last_run = isset($hook['log']['start']) ? $hook['log']['start'] : 0; return ultimate_cron_should_run($hook['settings']['rules'], $last_run, time(), $hook['settings']['catch_up'], $hook['delta']); } /** * Sort callback for ordering schedule. * * @param type $a * @param type $b * @return type */ function _ultimate_cron_sort_schedule($a, $b) { return $a['last_run'] == $b['last_run'] ? 0 : ($a['last_run'] < $b['last_run'] ? -1 : 1); } /** * Get cron hooks available. * * @return array * List of modules. */ function ultimate_cron_get_hooks() { static $hooks = NULL; if (isset($hooks)) { return $hooks; } $hooks = array(); $delta = 0; // Generate list of hooks $modules = module_list(); foreach ($modules as $module) { $file = drupal_get_path('module', $module) . '/' . $module . '.info'; $info = drupal_parse_info_file($file); foreach (ultimate_cron_easy_hooks() as $hook => $description) { if (module_hook($module, $hook)) { $name = $module . '_' . $hook; $hooks[$name]['description'] = $description; $hooks[$name]['module'] = $module; $hooks[$name]['configure'] = isset($info['configure']) ? $info['configure'] : ''; $hooks[$name]['settings'] = ultimate_cron_get_default_settings($module, $name, ultimate_cron_easy_hooks_rule($hook)); } } if ($cronapi = module_invoke($module, 'cronapi', 'list')) { foreach ($cronapi as $name => $description) { $hooks[$name]['description'] = $description; $hooks[$name]['module'] = $module; $hooks[$name]['settings'] = ultimate_cron_get_default_settings($module, $name, ultimate_cron_easy_hooks_rule('cron')); $hooks[$name]['configure'] = module_invoke($module, 'cronapi', 'configure', $name); } } } foreach ($hooks as $name => &$hook) { $hook['callback'] = $hook['function'] = $name; $hook['background_process'] = array(); $hook['delta'] = $delta++; } // Remove ourselves from the list unset($hooks['ultimate_cron_cron']); // Allow other to manipulate the hook list drupal_alter('cron', $hooks); return $hooks; } /** * Get default settings for job. * * @param type $module * @param type $name * @return array */ function ultimate_cron_get_default_settings($module, $name, $default_rule) { $conf = module_invoke($module, 'cronapi', 'settings', $name); if (!is_array($conf)) { $conf = array(); } $rule = module_invoke($module, 'cronapi', 'rule', $name); if (empty($conf['rules']) && !empty($rule)) { $conf['rules'] = is_array($rule) ? $rule : array($rule); } $conf += _ultimate_cron_default_settings($default_rule); return $conf; } /** * Get settings for a function. * * @param $name * @return array * Settings for function */ function ultimate_cron_get_settings($name) { $settings = array(); if (module_exists('ctools')) { ctools_include('export'); $function = ctools_export_crud_load('ultimate_cron', $name); if ($function) { $settings = (array) $function->settings; } } else { $function = db_query("SELECT settings FROM {ultimate_cron} WHERE name = :name", array(':name' => $name))->fetchObject(); if (!empty($function) && !empty($function->settings)) { $settings = (array) unserialize($function->settings); } } if (empty($settings['catch_up'])) { unset($settings['catch_up']); } return $settings; } /** * CRUD save. Also used for ctools integration. * @param object $object * object to be saved ->name containing unique machine name. * @return boolean * result of query. */ function ultimate_cron_crud_save($object) { return db_merge('ultimate_cron') ->key(array('name' => $object->name)) ->fields(array( 'settings' => serialize($object->settings), )) ->execute(); } /** * Set settings for a function. * * @param $name * Function to set settings for. * @param $settings * Settings data * @return boolean * TRUE on success, FALSE on failure. */ function ultimate_cron_set_settings($name, $settings) { if (module_exists('ctools')) { ctools_include('export'); $function = ctools_export_crud_new('ultimate_cron'); $function->name = $name; $function->settings = $settings; return ctools_export_crud_save('ultimate_cron', $function); } else { $function = new stdClass(); $function->name = $name; $function->settings = $settings; return ultimate_cron_crud_save($function); } } /** * Get latest log line for a function. * * @param $name * Function to get latest log line for, * @return object * Log line. */ function ultimate_cron_get_log($name) { $log = db_query_range(" SELECT l.* FROM {ultimate_cron_log} l WHERE l.name = :name ORDER BY l.start_stamp DESC", 0, 1, array(':name' => $name)) ->fetchAssoc(); if ($log) { $log['start'] = $log['start_stamp']; $log['status'] = $log['exec_status']; $log['end'] = $log['end_stamp']; } return $log; } /** * Store watchdog error messages for later use. * * @staticvar string $log * @param $msg * Message to record. * @param $reset * Reset recorded message. * @return string * Message recorded. */ function ultimate_cron_record_log($msg = NULL, $reset = FALSE, $severity = -1) { $log = &drupal_static('ultimate_cron_record_log', array()); if ($reset) { $log = array(); } $log += array('msg' => '', 'severity' => -1); if ($msg) { $log['msg'] .= "$msg\n"; } $log['severity'] = $log['severity'] < 0 || ($severity >= 0 && $severity < $log['severity']) ? $severity : $log['severity']; return $log['msg']; } /** * Check if rule is valid. * * @param $rule * rule to validate. * @return * TRUE if valid, FALSE if not. */ function ultimate_cron_validate_rule($rule) { require_once 'CronRule.class.php'; $cron = new CronRule($rule); if (!$cron->isValid()) { return FALSE; } else { return TRUE; } } /** * Check if rule is scheduled to run at a given time. * * @param $rules * rules to validate. * @param $last_run * last time the rule was run. * @param $now * time of validation, set to NULL for now. * @param $catch_up * run if we missed our time window? * @return boolean * TRUE if rule is scheduled to run, FALSE if not. */ function ultimate_cron_should_run($rules, $last_run, $now = NULL, $catch_up = 0, $offset = 0) { $now = is_null($now) ? time() : $now; require_once 'CronRule.class.php'; $cron = new CronRule(); foreach ($rules as $rule) { $cron->rule = $rule; $cron->offset = $offset; $last_ran = $cron->getLastRan($now); if ($last_ran > $last_run && $last_ran >= $now - $catch_up) { return TRUE; } } return FALSE; } /** * Get a list of the "easy-hooks". * * @return array * hooks (hook_name => hook_description). */ function ultimate_cron_easy_hooks() { return array( 'cron' => 'Default cron handler', 'hourly' => 'Hourly', 'daily' => 'Daily', 'weekly' => 'Weekly', 'monthly' => 'Monthly', 'yearly' => 'Yearly' ); } /** * Get rule(s) for easy hook(s) * * @param $hook * Hook to get rule for (optional). * @return mixed * Rule for $hook if specified, otherwise all rules for all easy hooks. */ function ultimate_cron_easy_hooks_rule($hook = NULL) { $rules = array( 'cron' => variable_get('ultimate_cron_rule', ULTIMATE_CRON_RULE), 'hourly' => ULTIMATE_CRON_HOURLY_RULE, 'daily' => ULTIMATE_CRON_DAILY_RULE, 'weekly' => ULTIMATE_CRON_WEEKLY_RULE, 'monthly' => ULTIMATE_CRON_MONTHLY_RULE, 'yearly' => ULTIMATE_CRON_YEARLY_RULE, ); return isset($rules[$hook]) ? $rules[$hook] : variable_get('ultimate_cron_rule', ULTIMATE_CRON_RULE); } /** * Get module name * @param $module * @return string * Name of module */ function ultimate_cron_module_name($module) { $file = drupal_get_path('module', $module) . '/' . $module . '.info'; $info = drupal_parse_info_file($file); return $info['name'] ? $info['name'] : $module; } /** * Load all cronjob settings and processes. * * @return array * Array of cronjobs and their data. */ function _ultimate_cron_preload_cron_data() { $handle_prefix = variable_get('ultimate_cron_handle_prefix', ULTIMATE_CRON_HANDLE_PREFIX); if (module_exists('ctools')) { ctools_include('export'); $functions = ctools_export_crud_load_all('ultimate_cron'); } else { $functions = db_select('ultimate_cron', 'u') ->fields('u', array('name', 'settings')) ->execute() ->fetchAllAssoc('name', PDO::FETCH_ASSOC); foreach ($functions as &$function) { $function = (object)$function; if (!empty($function->settings)) { $function->settings = unserialize($function->settings); } } } $hooks = ultimate_cron_get_hooks(); if (function_exists('background_process_update_status')) { $query = db_select('background_process', 'b') ->fields('b', array('handle', 'service_host')) ->condition('handle', $handle_prefix . '%', 'LIKE'); $query->addField('b', 'start_stamp', 'start'); $query->addField('b', 'exec_status', 'status'); $processes = $query->execute()->fetchAllAssoc('handle', PDO::FETCH_ASSOC); } else { $processes = db_select('background_process', 'b') ->fields('b', array('handle', 'service_host', 'start', 'status')) ->condition('handle', $handle_prefix . '%', 'LIKE') ->execute() ->fetchAllAssoc('handle', PDO::FETCH_ASSOC); } $data = array(); foreach ($hooks as $name => $hook) { $settings = empty($functions[$name]->settings) ? array() : $functions[$name]->settings; $settings += $hook['settings']; $handle = $handle_prefix . $name; $data[$name] = array( 'settings' => $settings, 'background_process' => empty($processes[$handle]) ? NULL : (object)$processes[$handle], ); } return $data; } /** * Return a list of modules that are incompatible with Ultimate Cron */ function _ultimate_cron_incompatible_modules() { static $modules = NULL; if (isset($modules)) { return $modules; } $modules = array(); $candidates = array('parallel_cron'); foreach ($candidates as $module) { if (module_exists($module)) { $modules[$module] = ultimate_cron_module_name($module); } } return $modules; } /** * Get service hosts defined in the system. */ function ultimate_cron_get_service_groups() { if (function_exists('background_process_get_service_groups')) { return background_process_get_service_groups(); } // Fallback for setups that havent upgraded Background Process. // We have this to avoid upgrade dependencies or majer version bump. $service_groups = variable_get('background_process_service_groups', array()); $service_groups += array( 'default' => array( 'hosts' => array(variable_get('background_process_default_service_host', 'default')), ), ); foreach ($service_groups as &$service_group) { $service_group += array( 'method' => 'background_process_service_group_random' ); } return $service_groups; } /** * Smelly code; $default_rule determines if we should populate ... */ function _ultimate_cron_default_settings($default_rule = NULL) { return array( 'enabled' => TRUE, 'rules' => $default_rule ? array($default_rule) : array(), 'catch_up' => $default_rule ? variable_get('ultimate_cron_catch_up', ULTIMATE_CRON_CATCH_UP) : '', 'queue_lease_time' => '', ); }