'_ultimate_cron_out_of_memory_protection', 'arguments' => array())); // Reset internal array pointer just in case ... reset($callbacks); } } /** * Registers a function for execution on shutdown. * * Wrapper for register_shutdown_function() that catches thrown exceptions to * avoid "Exception thrown without a stack frame in Unknown". * * This is a duplicate of the built-in functionality in Drupal, however we * need to perform our tasks before that. * * @param callback $callback * The shutdown function to register. * @param ... * Additional arguments to pass to the shutdown function. * * @see register_shutdown_function() * * @ingroup php_wrappers */ function ultimate_cron_register_shutdown_function($callback) { $args = func_get_args(); array_shift($args); $GLOBALS['ultimate_cron_shutdown_functions'][] = array( 'callback' => $callback, 'arguments' => $args, ); } // ---------- CTOOLS INTEGRATION ---------- /** * Implements hook_ctools_plugin_api(). */ function ultimate_cron_ctools_plugin_api($module, $api) { if ($module == 'ultimate_cron' && $api == 'plugins') { return array('version' => 3); } if ($module == 'ultimate_cron' && $api == 'ultimate_cron') { return array('version' => 3); } } /** * Implements hook_ctools_plugin_directory(). * * This module implements plugins for cTools and Ultimate Cron. */ function ultimate_cron_ctools_plugin_directory($module, $type) { // Safety: go away if CTools is not at an appropriate version. if (!module_invoke('ctools', 'api_version', '1.7')) { return; } $supported = array( 'ctools' => array( 'export_ui' => 'export_ui', ), 'ultimate_cron' => array( 'settings' => 'settings', 'scheduler' => 'scheduler', 'launcher' => 'launcher', 'logger' => 'logger', ), ); if (isset($supported[$module][$type])) { return "plugins/$module/" . $supported[$module][$type]; } } /** * Implements hook_ctools_plugin_type(). * * Ultimate Cron provides for plugins. * * Settings : Attaches settings to a cron job * Scheduler : Determines when a job should run. * Launcher : Responsible for launching/running the job. * Logger : The backend for a jobs logs. */ function ultimate_cron_ctools_plugin_type() { return array( 'settings' => array( 'use hooks' => FALSE, 'defaults' => array( 'static' => array( 'default plugin' => '', 'title singular' => t('settings'), 'title plural' => t('settings'), 'title singular proper' => t('Settings'), 'title plural proper' => t('Settings'), 'class' => 'UltimateCronSettings', 'multiple' => TRUE, ), ), 'classes' => array('handler'), 'cache' => TRUE, ), 'scheduler' => array( 'use hooks' => FALSE, 'defaults' => array( 'static' => array( 'default plugin' => 'simple', 'title singular' => t('scheduler'), 'title plural' => t('schedulers'), 'title singular proper' => t('Scheduler'), 'title plural proper' => t('Schedulers'), 'class' => 'UltimateCronScheduler', ), ), 'classes' => array('handler'), 'cache' => TRUE, ), 'launcher' => array( 'use hooks' => FALSE, 'defaults' => array( 'static' => array( 'default plugin' => 'serial', 'title singular' => t('launcher'), 'title plural' => t('launchers'), 'title singular proper' => t('Launcher'), 'title plural proper' => t('Launchers'), 'class' => 'UltimateCronLauncher', ), ), 'classes' => array('handler'), 'cache' => TRUE, ), 'logger' => array( 'use hooks' => FALSE, 'defaults' => array( 'static' => array( 'default plugin' => 'database', 'title singular' => t('logger'), 'title plural' => t('loggers'), 'title singular proper' => t('Logger'), 'title plural proper' => t('Loggers'), 'class' => 'UltimateCronLogger', ), ), 'classes' => array('handler'), 'cache' => TRUE, ), ); } /** * Get plugin instance. * * @param string $type * Type of the plugin (settings, scheduler, launcher, logger). * * @return object * The instance of the plugin (singleton). */ function ultimate_cron_ctools_plugin_instance($type, $plugin) { static $cache; if (!isset($cache[$plugin['name']])) { $cache[$plugin['name']] = ctools_plugin_get_class($plugin, 'handler'); } if (isset($cache[$plugin['name']]) && class_exists($cache[$plugin['name']])) { $class = $cache[$plugin['name']]; return $class::factory($class, $type, $plugin); } else { return NULL; } } /** * Require callback for plugins. * * @param string $type * Type of the plugin (settings, scheduler, launcher, logger). * @param string $name * Name of the plugin (general, queue, serial, database, etc.). * * @return object * The instance of the plugin (singleton). * * @throws \RuntimeException * Throws a \RuntimeException if the plugin could not be loaded. */ function _ultimate_cron_plugin_require($type, $name) { $object = _ultimate_cron_plugin_load($type, $name); if (!is_object($object)) { throw new \RuntimeException(t('Ultimate Cron failed to require ctools "!type" plugin "!name"', array('!type' => $type, '!name' => $name))); } return $object; } /** * Load callback for plugins. * * @param string $type * Type of the plugin (settings, scheduler, launcher, logger). * @param string $name * Name of the plugin (general, queue, serial, database, etc.). * * @return object * The instance of the plugin (singleton). */ function _ultimate_cron_plugin_load($type, $name) { $cache = &drupal_static('ultimate_cron_plugin_load_all', array()); if (!isset($cache[$type][$name])) { // Will populate the $cache static variable if a plugin is loaded. _ultimate_cron_plugin_load_all($type); } if (isset($cache[$type][$name])) { return $cache[$type][$name]; } else { return NULL; } } /** * Load all callback for plugins. * * @param string $type * Type of the plugin (settings, scheduler, launcher, logger). * * @return array * The instances of the plugin type (singletons). */ function _ultimate_cron_plugin_load_all($type, $reset = FALSE) { $cache = &drupal_static('ultimate_cron_plugin_load_all', array()); if (!$reset && isset($cache[$type])) { return $cache[$type]; } if ($reset) { drupal_static_reset('ctools_plugins'); drupal_static_reset('ctools_plugin_setup'); } ctools_include('plugins'); $plugin_infos = ctools_get_plugins('ultimate_cron', $type); $plugins = array(); foreach ($plugin_infos as $name => $plugin) { if ($object = ultimate_cron_ctools_plugin_instance($name, $plugin)) { $plugins[$name] = $object; } } $cache[$type] = $plugins; return $cache[$type]; } /** * Load all jobs available from cTools export. * * This function loads the raw job "entities", and is NOT the function * exposed to cTools export and export ui. * * @param bool $reset * Reset the object cache. * * @return array * UltimateCronJob objects. */ function _ultimate_cron_job_load_all_raw($reset = FALSE) { ctools_include('export'); $table = 'ultimate_cron_job'; if ($reset) { ctools_export_load_object_reset($table); } $class = _ultimate_cron_get_class('job'); $results = ctools_export_load_object($table); foreach ($results as $name => &$result) { $result->disabled = ultimate_cron_job_get_status($name); $result->settings = !empty($result->settings) ? $result->settings : array(); // Make sure the object is of the correcty type. if (!$result instanceOf $class) { $result = $class::factory($result); } } return $results; } /** * CTools Export load callback. * * @param string $name * Name of the job to load. * @param bool $reset * (optional) Reset the ctools export object cache. * * @return UltimateCronJob * The job loaded. */ function _ultimate_cron_job_load($name, $reset = FALSE) { $jobs = _ultimate_cron_job_load_all($reset); return isset($jobs[$name]) ? $jobs[$name] : FALSE; } /** * CTools Export load multiple callback. * * @param array $names * Names of the jobs to load. * @param bool $reset * (optional) Reset the ctools export object cache. * * @return array * Array of UltimateCronJob objects. */ function _ultimate_cron_job_load_multiple($names, $reset = FALSE) { $jobs = array(); foreach (_ultimate_cron_job_load_all($reset) as $name => $job) { if (in_array($name, $names)) { $jobs[$name] = $job; } } return $jobs; } /** * CTools Export load all callback. * * @param bool $reset * (optional) Reset the ctools export object cache. * * @return array * Array of UltimateCronJob objects. */ function _ultimate_cron_job_load_all($reset = FALSE) { static $cache = NULL; if (!$reset && isset($cache)) { return $cache; } $raw_jobs = _ultimate_cron_job_load_all_raw($reset); $jobs = array(); foreach (ultimate_cron_get_hooks($reset) as $name => $hook) { $jobs[$name] = ultimate_cron_prepare_job($name, $hook, isset($raw_jobs[$name]) ? $raw_jobs[$name] : NULL); } $cache = $jobs; UltimateCronPlugin::hook_cron_alter($cache); return $cache; } /** * Prepare a UltimateCronJob object with hook data, etc. * * @param string $name * Name of the job. * @param array $hook * The cron hook data from hook_cronapi(), etc. * @param UltimateCronJob $job * (optional) The job to prepare with the hook data. If no job is given, * a new blank UltimateCronJob object will be used. * * @return UltimateCronJob * The prepared UltimateCronJob object. */ function ultimate_cron_prepare_job($name, $hook, $job = NULL) { $schema = ctools_export_get_schema('ultimate_cron_job'); $export = $schema['export']; if (!$job) { $job = ctools_export_crud_new('ultimate_cron_job'); $job->name = $name; $job->title = $hook['title']; $job->description = $hook['description']; $job->table = 'ultimate_cron_job'; $job->export_type = EXPORT_IN_CODE; $job->{$export['export type string']} = t('Default'); $job->disabled = ultimate_cron_job_get_status($name); if (!isset($job->disabled)) { $job->disabled = !$hook['enabled']; } } else { // If object lives in database, then it is overridden, since we do not // have objects living only in the database. if ($job->export_type & EXPORT_IN_DATABASE) { $job->{$export['export type string']} = t('Overridden'); $job->export_type |= EXPORT_IN_CODE; } } // We do alot of += on arrays. Let's make sure we have an array to begin with. ctools_include('plugins'); $plugin_types = ctools_plugin_get_plugin_type_info(); foreach ($plugin_types['ultimate_cron'] as $plugin_type => $info) { $class = $info['defaults']['static']['class']; if ($class::$multiple) { $plugins = _ultimate_cron_plugin_load_all($plugin_type); foreach ($plugins as $plugin) { if (!isset($job->settings[$plugin_type][$plugin->name])) { $job->settings[$plugin_type][$plugin->name] = array(); } } } else { if (!isset($job->settings[$plugin_type])) { $job->settings[$plugin_type] = array(); } } } $job->hook = $hook; return $job; } /** * CTools Export set status callback. * * Set job status and log it. * * @param mixed $object * Name of job or UltimateCronJob object. * @param bool $status * The status to set (TRUE = disabled). */ function _ultimate_cron_job_set_status($object, $status) { if (!is_object($object)) { $object = _ultimate_cron_job_load($object); } if (empty($object->dont_log)) { $log_entry = $object->startLog(uniqid($object->name, TRUE), 'modification', ULTIMATE_CRON_LOG_TYPE_ADMIN); $log_entry->log($object->name, 'Job @status by ' . $log_entry->formatUser(), array( '@status' => $status ? t('disabled') : t('enabled'), ), WATCHDOG_INFO); $log_entry->finish(); } variable_set('default_ultimate_cron_job_' . $object->name, $status ? TRUE : FALSE); $object->disabled = $status; } /** * CTools Export save callback. * * Save a job and log it. * * @param UltimateCronJob &$object * The UltimateCronJob to save. * * @return bool * Result of drupal_write_record(). */ function _ultimate_cron_job_save(&$object) { $table = 'ultimate_cron_job'; $schema = ctools_export_get_schema($table); $export = $schema['export']; // Objects should have a serial primary key. If not, simply fail to write. if (empty($export['primary key'])) { return FALSE; } $key = $export['primary key']; if ($object->export_type & EXPORT_IN_DATABASE) { // Existing record. $update = array($key); } else { // New record. $update = array(); $object->export_type = EXPORT_IN_DATABASE; } $result = drupal_write_record($table, $object, $update); if (empty($object->dont_log)) { $log = $object->startLog(uniqid($object->name, TRUE), 'modification', ULTIMATE_CRON_LOG_TYPE_ADMIN); $log->log($object->name, 'Job modified by ' . $log->formatUser(), array(), WATCHDOG_INFO); $log->finish(); } return $result; } /** * CTools Export delete callback. * * Revert a job and log it. * * @param mixed $object * Name of job or UltimateCronJob object. */ function _ultimate_cron_job_delete($object) { $table = 'ultimate_cron_job'; $schema = ctools_export_get_schema($table); $export = $schema['export']; // If we were sent an object, get the export key from it. Otherwise // assume we were sent the export key. if (!is_object($object)) { $name = $object; if (!($object = _ultimate_cron_job_load($name))) { throw new Exception(t('Unable to revert unknown job: @name', array( '@name' => $name, ))); } } db_delete($table) ->condition($export['key'], $object->{$export['key']}) ->execute(); if (empty($object->dont_log)) { $log = $object->startLog(uniqid($object->name, TRUE), 'modification', ULTIMATE_CRON_LOG_TYPE_ADMIN); $log->log($object->name, 'Job reverted by ' . $log->formatUser(), array(), WATCHDOG_INFO); $log->finish(); } } /** * Get status "callback". * * @param string $name * Name of job. * * @return bool * TRUE if job is disabled. */ function ultimate_cron_job_get_status($name) { return variable_get('default_ultimate_cron_job_' . $name, NULL); } /** * CTools Export field export callback. * * Export job. * * @param mixed $object * Name of job or UltimateCronJob object. * @param int $indent * Indent to use. */ function _ultimate_cron_job_export_settings($object, $field, $value, $indent = '') { // $value = $object->getSettings(); return ctools_var_export($value, $indent); } /** * CTools Export export callback. * * Export job. * * @param mixed $object * Name of job or UltimateCronJob object. * @param int $indent * Indent to use. */ function _ultimate_cron_job_export($object, $indent = '') { $object = (object) (array) $object; return ctools_export_object('ultimate_cron_job', $object, $indent); } /** * Turn exported code into an object. * * Note: If the code is poorly formed, this could crash and there is no * way to prevent this. * * @param string $code * The code to eval to create the object. * * @return object * An object created from the export. This object will NOT have been saved * to the database. In the case of failure, a string containing all errors * that the system was able to determine. */ function _ultimate_cron_job_import($code) { $table = 'ultimate_cron_job'; $schema = ctools_export_get_schema($table); $export = $schema['export']; ob_start(); eval($code); ob_end_clean(); if (empty(${$export['identifier']})) { $errors = ob_get_contents(); if (empty($errors)) { $errors = t('No item found.'); } return $errors; } $item = ${$export['identifier']}; $jobs = _ultimate_cron_job_load_all(); if (isset($jobs[$item->name])) { $job = clone $jobs[$item->name]; $job->disabled = $item->disabled; $job->api_version = $item->api_version; $job->name = $item->name; $job->title = $item->title; $job->settings = $item->settings; $item = $job; } else { return t('Job "@name" doesn\'t exist. You can only import jobs already defined by the system.', array( '@name' => $item->name, )); } // Set these defaults just the same way that ctools_export_new_object sets // them. $item->export_type = NULL; $item->{$export['export type string']} = t('Overridden'); return $item; } /** * Only overriden jobs should be exportable. */ function ultimate_cron_ultimate_cron_job_list() { $table = 'ultimate_cron_job'; $list = array(); $items = _ultimate_cron_job_load_all_raw(); $schema = ctools_export_get_schema($table); $export_key = $schema['export']['key']; foreach ($items as $item) { // Try a couple of possible obvious title keys: $keys = array('admin_title', 'title'); if (isset($schema['export']['admin_title'])) { array_unshift($keys, $schema['export']['admin_title']); } $string = ''; foreach ($keys as $key) { if (!empty($item->$key)) { $string = $item->$key . " (" . $item->$export_key . ")"; break; } } if (empty($string)) { $string = $item->$export_key; } $list[$item->$export_key] = check_plain($string); } return $list; } // ---------- HOOKS ---------- /** * Implements hook_flush_caches(). */ function ultimate_cron_flush_caches() { return array('cache_ultimate_cron'); } /** * Implements hook_hook_info(). */ function ultimate_cron_hook_info() { $hooks = array(); // Add all easy hooks to cron group. $easy_hooks = ultimate_cron_get_easy_hooks(); // Always ensure hook_cron() plus more is present in group. $easy_hooks += array( 'cron' => array(), 'cron_alter' => array(), 'cronapi' => array(), ); foreach (array_keys($easy_hooks) as $name) { $hooks[$name] = array('group' => 'cron'); } $hooks['cron_easy_hooks'] = array('group' => 'cron'); $hooks['cron_easy_hooks_alter'] = array('group' => 'cron'); $hooks['cron_pre_schedule'] = array('group' => 'cron'); $hooks['cron_post_schedule'] = array('group' => 'cron'); $hooks['cron_pre_launch'] = array('group' => 'cron'); $hooks['cron_post_launch'] = array('group' => 'cron'); $hooks['cron_pre_run'] = array('group' => 'cron'); $hooks['cron_post_run'] = array('group' => 'cron'); $hooks['cron_pre_invoke'] = array('group' => 'cron'); $hooks['cron_post_invoke'] = array('group' => 'cron'); $hooks['ultimate_cron_plugin_build_operations_alter'] = array('group' => 'cron'); return $hooks; } /** * Implements hook_hook_info_alter(). */ function ultimate_cron_hook_info_alter(&$hooks) { if (module_exists('background_process')) { $info = system_get_info('module', 'background_process'); if (!empty($info['dependencies']) && in_array('progress', $info['dependencies'])) { $hooks['background_process_shutdown']['group'] = 'background_process'; } } } /** * Implements hook_init(). * * Make sure we have the proper "last run" of cron in global $conf * for maximum compatibility with core. */ function ultimate_cron_init() { if (!variable_get('ultimate_cron_bypass_transactional_safe_connection')) { $info = Database::getConnectionInfo('default'); Database::addConnectionInfo('default', 'ultimate_cron', $info['default']); } $GLOBALS['ultimate_cron_original_session_saving'] = drupal_save_session(); $GLOBALS['ultimate_cron_original_user'] = $GLOBALS['user']; _ultimate_cron_variable_load('cron_last'); } /** * Implements hook_help(). * * @todo Please update this... */ function ultimate_cron_help($path, $arg) { switch ($path) { case 'admin/help#ultimate_cron': $readme = dirname(__FILE__) . '/README.txt'; if (is_readable($readme)) { // Return a line-break version of the module README. return '
' . file_get_contents($readme) . '
'; } else { $output = '

