<?php
// $Id: daterange.module,v 1.5.2.32 2008/02/25 11:24:03 douggreen Exp $

/** @file
 * Add Date Range support to views
 */ 

/**
 * Implementation of hook_form_alter()
 */
function daterange_form_alter($form_id, &$form) {
  if ($form_id == 'views_filters' || $form_id == 'views_filterblock') {
    _daterange_form_alter_views_filters($form);
  }
  elseif ($form_id == 'views_edit_view') {
    _daterange_form_alter_views_edit($form);
  }
}

function _daterange_form_alter_views_edit(&$form) {
  // use javascript to fill in dates for preset operators
  $count = $form['filter']['count']['#value'];
  for ($index = 0; $index < $count; $index ++) {
    $field = $form['filter'][$index];
    if (isset($field['value']['date1']['#daterange'])) {
      $path = drupal_get_path('module', 'daterange');
      drupal_add_js($path .'/daterange.js');
      $form['filter'][$index]['operator']['#attributes'] = array(
        'class' => 'daterange-edit',
      );
    }
  }
}

function _daterange_form_alter_views_filters(&$form) {
  // fill in dates for preset operators
  // NOTE: this needs to be done again because $filter isn't passed by argument
  // in views_handler_filter_between_date
  foreach ($form as $name => $field) {
    if (substr($name, 0, 6) == 'filter') {
      if (isset($field['date1']['#daterange'])) {
        $index = drupal_substr($name, 6);
        foreach (views_handler_operator_daterange() as $key => $value) {
          if (substr($key, 0, 10) == 'daterange:') {
            _daterange_form_fill_missing_op_dates($form, $index, $key);
          }
        }
        $path = drupal_get_path('module', 'daterange');
        drupal_add_js($path .'/daterange.js');
        $form["op$index"]['#attributes'] = array(
          'class' => 'daterange-filter',
        );

        // if the filter needs to be restricted, it must be done here
        $view = $form['view']['#value'];
        if ($view->filter[$index + 1]['options'] != 0) {
          $form["op$index"]['#options'] = views_handler_operator_daterange($view->filter[$index + 1]['options']);
        }

        // add mouseover title text
        $title = t('YYYY-MM-DD, today, yesterday, today-7, today-2M, today-1Y');
        $form["filter$index"]['date1']['#attributes'] = 
          array_merge(
            array('title' => $title),
            $form["filter$index"]['date1']['#attributes']);
        $form["filter$index"]['date2']['#attributes'] =
          array_merge(
            array('title' => $title),
            $form["filter$index"]['date2']['#attributes']);
        $form["filter$index"]['#tree'] = TRUE;
      }
    }
  }
}

/**
 * Implementation of views hook_views_tables()
 */
function daterange_views_tables() {
  // add the node date fields
  $tables['daterange_node'] = array(
    'name' => 'daterange_node',
    'table' => 'daterange_node',
    'filters' => array(
      'created' => array(
        'name' => t('Node: Created Time Between'),
        'operator' => 'views_handler_operator_daterange',
        'option' => array(
           '#type' => 'select',
           '#options' => views_handler_operator_daterange_options(),
        ),
        'value' => views_handler_filter_date_between_value_form(),
        'handler' => 'views_handler_filter_between_date',
        'value-type' => 'array',
        'cacheable' => 'no',
      ),
      'changed' => array(
        'name' => t('Node: Changed Time Between'),
        'operator' => 'views_handler_operator_daterange',
        'option' => array(
           '#type' => 'select',
           '#options' => views_handler_operator_daterange_options(),
        ),
        'value' => views_handler_filter_date_between_value_form(),
        'handler' => 'views_handler_filter_between_date',
        'value-type' => 'array',
        'cacheable' => 'no',
      ),
    ),
  );

  // add CCK date types
  if (module_exists('content') && module_exists('date')) {
    $fields = content_fields();
    foreach ($fields as $field) {
      if ($field['type'] == 'date' || $field['type'] == 'datestamp') {
        if ($field['db_storage']) {
          $table = 'daterange_node_'. $field['type_name'];
        }
        else {
          $table = 'daterange_node_data_'. $field['field_name'];
        }
        if (!isset($tables[$table])) {
          $tables[$table] = array('name' => $table, 'table' => $table, 'filters' => array());
        }
        $title = $field['widget']['label'];
        $tables[$table]['filters'] = array_merge($tables[$table]['filters'],
          array(
            $field['field_name'] => array(
              'name' => 'Date Range: '. $title .' ('. $field['field_name'] .')',
              'operator' => 'views_handler_operator_daterange',
              'option' => array(
                 '#type' => 'select',
                 '#options' => views_handler_operator_daterange_options(),
              ),
              'value' => views_handler_filter_date_between_value_form(),
              'handler' => 'views_handler_filter_between_date',
              'value-type' => 'array',
              'cacheable' => 'no',
              'help' => t('Use <i>today</i> for the current date, <i>yesterday</i> for one day before, <i>today+5</i> for five days from now, <i>today-1M</i> for a month before the current date, and so on.  <i>M</i> indicates 31 days and <i>Y</i> indicates 365 days.'),
            ),
          )
        );
      }
    }
  }
  return $tables;
}

