You are here

ultimate_cron.plugin.inc in Ultimate Cron 7.2

Plugin framework for Ultimate Cron.

File

ultimate_cron.plugin.inc
View source
<?php

/**
 * @file
 * Plugin framework for Ultimate Cron.
 */

/**
 * This is the base class for all Ultimate Cron plugins.
 *
 * This class handles all the load/save settings for a plugin as well as the
 * forms, etc.
 */
class UltimateCronPlugin {
  public $name = '';
  public $title = '';
  public $description = '';
  public $plugin;
  public $settings = array();
  public static $multiple = FALSE;
  public static $instances = array();
  public $weight = 0;
  public static $globalOptions = array();

  /**
   * Constructor.
   *
   * Setup object.
   *
   * @param string $name
   *   Name of plugin.
   * @param array $plugin
   *   The plugin definition.
   */
  public function __construct($name, $plugin) {
    $this->plugin = $plugin;
    $this->title = $plugin['title'];
    $this->description = $plugin['description'];
    $this->name = $name;
    $this->type = $plugin['plugin type'];
    $this->key = 'ultimate_cron_plugin_' . $plugin['plugin type'] . '_' . $name . '_settings';
    $this->settings = variable_get($this->key, array());
  }

  /**
   * Singleton factoryLogEntry.
   */
  public static function factory($class, $name, $plugin) {
    if (empty($class::$instances[$plugin['plugin type']][$name])) {
      self::$instances[$plugin['plugin type']][$name] = new $class($name, $plugin);
    }
    return self::$instances[$plugin['plugin type']][$name];
  }

  /**
   * Get global plugin option.
   *
   * @param string $name
   *   Name of global plugin option to get.
   *
   * @return mixed
   *   Value of option if any, NULL if not found.
   */
  public static function getGlobalOption($name) {
    return isset(self::$globalOptions[$name]) ? self::$globalOptions[$name] : NULL;
  }

  /**
   * Get all global plugin options.
   *
   * @return array
   *   All options currently set, keyed by name.
   */
  public static function getGlobalOptions() {
    return self::$globalOptions;
  }

  /**
   * Set global plugin option.
   *
   * @param string $name
   *   Name of global plugin option to get.
   * @param string $value
   *   The value to give it.
   */
  public static function setGlobalOption($name, $value) {
    self::$globalOptions[$name] = $value;
  }

  /**
   * Remove a global plugin option.
   *
   * @param string $name
   *   Name of global plugin option to remove.
   */
  public static function unsetGlobalOption($name) {
    unset(self::$globalOptions[$name]);
  }

  /**
   * Remove all global plugin options.
   */
  public static function unsetGlobalOptions() {
    self::$globalOptions = array();
  }

