You are here

scheduler.module in Scheduler 5

File

scheduler.module
View source
<?php

define("SCHEDULER_DATE_FORMAT", 'Y-m-d H:i:s');

/**
 * Implementation of hook_help().
 */
function scheduler_help($section) {
  if ($section == 'admin/modules#description') {
    return t('A module to schedule when nodes are (un)published.');
  }
}

/**
 * Implementation of hook_perm().
 */
function scheduler_perm() {
  return array(
    'schedule (un)publishing of nodes',
    'administer scheduler',
  );
}

/**
 * Implementation of hook_menu().
 */
function scheduler_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'scheduler/cron',
      'type' => MENU_CALLBACK,
      'callback' => '_scheduler_run_cron',
      'access' => TRUE,
    );
    $items[] = array(
      'path' => 'scheduler/timecheck',
      'type' => MENU_CALLBACK,
      'callback' => '_scheduler_timecheck',
      'access' => user_access('schedule (un)publishing of nodes'),
    );
  }
  $items[] = array(
    'path' => 'admin/settings/scheduler',
    'title' => t('Scheduler module settings'),
    'callback' => 'drupal_get_form',
    'callback arguments' => 'scheduler_admin',
    'access' => user_access('administer scheduler'),
    'type' => MENU_NORMAL_ITEM,
  );
  $items[] = array(
    'path' => 'admin/content/node/scheduler',
    'type' => MENU_LOCAL_TASK,
    'title' => t('Scheduled'),
    'callback' => 'scheduler_list',
    'access' => user_access('administer nodes') && user_access('schedule (un)publishing of nodes'),
    'weight' => 10,
  );
  return $items;
}
function scheduler_admin() {
  $form['scheduler_date_format'] = array(
    '#type' => 'textfield',
    '#title' => t('Date format'),
    '#default_value' => variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT),
    '#size' => 20,
    '#maxlength' => 100,
    '#description' => t('The input format for the (un)scheduling time/date. See the date() function for formatting options: http://www.php.net/manual/en/function.date.php'),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_form_alter().
 */
function scheduler_form_alter($form_id, &$form) {

  //allow scheduling on a per-node-type basis
  if ('node_type_form' == $form_id) {
    $form['workflow']['scheduler'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable scheduled (un)publishing'),
      '#default_value' => variable_get('scheduler_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to enable scheduled (un)publishing for this node type.'),
    );
    $form['workflow']['scheduler_touch'] = array(
      '#type' => 'checkbox',
      '#title' => t('Alter published on time'),
      '#default_value' => variable_get('scheduler_touch_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to alter the published on time to match the scheduled time ("touch feature").'),
    );
  }
  else {
    if (isset($form['type']['#value']) && $form['type']['#value'] . '_node_form' == $form_id) {
      if (user_access('schedule (un)publishing of nodes')) {

        // if scheduling has been enabled for this node type
        if (variable_get('scheduler_' . $form['type']['#value'], 0) == 1) {

          // We add the additional validation function this way to preserve any existing validation function
          $form['#validate']['_scheduler_form_validate'] = array();

          //use JScalendar picker for dates if the module exists and is enabled
          $jscalendar = FALSE;
          if (module_exists('jscalendar')) {
            $jscalendar = TRUE;
          }
          $node = $form['#node'];

          //only load the values if we are viewing an existing node
          if ($node->nid > 0) {
            $defaults = db_fetch_object(db_query('SELECT publish_on, unpublish_on FROM {scheduler} WHERE nid = %d', $node->nid));
          }
          else {

            // init standard values
            $defaults = new StdClass();
            $defaults->publish_on = $defaults->unpublish_on = NULL;
          }
          $form['scheduler_settings'] = array(
            '#type' => 'fieldset',
            '#title' => t('Scheduling options'),
            '#collapsible' => TRUE,
            '#collapsed' => $defaults->publish_on != 0 || $defaults->unpublish_on != 0 ? FALSE : TRUE,
            '#weight' => 35,
          );
          $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
          $form['scheduler_settings']['publish_on'] = array(
            '#type' => 'textfield',
            '#title' => t('Publish on'),
            '#maxlength' => 25,
            '#default_value' => $defaults->publish_on ? format_date($defaults->publish_on, 'custom', $date_format) : '',
            '#description' => t('Format: %time. Leave blank to disable scheduled publishing.', array(
              '%time' => format_date(time(), 'custom', $date_format),
            )),
            '#attributes' => $jscalendar ? array(
              'class' => 'jscalendar',
            ) : array(),
            '#jscalendar_timeFormat' => _scheduler_get_jscalendar_time_format(),
            '#jscalendar_ifFormat' => _scheduler_date_format_in_strftime_syntax(),
          );
          $form['scheduler_settings']['unpublish_on'] = array(
            '#type' => 'textfield',
            '#title' => t('Unpublish on'),
            '#maxlength' => 25,
            '#default_value' => $defaults->unpublish_on ? format_date($defaults->unpublish_on, 'custom', $date_format) : '',
            '#description' => t('Format: %time. Leave blank to disable scheduled unpublishing.', array(
              '%time' => format_date(time(), 'custom', $date_format),
            )),
            '#attributes' => $jscalendar ? array(
              'class' => 'jscalendar',
            ) : array(),
            '#jscalendar_timeFormat' => _scheduler_get_jscalendar_time_format(),
            '#jscalendar_ifFormat' => _scheduler_date_format_in_strftime_syntax(),
          );
        }
      }
    }
  }
}

/*
 * Displays a list of nodes that are scheduled for (un)publication. This will
 * appear as a tab on the content admin page ('admin/content/node').
 */
function scheduler_list() {
  $header = array(
    array(
      'data' => t('Title'),
      'field' => 'n.title',
    ),
    array(
      'data' => t('Author'),
      'field' => 'u.name',
    ),
    array(
      'data' => t('Publish on'),
      'field' => 's.publish_on',
    ),
    array(
      'data' => t('Unpublish on'),
      'field' => 's.unpublish_on',
    ),
    array(
      'data' => t('Operations'),
    ),
  );

  // Default ordering
  if (!isset($_GET['order']) && !isset($_GET['sort'])) {
    $_GET['order'] = t('Publish on');
    $_GET['sort'] = 'ASC';
  }
  $sql = 'SELECT n.nid, n.uid, n.status, u.name, n.title, s.publish_on, s.unpublish_on FROM {scheduler} s LEFT JOIN {node} n ON s.nid = n.nid LEFT JOIN {users} u ON n.uid = u.uid' . tablesort_sql($header);
  $result = pager_query($sql, 50);
  while ($node = db_fetch_object($result)) {
    $rows[] = array(
      l($node->title, "node/{$node->nid}"),
      theme('username', $node),
      $node->publish_on ? format_date($node->publish_on) : '&nbsp;',
      $node->unpublish_on ? format_date($node->unpublish_on) : '&nbsp;',
      l(t('edit'), 'node/' . $node->nid . '/edit', array(), 'destination=admin/content/node/scheduler'),
    );
  }
  if (count($rows)) {
    if ($pager = theme('pager', NULL, 50, 0)) {
      $rows[] = array(
        array(
          'data' => $pager,
          'colspan' => 6,
        ),
      );
    }
    print theme('page', theme('table', $header, $rows));
  }
  else {
    print theme('page', t('There are no scheduled nodes.'));
  }
}
function _scheduler_date_format_in_strftime_syntax() {
  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
  $date_entities = array(
    'd',
    'H',
    'h',
    'm',
    'i',
    'a',
    'A',
    's',
    'y',
    'Y',
  );
  $strftime_replacements = array(
    '%d',
    '%H',
    '%I',
    '%m',
    '%M',
    '%p',
    '%p',
    '%S',
    '%y',
    '%Y',
  );
  return str_replace($date_entities, $strftime_replacements, $date_format);
}
function _scheduler_get_jscalendar_time_format() {
  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
  $result = strpos($date_format, stristr($date_format, "a"));
  return $result !== FALSE ? '12' : '24';
}

/**
 * Converts an english time string ('Y-m-d H:i:s') from the users timezone into an unix timestamp
 * @param string $str the time string ('Y-m-d H:i:s')
 * @return the time in unix timestamp representation (utc);
 * NULL, if $str is NULL, FALSE, empty, or contains only white spaces;
 * FALSE, if $str is malformed
 */
function _scheduler_strtotime($str) {
  if ($str && trim($str) != "") {
    $time = _scheduler_strptime(trim($str), variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT));
    if ($time !== FALSE) {

      // success
      $time -= _scheduler_get_user_timezone();
    }
  }
  else {

    // $str is empty
    $time = NULL;
  }
  return $time;
}

