<?php

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

 /**
  * Implementation of hook_permission().
  */
function scheduler_permission() {
  return array(
    'administer scheduler' => array(
      'title' => t('Administer scheduler'),
    ),
    'schedule (un)publishing of nodes' => array(
      'title' => t('Schedule content publication'),
      'description' => t('Allows users to set a start and end time for content publication'),
    ),
  );
}

/**
 * Implementation of hook_menu().
 */
function scheduler_menu() {
  $items = array();
  $items['scheduler/cron'] = array(
    'title' => 'Light weight cron handler',
    'description' => 'A light weight cron handler to allow more frequent runs of Schedulers internal cron system',
    'page callback' => '_scheduler_run_cron',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['scheduler/timecheck'] = array(
    'title' => 'Test your servers UTC clock',
    'description' => 'Allows site admin to check their servers internal clock',
    'page callback' => '_scheduler_timecheck',
    'access arguments' => array('access administration pages'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/content/scheduler'] = array(
      'title' => 'Scheduler module settings',
    'description' => 'Allows site admins to configure scheduler.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('scheduler_admin'),
    'access arguments' => array('administer scheduler'),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/content/scheduler/default'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 5,
  );
  $items['admin/config/content/scheduler/cron'] = array(
    'title' => 'Lightweight Cron',
    'description' => 'A lightweight cron handler to allow more frequent runs of Schedulers internal cron system.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_scheduler_lightweight_cron'),
    'access callback' => TRUE,
    'access arguments' => array('administer scheduler'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );
  $items['admin/config/content/scheduler/timecheck'] = array(
    'title' => 'Time Check',
    'description' => 'Allows site admin to check their servers internal clock',
    'page callback' => '_scheduler_timecheck',
    'access arguments' => array('access administration pages'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 15,
  );
  $items['admin/content/scheduler'] = array(
    'type' => MENU_LOCAL_TASK,
    'title' => 'Scheduled',
    'page callback' => 'scheduler_list',
    'access callback' => 'scheduler_list_access_callback',
    'description' => 'Display a list of scheduled nodes',
    'file' => NULL,
  );
  return $items;
}

/**
 * Return the users access to the scheduler list page. Separate function required because of the two access values to be checked.
 */
function scheduler_list_access_callback() {
  return user_access('administer nodes') && user_access('schedule (un)publishing of nodes');
}

/**
 * Implements hook_help().
 */
function scheduler_help($section) {
  $output = '';
  switch ($section) {
    case 'admin/config/content/scheduler':
      $output = '<p>'
                . t('Some scheduler actions are set for each different content type. These are accessed via the <a href="@link">admin content type</a> list.' , array('@link' => url('admin/structure/types')))
                . '<br/>' . t('The options and settings below are common to all content types.') . '</p>';
      break;
    case 'admin/config/content/scheduler/cron':
      $output = '<p>' . t("When you have set up Drupal's standard crontab job cron.php then Scheduler will be executed during each cron run. "
        . "However, if you would like finer granularity to scheduler, but don't want to run Drupal's cron more often then you can use the "
        . "lightweight cron handler provided by Scheduler. This is an independent cron job which only runs the scheduler process and does not "
        . "execute any cron tasks defined by Drupal core or any other modules.") . '</p>'
        . '<p>' . t("Scheduler's cron is at /scheduler/cron and a sample crontab entry to run scheduler every minute might look like:") . '</p>'
        . '<code>* * * * * /usr/bin/wget -O - -q "http://example.com/scheduler/cron"</code>'
        . '<p>or</p>'
        . '<code>* * * * * curl "http://example.com/scheduler/cron" > /dev/null 2>&1</code><br/><br/>';
      break;
    case 'admin/help#scheduler':
      // This is shown at the top of admin/help/scheduler.
      $output = '<p>' . t('The Scheduler module provides the functionality for automatic publishing and unpublishing of nodes at specified future dates.') . '</p>'
                . '<p>' . t('You can read more in the <a href="@readme">readme.txt</a> file.', array('@readme' => $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'scheduler') . '/README.txt')) . '</p>';
      break;
    default:
  }
  return $output;
}

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' => 20,
    '#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 (only the following format characters are supported (don\'t use \'G\', \'a\' or \'A\' with Date Popup): djmnyYhHgGisaA)'),
  );

  $form['scheduler_field_type'] = array(
    '#type' => 'radios',
    '#title' => t('Field type'),
    '#default_value' => variable_get('scheduler_field_type', 'date_popup'),
    '#options' => array(
      'textfield' => t('Standard text field'),
      'date_popup' => t('Date Popup field'),
    ),
    '#description' => t("If the Date module's Date Popup module is enabled you may use the popup calendar for your field type."),
  );

  if (!module_exists('date_popup')) {
    $form['scheduler_field_type']['#default_value'] = 'textfield';
    $form['scheduler_field_type']['#disabled'] = TRUE;
  }
  else {
    $acceptable = implode(date_popup_time_formats(), ', ');
    $form['scheduler_date_format']['#description'] .= t('If you are using Date Popup, the following time formats are supported: !formats', array('!formats' => $acceptable));
  }

  $form['scheduler_extra_info'] = array(
    '#type' => 'textarea',
    '#title' => t('Extra Info'),
    '#default_value' => variable_get('scheduler_extra_info', ''),
    '#description' => t('The text entered into this field will be displayed above the scheduling fields in the node edit form.'),
  );
  return system_settings_form($form);
}

