<?php
// $Id: scheduler.module,v 1.34 2006/03/17 21:08:39 deekayen Exp $

/**
 * Implementation of hook_help().
 */
function scheduler_help($section){
  if ($section == 'admin/modules#description') {
    return t('A module to schedule node (un)publishing.');
  }
}

/**
 * Implementation of hook_menu().
 */
function scheduler_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $items[] = array('path' => 'admin/node/scheduler',
      'title' => t('schedule'),
      'callback' => 'scheduler_admin',
      'access' => user_access('administer nodes') && user_access('schedule nodes'),
      'type' => MENU_LOCAL_TASK);
  }
  return $items;
}

function scheduler_settings() {
  $form['scheduler_form_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Form display mode'),
    '#default_value' => variable_get('scheduler_form_mode', 0),
    '#options' => array(
       '0' => t('Allow post and hide scheduling'),
       '1' => t('Only allow post scheduling'),
       '2' => t('Only allow hide scheduling'),
    ),
    '#description' => t('What options should the scheduler form offer?'),
  );

  return $form;
}

function scheduler_write($node, $op, $arg) {
  if (user_access('schedule nodes')) {

    $conds = array();
    if ($node->scheduler_post) {
      $conds['timestamp_posted'] = scheduler_nodetime($node, 'post');
    }
    else {
      $conds['timestamp_posted'] = 0;
    }
    if ($node->scheduler_hide) {
      $conds['timestamp_hidden'] = scheduler_nodetime($node, 'hide');
    }
    else {
      $conds['timestamp_hidden'] = 0;
    }

    // No schedule needs to take place (works for update and insert too)
    if (array_sum($conds) == 0) {
      scheduler_remove_schedule($node);
      return;
    }

    if ($op !== 'insert') {
      // Determine whether we must UPDATE or INSERT, then do it
      $result = db_query('SELECT * FROM {scheduler} WHERE nid = %d', $node->nid);
      if (db_num_rows($result)) {
        foreach ($conds as $key => $value) {
          $update_conds[] = "$key=$value";
        }
        db_query('UPDATE {scheduler} SET %s WHERE nid = %d', implode(', ', $update_conds), $node->nid);
      }
      else {
        scheduler_insert_schedule($node, $conds);
      }
    }
    else {
      scheduler_insert_schedule($node, $conds);
    }
  }
}

function scheduler_insert_schedule($node, $conds) {
  $conds['nid'] = $node->nid;
  db_query('INSERT INTO {scheduler} (%s) VALUES (%s)', implode(', ', array_keys($conds)), implode(', ', array_values($conds)));
}

function scheduler_remove_schedule($node) {
  db_query('DELETE FROM {scheduler} WHERE nid = %d', $node->nid);
}

/**
 * Implementation of hook_nodeapi().
 */
