You are here

interval.module in Interval Field 7

Same filename and directory in other branches
  1. 8 interval.module

Defines an interval field @copyright Copyright(c) 2011 Rowlands Group @license GPL v2+ http://www.fsf.org/licensing/licenses/gpl.html @author Lee Rowlands leerowlands at rowlandsgroup dot com

File

interval.module
View source
<?php

/**
 * @file
 * Defines an interval field
 * @copyright Copyright(c) 2011 Rowlands Group
 * @license GPL v2+ http://www.fsf.org/licensing/licenses/gpl.html
 * @author Lee Rowlands leerowlands at rowlandsgroup dot com
 *
 */

/***************************************************************
 * Field Type API hooks
 ***************************************************************/

/**
 * Implements hook_field_info().
 *
 * Provides the description of the field.
 */
function interval_field_info() {
  return array(
    // We name our field as the associative name of the array.
    'interval' => array(
      'label' => t('Interval'),
      'description' => t('Provides an interval field allowing you to enter a number and select a period.'),
      'default_widget' => 'interval_default',
      'default_formatter' => 'interval_default',
      // Add entity property metadata for the entity api module.
      'property_type' => 'field_item_interval',
      'property_callbacks' => array(
        'interval_field_entity_property_info_alter',
      ),
    ),
  );
}

/**
 * Defines the data structure used for interval field items.
 */
function interval_entity_property_field_item_interval_info() {
  return array(
    'interval' => array(
      'type' => 'integer',
      'label' => t('Interval number'),
      'description' => t('The number of multiples of the period.'),
    ),
    'period' => array(
      'type' => 'token',
      'label' => t('Interval period'),
      'options list' => 'interval_period_options_list',
    ),
  );
}

/**
 * Callback to alter the property info of interval fields.
 *
 * @see interval_field_info()
 */
function interval_field_entity_property_info_alter(&$info, $entity_type, $field, $instance, $field_type) {
  $name = $field['field_name'];
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  $property['property info'] = interval_entity_property_field_item_interval_info();
  unset($property['query callback']);
}

/**
 * Implements hook_field_validate().
 *
 * @see interval_field_widget_error()
 */
function interval_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if (!empty($item['interval'])) {
      if (!is_numeric($item['interval'])) {
        $errors[$field['field_name']][$langcode][$delta][] = array(
          'error' => 'interval_non_numeric',
          'message' => t('You must enter a numeric value.'),
        );
      }
    }
  }
}

/**
 * Implements hook_field_is_empty().
 */
function interval_field_is_empty($item, $field) {
  return $item['interval'] == '';
}

/**
 * Implements hook_field_formatter_info().
 *
 * We need to tell Drupal that we have two different types of formatters
 * for this field. One will change the text color, and the other will
 * change the background color.
 *
 * @see interval_field_formatter_view()
 */
function interval_field_formatter_info() {
  return array(
    // This formatter just displays the interval and period wrapped in a div.
    'interval_default' => array(
      'label' => t('Plain'),
      'field types' => array(
        'interval',
      ),
    ),
    // Same as default formatter but without the wrapper div.
    'interval_raw' => array(
      'label' => t('Raw'),
      'field types' => array(
        'interval',
      ),
    ),
    'interval_php' => array(
      'label' => t('PHP date/time'),
      'field types' => array(
        'interval',
      ),
      'description' => t('A valid PHP date/time string.'),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 *
 * @see interval_field_formatter_info()
 */
function interval_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {

    // This formatter simply outputs the interval/period wrapped in a div.
    case 'interval_default':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#type' => 'html_tag',
          '#attributes' => array(
            'class' => array(
              'interval-value',
            ),
          ),
          '#tag' => 'div',
          '#value' => interval_format_interval($item),
        );
      }
      break;

    // This formatter outputs the interval/period.
    case 'interval_raw':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => check_plain(interval_format_interval($item)),
        );
      }
      break;

    // This formatter outputs the interval/period as a PHP date/time string.
    case 'interval_php':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => check_plain(interval_php_interval($item)),
        );
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_instance_settings_form().
 *
 * Allow the user to choose from available periods
 */
function interval_field_instance_settings_form($field, $instance) {
  $form = array();
  $settings = $instance['settings'];
  $widget = $instance['widget']['type'];
  if ($widget == 'interval_default') {
    $options = array();
    $intervals = interval_get_intervals();
    foreach ($intervals as $key => $detail) {
      $options[$key] = $detail['plural'];
    }
    $form['allowed_periods'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Allowed periods'),
      '#options' => $options,
      '#description' => t('Select the periods you wish to be available in the dropdown. Selecting none will make all of them available.'),
      '#default_value' => isset($settings['allowed_periods']) ? $settings['allowed_periods'] : array(),
    );
  }
  return $form;
}

/**
 * Implements hook_field_widget_info().
 *
 * @see interval_field_widget_form()
 */