function scheduler_admin_validate($form, &$form_state) {
  if ($form_state['values']['scheduler_field_type'] == 'date_popup') {
    $format = $form_state['values']['scheduler_date_format'];
    $time_format = date_limit_format($format, array('hour', 'minute', 'second'));
    $acceptable = date_popup_time_formats();

    if (!in_array($time_format, $acceptable)) {
      form_set_error('scheduler_date_format', t('The Date Popup module only accepts the following formats: !formats', array('!formats' => implode($acceptable, ', '))));
    }
  }
}

/**
 * Do we use the date_popup for date/time selection?
 */
function _scheduler_use_date_popup() {
  return module_exists('date_popup') && variable_get('scheduler_field_type', 'date_popup') == 'date_popup';
}

/**
 * Implements hook_form_node_type_form_alter().
 */
function scheduler_form_node_type_form_alter(&$form, $form_state) {
  drupal_add_css(drupal_get_path('module', 'scheduler') .'/scheduler.css');
  $form['scheduler'] = array(
    '#type' => 'fieldset',
    '#title' => 'Scheduler settings',
    '#weight' => 35,
    '#group' => 'additional_settings',
    '#attached' => array(
      'js' => array(
        'vertical-tabs' => drupal_get_path('module', 'scheduler') . "/scheduler_vertical_tabs.js"
      ),
    ),
  );

  $form['scheduler']['publish'] = array(
      '#type' => 'fieldset',
      '#title' => 'Publishing settings',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 1,
      '#group' => 'scheduler',
  );
  $form['scheduler']['publish']['scheduler_publish_enable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable scheduled publishing'),
      '#default_value' => variable_get('scheduler_publish_enable_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to enable scheduled publishing for this node type.')
  );
  $form['scheduler']['publish']['scheduler_publish_touch'] = array(
      '#type' => 'checkbox',
      '#title' => t('Alter published on time'),
      '#default_value' => variable_get('scheduler_publish_touch_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to alter the published on time to match the scheduled time ("touch feature").')
  );
  $form['scheduler']['publish']['scheduler_publish_required'] = array(
      '#type' => 'checkbox',
      '#title' => t('Publishing date/time is required.'),
      '#default_value' => variable_get('scheduler_publish_required_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to if scheduled publishing is required (e.g. the user must enter a date/time).')
  );
  $form['scheduler']['publish']['scheduler_publish_revision'] = array(
      '#type' => 'checkbox',
      '#title' => t('Create a new revision on publishing'),
      '#default_value' => variable_get('scheduler_publish_revision_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box if you want a new revision created when publishing.')
  );
  $form['scheduler']['unpublish'] = array(
      '#type' => 'fieldset',
      '#title' => 'Unpublishing settings',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => 2,
      '#group' => 'scheduler',
  );
  $form['scheduler']['unpublish']['scheduler_unpublish_enable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable scheduled unpublishing'),
      '#default_value' => variable_get('scheduler_unpublish_enable_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to enable scheduled unpublishing for this node type.')
  );
  $form['scheduler']['unpublish']['scheduler_unpublish_required'] = array(
      '#type' => 'checkbox',
      '#title' => t('Unpublishing date/time is required.'),
      '#default_value' => variable_get('scheduler_unpublish_required_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box to if scheduled unpublishing is required (e.g. the user must enter a date/time).')
  );
  $form['scheduler']['unpublish']['scheduler_unpublish_revision'] = array(
      '#type' => 'checkbox',
      '#title' => t('Create a new revision on unpublishing'),
      '#default_value' => variable_get('scheduler_unpublish_revision_' . $form['#node_type']->type, 0),
      '#description' => t('Check this box if you want a new revision created when unpublishing.')
  );
}

