You are here

date_repeat.inc in Date 6.2

Same filename and directory in other branches
  1. 5.2 date/date_repeat.inc
  2. 6 date/date_repeat.inc

Implementation of Date Repeat API calculations for the CCK Date field.

Consolidated here so the code isn't parsed if repeats aren't being used or processed, and to make it easier to maintain.

The current implementation adds a repeat form to the date field so the user can select the repeat rules. That selection is built into an RRULE which is stored in the zero position of the field values. During widget validation, the rule is parsed to see what dates it will create, and multiple values are added to the field, one for each repeat date. That update only happens when the rule, the from date, or the to date change, no need to waste processing cycles for other changes to the node values.

Lots of possible TODOs, the biggest one is figuring out the best way to handle dates with no UNTIL date since we can't add an infinite number of values to the field. For now, we require the UNTIL date.

File

date/date_repeat.inc
View source
<?php

/**
 * @file
 * Implementation of Date Repeat API calculations for the CCK Date field.
 *
 * Consolidated here so the code isn't parsed if repeats aren't being used
 * or processed, and to make it easier to maintain.
 *
 * The current implementation adds a repeat form to the date field so the user
 * can select the repeat rules. That selection is built into an RRULE
 * which is stored in the zero position of the field values. During widget
 * validation, the rule is parsed to see what dates it will create,
 * and multiple values are added to the field, one for each repeat date.
 * That update only happens when the rule, the from date, or the to date
 * change, no need to waste processing cycles for other changes to the node
 * values.
 *
 * Lots of possible TODOs, the biggest one is figuring out the best
 * way to handle dates with no UNTIL date since we can't add an infinite
 * number of values to the field. For now, we require the UNTIL date.
 */

/**
 * Widget processing for date repeat form element.
 *
 * Create the RRULE as a top-level element rather than a delta level
 * element, we'll compute the repeat sequence in the widget validation
 * to create the element delta values.
 */
function _date_repeat_widget(&$element, $field, $items, $delta) {
  $element['rrule'] = array(
    '#type' => 'date_repeat_rrule',
    '#default_value' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
    '#date_timezone' => $element['#date_timezone'],
    '#date_format' => date_limit_format(date_input_format($element, $field), $field['granularity']),
    '#date_text_parts' => (array) $field['widget']['text_parts'],
    '#date_increment' => $field['widget']['increment'],
    '#date_year_range' => $field['widget']['year_range'],
    '#date_label_position' => $field['widget']['label_position'],
    '#prev_value' => isset($items[0]['value']) ? $items[0]['value'] : '',
    '#prev_value2' => isset($items[0]['value2']) ? $items[0]['value2'] : '',
    '#prev_rrule' => isset($items[0]['rrule']) ? $items[0]['rrule'] : '',
    '#date_repeat_widget' => str_replace('_repeat', '', $field['widget']['type']),
    '#date_repeat_collapsed' => $field['repeat_collapsed'],
  );
  return $element;
}

/**
 * Validation for date repeat form element.
 *
 * Create multiple values from the RRULE results.
 * Lots more work needed here.
 */
function _date_repeat_widget_validate($element, &$form_state) {
  require_once './' . drupal_get_path('module', 'date_repeat') . '/date_repeat_form.inc';
  $form_values = $form_state['values'];
  $field_name = $element['#field_name'];
  $fields = content_fields();
  $field = $fields[$field_name];
  $item = $form_values[$field_name];
  $values = date_repeat_merge($element['#post'][$field_name]['rrule'], $element['rrule']);

  // If no start date was set, clean up the form and return.
  // If no repeats are set, clean up the form and return.
  if (empty($form_values[$field_name]['value']) || $values['FREQ'] == 'NONE') {
    $form_values[$field_name]['rrule'] = NULL;
    form_set_value($element, array(
      $form_values[$field_name],
    ), $form_state);
    return;
  }

  // Require the UNTIL date for now.
  // The RRULE has already been created by this point, so go back
  // to the posted values to see if this was filled out.
  $error_field = implode('][', $element['#parents']) . '][rrule][UNTIL][datetime][date';
  if (empty($values['UNTIL']['datetime'])) {
    form_set_error($error_field, t('The UNTIL value is required for repeating dates.'));
  }
  if (form_get_errors()) {
    return;
  }

  // If the rule, the start date, or the end date have changed, re-calculate
  // the repeating dates, wipe out the previous values, and populate the
  // field with the new values.
  // TODO
  // Is it right to not do anything unless there are changes? Will that
  // confuse anyone? Commenting that out for now...
  $rrule = $form_values[$field_name]['rrule'];
  if (!empty($rrule)) {
    $item = $form_values[$field_name];

    // Avoid undefined index problems on dates that don't have all parts.
    $possible_items = array(
      'value',
      'value2',
      'timezone',
      'offset',
      'offset2',
    );
    foreach ($possible_items as $key) {
      if (empty($item[$key])) {
        $item[$key] = '';
      }
    }
    $value = date_repeat_build_dates($rrule, $values, $field, $item);
    form_set_value($element, $value, $form_state);
  }
  else {

    // If no changes are needed, move the RRULE back to the zero value
    // item of the field.
    form_set_value(array(
      '#parents' => array(
        $field_name,
        0,
        'rrule',
      ),
    ), $rrule, $form_state);
    form_set_value($element['rrule'], NULL, $form_state);
  }
}

