You are here

class availability_calendars_handler_filter_availability in Availability Calendars 6.2

@class availability_calendars_handler_filter_availability Views handler to filter on availability.

This filter inherits from specific to:

  • Accept dates only (not DateTime's).
  • Not accepting relative dates.
  • Accept only future dates.
  • Allow only 2 operators:
    • =: available at a given date
    • between: available from a given departure date to a departure date
  • Add date popups to the (exposed) form elements.

Hierarchy

Expanded class hierarchy of availability_calendars_handler_filter_availability

See also

views_handler_filter_date. But we make it more

1 string reference to 'availability_calendars_handler_filter_availability'
availability_calendars_views_data_alter in ./availability_calendars.views.inc
Implements hook_views_data_alter().

File

./availability_calendars_handler_filter_availability.inc, line 17

View source
class availability_calendars_handler_filter_availability extends views_handler_filter_date {
  public static $instance;
  function __construct() {
    self::$instance = $this;
  }
  function option_definition() {
    $options = parent::option_definition();
    $options['operator'] = array(
      'default' => 'between',
    );
    return $options;
  }
  function operators() {

    // We only allow "available at <date>" and
    // "available between <from> and <to>" as operators.
    $operators = array(
      '=' => array(
        'title' => t('At (date)'),
        'method' => 'op_simple',
        'short' => t('at'),
        'values' => 1,
      ),
      'between' => array(
        'title' => t('Period'),
        'method' => 'op_between',
        'short' => t('between'),
        'values' => 2,
      ),
    );
    return $operators;
  }

  /**
   * Add validation and date popup(s) to the value form.
   */
  function value_form(&$form, &$form_state) {
    parent::value_form($form, $form_state);

    // Change labels.
    if (isset($form['value']['min'])) {
      $form['value']['min']['#title'] = t('Arrival date');
      $form['value']['max']['#title'] = t('Departure date');
    }
    else {
      $form['value']['#title'] = '';
    }

    // Remove the option, that date filter (our parent class) added, to define
    // dates relatively.
    if (empty($form_state['exposed']) && isset($form['value']['type'])) {
      $form['value']['type']['#type'] = 'hidden';
      $form['value']['type']['#default_value'] = 'date';
      $form['value']['type']['#access'] = FALSE;

      // Don't send to the client.
    }

    // Add validation.
    $form['value']['#element_validate'][] = 'availability_calendars_handler_filter_availability_validate_value';

    // Use date popups if that module is available.
    if (module_exists('date_popup')) {
      if (isset($form['value']['min'])) {
        $form['value']['min'] = $this
          ->change_element_into_date_popup($form['value']['min']);
        $form['value']['max'] = $this
          ->change_element_into_date_popup($form['value']['max']);
      }
      else {
        if (isset($form['value']['#type']) && $form['value']['#type'] == 'textfield') {
          $form['value'] = $this
            ->change_element_into_date_popup($form['value']);
        }
      }
    }
  }

  /**
   * Changes a (text) form element into a dete popup element.
   *
   * @param array $element
   * @return array
   *   The changed form element.
   */
  protected function change_element_into_date_popup($element) {
    $element['#type'] = 'date_popup';
    $element['#date_label_position'] = '';
    $element['#date_type'] = 'DATE_ISO';
    $element['#date_format'] = 'Y-m-d';
    $element['#date_year_range'] = '-0:+2';

    // @todo: where to place this code? is it needed in D6?
    availability_calendars_handler_filter_availability_js_alter($dummy = TRUE);
    return $element;
  }

  /**
   * Validate that the time values convert to something usable.
   */
  function validate_valid_time(&$form, $operator, $value) {
    $operators = $this
      ->operators();
    $required = FALSE;
    if ($operator === NULL) {

      // We are in exposed form validate.
      $required = (bool) $this->options['expose']['required'];
      $operator = array_key_exists('min', $form) ? 'between' : '=';
    }
    $now = format_date(time(), 'custom', 'Y-m-d');
    if ($operators[$operator]['values'] == 1) {

      // Note that the value can be an array with a date and time component.
      $value = is_array($value) && array_key_exists('date', $value) ? $value['date'] : $value;
      $this
        ->validate_valid_time_1($form, $value, $required, $now, t('Only future availability can be searched.'));
    }
    elseif ($operators[$operator]['values'] == 2) {

      // Note that the min and max values can be an array with a date and time
      // component or a string with a date and time component
      // (yyyy-mm-dd hh:mm:ss).
      $min_value = is_array($value['min']) && array_key_exists('date', $value['min']) ? $value['min']['date'] : substr($value['min'], 0, 10);
      $min_valid = $this
        ->validate_valid_time_1($form['min'], $min_value, $required, $now, t('Only future availability can be searched.'));
      $max_value = is_array($value['max']) && array_key_exists('date', $value['max']) ? $value['max']['date'] : substr($value['max'], 0, 10);
      $required = $required || !empty($max_value);
      $this
        ->validate_valid_time_1($form['max'], $max_value, $required, $min_valid ? $min_value : NULL, t('The departure date should be after the arrival date.'));
    }
  }
  protected function validate_valid_time_1(&$element, $value, $required, $minimum, $minimum_error_message) {
    $valid = TRUE;
    if (empty($value)) {
      if ($required) {
        form_error($element, t('Field %field is required.', array(
          '%field' => $element['#title'],
        )));
        $valid = FALSE;
      }
    }
    else {
      if (strlen($value) !== 10 || !checkdate(substr($value, 5, 2), substr($value, 8, 2), substr($value, 0, 4))) {
        form_error($element, t('Invalid date format.'));
        $valid = FALSE;
      }
      else {
        if (!empty($minimum) && $value <= $minimum) {
          form_error($element, $minimum_error_message);
          $valid = FALSE;
        }
      }
    }
    return $valid;
  }
  function op_between($field) {
    $arrival = is_array($this->value['min']) && array_key_exists('date', $this->value['min']) ? $this->value['min']['date'] : $this->value['min'];
    $departure = is_array($this->value['max']) && array_key_exists('date', $this->value['max']) ? $this->value['max']['date'] : $this->value['max'];
    $this
      ->query_available($arrival, $departure);
  }
  function op_simple($field) {
    $this->value['min'] = $this->value['value'];
    $this->value['max'] = format_date(mktime(12, 0, 0, substr($this->value['min'], 5, 2), (int) substr($this->value['min'], 8, 2) + 1, substr($this->value['min'], 0, 4)), 'custom', 'Y-m-d');
    return $this
      ->op_between($field);
  }

  /**
   * Add where clauses to the query to filter on availability.
   *
   * @param string $arrival yyyy-mm-dd.
   * @param string $departure yyyy-mm-dd.
   */
  function query_available($arrival, $departure) {
    if (empty($arrival) || empty($departure)) {

      // Don't add a clause if there's nothing to filter on.
      return;
    }
    module_load_include('inc', 'availability_calendars', 'availability_calendars');
    $states = availability_calendars_get_states();
    $settings = availability_calendars_get_settings();
    $default_is_available = $states[$settings->defaultstatus]['is_available'];
    $classes = array();
    foreach ($states as $class => $state) {
      if ($state['is_available'] != $default_is_available) {
        $classes[] = $class;
      }
    }
    $classes = empty($classes) ? '' : "'" . implode("', '", $classes) . "'";

    // Format from and to in a safe way.
    $start = format_date(mktime(12, 0, 0, substr($arrival, 5, 2), substr($arrival, 8, 2), substr($arrival, 0, 4)), 'custom', 'Y-m-d');
    $end = format_date(mktime(12, 0, 0, substr($departure, 5, 2), (int) substr($departure, 8, 2) - 1, substr($departure, 0, 4)), 'custom', 'Y-m-d');

    // Build common part of subquery.
    $base_table_alias = reset($this->query->tables[$this->table]);
    $base_table_alias = $base_table_alias['alias'];
    $from = 'FROM {availability_calendars_day} acd';
    $where = "WHERE acd.nid = {$base_table_alias}.{$this->query->base_field} ";
    $where .= "AND acd.date BETWEEN '{$start}' AND '{$end}' ";
    $where .= "AND acd.status in ({$classes})";
    if ($default_is_available == 1) {

      // Default status = available: so no single day may be marked as non
      // available. Check by doing a check on the existence of a non available
      // day in the given period.
      $subquery = "SELECT 1 {$from} {$where}";
      $this->query
        ->add_where(0, "NOT EXISTS ({$subquery})");
    }
    else {

      // Default status = not available: so all days must be marked as
      // available. Check by doing a count on the available days in the given
      // period which should equal the total number of days.
      $timestamp_arrival = strtotime($arrival);
      $timestamp_departure = strtotime($departure);
      $days = (int) round(($timestamp_departure - $timestamp_arrival) / (60 * 60 * 24));
      $subquery = "SELECT count(*) {$from} {$where}";
      $this->query
        ->add_where(0, "({$subquery}) = {$days}");
    }
  }

}

Members