/**
 * Implementation of hook_form_alter().
 */
function scheduler_form_alter(&$form, $form_state) {
  // Is this a node form and scheduling has been enabled for this node type
  if (!empty($form['#node_edit_form']) && user_access('schedule (un)publishing of nodes')) {
    $publishing_enabled = variable_get('scheduler_publish_enable_'. $form['type']['#value'], 0) == 1;
    $unpublishing_enabled = variable_get('scheduler_unpublish_enable_'. $form['type']['#value'], 0) == 1;

    // if scheduling has been enabled for this node type
    if ($publishing_enabled || $unpublishing_enabled) {

      $node = $form['#node'];

      $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
      $use_date_popup = _scheduler_use_date_popup();

      $internal_date_format = $use_date_popup ? SCHEDULER_DATE_FORMAT : $date_format;

      // if this is a preview then get the values from the form, not the db
      if (isset($form_state['values']['op']) && $form_state['values']['op'] == t('Preview')) {
        $defaults = new StdClass;
        $defaults->publish_on = $publishing_enabled ? $form_state['values']['publish_on'] : NULL;
        $defaults->unpublish_on = $unpublishing_enabled ? $form_state['values']['unpublish_on'] : NULL;
      }
      elseif (isset($node->nid) && $node->nid > 0) {
        // Load the values from the db if we are viewing an existing node.
        $query = db_select('scheduler', 's');
        $query->fields('s', array('publish_on', 'unpublish_on'));
        $query->condition('s.nid', $node->nid, '=');
        $defaults = $query->execute()->fetchObject();
      }
      else {
        // init standard values
        $defaults = new StdClass;
        $defaults->publish_on = $defaults->unpublish_on = NULL;
      }

      // if there is a text value then convert it to a unix timestamp
      if (isset($defaults->publish_on) && $defaults->publish_on && !is_numeric($defaults->publish_on)) {
        $defaults->publish_on = _scheduler_strtotime($defaults->publish_on);
      }
      if (isset($defaults->unpublish_on) && $defaults->unpublish_on && !is_numeric($defaults->unpublish_on)) {
        $defaults->unpublish_on = _scheduler_strtotime($defaults->unpublish_on);
      }

      $publishing_required = variable_get('scheduler_publish_required_'. $form['type']['#value'], 0) == 1;
      $unpublishing_required = variable_get('scheduler_unpublish_required_'. $form['type']['#value'], 0) == 1;

      $fieldset_extended = (
        (isset($defaults->publish_on) && $defaults->publish_on != 0)
        || (isset($defaults->unpublish_on) && $defaults->unpublish_on != 0)
        || $publishing_required
        || $unpublishing_required
      );

      $form['scheduler_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('Scheduling options'),
        '#collapsible' => TRUE,
        '#collapsed' => !$fieldset_extended,
        '#weight' => 35,
        '#group' => 'additional_settings',
        '#attached' => array(
          'js' => array(
            'vertical-tabs' => drupal_get_path('module', 'scheduler') . "/scheduler_vertical_tabs.js"
          ),
        )
      );

      $extra_info = variable_get('scheduler_extra_info', '');
      if ($extra_info && $extra_info != '') {
        $form['scheduler_settings']['extra_info'] = array(
          '#type' => 'item',
          '#markup' => $extra_info,
        );
      }

      $description_format = t('Format: %time.', array('%time' => format_date(time(), 'custom', $date_format)));
      if ($publishing_enabled) {
        $description_blank = '';
        if (!$publishing_required) {
          $description_blank .= ' '.t('Leave blank to disable scheduled publishing.');
        }

        $form['scheduler_settings']['publish_on'] = array(
          '#type' => 'textfield',
          '#title' => t('Publish on'),
          '#maxlength' => 25,
          '#required' => $publishing_required,
          '#default_value' => isset($defaults->publish_on) && $defaults->publish_on ? format_date($defaults->publish_on, 'custom', $internal_date_format) : '',
          '#description' => $description_format.$description_blank,
        );
      }

      if ($unpublishing_enabled) {
        $description_blank = '';
        if (!$unpublishing_required) {
          $description_blank .= ' '.t('Leave blank to disable scheduled unpublishing.');
        }
        $form['scheduler_settings']['unpublish_on'] = array(
          '#type' => 'textfield',
          '#title' => t('Unpublish on'),
          '#maxlength' => 25,
          '#required' => $unpublishing_required,
          '#default_value' => isset($defaults->unpublish_on) && $defaults->unpublish_on ? format_date($defaults->unpublish_on, 'custom', $internal_date_format) : '',
          '#description' => $description_format.$description_blank,
        );
      }

      if ($use_date_popup) {
        // Make this a popup calendar widget if Date Popup module is enabled.
        if ($publishing_enabled) {
          $form['scheduler_settings']['publish_on']['#type'] = 'date_popup';
          $form['scheduler_settings']['publish_on']['#date_format'] = $date_format;
          $form['scheduler_settings']['publish_on']['#date_year_range'] = '0:+10';
          if (!$publishing_required) $form['scheduler_settings']['publish_on']['#description'] = t('Leave blank to disable scheduled publishing.');
          unset($form['scheduler_settings']['publish_on']['#maxlength']);
        }
        if ($unpublishing_enabled) {
          $form['scheduler_settings']['unpublish_on']['#type'] = 'date_popup';
          $form['scheduler_settings']['unpublish_on']['#date_format'] = $date_format;
          $form['scheduler_settings']['unpublish_on']['#date_year_range'] = '0:+10';
          if (!$unpublishing_required) $form['scheduler_settings']['unpublish_on']['#description'] = t('Leave blank to disable scheduled unpublishing.');
          unset($form['scheduler_settings']['unpublish_on']['#maxlength']);
        }
      }
    }
  }
}