  /**
   * Invoke hook_cron_alter() on plugins.
   */
  public static final function hook_cron_alter(&$jobs) {
    ctools_include('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()) {
          $plugin
            ->cron_alter($jobs);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_pre_schedule() on plugins.
   */
  public static final function hook_cron_pre_schedule($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_pre_schedule($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_post_schedule() on plugins.
   */
  public static final function hook_cron_post_schedule($job, &$result) {
    ctools_include('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($job)) {
          $plugin
            ->cron_post_schedule($job, $result);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_pre_launch() on plugins.
   */
  public static final function hook_cron_pre_launch($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_pre_launch($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_post_launch() on plugins.
   */
  public static final function hook_cron_post_launch($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_post_launch($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_pre_run() on plugins.
   */
  public static final function hook_cron_pre_run($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_pre_run($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_post_run() on plugins.
   */
  public static final function hook_cron_post_run($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_post_run($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_pre_invoke() on plugins.
   */
  public static final function hook_cron_pre_invoke($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_pre_invoke($job);
        }
      }
    }
  }

  /**
   * Invoke hook_cron_post_invoke() on plugins.
   */
  public static final function hook_cron_post_invoke($job) {
    ctools_include('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($job)) {
          $plugin
            ->cron_post_invoke($job);
        }
      }
    }
  }

  /**
   * A hook_cronapi() for plugins.
   */
  public function cronapi() {
    return array();
  }

  /**
   * A hook_cron_alter() for plugins.
   */
  public function cron_alter(&$jobs) {
  }

  /**
   * A hook_cron_pre_schedule() for plugins.
   */
  public function cron_pre_schedule($job) {
  }

  /**
   * A hook_cron_post_schedule() for plugins.
   */
  public function cron_post_schedule($job, &$result) {
  }

  /**
   * A hook_cron_pre_launch() for plugins.
   */
  public function cron_pre_launch($job) {
  }

  /**
   * A hook_cron_post_launch() for plugins.
   */
  public function cron_post_launch($job) {
  }

  /**
   * A hook_cron_pre_run() for plugins.
   */
  public function cron_pre_run($job) {
  }

  /**
   * A hook_cron_post_run() for plugins.
   */
  public function cron_post_run($job) {
  }

  /**
   * A hook_cron_pre_invoke() for plugins.
   */
  public function cron_pre_invoke($job) {
  }

  /**
   * A hook_cron_post_invoke() for plugins.
   */
  public function cron_post_invoke($job) {
  }

  /**
   * Signal page for plugins.
   */
  public function signal($item, $signal) {
  }

  /**
   * Allow plugins to alter the allowed operations for a job.
   */
  public function build_operations_alter($job, &$allowed_operations) {
  }

  /**
   * Get default settings.
   */
  public function getDefaultSettings($job = NULL) {
    $settings = array();
    if ($job && !empty($job->hook[$this->type][$this->name])) {
      $settings += $job->hook[$this->type][$this->name];
    }
    $settings += $this->settings + $this
      ->defaultSettings();
    return $settings;
  }

  /**
   * Save settings to db.
   */
  public function setSettings() {
    variable_set($this->key, $this->settings);
  }

  /**
   * Default settings.
   */
  public function defaultSettings() {
    return array();
  }

  /**
   * Get label for a specific setting.
   */
  public function settingsLabel($name, $value) {
    if (is_array($value)) {
      return implode(', ', $value);
    }
    else {
      return $value;
    }
  }

  /**
   * Format label for the plugin.
   *
   * @param UltimateCronJob $job
   *   The job for format the plugin label for.
   *
   * @return string
   *   Formatted label.
   */
  public function formatLabel($job) {
    return $job->name;
  }

  /**
   * Format verbose label for the plugin.
   *
   * @param UltimateCronJob $job
   *   The job for format the verbose plugin label for.
   *
   * @return string
   *   Verbosely formatted label.
   */
  public function formatLabelVerbose($job) {
    return $job->title;
  }

  /**
   * Default plugin valid for all jobs.
   */
  public function isValid($job = NULL) {
    return TRUE;
  }

  /**
   * Modified version drupal_array_get_nested_value().
   *
   * Removes the specified parents leaf from the array.
   *
   * @param array $array
   *   Nested associative array.
   * @param array $parents
   *   Array of key names forming a "path" where the leaf will be removed
   *   from $array.
   */
  public function drupal_array_remove_nested_value(array &$array, array $parents) {
    $ref =& $array;
    $last_parent = array_pop($parents);
    foreach ($parents as $parent) {
      if (is_array($ref) && array_key_exists($parent, $ref)) {
        $ref =& $ref[$parent];
      }
      else {
        return;
      }
    }
    unset($ref[$last_parent]);
  }

  /**
   * Clean form of empty fallback values.
   */
  public function cleanForm($elements, &$values, $parents) {
    if (empty($elements)) {
      return;
    }
    foreach (element_children($elements) as $child) {
      if (empty($child) || empty($elements[$child]) || is_numeric($child)) {
        continue;
      }

      // Process children.
      $this
        ->cleanForm($elements[$child], $values, $parents);

      // Determine relative parents.
      $rel_parents = array_diff($elements[$child]['#parents'], $parents);
      $key_exists = NULL;
      $value = drupal_array_get_nested_value($values, $rel_parents, $key_exists);

      // Unset when applicable.
      if (!empty($elements[$child]['#markup'])) {
        self::drupal_array_remove_nested_value($values, $rel_parents);
      }
      elseif ($key_exists && empty($value) && !empty($elements[$child]['#fallback']) && $value !== '0') {
        self::drupal_array_remove_nested_value($values, $rel_parents);
      }
    }
  }

  /**
   * Default settings form.
   */
  public static function defaultSettingsForm(&$form, &$form_state, $plugin_info) {
    $plugin_type = $plugin_info['type'];
    $static = $plugin_info['defaults']['static'];
    $key = 'ultimate_cron_plugin_' . $plugin_type . '_default';
    $options = array();
    foreach (_ultimate_cron_plugin_load_all($plugin_type) as $name => $plugin) {
      if ($plugin
        ->isValid()) {
        $options[$name] = $plugin->title;
      }
    }
    $form[$key] = array(
      '#type' => 'select',
      '#options' => $options,
      '#default_value' => variable_get($key, $static['default plugin']),
      '#title' => t('Default @plugin_type', array(
        '@plugin_type' => $static['title singular'],
      )),
    );
    $form = system_settings_form($form);
  }

  /**
   * Job settings form.
   */
  public static function jobSettingsForm(&$form, &$form_state, $plugin_type, $job) {

    // Check valid plugins.
    $plugins = _ultimate_cron_plugin_load_all($plugin_type);
    foreach ($plugins as $name => $plugin) {
      if (!$plugin
        ->isValid($job)) {
        unset($plugins[$name]);
      }
    }

    // No plugins = no settings = no vertical tabs for you mister!
    if (empty($plugins)) {
      return;
    }
    ctools_include('plugins');
    $plugin_types = ctools_plugin_get_plugin_type_info();
    $plugin_info = $plugin_types['ultimate_cron'][$plugin_type];
    $static = $plugin_info['defaults']['static'];

    // Find plugin selected on this page.
    // If "0" (meaning default) use the one defined in the hook.
    if (empty($form_state['values']['settings'][$plugin_type]['name'])) {
      $form_state['values']['settings'][$plugin_type]['name'] = 0;
      $current_plugin = $plugins[$job->hook[$plugin_type]['name']];
    }
    else {
      $current_plugin = $plugins[$form_state['values']['settings'][$plugin_type]['name']];
    }
    $form_state['previous_plugin'][$plugin_type] = $current_plugin->name;

    // Determine original plugin.
    $original_plugin = !empty($job->settings[$plugin_type]['name']) ? $job->settings[$plugin_type]['name'] : $job->hook[$plugin_type]['name'];

    // Ensure blank array.
    if (empty($form_state['values']['settings'][$plugin_type][$current_plugin->name])) {
      $form_state['values']['settings'][$plugin_type][$current_plugin->name] = array();
    }

    // Default values for current selection. If selection differs from current
    // job, then take the job into account.
    $defaults = $current_plugin->name == $original_plugin ? $job->settings : array();
    $defaults += $current_plugin
      ->getDefaultSettings($job);

    // Plugin settings fieldset with vertical tab reference.
    $form['settings'][$plugin_type] = array(
      '#type' => 'fieldset',
      '#title' => $static['title singular proper'],
      '#group' => 'settings_tabs',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#tree' => TRUE,
    );

    // Ajax wrapper.
    $wrapper = 'wrapper-plugin-' . $plugin_type . '-settings';

    // Setup plugin selector.
    $options = array();
    $options[''] = t('Default (@default)', array(
      '@default' => $plugins[$job->hook[$plugin_type]['name']]->title,
    ));
    foreach ($plugins as $name => $plugin) {
      $options[$name] = $plugin->title;
    }
    $form['settings'][$plugin_type]['name'] = array(
      '#weight' => -10,
      '#type' => 'select',
      '#options' => $options,
      '#default_value' => $form_state['values']['settings'][$plugin_type]['name'],
      '#title' => $static['title singular proper'],
      '#description' => t('Select which @plugin to use for this job.', array(
        '@plugin' => $static['title singular'],
      )),
      '#ajax' => array(
        'callback' => 'ultimate_cron_job_plugin_settings_ajax',
        'wrapper' => $wrapper,
        'method' => 'replace',
        'effect' => 'none',
      ),
    );
    $default_settings_link = l(t('(change default settings)'), 'admin/config/system/cron/' . $current_plugin->type . '/' . $current_plugin->name);

    // Plugin specific settings wrapper for ajax replace.
    $form['settings'][$plugin_type][$current_plugin->name] = array(
      '#tree' => TRUE,
      '#type' => 'fieldset',
      '#title' => $current_plugin->title,
      '#description' => $current_plugin->description,
      '#prefix' => '<div id="' . $wrapper . '">',
      '#suffix' => '</div>',
    );
    $form_state['default_values']['settings'][$plugin_type][$current_plugin->name] = $defaults;
    if ($current_plugin->name == $original_plugin && isset($job->settings[$plugin_type][$current_plugin->name]) && is_array($job->settings[$plugin_type][$current_plugin->name])) {
      $form_state['values']['settings'][$plugin_type][$current_plugin->name] += $job->settings[$plugin_type][$current_plugin->name];
    }
    $form_state['values']['settings'][$plugin_type][$current_plugin->name] += ultimate_cron_blank_values($defaults);
    $current_plugin
      ->settingsForm($form, $form_state, $job);
    if (empty($form['settings'][$plugin_type][$current_plugin->name]['no_settings'])) {
      $current_plugin
        ->fallbackalize($form['settings'][$plugin_type][$current_plugin->name], $form_state['values']['settings'][$plugin_type][$current_plugin->name], $form_state['default_values']['settings'][$plugin_type][$current_plugin->name], FALSE);
      $form['settings'][$plugin_type][$current_plugin->name]['#description'] .= ' ' . $default_settings_link . '.';
    }
  }

  /**
   * Job settings form validate handler.
   */
  public static function jobSettingsFormValidate($form, &$form_state, $plugin_type, $job = NULL) {
    $name = !empty($form_state['values']['settings'][$plugin_type]['name']) ? $form_state['values']['settings'][$plugin_type]['name'] : $job->hook[$plugin_type]['name'];
    $plugin = _ultimate_cron_plugin_require($plugin_type, $name);
    $plugin
      ->settingsFormValidate($form, $form_state, $job);
  }

  /**
   * Job settings form submit handler.
   */
  public static function jobSettingsFormSubmit($form, &$form_state, $plugin_type, $job = NULL) {
    $name = !empty($form_state['values']['settings'][$plugin_type]['name']) ? $form_state['values']['settings'][$plugin_type]['name'] : $job->hook[$plugin_type]['name'];
    $plugin = _ultimate_cron_plugin_require($plugin_type, $name);
    $plugin
      ->settingsFormSubmit($form, $form_state, $job);

    // Weed out blank values that have fallbacks.
    $elements =& $form['settings'][$plugin_type][$name];
    $values =& $form_state['values']['settings'][$plugin_type][$name];
    $plugin
      ->cleanForm($elements, $values, array(
      'settings',
      $plugin_type,
      $name,
    ));
  }

  /**
   * Settings form.
   */
  public function settingsForm(&$form, &$form_state, $job = NULL) {
    $form['settings'][$this->type][$this->name]['no_settings'] = array(
      '#markup' => '<p>' . t('This plugin has no settings.') . '</p>',
    );
  }

  /**
   * Settings form validate handler.
   */
  public function settingsFormValidate(&$form, &$form_state, $job = NULL) {
  }

  /**
   * Settings form submit handler.
   */
  public function settingsFormSubmit(&$form, &$form_state, $job = NULL) {
  }

  /**
   * Process fallback form parameters.
   *
   * @param array $elements
   *   Elements to process.
   * @param array $defaults
   *   Default values to add to description.
   * @param bool $remove_non_fallbacks
   *   If TRUE, non fallback elements will be removed.
   */
  public function fallbackalize(&$elements, &$values, $defaults, $remove_non_fallbacks = FALSE) {
    if (empty($elements)) {
      return;
    }
    foreach (element_children($elements) as $child) {
      $element =& $elements[$child];
      if (empty($element['#tree'])) {
        $param_values =& $values;
        $param_defaults =& $defaults;
      }
      else {
        $param_values =& $values[$child];
        $param_defaults =& $defaults[$child];
      }
      $this
        ->fallbackalize($element, $param_values, $param_defaults, $remove_non_fallbacks);
      if (empty($element['#type']) || $element['#type'] == 'fieldset') {
        continue;
      }
      if (!empty($element['#fallback'])) {
        if (!$remove_non_fallbacks) {
          if ($element['#type'] == 'radios') {
            $label = $this
              ->settingsLabel($child, $defaults[$child]);
            $element['#options'] = array(
              '' => t('Default (@default)', array(
                '@default' => $label,
              )),
            ) + $element['#options'];
          }
          elseif ($element['#type'] == 'select' && empty($element['#multiple'])) {
            $label = $this
              ->settingsLabel($child, $defaults[$child]);
            $element['#options'] = array(
              '' => t('Default (@default)', array(
                '@default' => $label,
              )),
            ) + $element['#options'];
          }
          elseif ($defaults[$child] !== '') {
            $element['#description'] .= ' ' . t('(Blank = @default).', array(
              '@default' => $this
                ->settingsLabel($child, $defaults[$child]),
            ));
          }
          unset($element['#required']);
        }
      }
      elseif (!empty($element['#type']) && $remove_non_fallbacks) {
        unset($elements[$child]);
      }
      elseif (!isset($element['#default_value']) || $element['#default_value'] === '') {
        $empty = $element['#type'] == 'checkbox' ? FALSE : '';
        $values[$child] = !empty($defaults[$child]) ? $defaults[$child] : $empty;
        $element['#default_value'] = $values[$child];
      }
    }
  }

}

/**
 * Class for handling multiple plugins.
 */
class UltimateCronPluginMultiple extends UltimateCronPlugin {
  public static $multiple = TRUE;

  /**
   * Default settings form.
   */
  public static function defaultSettingsForm(&$form, &$form_state, $plugin_info) {
    $plugin_type = $plugin_info['type'];
    foreach (_ultimate_cron_plugin_load_all($plugin_type) as $name => $plugin) {
      if ($plugin
        ->isValid()) {
        $plugins[] = l($plugin->title, "admin/config/system/cron/{$plugin_type}/{$name}");
      }
    }
    $form['available'] = array(
      '#markup' => theme('item_list', array(
        'title' => $plugin_info['defaults']['static']['title plural proper'] . ' available',
        'items' => $plugins,
      )),
    );
  }

  /**
   * Job settings form.
   */
  public static function jobSettingsForm(&$form, &$form_state, $plugin_type, $job) {

    // Check valid plugins.
    $plugins = _ultimate_cron_plugin_load_all($plugin_type);
    foreach ($plugins as $name => $plugin) {
      if (!$plugin
        ->isValid($job)) {
        unset($plugins[$name]);
      }
    }

    // No plugins = no settings = no vertical tabs for you mister!
    if (empty($plugins)) {
      return;
    }
    $weight = 10;
    $form_state['default_values']['settings'][$plugin_type] = array();
    $form['settings'][$plugin_type]['#tree'] = TRUE;
    foreach ($plugins as $name => $plugin) {
      $form_state['default_values']['settings'][$plugin_type][$name] = array();
      if (empty($form_state['values']['settings'][$plugin_type][$name])) {
        $form_state['values']['settings'][$plugin_type][$name] = array();
      }
      $form['settings'][$plugin_type][$name] = array(
        '#title' => $plugin->title,
        '#group' => 'settings_tabs',
        '#type' => 'fieldset',
        '#tree' => TRUE,
        '#visible' => TRUE,
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => $weight++,
      );
      $defaults = $plugin
        ->getDefaultSettings($job);
      $form_state['default_values']['settings'][$plugin_type][$name] += $defaults;
      $form_state['values']['settings'][$plugin_type][$name] += ultimate_cron_blank_values($defaults);
      $plugin
        ->settingsForm($form, $form_state, $job);
      if (empty($form['settings'][$plugin_type][$name]['no_settings'])) {
        $plugin
          ->fallbackalize($form['settings'][$plugin_type][$name], $form_state['values']['settings'][$plugin_type][$name], $form_state['default_values']['settings'][$plugin_type][$name], FALSE);
      }
      else {
        unset($form['settings'][$plugin_type][$name]);
      }
    }
  }

  /**
   * Job settings form validate handler.
   */
  public static function jobSettingsFormValidate($form, &$form_state, $plugin_type, $job = NULL) {
    $plugins = _ultimate_cron_plugin_load_all($plugin_type);
    foreach ($plugins as $plugin) {
      if ($plugin
        ->isValid($job)) {
        $plugin
          ->settingsFormValidate($form, $form_state, $job);
      }
    }
  }

  /**
   * Job settings form submit handler.
   */
  public static function jobSettingsFormSubmit($form, &$form_state, $plugin_type, $job = NULL) {
    $plugins = _ultimate_cron_plugin_load_all($plugin_type);
    foreach ($plugins as $name => $plugin) {
      if ($plugin
        ->isValid($job)) {
        $plugin
          ->settingsFormSubmit($form, $form_state, $job);

        // Weed out blank values that have fallbacks.
        $elements =& $form['settings'][$plugin_type][$name];
        $values =& $form_state['values']['settings'][$plugin_type][$name];
        $plugin
          ->cleanForm($elements, $values, array(
          'settings',
          $plugin_type,
          $name,
        ));
      }
      else {
        unset($form_state['values']['settings'][$plugin_type][$name]);
      }
    }
  }

}

/**
 * Abstract class for Ultimate Cron schedulers.
 *
 * A scheduler is responsible for telling Ultimate Cron whether a job should
 * run or not.
 *
 * Abstract methods:
 *   isScheduled($job)
 *     - Check if the given job is scheduled for launch at this time.
 *       TRUE if it's scheduled for launch, otherwise FALSE.
 *
 *   isBehind($job)
 *     - Check if the given job is behind its schedule.
 *       FALSE if not behind, otherwise the amount of time it's behind
 *       in seconds.
 */
abstract class UltimateCronScheduler extends UltimateCronPlugin {

  /**
   * Check job schedule.
   *
   * @param UltimateCronJob $job
   *   The job to check schedule for.
   *
   * @return bool
   *   TRUE if job is scheduled to run.
   */
  public abstract function isScheduled($job);

  /**
   * Check if job is behind schedule.
   *
   * @param UltimateCronJob $job
   *   The job to check schedule for.
   *
   * @return bool
   *   TRUE if job is behind its schedule.
   */
  public abstract function isBehind($job);

}

/**
 * Abstract class for Ultimate Cron launchers.
 *
 * A launcher is responsible for locking and launching/running a job.
 *
 * Abstract methods:
 *   lock($job)
 *     - Lock a job. This method must return the lock_id on success
 *       or FALSE on failure.
 *
 *   unlock($lock_id, $manual = FALSE)
 *     - Release a specific lock id. If $manual is set, then the release
 *       was triggered manually by a user.
 *
 *   isLocked($job)
 *     - Check if a job is locked. This method must return the current
 *     - lock_id for the given job, or FALSE if it is not locked.
 *
 *   launch($job)
 *     - This method launches/runs the given job. This method must handle
 *       the locking of job before launching it. Returns TRUE on successful
 *       launch, FALSE if not.
 *
 * Important methods:
 *   isLockedMultiple($jobs)
 *     - Check locks for multiple jobs. Each launcher should implement an
 *       optimized version of this method if possible.
 *
 *   launchJobs($jobs)
 *     - Launches the jobs provided to it. A default implementation of this
 *       exists, but can be overridden. It is assumed that this function
 *       checks the jobs schedule before launching and that it also handles
 *       locking wrt concurrency for the launcher itself.
 *
 *   launchPoorman()
 *     - Launches all scheduled jobs via the proper launcher for each jobs.
 *       This method only needs to be implemented if the launcher wishes to
 *       provide a poormans cron launching mechanism. It is assumed that
 *       the poormans cron launcher handles locking wrt concurrency, etc.
 */
abstract class UltimateCronLauncher extends UltimateCronPlugin {

  /**
   * Default settings.
   */
  public function defaultSettings() {
    return array();
  }

  /**
   * Lock job.
   *
   * @param UltimateCronJob $job
   *   The job to lock.
   *
   * @return string
   *   Lock ID or FALSE.
   */
  public abstract function lock($job);

  /**
   * Unlock a lock.
   *
   * @param string $lock_id
   *   The lock id to unlock.
   * @param bool $manual
   *   Whether this is a manual unlock or not.
   *
   * @return bool
   *   TRUE on successful unlock.
   */
  public abstract function unlock($lock_id, $manual = FALSE);

  /**
   * Check if a job is locked.
   *
   * @param UltimateCronJob $job
   *   The job to check.
   *
   * @return string
   *   Lock ID of the locked job, FALSE if not locked.
   */
  public abstract function isLocked($job);

  /**
   * Launch job.
   *
   * @param UltimateCronJob $job
   *   The job to launch.
   *
   * @return bool
   *   TRUE on successful launch.
   */
  public abstract function launch($job);

  /**
   * Fallback implementation of multiple lock check.
   *
   * Each launcher should implement an optimized version of this method
   * if possible.
   *
   * @param array $jobs
   *   Array of UltimateCronJob to check.
   *
   * @return array
   *   Array of lock ids, keyed by job name.
   */
  public function isLockedMultiple($jobs) {
    $lock_ids = array();
    foreach ($jobs as $name => $job) {
      $lock_ids[$name] = $this
        ->isLocked($job);
    }
    return $lock_ids;
  }

  /**
   * Run the job.
   *
   * @param UltimateCronJob $job
   *   The job to run.
   */
  public function run($job) {

    // Prevent session information from being saved while cron is running.
    $original_session_saving = drupal_save_session();
    drupal_save_session(FALSE);

    // Force the current user to anonymous to ensure consistent permissions on
    // cron runs.
    $original_user = $GLOBALS['user'];
    $GLOBALS['user'] = drupal_anonymous_user();
    $php_self = NULL;
    try {

      // Signal to whomever might be listening, that we're cron!
      // @investigate Is this safe? (He asked knowingly ...)
      $php_self = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : '';
      $_SERVER['PHP_SELF'] = 'cron.php';
      $job
        ->invoke();

      // Restore state.
      $_SERVER['PHP_SELF'] = $php_self;
    } catch (Throwable $e) {

      // Restore state.
      if (isset($php_self)) {
        $_SERVER['PHP_SELF'] = $php_self;
      }
      ultimate_cron_watchdog_throwable('ultimate_cron', $e, 'Error running @name: @error', array(
        '@name' => $job->name,
        '@error' => (string) $e,
      ), WATCHDOG_ERROR);
    } catch (Exception $e) {

      // Restore state.
      if (isset($php_self)) {
        $_SERVER['PHP_SELF'] = $php_self;
      }
      watchdog_exception('ultimate_cron', $e, 'Error running @name: @error', array(
        '@name' => $job->name,
        '@error' => (string) $e,
      ), WATCHDOG_ERROR);
    }

    // Restore the user.
    $GLOBALS['user'] = $original_user;
    drupal_save_session($original_session_saving);
  }

  /**
   * Default implementation of jobs launcher.
   *
   * @param array $jobs
   *   Array of UltimateCronJob to launch.
   */
  public function launchJobs($jobs) {
    foreach ($jobs as $job) {
      if ($job
        ->isScheduled()) {
        $job
          ->launch();
      }
    }
  }

  /**
   * Format running state.
   */
  public function formatRunning($job) {
    $file = drupal_get_path('module', 'ultimate_cron') . '/icons/hourglass.png';
    $status = theme('image', array(
      'path' => $file,
    ));
    $title = t('running');
    return array(
      $status,
      $title,
    );
  }

  /**
   * Format unfinished state.
   */
  public function formatUnfinished($job) {
    $file = drupal_get_path('module', 'ultimate_cron') . '/icons/lock_open.png';
    $status = theme('image', array(
      'path' => $file,
    ));
    $title = t('unfinished but not locked?');
    return array(
      $status,
      $title,
    );
  }

  /**
   * Default implementation of formatProgress().
   *
   * @param UltimateCronJob $job
   *   Job to format progress for.
   *
   * @return string
   *   Formatted progress.
   */
  public function formatProgress($job, $progress) {
    $progress = $progress ? sprintf("(%d%%)", round($progress * 100)) : '';
    return $progress;
  }

  /**
   * Default implementation of initializeProgress().
   *
   * @param UltimateCronJob $job
   *   Job to initialize progress for.
   */
  public function initializeProgress($job) {
    $class = _ultimate_cron_get_class('progress');
    return $class::factory($job->name)
      ->setProgress(FALSE);
  }

  /**
   * Default implementation of finishProgress().
   *
   * @param UltimateCronJob $job
   *   Job to finish progress for.
   */
  public function finishProgress($job) {
    $class = _ultimate_cron_get_class('progress');
    return $class::factory($job->name)
      ->setProgress(FALSE);
  }

  /**
   * Default implementation of getProgress().
   *
   * @param UltimateCronJob $job
   *   Job to get progress for.
   *
   * @return float
   *   Progress for the job.
   */
  public function getProgress($job) {
    $class = _ultimate_cron_get_class('progress');
    return $class::factory($job->name)
      ->getProgress();
  }

  /**
   * Default implementation of getProgressMultiple().
   *
   * @param UltimateCronJob $jobs
   *   Jobs to get progresses for, keyed by job name.
   *
   * @return array
   *   Progresses, keyed by job name.
   */
  public function getProgressMultiple($jobs) {
    $class = _ultimate_cron_get_class('progress');
    return $class::getProgressMultiple(array_keys($jobs));
  }

  /**
   * Default implementation of setProgress().
   *
   * @param UltimateCronJob $job
   *   Job to set progress for.
   * @param float $progress
   *   Progress (0-1).
   */
  public function setProgress($job, $progress) {
    $class = _ultimate_cron_get_class('progress');
    return $class::factory($job->name)
      ->setProgress($progress);
  }

}

/**
 * Abstract class for Ultimate Cron loggers.
 *
 * Each logger must implement its own functions for getting/setting data
 * from the its storage backend.
 *
 * Abstract methods:
 *   load($name, $lock_id = NULL)
 *     - Load a log entry. If no $lock_id is provided, this method should
 *       load the latest log entry for $name.
 *
 * "Abstract" properties:
 *   $log_entry_class
 *     - The class name of the log entry class associated with this logger.
 */
abstract class UltimateCronLogger extends UltimateCronPlugin {
  public static $log_entries = NULL;
  public $log_entry_class = 'UltimateCronLogEntry';

  /**
   * Factory method for creating a new unsaved log entry object.
   *
   * @param string $name
   *   Name of the log entry (name of the job).
   *
   * @return UltimateCronLogEntry
   *   The log entry.
   */
  public function factoryLogEntry($name) {
    return new $this->log_entry_class($name, $this);
  }

  /**
   * Create a new log entry.
   *
   * @param string $name
   *   Name of the log entry (name of the job).
   * @param string $lock_id
   *   The lock id.
   * @param string $init_message
   *   (optional) The initial message for the log entry.
   *
   * @return UltimateCronLogEntry
   *   The log entry created.
   */
  public function create($name, $lock_id, $init_message = '', $log_type = ULTIMATE_CRON_LOG_TYPE_NORMAL) {
    $log_entry = new $this->log_entry_class($name, $this, $log_type);
    $log_entry->lid = $lock_id;
    $log_entry->start_time = microtime(TRUE);
    $log_entry->init_message = $init_message;
    $log_entry
      ->save();
    return $log_entry;
  }

  /**
   * Begin capturing messages.
   *
   * @param UltimateCronLogEntry $log_entry
   *   The log entry that should capture messages.
   */
  public function catchMessages($log_entry) {
    $class = get_class($this);
    if (!isset($class::$log_entries)) {
      $class::$log_entries = array();

      // Since we may already be inside a drupal_register_shutdown_function()
      // we cannot use that. Use PHPs register_shutdown_function() instead.
      ultimate_cron_register_shutdown_function(array(
        $class,
        'catchMessagesShutdownWrapper',
      ), $class);
    }
    $class::$log_entries[$log_entry->lid] = $log_entry;
  }

  /**
   * End message capturing.
   *
   * Effectively disables the shutdown function for the given log entry.
   *
   * @param UltimateCronLogEntry $log_entry
   *   The log entry.
   */
  public function unCatchMessages($log_entry) {
    $class = get_class($this);
    unset($class::$log_entries[$log_entry->lid]);
  }

  /**
   * Invoke loggers watchdog hooks.
   *
   * @param array $log_entry
   *   Watchdog log entry array.
   */
  public static final function hook_watchdog(array $log_entry) {
    if (self::$log_entries) {
      foreach (self::$log_entries as $log_entry_object) {
        $log_entry_object
          ->watchdog($log_entry);
      }
    }
  }

  /**
   * Log to ultimate cron logs only.
   *
   * @see watchdog()
   */
  public static final function log($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
    if (self::$log_entries) {
      foreach (self::$log_entries as $log_entry_object) {
        $log_entry_object
          ->log($type, $message, $variables, $severity, $link);
      }
    }
  }

  /**
   * Shutdown handler wrapper for catching messages.
   *
   * @param string $class
   *   The class in question.
   */
  public static function catchMessagesShutdownWrapper($class) {
    if ($class::$log_entries) {
      foreach ($class::$log_entries as $log_entry) {
        $log_entry->logger
          ->catchMessagesShutdown($log_entry);
      }
    }
  }

  /**
   * PHP shutdown function callback.
   *
   * Ensures that a log entry has been closed properly on shutdown.
   *
   * @param UltimateCronLogEntry $log_entry
   *   The log entry to close.
   */
  public function catchMessagesShutdown($log_entry) {
    $this
      ->unCatchMessages($log_entry);
    if ($log_entry->finished) {
      return;
    }

    // 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;
      }
      $log_entry
        ->log($log_entry->name, $message, array(), $severity);
    }
    $log_entry
      ->finish();
  }

  /**
   * Load latest log entry for multiple jobs.
   *
   * This is the fallback method. Loggers should implement an optimized
   * version if possible.
   */
  public function loadLatestLogEntries($jobs, $log_types) {
    $logs = array();
    foreach ($jobs as $job) {
      $logs[$job->name] = $job
        ->loadLatestLogEntry($log_types);
    }
    return $logs;
  }

  /**
   * Load a log.
   *
   * @param string $name
   *   Name of log.
   * @param string $lock_id
   *   Specific lock id.
   *
   * @return UltimateCronLogEntry
   *   Log entry
   */
  public abstract function load($name, $lock_id = NULL, $log_types = array(
    ULTIMATE_CRON_LOG_TYPE_NORMAL,
  ));

  /**
   * Get page with log entries for a job.
   *
   * @param string $name
   *   Name of job.
   * @param array $log_types
   *   Log types to get.
   * @param int $limit
   *   (optional) Number of log entries per page.
   *
   * @return array
   *   Log entries.
   */
  public abstract function getLogEntries($name, $log_types, $limit = 10);

}

/**
 * Abstract class for Ultimate Cron log entries.
 *
 * Each logger must implement its own log entry class based on this one.
 *
 * Abstract methods:
 *   save()
 *     - Save the actual log entry to whereever you please.
 *
 * Important properties:
 *   $log_entry_size
 *     - The maximum number of characters of the message in the log entry.
 */
abstract class UltimateCronLogEntry {
  public $lid = NULL;
  public $name = '';
  public $log_type = ULTIMATE_CRON_LOG_TYPE_NORMAL;
  public $uid = NULL;
  public $start_time = 0;
  public $end_time = 0;
  public $init_message = '';
  public $message = '';
  public $severity = -1;

  // Default 1MiB log entry.
  public $log_entry_size = 1048576;
  public $log_entry_fields = array(
    'lid',
    'uid',
    'log_type',
    'start_time',
    'end_time',
    'init_message',
    'message',
    'severity',
  );
  public $logger;
  public $job;
  public $finished = FALSE;

  /**
   * Constructor.
   *
   * @param string $name
   *   Name of log.
   * @param UltimateCronLogger $logger
   *   A logger object.
   */
  public function __construct($name, $logger, $log_type = ULTIMATE_CRON_LOG_TYPE_NORMAL) {
    $this->name = $name;
    $this->logger = $logger;
    $this->log_type = $log_type;
    if (!isset($this->uid)) {
      global $user;
      $this->uid = $user->uid;
    }
  }

  /**
   * Get current log entry data as an associative array.
   *
   * @return array
   *   Log entry data.
   */
  public function getData() {
    $result = array();
    foreach ($this->log_entry_fields as $field) {
      $result[$field] = $this->{$field};
    }
    return $result;
  }

  /**
   * Set current log entry data from an associative array.
   *
   * @param array $data
   *   Log entry data.
   */
  public function setData($data) {
    foreach ($this->log_entry_fields as $field) {
      if (array_key_exists($field, $data)) {
        $this->{$field} = $data[$field];
      }
    }
  }

  /**
   * Finish a log and save it if applicable.
   */
  public function finish() {
    if (!$this->finished) {
      $this->logger
        ->unCatchMessages($this);
      $this->end_time = microtime(TRUE);
      $this->finished = TRUE;
      $this
        ->save();
    }
  }

  /**
   * Implements hook_watchdog().
   *
   * Capture watchdog message and append it to the log entry.
   */
  public function watchdog(array $log_entry) {
    if (isset($log_entry['variables']) && is_array($log_entry['variables'])) {
      $this->message .= t($log_entry['message'], $log_entry['variables']) . "\n";
    }
    else {
      $this->message .= $log_entry['message'];
    }
    if ($this->severity < 0 || $this->severity > $log_entry['severity']) {
      $this->severity = $log_entry['severity'];
    }

    // Make sure that message doesn't become too big.
    if (mb_strlen($this->message) > $this->log_entry_size) {
      while (mb_strlen($this->message) > $this->log_entry_size) {
        $firstline = mb_strpos(rtrim($this->message, "\n"), "\n");
        if ($firstline === FALSE || $firstline == mb_strlen($this->message)) {

          // Only one line? That's a big line ... truncate it without mercy!
          $this->message = mb_substr($this->message, -$this->log_entry_size);
          break;
        }
        $this->message = substr($this->message, $firstline + 1);
      }
      $this->message = '.....' . $this->message;
    }
  }

  /**
   * Re-implementation of watchdog().
   *
   * @see watchdog()
   */
  public function log($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
    global $user, $base_root;

    // The user object may not exist in all conditions,
    // so 0 is substituted if needed.
    $user_uid = isset($user->uid) ? $user->uid : 0;

    // Prepare the fields to be logged.
    $log_entry = array(
      'type' => $type,
      'message' => $message,
      'variables' => $variables,
      'severity' => $severity,
      'link' => $link,
      'user' => $user,
      'uid' => $user_uid,
      'request_uri' => $base_root . request_uri(),
      'referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
      'ip' => ip_address(),
      // Request time isn't accurate for long processes, use time() instead.
      'timestamp' => time(),
    );
    $this
      ->watchdog($log_entry);
  }

  /**
   * Start catching watchdog messages.
   */
  public function catchMessages() {
    return $this->logger
      ->catchMessages($this);
  }

  /**
   * Stop catching watchdog messages.
   */
  public function unCatchMessages() {
    return $this->logger
      ->unCatchMessages($this);
  }

  /**
   * Get duration.
   */
  public function getDuration() {
    $duration = 0;
    if ($this->start_time && $this->end_time) {
      $duration = (int) ($this->end_time - $this->start_time);
    }
    elseif ($this->start_time) {
      $duration = (int) (microtime(TRUE) - $this->start_time);
    }
    return $duration;
  }

  /**
   * Format duration.
   */
  public function formatDuration() {
    $duration = $this
      ->getDuration();
    switch (TRUE) {
      case $duration >= 86400:
        $format = 'd H:i:s';
        break;
      case $duration >= 3600:
        $format = 'H:i:s';
        break;
      default:
        $format = 'i:s';
    }
    return isset($duration) ? gmdate($format, $duration) : t('N/A');
  }

  /**
   * Format start time.
   */
  public function formatStartTime() {
    return $this->start_time ? format_date((int) $this->start_time, 'custom', 'Y-m-d H:i:s') : t('Never');
  }

  /**
   * Format end time.
   */
  public function formatEndTime() {
    return $this->end_time ? t('Previous run finished @ @end_time', array(
      '@end_time' => format_date((int) $this->end_time, 'custom', 'Y-m-d H:i:s'),
    )) : '';
  }

  /**
   * Format user.
   */
  public function formatUser() {
    $username = t('anonymous') . ' (0)';
    if ($this->uid) {
      $user = user_load($this->uid);
      $username = $user ? $user->name . " ({$user->uid})" : t('N/A');
    }
    return $username;
  }

  /**
   * Format initial message.
   */
  public function formatInitMessage() {
    if ($this->start_time) {
      return $this->init_message ? $this->init_message . ' ' . t('by') . ' ' . $this
        ->formatUser() : t('N/A');
    }
    else {
      $registered = variable_get('ultimate_cron_hooks_registered', array());
      return !empty($registered[$this->name]) ? t('Registered at @datetime', array(
        '@datetime' => format_date($registered[$this->name], 'custom', 'Y-m-d H:i:s'),
      )) : t('N/A');
    }
  }

  /**
   * Format severity.
   */
  public function formatSeverity() {
    switch ($this->severity) {
      case WATCHDOG_EMERGENCY:
      case WATCHDOG_ALERT:
      case WATCHDOG_CRITICAL:
      case WATCHDOG_ERROR:
        $file = 'misc/message-16-error.png';
        break;
      case WATCHDOG_WARNING:
        $file = 'misc/message-16-warning.png';
        break;
      case WATCHDOG_NOTICE:
        $file = 'misc/message-16-info.png';
        break;
      case WATCHDOG_INFO:
      case WATCHDOG_DEBUG:
      default:
        $file = 'misc/message-16-ok.png';
    }
    $status = theme('image', array(
      'path' => $file,
    ));
    $severity_levels = array(
      -1 => t('no info'),
    ) + watchdog_severity_levels();
    $title = $severity_levels[$this->severity];
    return array(
      $status,
      $title,
    );
  }

  /**
   * Save log entry.
   */
  public abstract function save();

}

/**
 * Base class for settings.
 *
 * There's nothing special about this plugin.
 */
class UltimateCronSettings extends UltimateCronPluginMultiple {

}

/**
 * Base class for tagged settings.
 *
 * Settings plugins using this as a base class, will only be available
 * to jobs having the same tag as the name of the plugin.
 */
class UltimateCronTaggedSettings extends UltimateCronSettings {

  /**
   * Only valid for jobs tagged with the proper tag.
   */
  public function isValid($job = NULL) {
    return $job ? in_array($this->name, $job->hook['tags']) : parent::isValid();
  }

}

Classes

Namesort descending Description
UltimateCronLauncher Abstract class for Ultimate Cron launchers.
UltimateCronLogEntry Abstract class for Ultimate Cron log entries.
UltimateCronLogger Abstract class for Ultimate Cron loggers.
UltimateCronPlugin This is the base class for all Ultimate Cron plugins.
UltimateCronPluginMultiple Class for handling multiple plugins.
UltimateCronScheduler Abstract class for Ultimate Cron schedulers.
UltimateCronSettings Base class for settings.
UltimateCronTaggedSettings Base class for tagged settings.