/**
 * Helper function to build repeating dates from a $node_field.
 *
 * Pass in either the RRULE or the $form_values array for the RRULE,
 * whichever is missing will be created when needed.
 */
function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) {
  module_load_include('inc', 'date_api', 'date_api_ical');
  $field_name = $field['field_name'];
  if (empty($rrule)) {
    $rrule = date_api_ical_build_rrule($rrule_values);
  }
  elseif (empty($rrule_values)) {
    $rrule_values = date_ical_parse_rrule(NULL, $rrule);
  }

  // By the time we get here, the start and end dates have been
  // adjusted back to UTC, but we want localtime dates to do
  // things like '+1 Tuesday', so adjust back to localtime.
  $timezone = date_get_timezone($field['tz_handling'], $item['timezone']);
  $timezone_db = date_get_timezone_db($field['tz_handling']);
  $start = date_make_date($item['value'], $timezone_db, $field['type'], $field['granularity']);
  if ($timezone != $timezone_db) {
    date_timezone_set($start, timezone_open($timezone));
  }
  if (!empty($item['value2']) && $item['value2'] != $item['value']) {
    $end = date_make_date($item['value2'], date_get_timezone_db($field['tz_handling']), $field['type'], $field['granularity']);
    date_timezone_set($end, timezone_open($timezone));
  }
  else {
    $end = $start;
  }
  $duration = date_difference($start, $end);
  $start_datetime = date_format($start, DATE_FORMAT_DATETIME);
  if (!empty($rrule_values['UNTIL']['datetime'])) {
    if (strlen($rrule_values['UNTIL']['datetime']) < 11) {
      $rrule_values['UNTIL']['datetime'] .= ' 23:59:59';
    }
    $end = date_ical_date($rrule_values['UNTIL'], $timezone);
    $end_datetime = date_format($end, DATE_FORMAT_DATETIME);
  }
  elseif (!empty($rrule_values['COUNT'])) {
    $end_datetime = NULL;
  }
  else {

    // No UNTIL and no COUNT?
    return array();
  }

  // Split the RRULE into RRULE, EXDATE, and RDATE parts.
  $parts = date_repeat_split_rrule($rrule);
  $parsed_exceptions = (array) $parts[1];
  $exceptions = array();
  foreach ($parsed_exceptions as $exception) {
    if (strlen($exception['datetime']) < 11) {
      $exception['datetime'] .= ' 00:00:00';
    }
    $date = date_ical_date($exception, $timezone);
    $exceptions[] = date_format($date, 'Y-m-d');
  }
  $parsed_rdates = (array) $parts[2];
  $additions = array();
  foreach ($parsed_rdates as $rdate) {
    if (strlen($rdate['datetime']) < 11) {
      $rdate['datetime'] .= ' 00:00:00';
    }
    $date = date_ical_date($rdate, $timezone);
    $additions[] = date_format($date, 'Y-m-d');
  }
  $dates = date_repeat_calc($rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions);
  $value = array();
  foreach ($dates as $delta => $date) {

    // date_repeat_calc always returns DATE_DATETIME dates, which is
    // not necessarily $field['type'] dates.
    // Convert returned dates back to db timezone before storing.
    $date_start = date_make_date($date, $timezone, DATE_DATETIME, $field['granularity']);
    date_timezone_set($date_start, timezone_open($timezone_db));
    $date_end = drupal_clone($date_start);
    date_modify($date_end, '+' . $duration . ' seconds');
    $value[$delta] = array(
      'value' => date_format($date_start, date_type_format($field['type'])),
      'value2' => date_format($date_end, date_type_format($field['type'])),
      'offset' => date_offset_get($date_start),
      'offset2' => date_offset_get($date_end),
      'timezone' => $timezone,
      'rrule' => $rrule,
    );
  }
  return $value;
}

Functions

Namesort descending Description
date_repeat_build_dates Helper function to build repeating dates from a $node_field.
_date_repeat_widget Widget processing for date repeat form element.
_date_repeat_widget_validate Validation for date repeat form element.