/*
 * Displays a list of nodes that are scheduled for (un)publication. This will
 * appear as a tab on the content admin page ('admin/content').
 */
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';
  }

  $query = db_select('scheduler', 's')->extend('PagerDefault');
  $query->limit(50);
  $query->addJoin('LEFT ', 'node', 'n', 's.nid = n.nid');
  $query->addJoin('LEFT ', 'users', 'u', 'u.uid = n.uid');
  $query->fields('s', array('publish_on', 'unpublish_on'));
  $query->fields('n', array('nid', 'uid', 'status', 'title'));
  $query->addField('u', 'name');
  $query = $query
    ->extend('TableSort')
    ->orderByHeader($header);
  $result = $query->execute();

  $rows = array();

  foreach ($result as $node) {
    $rows[] = array(
      l($node->title, "node/$node->nid"),
        theme('username', array('account' => $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) && ($pager = theme('pager'))) {
    $rows[] = array(
      array(
        'data' => $pager,
        'colspan' => 6,
      ),
    );
  }
  $build['scheduler_list'] = array(
    '#theme' => 'table',
    '#header' => $header,
    '#rows' => $rows,
    '#empty' => t('There are no scheduled nodes.'),
  );
  return $build;
}

/**
 * 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) != "" ) {
    $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);

    if (_scheduler_use_date_popup()) {
      $date_format = SCHEDULER_DATE_FORMAT;
    }
    $time = _scheduler_strptime(trim($str), $date_format);
  }
  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', 'n', 'j', 'g', 'G');
  $date_regex_replacements = array('(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})', '([ap]m)', '([AP]M)', '(\d{2})', '(\d{2})', '(\d{4})', '(\d{1,2})', '(\d{1,2})', '(\d{1,2})', '(\d{1,2})');
  $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':
      case 'j':
        $results['day'] = $value;
        break;
      case 'H':
      case 'h':
      case 'g':
      case 'G':
        $results['hour'] = $value;
        break;
      case 'm':
      case 'n':
        $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;
  }
  if ((strncasecmp($results['meridiem'], "am", 2) == 0) && ($results['hour'] == 12)) {
    $results['hour'] -= 12;
  }

  $time = mktime( $results['hour'], $results['minute'], $results['second'], $results['month'], $results['day'], $results['year'] );
  return $time;
}

/**
 * Implements hook_node_load().
 */
function scheduler_node_load($nodes, $types) {
  $nids = array_keys($nodes);
  $result = db_query('SELECT * FROM {scheduler} WHERE nid IN (:nids)', array(':nids' => $nids));
  foreach ($result as $record) {
    $nid = $record->nid;
    $nodes[$nid]->publish_on = $record->publish_on;
    $nodes[$nid]->unpublish_on = $record->unpublish_on;
    $row = array();
    $row['published'] = $record->publish_on ? date(variable_get('date_format_long', 'l, F j, Y - H:i'), $record->publish_on) : NULL;
    $row['unpublished'] = $record->unpublish_on ? date(variable_get('date_format_long', 'l, F j, Y - H:i'), $record->unpublish_on) : NULL;
    $row['publish_on'] = $record->publish_on;
    $row['unpublish_on'] = $record->unpublish_on;
    $nodes[$nid]->scheduler = $row;
  }
}

function scheduler_node_view($node, $view_mode = 'full', $langcode) {
  if ($view_mode == 'full' && !empty($node->unpublish_on)) {
    $unavailable_after = date("d-M-Y H:i:s T", $node->unpublish_on);
    $element = array(
      '#tag' => 'meta',
      '#attributes' => array(
        'name' => 'googlebot',
        'content' => 'unavailable_after: '.$unavailable_after,
      ),
    );
    drupal_add_html_head($element, 'scheduler_unavailable');
  }
}

function scheduler_node_validate($node, $form) {
  // adjust the entered times for timezone consideration.
  // Note, we must check to see if the value is numeric,
  // if it is, assume we have already done the strtotime
  // conversion. This prevents us running strtotime on
  // a value we have already converted. This is needed
  // because DRUPAL6 removed 'submit' and added 'presave'
  // and all this happens at different times.

  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);

  if (!empty($node->publish_on) && !is_numeric($node->publish_on)) {
    $publishtime = _scheduler_strtotime($node->publish_on);
    if ($publishtime === FALSE) {
      form_set_error('publish_on', t("The 'publish on' value does not match the expected format of %time", array('%time' => format_date(REQUEST_TIME, 'custom', $date_format))));
    }
    elseif ($publishtime && $publishtime < REQUEST_TIME) {
      form_set_error('publish_on', t("The 'publish on' date must be in the future"));
    }
  }

  if (!empty($node->unpublish_on) && !is_numeric($node->unpublish_on)) {
    $unpublishtime = _scheduler_strtotime($node->unpublish_on);
    if ($unpublishtime === FALSE) {
      form_set_error('unpublish_on', t("The 'unpublish on' value does not match the expected format of %time", array('%time' => format_date(REQUEST_TIME, 'custom', $date_format))));
    }
    elseif ($unpublishtime && $unpublishtime < REQUEST_TIME) {
      form_set_error('unpublish_on', t("The 'unpublish on' date must be in the future"));
    }
  }
  if (isset($publishtime) && isset($unpublishtime) && $unpublishtime < $publishtime) {
    form_set_error('unpublish_on', t("The 'unpublish on' date must be later than the 'publish on' date."));
  }
}