/**
 * Parse a time/date as UTC time
 *
 * @param string $date The string to parse
 * @param string $format The format used in $date. See date() (http://www.php.net/manual/en/function.date.php) 
 * specification for format options. Right now only dHhmiaAsyY are supported. 
 * @return the parsed time as a UTC timestamp
 * @see date()
 */
function _scheduler_strptime($date, $format) {

  # we need to build a regex pattern for the date format
  $date_entities = array(
    'd',
    'H',
    'h',
    'm',
    'i',
    'a',
    'A',
    's',
    'y',
    'Y',
  );
  $date_regex_replacements = array(
    '(\\d{2})',
    '(\\d{2})',
    '(\\d{2})',
    '(\\d{2})',
    '(\\d{2})',
    '([ap]m)',
    '([AP]M)',
    '(\\d{2})',
    '(\\d{2})',
    '(\\d{4})',
  );
  $custom_pattern = str_replace($date_entities, $date_regex_replacements, $format);
  if (!preg_match("#{$custom_pattern}#", $date, $value_matches)) {
    return FALSE;
  }
  if (!preg_match_all("/(\\w)/", $format, $entity_matches)) {
    return FALSE;
  }
  $results = array(
    'day' => 0,
    'hour' => 0,
    'month' => 0,
    'minute' => 0,
    'meridiem' => NULL,
    'second' => 0,
    'year' => 0,
  );
  $index = 1;
  foreach ($entity_matches[1] as $entity) {
    $value = intval($value_matches[$index]);
    switch ($entity) {
      case 'd':
        $results['day'] = $value;
        break;
      case 'H':
      case 'h':
        $results['hour'] = $value;
        break;
      case 'm':
        $results['month'] = $value;
        break;
      case 'i':
        $results['minute'] = $value;
        break;
      case 'a':
      case 'A':
        $results['meridiem'] = $value_matches[$index];
        break;
      case 's':
        $results['second'] = $value;
        break;
      case 'y':
      case 'Y':
        $results['year'] = $value;
        break;
    }
    $index++;
  }
  if (strncasecmp($results['meridiem'], "pm", 2) == 0 && $results['hour'] <= 12) {
    $results['hour'] += 12;
  }
  $time = gmmktime($results['hour'], $results['minute'], $results['second'], $results['month'], $results['day'], $results['year']);
  return $time;
}