function scheduler_nodeapi(&$node, $op, &$arg) {
  static $error;
  switch ($op) {
    case 'validate':
      if (user_access('schedule nodes')) {
        if ($node->scheduler_post) {
          if (!checkdate($node->scheduler_post_month, $node->scheduler_post_day, $node->scheduler_post_year)) {
            form_set_error('scheduler_post', t('Invalid scheduled post date set; automatically adjusted'));
            $error['scheduler_post'] = theme('error', t('Invalid date set, automatically adjusted'));
          }
          else {
            $post_date = scheduler_nodetime($node, 'post');
            if (time() > $post_date) {
              form_set_error('scheduler_post', t('Scheduled post date in the past'));
              $error['scheduler_post'] = theme('error', t('Date in the past'));
            }
            // Automatically depublish node if date is fine
            else {
              $node->status = 0;
            }
          }
        }

        if ($node->scheduler_hide) {
          if (!checkdate($node->scheduler_hide_month, $node->scheduler_hide_day, $node->scheduler_hide_year)) {
            form_set_error('scheduler_hide', t('Invalid scheduled hide date set; automatically adjusted'));
            $error["scheduler_hide"] = theme("error", t('Invalid date set, automatically adjusted'));
          }
          else {
            $hide_date = scheduler_nodetime($node, 'hide');
            if (time() > $hide_date) {
              form_set_error('scheduler_hide', t('Scheduled hide date in the past'));
              $error["scheduler_hide"] = theme("error", t('Date in the past'));
            }
          }
        }
      }
      break;
    case 'insert':
      scheduler_write($node, $op, $arg);
      break;
    case 'update':
      scheduler_write($node, $op, $arg);
      break;
    case 'delete':
      scheduler_remove_schedule($node);
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function scheduler_form_alter($form_id, &$form) {
  if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
    if (user_access('schedule nodes')) {
      // Build up date components array
      $month_names = array(1 => t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December'));
      $date_components = array(
        'year'   => array('Y', drupal_map_assoc(range(date('Y'), date('Y')+5))),
        'month'  => array('m', $month_names),
        'day'    => array('d', drupal_map_assoc(range(1, 31))),
        'hour'   => array('H', drupal_map_assoc(range(0, 23))),
        'minute' => array('i', drupal_map_assoc(range(0, 59)))
      );

      // Form element specs (time, enabled, title, description)
      $form_elements = array(
        'scheduler_post' => array(time(), FALSE, t('Automatically post document'), t('The date at which your document will be automatically posted.<br />Do not check the enabling checkbox unless you want to schedule this document.')),
        'scheduler_hide' => array(time(), FALSE, t('Automatically hide document'), t('The date at which your document will be automatically hidden.<br />Do not check the enabling checkbox unless you want to schedule this document.'))
      );

      // Get edit information from post if possible
      $node = isset($form['#node']) ? $form['#node'] : NULL;
      if (isset($_POST['edit']) && count($_POST['edit'])) {
        $edit = (object) $_POST['edit'];
        if ($edit->scheduler_post) {
          $form_elements['scheduler_post'][0] = scheduler_nodetime($edit, 'post');
          $form_elements['scheduler_post'][1] = TRUE;
        }
        if ($edit->scheduler_hide) {
          $form_elements['scheduler_hide'][0] = scheduler_nodetime($edit, 'hide');
          $form_elements['scheduler_hide'][1] = TRUE;
        }
      }
      elseif ($node->nid) {
        $result = db_query('SELECT timestamp_hidden, timestamp_posted FROM {scheduler} WHERE nid = %d', $node->nid);
        $schedule = db_fetch_object($result);
        if ($schedule->timestamp_posted) {
          $form_elements['scheduler_post'][0] = $schedule->timestamp_posted;
          $form_elements['scheduler_post'][1] = TRUE;
        }
        if ($schedule->timestamp_hidden) {
          $form_elements['scheduler_hide'][0] = $schedule->timestamp_hidden;
          $form_elements['scheduler_hide'][1] = TRUE;
        }
      }

      // Hide controls if admin requested
      switch (variable_get('scheduler_form_mode', 0)) {
        case 0:
          // Allow both controls
          break;
        case 1:
          // Allow post control
          unset($form_elements['scheduler_hide']);
          break;
        case 2:
          // Allow hide control
          unset($form_elements['scheduler_post']);
          break;
      }

      // Compose form to post and hide nodes
      $form['scheduler'] = array(
        '#type' => 'fieldset',
        '#title' => t('Scheduling'),
        '#weight' => 26,
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      foreach ($form_elements as $fname => $felem) {
        $form['scheduler'][$fname]['begin'] = array(
          '#type' => 'markup',
          '#value' => '<div class="form-item"><label>'. $felem[2] .':</label><div class="container-inline">',
        );

        $form['scheduler'][$fname][$fname] = array(
          '#type' => 'checkbox',
          '#value' => $felem[1],
          '#suffix' => t('Enabled'),
        );

        foreach ($date_components as $dname => $ddata) {
          if ($dname == 'hour' && strpos(variable_get('date_format_medium', 'D, Y-m-d H:i'), 'g:ia')) {
            $form['scheduler'][$fname][$fname .'_'. $dname] = array(
              '#type' => 'select',
              '#default_value' => date($ddata[0], $felem[0]),
              '#options' => drupal_map_assoc($ddata[1], '_scheduler_hour_format'),
            );
          }
          else {
            $form['scheduler'][$fname][$fname .'_'. $dname] = array(
              '#type' => 'select',
              '#default_value' => date($ddata[0], $felem[0]),
              '#options' => $ddata[1],
            );
          }
        }

        $form['scheduler'][$fname]['end'] = array(
          '#type' => 'markup',
          '#value' => '</div><div class="description">'. $felem[3] .'</div></div>',
        );
      }
    }
  }
}

/**
 * Implementation of hook_perm().
 */
function scheduler_perm() {
  return array('schedule nodes');
}

function _scheduler_hour_format($hour) {
  if ($hour == 0) {
    return t('12 am');
  }
  if ($hour == 12) {
    return t('12 pm');
  }
  if ($hour > 12) {
    $hour = $hour - 12;
    return t('%hour pm', array('%hour' => $hour));
  }
  else {
    return t('%hour am', array('%hour' => $hour));
  }
}

function scheduler_cron() {
  // If we are past the posting time, then post.
  $result = db_query('SELECT s.nid, s.timestamp_posted, s.timestamp_hidden FROM {scheduler} s LEFT JOIN {node} n ON s.nid = n.nid WHERE n.moderate != 1 AND n.status = 0 AND s.timestamp_posted > 0 AND s.timestamp_posted < %d', time());
  $numpost = db_num_rows($result);
  while ($node = db_fetch_object($result)) {
    db_query('UPDATE {node} SET created = %d, changed = %d, status = 1 WHERE nid = %d', $node->timestamp_posted, $node->timestamp_posted, $node->nid);
    _scheduler_scheduler_api($node, "post");

    // No hide information for node, no need to keep record
    if ($node->timestamp_hidden == 0) {
      db_query('DELETE FROM {scheduler} WHERE nid = %d', $node->nid);
    }
    // This node needs to be hidden later, only set posted to empty
    else {
      db_query('UPDATE {scheduler} SET timestamp_posted = 0 WHERE nid = %d', $node->nid);
    }
  }

  //  If we are past the hide time, then hide.
  $result = db_query('SELECT s.nid FROM {scheduler} s LEFT JOIN {node} n ON s.nid = n.nid WHERE n.status = 1 AND s.timestamp_hidden > 0 AND s.timestamp_hidden < %d', time());
  $numhide = db_num_rows($result);
  while ($node = db_fetch_object($result)) {
    // Hide node and remove schedule entry
    db_query('UPDATE {node} SET status = 0 WHERE nid = %d', $node->nid);
    db_query('DELETE FROM {scheduler} WHERE nid = %d', $node->nid);
    _scheduler_scheduler_api($node, "hide");
  }

  if ($numhide || $numpost) {
    watchdog('special', t('Scheduler posted %numpost nodes and hid %numhide nodes.', array('%numpost' => $numpost, '%numhide' => $numhide)));
    // Clear cache so anonymous users see changes
    cache_clear_all();
  }
}

//TODO: provide more options for filtering
function scheduler_admin() {
  $header = array(
    array('data' => t('Title'), 'field' => 'n.title'),
    array('data' => t('Author'), 'field' => 'u.name'),
    array('data' => t('Post'), 'field' => 's.timestamp_posted'),
    array('data' => t('Hide'), 'field' => 's.timestamp_hidden'),
    array('data' => t('Operations'), 'colspan' => 2)
  );

  // Default ordering
  if (!isset($_GET['order']) && !isset($_GET['sort'])) {
    $_GET['order'] = t('Post');
    $_GET['sort'] = 'desc';
  }

  $sql = 'SELECT n.nid, n.uid, n.status, u.name, n.title, s.timestamp_posted, s.timestamp_hidden 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(
      ($node->status ? l($node->title, "node/$node->nid") : $node->title),
      theme('username', $node),
      ($node->timestamp_posted ? format_date($node->timestamp_posted) : '&nbsp;'),
      ($node->timestamp_hidden ? format_date($node->timestamp_hidden) : '&nbsp;'),
      l(t('edit'), 'node/'. $node->nid .'/edit'),
      l(t('delete'), "admin/node/delete/$node->nid")
    );
  }

  if (count($rows)) {
    if ($pager = theme('pager', NULL, 50, 0)) {
      $rows[] = array(array('data' => $pager, 'colspan' => 6));
    }

    return theme('table', $header, $rows);
  }
  else {
    return t('There are no scheduled nodes.');
  }
}

// Return the time of post or hide operation on the node
function scheduler_nodetime(&$node, $type = 'post') {
  if (!in_array($type, array('post', 'hide'))) {
    return time();
  }
  else {
    return mktime(
      $node->{"scheduler_{$type}_hour"}, $node->{"scheduler_{$type}_minute"}, rand(0, 59),
      $node->{"scheduler_{$type}_month"}, $node->{"scheduler_{$type}_day"}, $node->{"scheduler_{$type}_year"}
    );
  }
}

// Invoke any scheduler_api functions
// $node is obvious, $action is either "post" OR "hide"
// depending on the required action
function _scheduler_scheduler_api($node, $action) {
  $modules = module_list();
  if(is_array($modules)) foreach($modules as $module) {
    $function = $module . '_scheduler_api';
    if(function_exists($function)) {
      $function($node, $action);
    }
  }
}


?>