function scheduler_node_presave($node) {
  foreach (array('publish_on', 'unpublish_on') as $key) {
    if (empty($node->$key)) {
    // Make sure publish_on and unpublish_on are not empty strings.
      $node->$key = 0;
    }
    elseif (!is_numeric($node->$key)) {
      $node->$key= _scheduler_strtotime($node->$key);
    }
  }
  // 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
  if (isset($node->publish_on) && $node->publish_on != '' && is_numeric($node->publish_on) && $node->publish_on > REQUEST_TIME) {
    $node->status = 0;
    $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
    drupal_set_message(t('This post is unpublished and will be published @publish_time.', array('@publish_time' => format_date($node->publish_on, 'custom', $date_format))), 'status', FALSE);
  }
}

function scheduler_node_insert($node) {
  // only insert into database if we need to (un)publish this node at some date
  if (!empty($node->publish_on) || !empty($node->unpublish_on)) {
    db_insert('scheduler')
      ->fields(array(
        'nid' => $node->nid,
        'publish_on' => $node->publish_on,
        'unpublish_on' => $node->unpublish_on,
      ))
      ->execute();
  }
}

function scheduler_node_update($node) {
  // 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 (!empty($node->publish_on) || !empty($node->unpublish_on)) {
    db_merge('scheduler')
      ->key(array('nid' => $node->nid))
      ->fields(array(
          'publish_on' => $node->publish_on,
          'unpublish_on' => $node->unpublish_on,
        ))
      ->execute();
  }
  else {
    scheduler_node_delete($node);
  }
}