function interval_field_widget_info() {
  return array(
    'interval_default' => array(
      'label' => t('Interval and Period'),
      'field types' => array(
        'interval',
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function interval_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  // Convenience variables.
  $value = isset($items[$delta]) ? $items[$delta] : array(
    'interval' => NULL,
    'period' => NULL,
  );
  $field_name = $instance['field_name'];

  // We need to check our form_state values for ajax completion.
  if (isset($form_state['values']) && !empty($form_state['values'][$field_name][$langcode][$delta])) {

    // We use the current form state values instead of those from the db.
    $value = $form_state['values'][$field_name][$langcode][$delta];
  }
  $element['#default_value'] = $value;
  $settings = $instance['settings'];
  $widget = $instance['widget'];

  // Available periods.
  if (isset($settings['allowed_periods'])) {
    $element['#periods'] = array_keys(array_filter($settings['allowed_periods']));
  }
  switch ($widget['type']) {
    case 'interval_default':
      $element += array(
        '#type' => 'interval',
      );
      break;
  }
  return $element;
}

/**
 * Implements hook_field_widget_error().
 *
 * @see interval_field_validate()
 * @see form_error()
 */
function interval_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'interval_non_numeric':
      form_error($element['interval'], $error['message']);
      break;
  }
}

/***************************************************************
 * Form element hooks
 ***************************************************************/

/**
 * Implements hook_element_info().
 */
function interval_element_info() {
  $types = array();
  $types['interval'] = array(
    '#input' => TRUE,
    '#process' => array(
      'interval_element_process',
    ),
    '#theme' => 'interval',
    '#theme_wrappers' => array(
      'form_element',
    ),
  );
  return $types;
}

/**
 * Implements hook_theme().
 */
function interval_theme() {
  $hooks = array(
    'interval' => array(
      'render element' => 'element',
    ),
  );
  return $hooks;
}

/**
 * Returns HTML for an interval form element.
 */
function theme_interval($variables) {
  $element = $variables['element'];
  $element['interval']['#size'] = 8;
  $attributes = array();
  if (isset($element['#id'])) {
    $attributes['id'] = $element['#id'];
  }
  if (!empty($element['#attributes']['class'])) {
    $attributes['class'] = (array) $element['#attributes']['class'];
  }
  $attributes['class'][] = 'container-inline';
  return '<div' . drupal_attributes($attributes) . '>' . drupal_render_children($element) . '</div>';
}

/**
 * Process function to expand the interval element type.
 */
function interval_element_process($element, &$form_state, $form) {
  $value = !empty($element['#default_value']) ? $element['#default_value'] : array(
    'interval' => NULL,
    'period' => NULL,
  );
  $element['interval'] = array(
    '#type' => 'textfield',
    '#default_value' => $value['interval'],
    '#required' => $element['#required'],
  );
  $intervals = interval_get_intervals();
  $periods = !empty($element['#periods']) ? $element['#periods'] : array_keys($intervals);
  $period_options = array();
  foreach ($intervals as $key => $detail) {
    if (in_array($key, $periods) && isset($detail['plural'])) {
      $period_options[$key] = $detail['plural'];
    }
  }
  $element['period'] = array(
    '#type' => 'select',
    '#options' => $period_options,
    '#default_value' => $value['period'],
    '#required' => $element['#required'],
  );
  return $element;
}

/***************************************************************
 * Non core hooks
 ***************************************************************/

/**
 * Implements hook_interval_intervals
 */
function interval_interval_intervals() {
  return array(
    'second' => array(
      'plural' => t('Seconds', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Second', array(), array(
        'context' => 'interval',
      )),
      'php' => 'seconds',
      'multiplier' => 1,
    ),
    'minute' => array(
      'plural' => t('Minutes', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Minute', array(), array(
        'context' => 'interval',
      )),
      'php' => 'minutes',
      'multiplier' => 1,
    ),
    'hour' => array(
      'plural' => t('Hours', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Hour', array(), array(
        'context' => 'interval',
      )),
      'php' => 'hours',
      'multiplier' => 1,
    ),
    'day' => array(
      'plural' => t('Days', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Day', array(), array(
        'context' => 'interval',
      )),
      'php' => 'days',
      'multiplier' => 1,
    ),
    'month' => array(
      'plural' => t('Months', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Month', array(), array(
        'context' => 'interval',
      )),
      'php' => 'months',
      'multiplier' => 1,
    ),
    'year' => array(
      'plural' => t('Years', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Year', array(), array(
        'context' => 'interval',
      )),
      'php' => 'years',
      'multiplier' => 1,
    ),
    'week' => array(
      'plural' => t('Weeks', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Week', array(), array(
        'context' => 'interval',
      )),
      'php' => 'days',
      'multiplier' => 7,
    ),
    'fortnight' => array(
      'plural' => t('Fortnights', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Fortnight', array(), array(
        'context' => 'interval',
      )),
      'php' => 'days',
      'multiplier' => 14,
    ),
    'quarter' => array(
      'plural' => t('Quarters', array(), array(
        'context' => 'interval',
      )),
      'singular' => t('Quarter', array(), array(
        'context' => 'interval',
      )),
      'php' => 'months',
      'multiplier' => 3,
    ),
  );
}