function views_handler_operator_daterange($option = 0) {
  $ops = array(
    'inside'  => t('Custom date range'),
    'outside' => t('Outside date range'),
    'before'  => t('Before'),
    'after'   => t('After'),
  );
  if ($option <= 0) {
    $ops['daterange:today-7']  = t('within the past week');
    $ops['daterange:today-1M'] = t('within the past month');
    $ops['daterange:today-3M'] = t('within the past 3 months');
    $ops['daterange:today-1Y'] = t('within the past year');
  }
  if ($option >= 0) {
    $ops['daterange:today+7']  = t('in the next week');
    $ops['daterange:today+1M'] = t('in the next month');
    $ops['daterange:today+3M'] = t('in the next 3 months');
    $ops['daterange:today+1Y'] = t('in the next year');
  }
  return $ops;
}

function views_handler_operator_daterange_options() {
  return array(
    -1 => t('Past Date Only'),
     0 => t('Past and Future Dates'),
     1 => t('Future Dates Only'),
  );
}

function views_handler_filter_date_between_value_form() {
  return array(
    'date1' => array(
      '#type' => 'textfield',
      '#attributes' => array('class' => 'jscalendar'),
      '#jscalendar_showsTime' => 'false',
      '#jscalendar_ifFormat' => '%Y-%m-%d',
      '#daterange' => 1,
    ),
    'date2' => array(
      '#type' => 'textfield',
      '#attributes' => array('class' => 'jscalendar'),
      '#jscalendar_showsTime' => 'false',
      '#jscalendar_ifFormat' => '%Y-%m-%d',
      '#daterange' => 2,
    ),
    '#type' => 'daterange_output',
    '#process' => array('views_handler_filter_between_date_process' => array()),
    '#after_build' => array('views_handler_filter_between_date_after_build'),
  );
}

function theme_daterange_output($element) {
  return $element['#children'];
}

function views_handler_filter_between_date_process($form_element) {
  $values = unserialize($form_element['#default_value'][0]);
  if (!is_array($values)) {
    $values = array('date1' => '', 'date2' => '');
  }
  if (!preg_match('/^filter[0-9]+\[date1]$/', $form_element['date1']['#name'])) {
    $form_element['date1']['#default_value'] = $values['date1'];
    $form_element['date2']['#default_value'] = $values['date2'];
  }
  elseif (!isset($form_element['#default_value']['date1'])) {
    $form_element['#default_value']['date1'] = $values['date1'];
    $form_element['#default_value']['date2'] = $values['date2'];
  }
  return $form_element;
}

function views_handler_filter_between_date_after_build($form_element) {
  if (preg_match('/^filter[0-9]+\[date1]$/', $form_element['date1']['#name'])) {
    if (is_array($form_element['#default_value'])) {
      if (isset($form_element['#default_value']['date1'])) {
        // set the select elements to the previously chosen date
        $form_element['date1']['#value'] = $form_element['#default_value']['date1'];
        $form_element['date2']['#value'] = $form_element['#default_value']['date2'];
      }
      else {
        $values = unserialize($form_element['#default_value'][0]);
        if (!is_array($values)) {
          $values = array('date1' => '', 'date2' => '');
        }
        $form_element['date1']['#value'] = $values['date1'];
        $form_element['date2']['#value'] = $values['date2'];
      }
    }
  }
  else {
    $values = array(
      'date1' => $form_element['date1']['#value'],
      'date2' => $form_element['date2']['#value'],
    );
    $form_element['#value'] = serialize($values);
    form_set_value($form_element, $form_element['#value']);
  }
  return $form_element;
}

function _get_date_value($text, $hour = 0, $min = 0, $sec = 0) {
  if ($text) {
    if ($value = _get_date_named_value($text)) {
      $parts = localtime($value);
      return mktime($hour, $min, $sec, $parts[4] + 1, $parts[3], $parts[5] + 1900);
    }

    $time = strtotime($text);
    if ($time > 0) {
      return mktime($hour, $min, $sec, date('m', $time), date('d', $time), date('Y', $time));
    }
  }
  return 0;
}

function _get_date_named_value($text) {
  if (strpos($text, '-') !== false) {
    $math = explode('-', $text);
    $op = -1;
  }
  else {
    $math = explode('+', $text);
    $op = 1;
  }
  switch ($math[0]) {
    case 'today':
      $value = time();
      break;
    case 'yesterday': // shorthand for today-1
      $value = time() - 3600*24;
      break;
    case 'weekago': // for backwards compatibility, should deprecate
      $value = time() - 3600*24*7;
      break;
  }
  if ($value && $op) {
    switch (substr($math[1], -1)) {
      default:
        $value += $op * intval($math[1]) * 3600*24;
        break;
      case 'M':
        $value += $op * intval($math[1]) * 3600*24*31;
        break;
      case 'Y':
        $value += $op * intval($math[1]) * 3600*24*365;
        break;
    }
  }
  return $value;
}