function scheduler_node_delete($node) {
  db_delete('scheduler')
    ->condition('nid', $node->nid)
    ->execute();
}


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

  if ($clear_cache) {
    // Clear the cache so an anonymous poster can see any changes to nodes
    cache_clear_all();
  }
}

/**
 * Publish scheduled nodes.
 *
 * @return
 *   TRUE if any node has been published, FALSE otherwise.
 */
function _scheduler_publish() {
  $result = FALSE;
  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);

  // If the time now is greater than the time to publish a node, publish it.
  $query = db_select('scheduler', 's');
  $query->addField('s', 'nid');
  $query->addJoin('LEFT ', 'node', 'n', 's.nid = n.nid');
  $query->condition('s.publish_on', 0, '>');
  $query->condition('s.publish_on', REQUEST_TIME, '<');
  //  $query_result = db_query('SELECT s.nid AS nid 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 < :now ', array(':now' => REQUEST_TIME));
  $queryResult = $query->execute();
  $nids = array();
  while ($node = $queryResult->fetchObject()) {
    $nids[] = $node->nid;
  }

  $nids = array_unique(array_merge($nids, _scheduler_scheduler_nid_list('publish')));

  foreach ($nids as $nid) {
    $n = node_load($nid);
    $n->changed = $n->publish_on;
    $old_creation_date = $n->created;
    if (variable_get('scheduler_publish_touch_'. $n->type, 0) == 1) {
      $n->created = $n->publish_on;
    }

    $create_publishing_revision = variable_get('scheduler_publish_revision_'. $n->type, 0) == 1;
    if ($create_publishing_revision) {
      $n->revision = TRUE;
      $n->log = "Node published by scheduler module. Original creation date was ".  format_date($old_creation_date, 'custom', $date_format) .".";
    }

    // Use the actions system to publish the node.
    watchdog('scheduler', '@type: scheduled publishing of %title.', array('@type' => $n->type, '%title' => $n->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $n->nid));
    $actions = array('node_publish_action', 'node_save_action');
    $context['node'] = $n;
    actions_do($actions, $n, $context, NULL, NULL);

    // If this node is not to be unpublished, then we can delete the record.
    if (isset($n->unpublish_on) && $n->unpublish_on == 0) {
      db_delete('scheduler')
        ->condition('nid', $n->nid)
        ->execute();
    }
    // We need to unpublish this node at some time so clear the publish on since
    // it has been published.
    else {
      db_update('scheduler')
        ->fields(array('publish_on' => 0))
        ->condition('nid', $n->nid)
        ->execute();
    }

    // Invoke scheduler API.
    _scheduler_scheduler_api($n, 'publish');

    $result = TRUE;
  }

  return $result;
}