/**
 * Gets the users timezone if configurable timezones are enabled or otherwise the default timezone of the site
 *
 * @return the offset of the users timezone in seconds
 */
function _scheduler_get_user_timezone() {
  global $user;
  $timezone = variable_get('date_default_timezone', 0);
  if (variable_get('configurable_timezones', 1) == 1 && strlen($user->timezone)) {
    $timezone = $user->timezone;
  }
  return $timezone;
}

/**
 * Implementation of hook_form_validate()
 * Validate the input of the publish and unpublish date fields
 */
function _scheduler_form_validate($form_id, $form) {
  $publishtime = NULL;
  $unpublishtime = NULL;
  if (isset($form['publish_on'])) {
    $publishtime = _scheduler_strtotime($form['publish_on']);
    if ($publishtime === FALSE) {
      form_set_error('publish_on', t('The entered publication date is invalid.'));
    }
  }
  if (isset($form['unpublish_on'])) {
    $unpublishtime = _scheduler_strtotime($form['unpublish_on']);
    if ($unpublishtime === FALSE) {
      form_set_error('unpublish_on', t('The entered expiration date is invalid.'));
    }
  }
  if (is_integer($publishtime) && is_integer($unpublishtime) && $unpublishtime < $publishtime) {
    form_set_error('unpublish_on', t("The expiration date is before publication date."));
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function scheduler_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {

  // Run $op == load for any user.
  if ($op == 'load') {
    if (isset($node->nid) && $node->nid && variable_get('scheduler_' . $node->type, 0) == 1) {
      $result = db_query('SELECT * FROM {scheduler} WHERE nid = %d', $node->nid);
      if ($result) {
        $row = db_fetch_array($result);
        if (isset($row['nid'])) {
          unset($row['nid']);
          $node->publish_on = $row['publish_on'];
          $node->unpublish_on = $row['unpublish_on'];
          $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
          $row['published'] = $row['publish_on'] ? format_date($row['publish_on'], 'custom', $date_format) : NULL;
          $row['unpublished'] = $row['unpublish_on'] ? format_date($row['unpublish_on'], 'custom', $date_format) : NULL;
          $node->scheduler = $row;
        }
      }
    }
  }
  elseif (user_access('schedule (un)publishing of nodes')) {
    switch ($op) {
      case 'view':
        if ($page && $node->unpublish_on) {
          $unavailable_after = date("d-M-Y H:i:s T", $node->unpublish_on);
          drupal_set_html_head('<meta name="googlebot" content="unavailable_after: ' . $unavailable_after . '">');
        }
        break;
      case 'submit':

        //adjust the entered times for timezone consideration
        $node->publish_on = _scheduler_strtotime($node->publish_on);
        $node->unpublish_on = _scheduler_strtotime($node->unpublish_on);

        // right before we save the node, we need to check if a "publish on" value has been set
        // if it has been set, we want to make sure the node is unpublished
        // since it will be published at a later date (but only if the value is in the future.
        if ($node->publish_on != '' && is_numeric($node->publish_on) && $node->publish_on > time()) {
          $node->status = 0;
        }
        break;
      case 'insert':

        //only insert into database if we need to (un)publish this node at some date
        if (isset($node->nid) && $node->nid && $node->publish_on != NULL || $node->unpublish_on != NULL) {
          db_query('INSERT INTO {scheduler} (nid, publish_on, unpublish_on) VALUES (%d, %d, %d)', $node->nid, $node->publish_on, $node->unpublish_on);
        }
        break;
      case 'update':
        if (isset($node->nid) && $node->nid) {
          $exists = db_result(db_query('SELECT nid FROM {scheduler} WHERE nid = %d', $node->nid));

          // if this node has already been scheduled, update its record
          if ($exists) {

            // only update database if we need to (un)publish this node at some date
            // otherwise the user probably cleared out the (un)publish dates so we should remove the record
            if ($node->publish_on != NULL || $node->unpublish_on != NULL) {
              db_query('UPDATE {scheduler} SET publish_on = %d, unpublish_on = %d WHERE nid = %d', $node->publish_on, $node->unpublish_on, $node->nid);
            }
            else {
              db_query('DELETE FROM {scheduler} WHERE nid = %d', $node->nid);
            }
          }
          else {
            if ($node->publish_on != NULL || $node->unpublish_on != NULL) {
              db_query('INSERT INTO {scheduler} (nid, publish_on, unpublish_on) VALUES (%d, %d, %d)', $node->nid, $node->publish_on, $node->unpublish_on);
            }
          }
        }
        break;
      case 'delete':
        if (isset($node->nid) && $node->nid) {
          db_query('DELETE FROM {scheduler} WHERE nid = %d', $node->nid);
        }
        break;
    }
  }
}

/**
 * Implementation of hook_cron().
 */
function scheduler_cron() {
  $clear_cache = FALSE;

  //if the time now is greater than the time to publish a node, publish it
  $nodes = db_query('SELECT * FROM {scheduler} s LEFT JOIN {node} n ON s.nid = n.nid WHERE n.status = 0 AND s.publish_on > 0 AND s.publish_on < %d', time());
  while ($node = db_fetch_object($nodes)) {
    $n = node_load($node->nid);
    $n->changed = $node->publish_on;
    if (variable_get('scheduler_touch_' . $n->type, 0) == 1) {
      $n->created = $node->publish_on;
    }
    $n->status = 1;
    node_save($n);

    //if this node is not to be unpublished, then we can delete the record
    if ($n->unpublish_on == 0) {
      db_query('DELETE FROM {scheduler} WHERE nid = %d', $n->nid);
    }
    else {
      db_query('UPDATE {scheduler} SET publish_on = 0 WHERE nid = %d', $n->nid);
    }

    //invoke scheduler API
    _scheduler_scheduler_api($n, 'publish');
    watchdog('content', t('@type: scheduled publishing of %title.', array(
      '@type' => $n->type,
      '%title' => $n->title,
    )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $n->nid));
    $clear_cache = TRUE;
  }

  //if the time is greater than the time to unpublish a node, unpublish it
  $nodes = db_query('SELECT * FROM {scheduler} s LEFT JOIN {node} n ON s.nid = n.nid WHERE n.status = 1 AND s.unpublish_on > 0 AND s.unpublish_on < %d', time());
  while ($node = db_fetch_object($nodes)) {

    //if this node is to be unpublished, we can update the node and remove the record since it can't be republished
    $n = node_load($node->nid);
    $n->changed = $node->unpublish_on;
    $n->status = 0;
    node_save($n);
    db_query('DELETE FROM {scheduler} WHERE nid = %d', $n->nid);

    //invoke scheduler API
    _scheduler_scheduler_api($n, 'unpublish');
    watchdog('content', t('@type: scheduled unpublishing of %title.', array(
      '@type' => $n->type,
      '%title' => $n->title,
    )), WATCHDOG_NOTICE, l(t('view'), 'node/' . $n->nid));
    $clear_cache = TRUE;
  }
  if ($clear_cache) {

    // clear the cache so an anonymous poster can see the node being published or unpublished
    cache_clear_all();
  }
}
function _scheduler_run_cron() {
  scheduler_cron();
  if (ob_get_level() > 0) {
    ob_clean();
  }
  exit;
}

/**
 * Scheduler API to perform actions when nodes are (un)published
 *
 * @param $node
 *  The node object
 * @param $action
 *  The action being performed, either "publish" or "unpublish"
 */
function _scheduler_scheduler_api($node, $action) {
  foreach (module_implements('scheduler_api') as $module) {
    $function = $module . '_scheduler_api';
    $function($node, $action);
  }
}
function _scheduler_timecheck() {
  $now = time();
  return theme('scheduler_timecheck', $now);
}
function theme_scheduler_timecheck($now) {
  drupal_set_title(t('Scheduler OS time check'));
  $t_options = array(
    '%time' => gmdate("Y-m-d H:i:s", $now),
    '%lt' => date("Y-m-d H:i:s P", $now),
  );
  return t('Your server reports the UTC time as %time and "localtime" as %lt.', $t_options) . '<p />' . t('If all is well with your server\'s time configuration UTC should match <a target="_blank" href="http://wwp.greenwichmeantime.com/">UTC London Time</a> and the localtime should be the time where you are.') . '<p />' . t('If this is not the case please have your Unix System Administrator fix your servers time/date configuration.');
}

/**
 * Implementation of "contrib module views" hook_views_tables()
 */
function scheduler_views_tables() {
  $tables['scheduler'] = array(
    'name' => 'scheduler',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid',
      ),
      'right' => array(
        'field' => 'nid',
      ),
    ),
    'fields' => array(
      'publish_on' => array(
        'name' => t('Scheduler: publish on'),
        'help' => t('Date/time on which the article will be automatically published'),
        'sortable' => TRUE,
        'handler' => views_handler_field_dates(),
        'option' => 'string',
      ),
      'unpublish_on' => array(
        'name' => t('Scheduler: unpublish on'),
        'help' => t('Date/time on which the article will be automatically un-published'),
        'sortable' => TRUE,
        'handler' => views_handler_field_dates(),
        'option' => 'string',
      ),
    ),
    'sorts' => array(
      'publish_on' => array(
        'name' => t('Scheduler: publish on'),
        'help' => t('Sort by the date the article will be automatically published.'),
      ),
      'unpublish_on' => array(
        'name' => t('Scheduler: unpublish on'),
        'help' => t('Sort by the date/time on which the article will be automatically un-published.'),
      ),
    ),
    'filters' => array(
      'publish_on' => array(
        'name' => t('Scheduler: publish on'),
        'help' => t('This filter allows nodes to be filtered by the date they will be automatically published.') . ' ' . views_t_strings('filter date'),
        'operator' => 'views_handler_operator_gtlt',
        'value' => views_handler_filter_date_value_form(),
        'handler' => 'views_handler_filter_timestamp',
        'option' => 'string',
      ),
      'unpublish_on' => array(
        'name' => t('Scheduler: unpublish on'),
        'help' => t('This filter allows nodes to be filtered by the date they will be automatically un-published.') . ' ' . views_t_strings('filter date'),
        'operator' => 'views_handler_operator_gtlt',
        'value' => views_handler_filter_date_value_form(),
        'handler' => 'views_handler_filter_timestamp',
        'option' => 'string',
      ),
    ),
  );
  return $tables;
}

Functions

Namesort descending Description
scheduler_admin
scheduler_cron Implementation of hook_cron().
scheduler_form_alter Implementation of hook_form_alter().
scheduler_help Implementation of hook_help().
scheduler_list
scheduler_menu Implementation of hook_menu().
scheduler_nodeapi Implementation of hook_nodeapi().
scheduler_perm Implementation of hook_perm().
scheduler_views_tables Implementation of "contrib module views" hook_views_tables()
theme_scheduler_timecheck
_scheduler_date_format_in_strftime_syntax
_scheduler_form_validate Implementation of hook_form_validate() Validate the input of the publish and unpublish date fields
_scheduler_get_jscalendar_time_format
_scheduler_get_user_timezone Gets the users timezone if configurable timezones are enabled or otherwise the default timezone of the site
_scheduler_run_cron
_scheduler_scheduler_api Scheduler API to perform actions when nodes are (un)published
_scheduler_strptime Parse a time/date as UTC time
_scheduler_strtotime Converts an english time string ('Y-m-d H:i:s') from the users timezone into an unix timestamp
_scheduler_timecheck

Constants