function _daterange_fill_missing_op_dates(&$filter, $key) {
  if (isset($filter['value']['date1']) && isset($filter['value']['date2'])) {
    if ($filter['value']['date1'] == '') {
      $filter['value']['date1'] = drupal_substr($key, 0, 10);
    }
    if ($filter['value']['date2'] == '') {
      $filter['value']['date2'] = 'today';
    }
  }
}

function _daterange_form_fill_missing_op_dates(&$form, $index, $key) {
  if ($form["op$index"]['#default_value'] == $key) {
    if ($form["filter$index"]['#default_value']['date1'] == '') {
      $form["filter$index"]['#default_value']['date1'] = drupal_substr($key, 10);
    }
    if ($form["filter$index"]['#default_value']['date2'] == '') {
      $form["filter$index"]['#default_value']['date2'] = 'today';
    }
  }
  $form["filter$index"]['#tree'] = TRUE;
}

function views_handler_filter_between_date($op, $filter, &$filterinfo, &$query) {
  $values = unserialize($filter['value'][0]);
  if (is_array($values)) {
    $filter['value']['date1'] = $values['date1'];
    $filter['value']['date2'] = $values['date2'];
  }

  // fill in dates for preset operators
  foreach (views_handler_operator_daterange() as $key => $value) {
    if (substr($key, 0, 10) == 'daterange:') {
      _daterange_fill_missing_op_dates($filter, $key);
    }
  }
  if (isset($filter['value']['date1'])) {
    if ($op == 'after') {
      $value1 = _get_date_value($filter['value']['date1'], 23, 59, 59);
    }
    else {
      $value1 = _get_date_value($filter['value']['date1']);
    }
    if ($value1 > 0) {
      $value2 = _get_date_value($filter['value']['date2'], 23, 59, 59);
      if ($value2 == 0) {
        $value2 = _get_date_value($filter['value']['date1'], 23, 59, 59);
      }
      $field = $filter['field'];
      if (substr($field, 0, 15) == 'daterange_node_') {
        if (module_exists('content')) {
          $parts = explode('.', $field);
          $type_name = substr($parts[0], 15);
          if (substr($type_name, 0, 5) == 'data_') {
            $field_name = substr($type_name, 5);
            $parts[0] = _content_tablename($field_name, CONTENT_DB_STORAGE_PER_FIELD);
          }
          else {
            $parts[0] = _content_tablename($type_name, CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
          }
          $joininfo = array(
            'left'  => array('table' => 'node', 'field' => 'vid'),
            'right' => array('field' => 'vid')
          );
          $query->add_table($parts[0], false, 1, $joininfo);
          $cckinfo = content_fields($parts[1]);
          if ($cckinfo['type'] == 'datestamp') {
            $now = time();
            $gmnow = date_gmadj_zone($now);
            $field = ' ('. $parts[0] .'.'. $parts[1] .'_value + '. ($now - $gmnow) .')';
          }
          else {
            $field = ' UNIX_TIMESTAMP(REPLACE('. $parts[0] .'.'. $parts[1] .'_value, \'T\', \' \'))';
          }
        }
      }
      else {
        $field = str_replace('daterange_', '', $field);
      }
      _views_handler_filter_between_date($field, $value1, $filter['operator'], $value2, $query);
    }
  }
}

if (module_exists('date_api')) {
  /**
   * Include date.inc for it's implementation of date_gmadj_zone().
   */
  $path = drupal_get_path('module', 'date_api') .'/date.inc';
  if (file_exists($path)) {
    include_once($path);
  }
}

if (!function_exists('date_gmadj_zone')) {
  /**
   * Implement date module's date_gmadj_zone if it doesn't exist.
   * This is needed for date-5.x-2.x.
   */
  function date_gmadj_zone($timestamp) {
    // No timezone adjustment for very old dates.
    if ($timestamp < 86400) {
      return $timestamp;
    }
    return intval($timestamp + date('Z', $timestamp));
  }
}

function _views_handler_filter_between_date($field, $value1, $op, $value2, &$query) {
  switch ($op) { 
    default:
      $where = "$field >= $value1 && $field <= $value2";
      break;
    case 'outside':
      $where = "($field < $value1 || $field > $value2)";
      break;
    case 'before': 
      $where = "$field < $value1";
      break;
    case 'after': 
      $where = "$field > $value1";
      break;
  }
  $query->add_where($where);
}

/**
 * Implementation of hook_views_arguments
 */
function daterange_views_arguments() {
  return array(
    'node_created_daterange_before' => array(
      'name' => t('Node: Created Date Before'),
      'handler' => 'daterange_node_created_argument_handler',
    ),
    'node_created_daterange_after' => array(
      'name' => t('Node: Created Date After'),
      'handler' => 'daterange_node_created_argument_handler',
    ),
  );
}

function daterange_node_created_argument_handler($op, &$query, $argtype, $arg = '') {
  if ($op == 'filter') {
    $date_value = _get_date_named_value($arg);
    switch ($argtype['type']) {
      case 'node_created_daterange_before':
        _views_handler_filter_between_date('node.created', $date_value, 'before', NULL, $query);
        break;
      case 'node_created_daterange_after':
        _views_handler_filter_between_date('node.created', $date_value, 'after', NULL, $query);
        break;
    }
  }
}