/**
 * Unpublish scheduled nodes.
 *
 * @return
 *   TRUE is any node has been unpublished, FALSE otherwise.
 */
function _scheduler_unpublish() {
  $result = FALSE;
  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);

  // If the time is greater than the time to unpublish a node, unpublish it.
  $query = db_select('scheduler', 's');
  $query->addField('s', 'nid');
  $query->addJoin('LEFT', 'node', 'n', 's.nid = n.nid');
  $query->condition('s.unpublish_on', 0, '>');
  $query->condition('s.unpublish_on', REQUEST_TIME, '<');
  $queryResult = $query->execute();
  $nids = array();
  while ($node = $queryResult->fetchObject()) {
    $nids[] = $node->nid;
  }

  $nids = array_unique(array_merge($nids, _scheduler_scheduler_nid_list('unpublish')));

  foreach ($nids as $nid) {
    // If this node is to be unpublished, we can update the node and remove the
    // record since it cannot be republished.
    $n = node_load($nid);
    $old_change_date = $n->changed;
    $n->changed = $n->unpublish_on;

    if ($n->status == 1) {
      $create_unpublishing_revision = variable_get('scheduler_unpublish_revision_'. $n->type, 0) == 1;
      if ($create_unpublishing_revision) {
        $n->revision = TRUE;
        $n->log = "Node unpublished by scheduler module. Original change date was ".  format_date($old_change_date, 'custom', $date_format) .".";
      }

      // Use the actions system to unpublish the node.
      watchdog('scheduler', '@type: scheduled unpublishing of %title.', array('@type' => $n->type, '%title' => $n->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $n->nid));
      $actions = array('node_unpublish_action', 'node_save_action');
      $context['node'] = $n;
      actions_do($actions, $n, $context, NULL, NULL);
    }

    db_delete('scheduler')
      ->condition('nid', $n->nid)
      ->execute();

    // Invoke scheduler API.
    _scheduler_scheduler_api($n, 'unpublish');

    $result = TRUE;
  }

  return $result;
}

/**
 * Gather node IDs for all nodes that need to be $action'ed
 *
 * @param $action
 *  The action being performed, either "publish" or "unpublish"
 *
 * @return
 *   an array of node IDs
 */
function _scheduler_scheduler_nid_list($action) {
  $nids = array();

  foreach (module_implements('scheduler_nid_list') as $module) {
    $function = $module .'_scheduler_nid_list';
    $nids = array_merge($nids, $function($action));
  }

  return $nids;
}

/**
 * Implementation of hook_theme().
 */
function scheduler_theme() {
  return array(
    'scheduler_timecheck' => array(
      'arguments' => array('now' => NULL),
    ),
  );
}