/***************************************************************
 * Module functions
 ***************************************************************/

/**
 * Util function to fetch defined intervals
 */
function interval_get_intervals() {
  $intervals =& drupal_static(__FUNCTION__);
  if (!isset($intervals)) {

    // First prime of the static - try cache.
    $cached = cache_get('interval_intervals');
    if ($cached && $cached->data) {
      $intervals = $cached->data;
    }
    else {

      // Non-primed cache too - initialize with module_invoke_all.
      $intervals = module_invoke_all('interval_intervals');

      // Cache them.
      cache_set('interval_intervals', $intervals);
    }
  }
  return $intervals;
}

/**
 * Applies the interval values to a given date
 *
 * @param object $date
 *   A DateTime object to which the interval needs to be applied
 * @param array $item
 *   An array of values for the interval item consisting of:
 *   - interval: the interval (int)
 *   - period: the interval period
 * @param bool $limit
 *   When calling the interval apply function with months or a month multiplier,
 *   keep the date in the last day of the month if this was exceeded.
 *   Example, with $limit set to TRUE, January 31st +1 month will result in
 *   February 28th.
 *
 * @return bool
 *   TRUE/FALSE
 */
function interval_apply_interval($date, $item, $limit = FALSE) {
  if (is_object($date)) {
    $old_date = clone $date;
    $intervals = interval_get_intervals();
    $interval = $intervals[$item['period']];
    $datetime = interval_php_interval($item);
    $date
      ->modify($datetime);

    // "modify()" uses "strtotime()", so is really just adding X seconds. When
    // working with months, this can have counter-intuitive results. $limit is
    // supposed to fix these behaviors:
    if ($limit && $interval['php'] == 'months') {
      $dateinterval = $date
        ->diff($old_date);

      // Assert that the month interval is properly 1 month:
      while ($dateinterval->m < $interval['multiplier']) {

        // We went 30x days but didn't reach the right month: Dec 1 to Dec 31.
        // Appropriate behavior is to go to Jan 1.
        $date
          ->modify("first day of next month");
        $dateinterval = $date
          ->diff($old_date);
      }
      if ($dateinterval->d != 0) {

        // We went too far, which can happen around February. Collapse back to
        // the end of the proper (prior) month.
        $date
          ->modify("last day of last month");
      }
    }
    return TRUE;
  }
  return FALSE;
}

/**
 * Returns a PHP date/time string for a given interval.
 *
 * @param array $item
 *   An array of values for the interval item consisting of:
 *   - interval: the interval (int)
 *   - period: the interval period
 * @param array $interval
 *   An array of interval properties.
 *
 * @return string
 *   The PHP date/time string.
 *
 * @see interval_get_intervals
 */
function interval_php_interval($item, $interval = NULL) {
  if (!isset($interval)) {
    $intervals = interval_get_intervals();
    $interval = $intervals[$item['period']];
  }
  $value = $item['interval'] * $interval['multiplier'];
  $datetime = $value . ' ' . $interval['php'];
  return $datetime;
}

/**
 * Formats an interval
 *
 * Takes the given interval values and formats them as a string
 *
 * @param array $item
 *   An array of values for the interval item consisting of:
 *   - interval: the interval (int)
 *   - period: the interval period
 *
 * @return string
 *   The formatted interval
 */
function interval_format_interval($item) {
  $intervals = interval_get_intervals();
  $interval = $intervals[$item['period']];
  return format_plural($item['interval'], '1 @singular', '@count @plural', array(
    '@singular' => $interval['singular'],
    '@plural' => $interval['plural'],
  ));
}

/**
 * Returns an options list of all supported interval periods.
 */
function interval_period_options_list() {
  $options = array();
  foreach (interval_get_intervals() as $interval => $info) {
    $options[$interval] = $info['plural'];
  }
  return $options;
}

Functions

Namesort descending Description
interval_apply_interval Applies the interval values to a given date
interval_element_info Implements hook_element_info().
interval_element_process Process function to expand the interval element type.
interval_entity_property_field_item_interval_info Defines the data structure used for interval field items.
interval_field_entity_property_info_alter Callback to alter the property info of interval fields.
interval_field_formatter_info Implements hook_field_formatter_info().
interval_field_formatter_view Implements hook_field_formatter_view().
interval_field_info Implements hook_field_info().
interval_field_instance_settings_form Implements hook_field_instance_settings_form().
interval_field_is_empty Implements hook_field_is_empty().
interval_field_validate Implements hook_field_validate().
interval_field_widget_error Implements hook_field_widget_error().
interval_field_widget_form Implements hook_field_widget_form().
interval_field_widget_info Implements hook_field_widget_info().
interval_format_interval Formats an interval
interval_get_intervals Util function to fetch defined intervals
interval_interval_intervals Implements hook_interval_intervals
interval_period_options_list Returns an options list of all supported interval periods.
interval_php_interval Returns a PHP date/time string for a given interval.
interval_theme Implements hook_theme().
theme_interval Returns HTML for an interval form element.