' . t('About') . '

'; $output .= '

' . t('The Ultimate Cron handling for Drupal. Runs cron jobs individually in parallel using configurable rules, pool management and load balancing.') . '

'; $output .= '

' . t('Features') . '

'; $output .= ''; $output .= '

' . t('Useful links') . '

'; $output .= ''; return $output; } } } /** * Implements hook_modules_enabled(). */ function ultimate_cron_modules_enabled() { // Clear ctools pluin cache for ultimate and rebuild menu, // so that Ultimate Cron won't end up in an inconsistent state. cache_clear_all('plugins:ultimate_cron:', 'cache', TRUE); menu_rebuild(); } /** * Implements hook_modules_disabled(). */ function ultimate_cron_modules_disabled() { // Clear ctools pluin cache for ultimate and rebuild menu, // so that Ultimate Cron won't end up in an inconsistent state. cache_clear_all('plugins:ultimate_cron:', 'cache', TRUE); menu_rebuild(); } /** * Implements hook_menu(). */ function ultimate_cron_menu() { $items = array(); // Poormans cron trigger. $items['admin/config/system/cron/poorman'] = array( 'title' => 'Poormans cron', 'description' => 'Trigger poormans cron', 'page callback' => 'ultimate_cron_poorman_page', 'access callback' => TRUE, 'type' => MENU_CALLBACK, 'file' => 'ultimate_cron.poorman.inc', ); ctools_include('plugins'); // In case a plugin has been removed, we need to clear the plugin cache // first, so cTools won't try to include non-existant files. $plugin_types = ctools_plugin_get_plugin_type_info(TRUE); // Create callbacks for all plugins. $weight = 0; foreach ($plugin_types['ultimate_cron'] as $plugin_type => $info) { $static = $info['defaults']['static']; $class = $static['class']; $items["admin/config/system/cron/$plugin_type"] = array( 'type' => MENU_LOCAL_TASK, 'title' => $static['title plural proper'], 'description' => "Administer " . $static['title plural'], 'page callback' => 'drupal_get_form', 'page arguments' => array('ultimate_cron_plugin_form', $plugin_type), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', 'weight' => 2 + $weight, ); $items["admin/config/system/cron/$plugin_type/settings"] = array( 'type' => MENU_DEFAULT_LOCAL_TASK, 'title' => $class::$multiple ? 'List' : 'Settings', 'weight' => -1 + $weight, ); $weight++; foreach (_ultimate_cron_plugin_load_all($plugin_type, TRUE) as $name => $plugin) { if (!$plugin->isValid()) { continue; } $items["admin/config/system/cron/$plugin_type/$name"] = array( 'type' => MENU_LOCAL_TASK, 'title' => $plugin->title, 'description' => $plugin->description, 'page callback' => 'drupal_get_form', 'page arguments' => array( 'ultimate_cron_plugin_settings', $plugin_type, $name, ), 'access arguments' => array('administer ultimate cron'), 'file' => 'ultimate_cron.admin.inc', 'weight' => $weight++, ); } } return $items; } /** * 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) { // Relocate ctools export ui to main tab. // During install, the cTools export ui menu entries may not have been setup. // If so, skip this part and assume that the menu will be rebuild after // install is complete. if (isset($items['admin/config/system/cron/jobs'])) { $items['admin/config/system/cron'] = $items['admin/config/system/cron/jobs']; $items['admin/config/system/cron/jobs'] = array( 'title' => 'Jobs', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 20, ); unset($items['admin/config/system/cron']['type']); } // Steal the core cron run. $steal = &$items['admin/reports/status/run-cron']; $steal['page callback'] = 'ultimate_cron_run_scheduled_page'; $steal['page arguments'] = array('admin/reports/status'); $steal['module'] = 'ultimate_cron'; $steal['file'] = 'ultimate_cron.module'; } /** * 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'), ), 'view cron jobs' => array( 'title' => t('View cron jobs'), 'description' => t('Lets you view cron jobs and their logs'), ), 'run cron jobs' => array( 'title' => t('Run cron jobs'), 'description' => t('Lets you run cron jobs'), ), ); } /** * Implements hook_cron_queue_info(). * * Used for code injection in order to hijack cron runs. */ function ultimate_cron_cron_queue_info() { $debug = debug_backtrace(); $cron_debug_level = 3; if (version_compare(PHP_VERSION, '7.0.0', '>=')) { // PHP 7 will not include call_user_func_array in the backtrace. $cron_debug_level--; } if (!empty($debug[$cron_debug_level]['function']) && $debug[$cron_debug_level]['function'] == 'drupal_cron_run') { if (!empty($debug[$cron_debug_level + 1])) { try { if ($debug[$cron_debug_level + 1]['function'] == 'install_finished') { // Looks like drupal_cron_run() was called unexpectedly. watchdog('ultimate_cron', 'Running cron in compatibility mode during site install.', array(), WATCHDOG_DEBUG); } else { // Looks like drupal_cron_run() was called unexpectedly. watchdog('ultimate_cron', 'drupal_cron_run() called unexpectedly by @file:@line function @function. Running core in compatibility mode.', array( '@function' => $debug[$cron_debug_level + 1]['function'], '@line' => $debug[$cron_debug_level]['line'], '@file' => $debug[$cron_debug_level]['file'], ), WATCHDOG_DEBUG); } ultimate_cron_cron_run_compatibility(); } catch (Throwable $e) { // Restore the user. $GLOBALS['user'] = $GLOBALS['ultimate_cron_original_user']; drupal_save_session($GLOBALS['ultimate_cron_original_session_saving']); throw $e; } catch (Exception $e) { // Restore the user. $GLOBALS['user'] = $GLOBALS['ultimate_cron_original_user']; drupal_save_session($GLOBALS['ultimate_cron_original_session_saving']); throw $e; } return array(); } // Looks like drupal_cron_run() was called just the way we like it. ultimate_cron_cron_run(); exit; } return array(); } /** * Plugin clean up cron job. * * This is a cron callback for cleanup up plugins. * * @param UltimateCronJob $job * The cron job being run. * @param array $arguments * (optional) An array that can have one or more of the following elements: * - type: The type of the plugin (settings, scheduler, launcher, logger). * - name: The name of the plugin (queue, crontab, serial, database). */ function ultimate_cron_plugin_cleanup($job, $arguments) { $type = $arguments['type']; $name = $arguments['name']; $plugin = _ultimate_cron_plugin_require($type, $name); $plugin->cleanup(); } /** * Implements hook_watchdog(). * * Capture watchdog messages and send them to the loggers. */ function ultimate_cron_watchdog(array $log_entry) { if (class_exists('UltimateCronLogger')) { UltimateCronLogger::hook_watchdog($log_entry); } } // ---------- CALLBACK FUNCTIONS ---------- /** * Run cron. * * The cron handler takes over the normal Drupal cron handler, * and runs the normal hook_cron() plus the hook_cronapi(). */ function ultimate_cron_cron_run() { if (variable_get('install_task', FALSE) != 'done') { return; } // If run from core cron through CLI then don't do anything (drush core-cron) if (drupal_is_cli()) { return; } ultimate_cron_run_scheduled(FALSE); exit; } /** * Run cron in compatibility mode. * * Runs all non core cron jobs first, then locks core cron jobs, * and then assumes that the consumer of this function will execute the * core cron jobs. * * If a lock cannot be obtained for a core cron job, an exception will be * thrown since it will then be unsafe to run it. */ function ultimate_cron_cron_run_compatibility() { // Normally core cron just runs all the jobs regardless of schedule // and locks. // By default we will also run all Ultimate Cron jobs regardless of // their schedule. This behavior can be overridden via the variable: // "ultimate_cron_check_schedule_on_core_cron". // // Split up jobs between core and non-core, and run the non-core jobs // first. foreach (_ultimate_cron_job_load_all() as $job) { if (in_array('core', $job->hook['tags'])) { $core_jobs[] = $job; } else { if (!variable_get('ultimate_cron_check_schedule_on_core_cron', FALSE) || $job->isScheduled()) { if ($lock_id = $job->lock()) { $log_entry = $job->startLog($lock_id, 'Launched by Drupal core'); $job->run(); $log_entry->finish(); $job->unlock(); } } } } // Before passing control back to core, make sure that we can get a // lock on the jobs. If we can't, we don't allow core to run any // jobs, since we can't inform core which jobs are safe to run. foreach ($core_jobs as $job) { $lock_id = $job->lock(); if (!$lock_id) { throw new Exception(t('Could not acquire lock for @name', array( '@name' => $job->name, ))); } $job->startLog($lock_id, 'Launched by Drupal core'); } } // ---------- HELPER FUNCTIONS ---------- /** * Load a variable by-passing the cache. * * We update the the cron_last variable once a minute. In order to avoid * clearing the variable cache every minute, we handle that variable * directly. * * @param string $name * Name of variable to load. * * @return mixed * Value of the variable. The value is also stored in the global static * variables array, so variable_get() may retrieve the correct data * afterwards. */ function _ultimate_cron_variable_load($name, $default = NULL) { if ($value = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => $name))->fetchField()) { $value = unserialize($value); } else { $value = $default; } global $conf; $conf[$name] = $value; return $value; } /** * Load multiple variables by-passing the cache. * * We update the the cron_last variable once a minute. In order to avoid * clearing the variable cache every minute, we handle that variable * directly. * * @param array $names * Names of variables to load. * * @return array * Values of the variables. The values are also stored in the global static * variables array, so variable_get() may retrieve the correct data * afterwards. */ function _ultimate_cron_variable_load_multiple($names, $default = NULL) { $values = array(); $result = db_query("SELECT name, value FROM {variable} WHERE name IN (:names)", array(':names' => $names))->fetchAllKeyed(); global $conf; foreach ($names as $name) { $conf[$name] = $values[$name] = isset($result[$name]) ? unserialize($result[$name]) : $default; } return $values; } /** * Variable set with cache by-pass. * * We update the the cron_last variable once a minute. In order to avoid * clearing the variable cache every minute, we handle that variable * directly. * * The global static variables array is also updated, so variable_get() * may retrieve the correct data afterwards. * * @param string $name * Name of variables to save. * @param mixed $value * Value of the variable. */ function _ultimate_cron_variable_save($name, $value) { global $conf; db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute(); $conf[$name] = $value; } /** * Cron easy hooks. * * Provide a default set of easy hooks. * * @return array * Easy hooks. */ function ultimate_cron_get_easy_hooks() { static $easy_hooks; if (isset($easy_hooks)) { return $easy_hooks; } // Default provided easy hooks. $easy_hooks = array( 'cron' => array( 'title' => 'Default cron job', ), 'cron_hourly' => array( 'title' => 'Hourly cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ * * * *'), ), ), ), 'cron_daily' => array( 'title' => 'Daily cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ 12 * * *'), ), ), ), 'cron_nightly' => array( 'title' => 'Nightly cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ 0 * * *'), ), ), ), 'cron_weekly' => array( 'title' => 'Weekly cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ 0 * * 1'), ), ), ), 'cron_monthly' => array( 'title' => 'Monthly cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ 0 1 * *'), ), ), ), 'cron_yearly' => array( 'title' => 'Yearly cron job', 'scheduler' => array( 'name' => 'crontab', 'crontab' => array( 'rules' => array('0+@ 0 1 1 *'), ), ), ), ); // Allow modules to define their own easy hooks. $easy_hooks += module_invoke_all('cron_easy_hooks'); // Allow modules to alter the defined easy hooks. drupal_alter('cron_easy_hooks', $easy_hooks); return $easy_hooks; } /** * Get a specific cron hook. * * @param string $name * Name of hook. * @param bool $reset * Reset static cache. * * @return array * Hook definition. */ function ultimate_cron_get_hook($name, $reset = FALSE) { $hooks = ultimate_cron_get_hooks($reset); return $hooks[$name]; } /** * Get cron hooks declared by a module. * * @param string $module * Name of module. * * @return array * Hook definitions for the specified module. */ function ultimate_cron_get_module_hooks($module) { $items = array(); if (module_hook($module, 'cronapi')) { $items = module_invoke($module, 'cronapi', NULL); if (!is_array($items)) { // API Version 1.x $items = array(); $list = module_invoke($module, 'cronapi', 'list'); if (!$list) { $list = array(); } foreach ($list as $name => $title) { $items[$name] = array('title' => $title); } foreach ($items as $name => &$item) { $item['api_version'] = 'ultimate_cron-1'; $rules = module_invoke($module, 'cronapi', 'rule', $name); $rules = $rules ? $rules : array(); $settings = (array) module_invoke($module, 'cronapi', 'settings', $name); if (empty($settings['rules']) && $rules) { $settings['rules'] = is_array($rules) ? $rules : array($rules); } if (!empty($settings['rules'])) { $settings['scheduler'] = array( 'name' => 'crontab', 'crontab' => array( 'rules' => $settings['rules'], ), ); unset($settings['rules']); } $settings += array( 'configure' => module_invoke($module, 'cronapi', 'configure', $name), ); $item += $settings; } } else { foreach ($items as &$item) { if (!empty($item['rule'])) { // Elysia 2.x compatibility. $item['scheduler'] = array( 'name' => 'crontab', 'crontab' => array( 'rules' => array($item['rule']), ), ); $item['api_version'] = 'elysia_cron-2'; $item['title'] = $item['description']; } } } } // Add hook_cron() if applicable. if (module_hook($module, 'cron')) { if (empty($items["{$module}_cron"])) { $items["{$module}_cron"] = array(); } $info = system_get_info('module', $module); $items["{$module}_cron"] += array( 'module' => $module, 'title' => 'Default cron handler', 'configure' => empty($info['configure']) ? NULL : $info['configure'], 'tags' => array(), 'pass job argument' => FALSE, ); $items["{$module}_cron"]['tags'][] = 'core'; } foreach (ultimate_cron_get_easy_hooks() as $name => $easy_hook) { $hook_name = "{$module}_{$name}"; if (module_hook($module, $name)) { if (empty($items[$hook_name])) { $items[$hook_name] = array(); } $items[$hook_name] += $easy_hook; $info = system_get_info('module', $module); $items[$hook_name] += array( 'module' => $module, 'title' => 'Easy hook ' . $name, ); } } // Make sure the module is defined. foreach ($items as &$item) { $item += array( 'module' => $module, ); } return $items; } /** * Get hooks defined in plugins. */ function ultimate_cron_get_plugin_hooks() { $items = array(); // Invoke hook_cronapi() on the plugins. $plugin_types = ctools_plugin_get_plugin_type_info(); foreach ($plugin_types['ultimate_cron'] as $plugin_type => $info) { $plugins = _ultimate_cron_plugin_load_all($plugin_type); foreach ($plugins as $plugin) { if ($plugin->isValid()) { $items += $plugin->cronapi(); } } } return $items; } /** * Prepare the hooks by adding default values. * * @param array &$hooks * The hooks to be prepared. */ function ultimate_cron_prepare_hooks(&$hooks) { // Add default settings. static $plugin_types; static $plugins; if (!isset($plugin_types)) { ctools_include('plugins'); $plugin_types = ctools_plugin_get_plugin_type_info(); $plugins = array(); foreach ($plugin_types['ultimate_cron'] as $plugin_type => $info) { $plugins[$plugin_type] = _ultimate_cron_plugin_load_all($plugin_type); } } foreach ($hooks as $name => &$item) { foreach ($plugin_types['ultimate_cron'] as $plugin_type => $info) { $static = $info['defaults']['static']; $class = $static['class']; $item += array( $plugin_type => array(), ); if (!$class::$multiple) { $item[$plugin_type] += array( 'name' => variable_get('ultimate_cron_plugin_' . $plugin_type . '_default', $static['default plugin']), ); } foreach ($plugins[$plugin_type] as $plugin_name => $plugin) { if (!$plugin->isValid()) { continue; } $item[$plugin_type] += array( $plugin_name => array(), ); } } $item += array( 'title' => $name, 'description' => isset($item['title']) ? $item['title'] : $name, 'file path' => drupal_get_path('module', $item['module']), 'callback arguments' => array(), 'callback' => $name, 'enabled' => TRUE, 'tags' => array(), 'api_version' => 'ultimate_cron-2', 'pass job argument' => TRUE, ); } } /** * Get all cron hooks defined. * * @param bool $reset * Reset static cache. * * @return array * All hook definitions available. */ function ultimate_cron_get_hooks($reset = FALSE) { static $cache = NULL; if (!$reset && isset($cache)) { return $cache; } $cache = cache_get('ultimate_cron_hooks'); if ($cache && $cache->data) { $cache = $cache->data; return $cache; } $hooks = array(); // Generate list of jobs provided by modules. $modules = module_list(); foreach ($modules as $module) { $hooks += ultimate_cron_get_module_hooks($module); } // Generate list of jobs provided by plugins. $hooks += ultimate_cron_get_plugin_hooks(); // Add default values to hooks. ultimate_cron_prepare_hooks($hooks); // Allow other to manipulate the hook list. drupal_alter('cron', $hooks); // Keep track on when we first registered new cron jobs. // This is used for as a base for time comparison in behind schedule // calculation for jobs that haven't run. $registered = variable_get('ultimate_cron_hooks_registered', array()); $new = array(); foreach ($hooks as $name => $hook) { $new[$name] = empty($registered[$name]) ? REQUEST_TIME : $registered[$name]; } if ($registered != $new) { variable_set('ultimate_cron_hooks_registered', $new); } $cache = $hooks; cache_set('ultimate_cron_hooks', $cache); return $cache; } // ---------- CRON RULE FUNCTIONS ---------- /** * Form element validator for cron rules. */ function ultimate_cron_plugin_crontab_element_validate_rule($element, &$form_state) { $rules = array(); $value = $element['#value']; if (!empty($value)) { $rules = explode(';', $value); $rules = array_map('trim', $rules); } foreach ($rules as $rule) { if (!ultimate_cron_validate_rule($rule)) { form_error($element, t('%name: %rule is not a valid rule.', array('%name' => $element['#title'], '%rule' => $rule))); } } } /** * Check if rule is valid. * * @param string $rule * The rule to validate. * * @return bool * TRUE if valid, FALSE if not. */ function ultimate_cron_validate_rule($rule) { $cron = CronRule::factory($rule); if (!$cron->isValid()) { return FALSE; } else { return TRUE; } } /** * Return blank values for all keys in an array. * * @param array $array * Array to generate blank values from. * * @return array * Array with same keys as input, but with blank values (empty string). */ function ultimate_cron_blank_values($array) { $result = array(); foreach ($array as $key => $value) { switch (gettype($value)) { case 'array': $result[$key] = array(); break; default: $result[$key] = ''; } } return $result; } /** * Page callback for running scheduled jobs. */ function ultimate_cron_run_scheduled_page($dest = NULL) { ultimate_cron_run_scheduled(TRUE); $dest = $dest ? $dest : 'admin/config/system/cron'; drupal_goto($dest); } /** * Run scheduled jobs. * * @param bool $set_message * Set Drupal system message instead of watchdog logging. */ function ultimate_cron_run_scheduled($set_message = TRUE) { if (variable_get('ultimate_cron_disable_scheduled', 0)) { ultimate_cron_watchdog_message('ultimate_cron', 'Cannot launch scheduled jobs while ultimate_cron_disable_scheduled variable is set!', array(), WATCHDOG_NOTICE, 'error', $set_message); return; } if (variable_get('maintenance_mode', 0)) { ultimate_cron_watchdog_message('ultimate_cron', 'Cannot launch scheduled jobs while in maintenance mode!', array(), WATCHDOG_NOTICE, 'error', $set_message); return; } ultimate_cron_run_launchers(); } /** * Run all launchers. */ function ultimate_cron_run_launchers($launchers = NULL) { _ultimate_cron_variable_save('cron_last', time()); $launcher_jobs = array(); foreach (_ultimate_cron_job_load_all() as $job) { $launcher = $job->getPlugin('launcher'); if (!isset($launchers) || in_array($launcher->name, $launchers)) { $launcher_jobs[$launcher->name]['launcher'] = $launcher; $launcher_jobs[$launcher->name]['sort'] = array($launcher->weight); $launcher_jobs[$launcher->name]['jobs'][$job->name] = $job; $launcher_jobs[$launcher->name]['jobs'][$job->name]->sort = array($job->loadLatestLogEntry()->start_time); } } uasort($launcher_jobs, '_ultimate_cron_multi_column_sort'); foreach ($launcher_jobs as $name => $launcher_job) { uasort($launcher_job['jobs'], '_ultimate_cron_multi_column_sort'); $launcher_job['launcher']->launchJobs($launcher_job['jobs']); } } /** * Log message either to watchdog or to screen. * * @param string $type * The category to which this message belongs. Can be any string, but the * general practice is to use the name of the module calling watchdog(). * @param string $message * The message to store in the log. Keep $message translatable * by not concatenating dynamic values into it! Variables in the * message should be added by using placeholder strings alongside * the variables argument to declare the value of the placeholders. * See t() for documentation on how $message and $variables interact. * @param array $variables * Array of variables to replace in the message on display or * NULL if message is already translated or not possible to * translate. * @param int $severity * The severity of the message; one of the following values as defined in * @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink * - WATCHDOG_EMERGENCY: Emergency, system is unusable. * - WATCHDOG_ALERT: Alert, action must be taken immediately. * - WATCHDOG_CRITICAL: Critical conditions. * - WATCHDOG_ERROR: Error conditions. * - WATCHDOG_WARNING: Warning conditions. * - WATCHDOG_NOTICE: (default) Normal but significant conditions. * - WATCHDOG_INFO: Informational messages. * - WATCHDOG_DEBUG: Debug-level messages. * @param string $status * The message's type. * supported: * - 'status' * - 'warning' * - 'error' * @param bool $set_message * Use drupal_set_message() instead of watchdog logging. */ function ultimate_cron_watchdog_message($type, $message, $variables, $severity, $status, $set_message) { if ($set_message) { drupal_set_message(t($message, $variables), $status); } else { watchdog($type, $message, $variables, $severity); } } /** * Custom sort callback for sorting cron jobs by start time. */ function _ultimate_cron_sort_jobs_by_start_time($a, $b) { return $a->log_entry->start_time == $b->log_entry->start_time ? 0 : ($a->log_entry->start_time > $b->log_entry->start_time ? 1 : -1); } /** * Sort callback for multiple column sort. */ function _ultimate_cron_multi_column_sort($a, $b) { $a = (array) $a; $b = (array) $b; foreach ($a['sort'] as $i => $sort) { if ($a['sort'][$i] == $b['sort'][$i]) { continue; } return $a['sort'][$i] < $b['sort'][$i] ? -1 : 1; } return 0; } /** * Get transactional safe connection. * * @return string * Connection target. */ function _ultimate_cron_get_transactional_safe_connection() { return !variable_get('ultimate_cron_bypass_transactional_safe_connection') && Database::getConnection()->inTransaction() ? 'ultimate_cron' : 'default'; } /** * Get class for a specific task. * * @param string $name * Name of task. * * @return string * Name of class. */ function _ultimate_cron_get_class($name) { static $defaults = array( 'job' => 'UltimateCronJob', 'lock' => 'UltimateCronLock', 'progress' => 'UltimateCronProgress', 'signal' => 'UltimateCronSignal', ); return variable_get('ultimate_cron_class_' . $name, $defaults[$name]); } /** * Logs a Throwable. * * This is a copy of watchdog_exception() except that $exception is Throwable. * * @param $type * The category to which this message belongs. * @param $exception * The exception that is going to be logged. * @param $message * The message to store in the log. If empty, a text that contains all useful * information about the passed-in exception is used. * @param $variables * Array of variables to replace in the message on display. Defaults to the * return value of _drupal_decode_exception(). * @param $severity * The severity of the message, as per RFC 3164. * @param $link * A link to associate with the message. * * @see watchdog_exception() * @see _drupal_decode_exception() */ function ultimate_cron_watchdog_throwable($type, Throwable $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) { // Use a default value if $message is not set. if (empty($message)) { // The exception message is run through check_plain() by _drupal_decode_exception(). $message = '%type: !message in %function (line %line of %file).'; } // $variables must be an array so that we can add the exception information. if (!is_array($variables)) { $variables = array(); } require_once DRUPAL_ROOT . '/includes/errors.inc'; $variables += _drupal_decode_exception($exception); watchdog($type, $message, $variables, $severity, $link); }