function _scheduler_run_cron() {
  watchdog('scheduler', 'Internal scheduler cron run activated', array(), WATCHDOG_NOTICE);
  scheduler_cron();
  if (ob_get_level() > 0) {
    $handlers = ob_list_handlers();
    if (isset($handlers[0]) && $handlers[0] == 'default output handler') {
      ob_clean();
    }
  }
  watchdog('scheduler', 'Internal scheduler cron run completed', array(), WATCHDOG_NOTICE, l('settings', 'admin/settings/scheduler'));

  // This message is only seen when the lightweight cron is tested interactively.
  drupal_set_message(t("Scheduler's lightweight cron completed. See !log for details.", array('!log' => l('admin/reports/dblog', 'admin/reports/dblog'))));

  // Must return something so that an output page is created if testing the lightweight cron interactively.
  return ' ';
  exit();
}

/**
 * Return the lightweight cron form to allow a manual run.
 */
function _scheduler_lightweight_cron() {
  $form = array();
  $form['scheduler_cron'] = array(
    '#type' => 'submit',
    '#prefix' => t("You can test Scheduler's lightweight cron process interactively") . ':<div>' ,
    '#value' => t("Run Scheduler's cron now"),
    '#submit' => array('_scheduler_run_cron'),
    '#suffix' => "</div>\n",
  );
  return $form;
}

/**
 * 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() {
  return theme('scheduler_timecheck', array('now' => REQUEST_TIME));
}

function theme_scheduler_timecheck($variables) {
  $now = $variables['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_api() {
  $info['api'] = 2;
  return $info;
}

/**
 * Implementation of hook_content_extra_fields().
 */
function scheduler_content_extra_fields($type_name) {
  $fields = array();
  $publishing_enabled = variable_get('scheduler_publish_enable_'. $type_name, 0) == 1;
  $unpublishing_enabled = variable_get('scheduler_unpublish_enable_'. $type_name, 0) == 1;
  if ($publishing_enabled || $unpublishing_enabled) {
    $fields['scheduler_settings'] = array(
      'label' => t('Scheduler'),
      'description' => t('Scheduler module form.'),
      'weight' => 10,
    );
  }
  return $fields;
}

/**
* Implements hook_preprocess_node();
* Makes the publish_on and unpublish_on data available as theme variables.
*/
function scheduler_preprocess_node(&$variables, $hook) {
  $node = $variables['node'];
  $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT);
  if (!empty($node->publish_on) && $node->publish_on && is_numeric($node->publish_on)) {
    $variables['publish_on'] = format_date($node->publish_on, 'custom', $date_format);
  }
  if (!empty($node->unpublish_on) && $node->unpublish_on && is_numeric($node->unpublish_on)) {
    $variables['unpublish_on'] = format_date($node->unpublish_on, 'custom', $date_format);
  }
}

/**
* Implementation of hook_feeds_node_processor_targets_alter().
* advertises publish_on and unpublish_on as mappable values to the feeds module
*/
function scheduler_feeds_processor_targets_alter(&$targets, $processor, $content_type) {

  $publishing_enabled = variable_get('scheduler_publish_enable_'. $content_type, 0);
  $unpublishing_enabled = variable_get('scheduler_unpublish_enable_'. $content_type, 0);

  if ($publishing_enabled) {
    $targets['publish_on'] = array(
      'name' => t('Scheduler: publish on'),
      'description' => t('The date, when scheduler module should publish node.'),
      'callback' => 'scheduler_set_target'
    );
  }
  if ($unpublishing_enabled) {
    $targets['unpublish_on'] = array(
      'name' => t('Scheduler: unpublish on'),
      'description' => t('The date, when scheduler module should unpublish node.'),
      'callback' => 'scheduler_set_target'
    );
  }
}

/**
* Mapping callback for feeds module.
*
* This callback converts input from parser and converts it
* to timestamp form. After that it sets value of correct field of node.
*/
function scheduler_set_target($source, $node, $target, $value) {
  if (!is_array($value)) {
    if (!is_numeric($value)) {
      $timestamp = strtotime($value);
    }
    else {
      $timestamp = $value;
    }
    // if strtotime returned correct timestamp, we proceed with
    // processing. Otherwise do nothing..
    if (($timestamp !== FALSE) && ($timestamp != -1)) {
      $node->$target = $timestamp;
    }
  }
}
