first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
/* Pager plugin css */
div.date-views-pager {
margin-left: auto;
margin-right: auto;
}
div.date-views-pager li {
float: left;
text-align: left;
width: 20%;
}
div.date-views-pager div.current-page {
font-weight: bold;
text-align: center;
}
div.date-views-pager div.next-month,
div.date-views-pager div.next-year {
text-align: right;
}
/* Views filter form css */
.views-group-box div.date-views-filter-fieldset {
margin-left: 0;
}
.views-group-box fieldset.date-views-filter-fieldset {
padding-top: 0;
}
.views-group-box fieldset.date-views-filter-fieldset .container-inline-date > .form-item {
border-width: 0;
margin: 9px 3px 0 3px;
display: block;
}
.views-group-box fieldset.date-views-filter-fieldset .container-inline-date .date-padding {
padding: 0;
}
.date-views-filter-wrapper {
min-width: 250px;
}
.date-views-filter input {
float: left !important; /* LTR */
margin-right: 2px !important; /* LTR */
min-width: 12em;
padding: 0 !important;
width: 12em;
}
/**
* Style Header
*/
/* Give the navigation bar a little extra padding below so it will clear the new contextual links overlay of the teasers below it. */
.date-nav {
clear: both;
padding-bottom: 1.5em;
width: 100%;
}
.date-nav div.date-heading h3 {
margin: 0;
padding: 0;
}
.date-nav-wrapper .clear-block {
margin-bottom: 10px;
}
.date-nav-wrapper {
position: relative;
margin-top: 5px;
width: 100%;
}
.date-nav-wrapper .date-nav {
background-color: transparent;
border: 0px;
height: 30px;
height: auto;
min-height: 30px;
position: relative;
margin-bottom: 10px;
}
.date-nav-wrapper .date-prev a,
.date-nav-wrapper .date-next a {
text-decoration: none;
color: inherit;
font-size: 12px;
}
.date-nav-wrapper.date-nav a:hover {
text-decoration: underline;
}
.date-nav-wrapper .date-prev {
padding: 5px 0;
position: absolute;
left: 0px;
text-align: left;
top: 0px;
width: auto;
z-index: 1;
font-size: 12px;
}
.date-nav-wrapper .date-prev {
left: 0;
right: auto;
}
.date-nav-wrapper .date-prev a {
margin-left: 10px;
font-weight: bold;
}
.date-nav-wrapper .date-heading {
position: relative;
width: 100%;
top: 0px;
text-align: center;
z-index: 0;
}
.date-nav-wrapper .date-heading h3 {
line-height: 30px;
font-size: 1.7em;
}
.date-nav-wrapper .date-next {
padding: 5px 0;
position: absolute;
right: 0px;
text-align: right;
top: 0px;
width: auto;
z-index: 1;
font-size: 12px;
}
.date-nav-wrapper .date-next a {
margin-right: 10px;
font-weight: bold;
}

View File

@@ -0,0 +1,21 @@
name = Date Views
description = Views integration for date fields and date functionality.
package = Date/Time
dependencies[] = date_api
dependencies[] = views
core = 7.x
php = 5.2
files[] = includes/date_views_argument_handler.inc
files[] = includes/date_views_argument_handler_simple.inc
files[] = includes/date_views_filter_handler.inc
files[] = includes/date_views_filter_handler_simple.inc
files[] = includes/date_views.views_default.inc
files[] = includes/date_views.views.inc
files[] = includes/date_views_plugin_pager.inc
; Information added by drupal.org packaging script on 2012-09-30
version = "7.x-2.6+2-dev"
core = "7.x"
project = "date"
datestamp = "1348964349"

View File

@@ -0,0 +1,429 @@
<?php
/**
* Implements hook_views_api().
*
* This one is used as the base to reduce errors when updating.
*/
function date_views_theme() {
$path = drupal_get_path('module', 'date_views');
$base = array(
'file' => 'theme.inc',
'path' => "$path/theme",
);
return array(
'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)),
'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
'date_views_pager' => $base + array(
'variables' => array('plugin' => NULL, 'input' => NULL),
// Register a pattern so that it can work like all views templates.
'pattern' => 'date_views_pager__',
'template' => 'date-views-pager',
),
);
}
function date_views_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'date_views') . '/includes',
);
}
/**
* Wrapper function to make sure this function will always work.
*/
function date_views_views_fetch_fields($base, $type) {
if (!module_exists('views')) {
return array();
}
module_load_include('inc', 'views', 'includes/admin');
return views_fetch_fields($base, $type);
}
/**
* Identify all potential date/timestamp fields and cache the data.
*/
function date_views_fields($base = 'node', $reset = FALSE) {
static $fields = array();
$empty = array('name' => array(), 'alias' => array());
module_load_include('inc', 'date_views', 'includes/date_views_fields');
if (empty($fields[$base]) || $reset) {
$cid = 'date_views_fields_' . $base;
if (!$reset && $cached = cache_get($cid, 'cache_views')) {
$fields[$base] = $cached->data;
}
else {
$fields[$base] = _date_views_fields($base);
}
}
// Make sure that empty values will be arrays in he expected format.
return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
}
/**
* Implements hook_date_views_entities().
* Map extra Views tables to the entity that holds its date fields,
* needed for Views tables other than the primary tables identified in entity_info().
*/
function date_views_date_views_extra_tables() {
return array(
'node_revision' => 'node',
);
}
/**
* Helper function to map entity types to the Views base table they use,
* to make it easier to infer the entity type from a base table.
*
* Views has a new handler called views_handler_field_entity() that loads
* entities, and you can use something like the following to get the
* entity type from a view, but not all our base tables contain the
* entity information we need, (i.e. revisions) so it won't work here
* and we resort to creating information from entity_get_info().
*
* // A method to get the entity type for a base table.
* $table_data = views_fetch_data($base_table);
* if (!isset($table_data['table']['base']['entity type'])) {
* return FALSE;
* }
* $entity_type = $table_data['table']['base']['entity type'];
*/
function date_views_base_tables() {
$base_tables = &drupal_static(__FILE__, array());
if (empty($base_tables)) {
// First we get the base tables we can learn about from entity_info.
$entity_info = entity_get_info();
foreach ($entity_info as $entity_type => $info) {
if (!empty($info['base table'])) {
$base_tables[$info['base table']] = $entity_type;
}
if (!empty($info['revision table'])) {
$base_tables[$info['revision table']] = $entity_type;
}
}
// Then we let other modules tell us about other entity tables that hold date fields.
$base_tables += module_invoke_all('date_views_extra_tables');
}
return $base_tables;
}
/**
* Implements hook_date_views_fields().
*
* All modules that create custom fields that use the
* 'views_handler_field_date' handler can provide
* additional information here about the type of
* date they create so the date can be used by
* the Date API views date argument and date filter.
*/
function date_views_date_views_fields($field) {
$values = array(
// The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
'sql_type' => DATE_UNIX,
// Timezone handling options: 'none', 'site', 'date', 'utc' .
'tz_handling' => 'site',
// Needed only for dates that use 'date' tz_handling.
'timezone_field' => '',
// Needed only for dates that use 'date' tz_handling.
'offset_field' => '',
// Array of "table.field" values for related fields that should be
// loaded automatically in the Views SQL.
'related_fields' => array(),
// Granularity of this date field's db data.
'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
);
switch ($field) {
case 'users.created':
case 'users.access':
case 'users.login':
case 'node.created':
case 'node.changed':
case 'node_revision.timestamp':
case 'file_managed.timestamp':
case 'comment.timestamp':
return $values;
}
}
/**
* A version of date_real_url that formats links correctly for the new Date pager.
*/
function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_url = FALSE, $absolute = TRUE) {
// If someone adds a pager without a matching argument, there is not date information to work with.
if (empty($view->date_info) || !isset($view->date_info->date_arg_pos)) {
return '';
}
$args = $view->args;
$pos = $view->date_info->date_arg_pos;
// The View arguments array is indexed numerically but is not necessarily
// in numerical order. Sort the arguments to ensure the correct order.
ksort($args);
// If there are empty arguments before the date argument,
// pad them with the wildcard so the date argument will be in
// the right position.
if (count($args) < $pos) {
foreach ($view->argument as $name => $argument) {
if ($argument->position == $pos) {
break;
}
$args[] = $argument->options['exception']['value'];
}
}
if (!empty($date_type)) {
switch ($date_type) {
case 'year':
$args[$pos] = date_pad($view->date_info->year, 4);
break;
case 'week':
$args[$pos] = date_pad($view->date_info->year, 4) . '-W' . date_pad($view->date_info->week);
break;
case 'day':
$args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month) . '-' . date_pad($view->date_info->day);
break;
default:
$args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month);
break;
}
}
elseif (!empty($date_arg)) {
$args[$pos] = $date_arg;
}
else {
$args = $view->args;
}
// Is this an embedded or a block view?
// Return the pager query value.
if (!$force_view_url &&
(!empty($view->preview) || !empty($view->date_info->block_identifier))) {
$url = $args[$pos];
$key = date_block_identifier($view);
if (!empty($key)) {
return url($_GET['q'], array(
'query' => date_views_querystring($view, array($key => $url)),
'absolute' => $absolute));
}
}
// Normal views may need querystrings appended to them
// if they use exposed filters.
return url($view->get_url($args), array(
'query' => date_views_querystring($view),
'absolute' => $absolute));
}
function date_block_identifier($view) {
if (!empty($view->block_identifier)) {
return $view->block_identifier;
}
return isset($view->date_info->block_identifier) ? $view->date_info->block_identifier : NULL;
}
/**
* Implements hook_field_views_data_alter().
*
* Create a Views field for each date column we care about
* to supplement the generic 'entity_id' and 'revision_id'
* fields that are automatically created.
*
* Also use friendlier labels to distinguish the start date
* and end date in listings (for fields that use both).
*/
function date_views_field_views_data_alter(&$result, $field, $module) {
if ($module == 'date') {
$has_end_date = !empty($field['settings']['todate']);
if ($has_end_date) {
$labels = field_views_field_label($field['field_name']);
$label = array_shift($labels);
}
foreach ($result as $table => $data) {
$additional = array();
$field_name = $field['field_name'];
foreach ($data as $column => $value) {
// The old 'entity_id' and 'revision_id' values got rewritten in Views.
// The old values are still there with a 'moved to' key, so ignore them.
if (array_key_exists('field', $value) && !array_key_exists('moved to', $value['field'])) {
$result[$table][$column]['field']['is date'] = TRUE;
// Not sure yet if we still need a custom field handler in D7 now that custom formatters are available.
// Might still need it to handle grouping of multiple value dates.
//$result[$table][$column]['field']['handler'] = 'date_handler_field_date';
//$result[$table][$column]['field']['add fields to query'] = TRUE;
}
// For filters, arguments, and sorts, determine if this column is for
// the start date ('value') or the end date ('value2').
$this_column = NULL;
foreach (array_keys($field['columns']) as $candidate_column) {
if ($column == $field['field_name'] . '_' . $candidate_column) {
$this_column = $candidate_column;
break;
}
}
// Only alter the date fields, not timezone, rrule, offset, etc.
if ($this_column != 'value' && $this_column != 'value2') {
continue;
}
// We will replace the label with a friendlier name in the case of
// arguments, filters, and sorts (but only if this field uses an end
// date).
$replace_label = FALSE;
if (array_key_exists('argument', $value)) {
$result[$table][$column]['argument']['handler'] = 'date_views_argument_handler_simple';
$result[$table][$column]['argument']['empty field name'] = t('Undated');
$result[$table][$column]['argument']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if (array_key_exists('filter', $value)) {
$result[$table][$column]['filter']['handler'] = 'date_views_filter_handler_simple';
$result[$table][$column]['filter']['empty field name'] = t('Undated');
$result[$table][$column]['filter']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if (array_key_exists('sort', $value)) {
$result[$table][$column]['sort']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if ($replace_label) {
// Determine if this column is for the start date ('value') or the
// end date ('value2').
$this_column = NULL;
foreach (array_keys($field['columns']) as $candidate_column) {
if ($column == $field['field_name'] . '_' . $candidate_column) {
$this_column = $candidate_column;
break;
}
}
// Insert the phrase "start date" or "end date" after the label, so
// users can distinguish them in listings (compared to the default
// behavior of field_views_field_default_views_data(), which only
// uses the 'value2' column name to distinguish them).
switch ($this_column) {
case 'value':
// Insert a deliberate double space before 'start date' in the
// translatable string. This is a hack to get it to appear right
// before 'end date' in the listing (i.e., in a non-alphabetical,
// but more user friendly, order).
$result[$table][$column]['title'] = t('@label - start date (!name)', array('@label' => $label, '!name' => $field['field_name']));
$result[$table][$column]['title short'] = t('@label - start date', array('@label' => $label));
break;
case 'value2':
$result[$table][$column]['title'] = t('@label - end date (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $this_column));
$result[$table][$column]['title short'] = t('@label - end date:!column', array('@label' => $label, '!column' => $this_column));
break;
}
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for views_ui_edit_form().
*/
function date_views_form_views_ui_edit_form_alter(&$form, &$form_state, $form_id) {
// This CSS is needed for the configuration form provided by the Date filter
// (date_views_filter_handler_simple), but we have to add it here so that
// it's already on the edit form the first time a Date filter is being added
// to the View. See http://drupal.org/node/1239228#comment-4885288.
$form['#attached']['css'][] = drupal_get_path('module', 'date_views') . '/css/date_views.css';
}
/**
* The instanceof function makes this work for any handler that was derived
* from 'views_handler_filter_date' or 'views_handler_argument_date',
* which includes core date fields like the node updated field.
*
* The test for $handler->min_date tells us that this is an argument that
* not only is derived from the views date handler but also has been processed
* by the Date Views filter or argument code.
*/
function date_views_handler_is_date($handler, $type = 'argument') {
switch ($type) {
case 'filter':
return $handler instanceof views_handler_filter_date && !empty($handler->min_date);
case 'argument':
return $handler instanceof views_handler_argument_date && !empty($handler->min_date);
}
return FALSE;
}
/**
* Validation hook for exposed filters that use the select widget.
* This is to ensure the the user completes all parts of the date
* not just some parts. Only needed for the select widget.
*/
function date_views_select_validate(&$form, &$form_state) {
// If there are no values just return.
if (empty($form['value']) && empty($form['min'])) {
return;
}
$granularity = (!empty($form['min']['#date_format'])) ? date_format_order($form['min']['#date_format']) : date_format_order($form['value']['#date_format']);
$filled = array();
$value = drupal_array_get_nested_value($form_state['input'], $form['#parents']);
foreach ($granularity as $part) {
if (!empty($value['value'][$part])) {
$filled[] = $part;
}
}
if (!empty($filled) && count($filled) != count($granularity)) {
$unfilled = array_diff($granularity, $filled);
foreach ($unfilled as $part) {
switch ($part) {
case 'year':
form_error($form['value'][$part], t('Please choose a year.'), $form_state);
break;
case 'month':
form_error($form['value'][$part], t('Please choose a month.'), $form_state);
break;
case 'day':
form_error($form['value'][$part], t('Please choose a day.'), $form_state);
break;
case 'hour':
form_error($form['value'][$part], t('Please choose an hour.'), $form_state);
break;
case 'minute':
form_error($form['value'][$part], t('Please choose a minute.'), $form_state);
break;
case 'second':
form_error($form['value'][$part], t('Please choose a second.'), $form_state);
break;
}
}
}
}
/**
* Implements hook_date_formatter_view_alter().
*
* If we are displaying a date from a view, see if we have information about
* which multiple value to display. If so, set the date_id in the entity.
*/
function date_views_date_formatter_pre_view_alter(&$entity, &$variables) {
// Some views have no row index.
if (!empty($entity->view) && isset($entity->view->row_index)) {
$field = $variables['field'];
$date_id = 'date_id_' . $field['field_name'];
$date_delta = 'date_delta_' . $field['field_name'];
$date_item = $entity->view->result[$entity->view->row_index];
if (!empty($date_item->$date_id)) {
$entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0';
}
}
}

View File

@@ -0,0 +1,6 @@
<?php
/**
* @file
* Empty file to avoid fatal error if it doesn't exist.
* Formerly the attachment for the Date Browser.
*/

View File

@@ -0,0 +1,198 @@
<?php
/**
* @file
* Defines date-related Views data and plugins:
*
* Date argument:
* A generic date argument that has an option to select one or more
* Views date fields to filter on, automatically adds them to the view,
* and then filters the view by the value of the selected field(s).
* The flexible argument will accept and evaluate most ISO date
* and period formats, like 2009-05-01, 2008-W25, P1W.
*
* Date filter:
* A generic date filter that has an option to select a
* Views date field to filter on, with a choice of a widget to use
* for the filter form and an option to set the default value to
* a set date or something like 'now +90 days' . If the operator is
* set to 'between' or 'not between' you can set a default value for
* both the start and end dates.
*
* Current date argument default
* Adds a default option to set the argument to the current date
* when the argument is empty.
*
* Date navigation attachment
* Navigation that can be attached to any display to create back/next
* links by date, requires the date argument and uses the current
* date argument default to set a starting point for the view.
*/
/**
* Implements hook_views_plugins
*/
function date_views_views_plugins() {
$path = drupal_get_path('module', 'date_views');
$views_path = drupal_get_path('module', 'views');
module_load_include('inc', 'date_views', 'theme/theme');
return array(
'module' => 'date_views', // This just tells our themes are elsewhere.
'display' => array(
// Display plugin for date navigation.
'date_nav' => array(
'title' => t('Date browser'),
'help' => t('Date back/next navigation to attach to other displays. Requires the Date argument.'),
'handler' => 'date_plugin_display_attachment',
'parent' => 'attachment',
'path' => "$path/includes",
'theme' => 'views_view',
'use ajax' => TRUE,
'admin' => t('Date browser'),
'help topic' => 'date-browser',
),
),
'pager' => array(
'date_views_pager' => array(
'title' => t('Page by date'),
'help' => t('Page using the value of a date field.'),
'handler' => 'date_views_plugin_pager',
'path' => "$path/includes",
'help topic' => 'date-views-pager',
'uses options' => TRUE,
),
),
'style' => array(
'date_nav' => array(
'title' => t('Date browser style'),
'help' => t('Creates back/next navigation.'),
'handler' => 'date_navigation_plugin_style',
'path' => "$path/includes",
'theme' => 'date_navigation',
'theme file' => 'theme.inc',
'theme path' => "$path/theme",
'uses row plugin' => FALSE,
'uses fields' => FALSE,
'uses options' => TRUE,
'type' => 'date_nav',
'even empty' => TRUE,
),
),
);
}
/**
* Implements hook_views_data()
*/
function date_views_views_data() {
$data = array();
$tables = date_views_base_tables();
foreach ($tables as $base_table => $entity) {
// The flexible date argument.
$data[$base_table]['date_argument'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc). ', array('!base_table' => $base_table)),
'argument' => array(
'handler' => 'date_views_argument_handler',
'empty field name' => t('Undated'),
'is date' => TRUE,
//'skip base' => $base_table,
),
);
// The flexible date filter.
$data[$base_table]['date_filter'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field.', array('!base_table' => $base_table)),
'filter' => array(
'handler' => 'date_views_filter_handler',
'empty field name' => t('Undated'),
'is date' => TRUE,
//'skip base' => $base_table,
),
);
}
return $data;
}
/**
* Implements hook_views_data_alter().
*/
function date_views_views_data_alter(&$data) {
// Mark all the core date handlers as date fields.
// This will identify all handlers that directly use the _date handlers,
// will not pick up any that extend those handlers.
foreach ($data as $module => &$table) {
foreach ($table as $id => &$field) {
foreach (array('field', 'sort', 'filter', 'argument') as $type) {
if (isset($field[$type]) && isset($field[$type]['handler']) && ($field[$type]['handler'] == 'views_handler_' . $type . '_date')) {
$field[$type]['is date'] = TRUE;
}
}
}
}
}
/**
* Central function for setting up the right timezone values
* in the SQL date handler.
*
* The date handler will use this information to decide if the
* database value needs a timezone conversion.
*
* In Views, we will always be comparing to a local date value,
* so the goal is to convert the database value to the right
* value to compare to the local value.
*/
function date_views_set_timezone(&$date_handler, &$view, $field) {
switch ($field['tz_handling']) {
case 'date' :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone_field = $field['timezone_field'];
$date_handler->offset_field = $field['offset_field'];
break;
case 'none':
$date_handler->db_timezone = date_default_timezone();
$date_handler->local_timezone = date_default_timezone();
break;
case 'utc':
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = 'UTC';
break;
default :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = date_default_timezone();
break;
}
}
function date_views_querystring($view, $extra_params = array()) {
$query_params = array_merge($_GET, $extra_params);
// Allow NULL params to be removed from the query string.
foreach ($extra_params AS $key => $value) {
if (!isset($value)) {
unset($query_params[$key]);
}
}
// Filter the special "q" and "view" variables out of the query string.
$exclude = array('q');
// If we don't explicitly add a value for "view", filter it out.
if (empty($extra_params['view'])) {
$exclude[] = 'view';
}
// Clear out old date pager settings if not explicitly added.
if (!empty($view->date_info->pager_id) && empty($extra_params[$view->date_info->pager_id])) {
$exclude[] = $view->date_info->pager_id;
}
$query = drupal_get_query_parameters($query_params, $exclude);
// To prevent an empty query string from adding a "?" on to the end of a URL,
// we return NULL.
return !empty($query) ? $query : NULL;
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* @file
* Date API views argument handler.
* This argument combines multiple date arguments into a single argument
* where all fields are controlled by the same date and can be combined with either AND or OR.
*/
/**
* Date API argument handler.
*/
class date_views_argument_handler extends date_views_argument_handler_simple {
/**
* Get granularity and use it to create the formula and a format
* for the results.
*/
function init(&$view, &$options) {
parent::init($view, $options);
if (empty($this->view->date_info)) {
$this->view->date_info = new stdClass();
}
if (empty($this->view->date_info->date_fields)) {
$this->view->date_info->date_fields = array();
}
$this->view->date_info->date_fields = array_merge($this->view->date_info->date_fields, $this->options['date_fields']);
}
/**
* Default value for the date_fields option.
*/
function option_definition() {
$options = parent::option_definition();
$options['date_fields'] = array('default' => array());
$options['date_method'] = array('default' => 'OR');
$options['date_group'] = array('default' => 'date');
return $options;
}
/**
* Add a form element to select date_fields for this argument.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$fields = date_views_fields($this->base_table);
$options = array();
foreach ($fields['name'] as $name => $field) {
$options[$name] = $field['label'];
}
$form['date_fields'] = array(
'#title' => t('Date field(s)'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->options['date_fields'],
'#multiple' => TRUE,
'#description' => t("Select one or more date fields to filter with this argument."),
);
$form['date_method'] = array(
'#title' => t('Method'),
'#type' => 'radios',
'#options' => array('OR' => t('OR'), 'AND' => t('AND')),
'#default_value' => $this->options['date_method'],
'#description' => t('Method of handling multiple date fields in the same query. Return items that have any matching date field (date = field_1 OR field_2), or only those with matches in all selected date fields (date = field_1 AND field_2). '),
);
$form['date_group'] = array(
'#type' => 'hidden',
'#value' => $this->options['date_group'],
);
}
function options_validate(&$form, &$form_state) {
// Views will whine if we don't have something for the these values even though we removed the option for summaries.
$form_state['values']['options']['summary']['format'] = 'none';
$form_state['values']['options']['summary']['options']['none'] = array();
// It is very important to call the parent function here:
parent::options_validate($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$check_fields = array_filter($form_state['values']['options']['date_fields']);
if (empty($check_fields)) {
form_error($form['date_fields'], t('You must select at least one date field for this argument.'));
}
}
}
function options_submit(&$form, &$form_state) {
// It is very important to call the parent function here:
parent::options_submit($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$form_state['values']['options']['date_fields'] = array_filter($form_state['values']['options']['date_fields']);
}
}
// Update the summary values to show selected granularity.
function admin_summary() {
$fields = date_views_fields($this->base_table);
if (!empty($this->options['date_fields'])) {
$output = array();
foreach ($this->options['date_fields'] as $field) {
if (array_key_exists($field, $fields['name'])) {
$output[] = $fields['name'][$field]['label'];
}
}
return implode('<br />' . $this->options['date_method'] . ' ', $output);
}
else {
return parent::admin_summary();
}
}
/**
* Provide a list of default behaviors for this argument if the argument
* is not present.
*
* Override this method to provide additional (or fewer) default behaviors.
*/
function default_actions($which = NULL) {
$defaults = parent::default_actions();
// There is no easy way to do summary queries on multiple fields, so remove that option.
unset($defaults['summary']);
if ($which) {
if (!empty($defaults[$which])) {
return $defaults[$which];
}
}
else {
return $defaults;
}
}
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
function query($group_by = FALSE) {
// @TODO Not doing anything with $group_by yet, need to figure out what has to be done.
if ($this->date_forbid()) {
return;
}
$this->get_query_fields();
$this->query->set_where_group($this->options['date_method'], $this->options['date_group']);
$this->granularity = $this->date_handler->arg_granularity($this->argument);
$format = $this->date_handler->views_formats($this->granularity, 'sql');
$this->placeholders = array();
if (!empty($this->query_fields)) {
// Use set_where_group() with the selected date_method
// of 'AND' or 'OR' to create the where clause.
foreach ($this->query_fields as $count => $query_field) {
$field = $query_field['field'];
$this->date_handler = $query_field['date_handler'];
$this->field = $field['field_name'];
$this->real_field = $field['field_name'];
$this->table = $field['table_name'];
$this->original_table = $field['table_name'];
if ($field['table_name'] != $this->table || !empty($this->relationship)) {
$this->table = $this->query->ensure_table($field['table_name'], $this->relationship);
}
// $this->table_alias gets set when the first field is processed if otherwise empty.
// For subsequent fields, we need to be sure it is emptied again.
elseif (empty($this->relationship)) {
$this->table_alias = NULL;
}
parent::query($group_by);
$this->placeholders = array_merge($this->placeholders, $this->date_handler->placeholders);
}
}
}
/**
* Collect information about our fields we will need to create the right query.
*/
function get_query_fields() {
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$this->query_fields = array();
foreach ($this->options['date_fields'] as $delta => $name) {
if (array_key_exists($name, $fields) && $field = $fields[$name]) {
$date_handler = new date_sql_handler($field['sql_type'], date_default_timezone());
$date_handler->granularity = $this->options['granularity'];
$date_handler->db_timezone = date_get_timezone_db($field['tz_handling']);
$date_handler->local_timezone = date_get_timezone($field['tz_handling']);
date_views_set_timezone($date_handler, $this, $field);
$this->query_fields[] = array('field' => $field, 'date_handler' => $date_handler);
}
}
}
}

View File

@@ -0,0 +1,345 @@
<?php
/**
* @file
* Date API views argument handler.
*/
/**
* Date API argument handler.
*/
class date_views_argument_handler_simple extends views_handler_argument_date {
/**
* Get granularity and use it to create the formula and a format
* for the results.
*/
function init(&$view, &$options) {
parent::init($view, $options);
// Add a date handler.
module_load_include('inc', 'date_api', 'date_api_sql');
$this->date_handler = new date_sql_handler(DATE_UNIX);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
if (!empty($field) && !empty($field['type'])) {
$this->date_handler->date_type = $field['type'];
$this->original_table = $this->definition['table'];
}
$this->date_handler->db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
$this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']);
}
$this->date_handler->granularity = $this->options['granularity'];
// This value needs to be initialized so it exists even if the query doesn't run.
$this->date_handler->placeholders = array();
$this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display');
$this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql');
// $this->arg_format is the format the parent date handler will use to create a default argument.
$this->arg_format = $this->format();
// Identify the base table for this field.
// It will be used to call for the right query field options.
$this->base_table = $this->table;
}
function format() {
if (!empty($this->options['granularity'])) {
return $this->date_handler->views_formats($this->options['granularity']);
}
else {
return !empty($this->options[$this->option_name]) ? $this->options[$this->option_name] : 'Y-m';
}
}
/**
* Set the empty argument value to the current date,
* formatted appropriately for this argument.
*/
function get_default_argument($raw = FALSE) {
$is_default = FALSE;
if (!$raw && $this->options['default_argument_type'] == 'date') {
$granularity = $this->options['granularity'];
if ($granularity == 'week') {
$now = date_now();
$week = date_week(date_format($now, 'Y-m-d'));
$value = date_format($now, 'Y') . '-W' . $week;
}
else {
$value = date($this->arg_format, REQUEST_TIME);
}
drupal_alter('date_default_argument', $this, $value);
return $value;
}
// Let the parent argument handle options like node created date.
return parent::get_default_argument($raw);
}
/**
* Default value for the date_fields option.
*/
function option_definition() {
$options = parent::option_definition();
$options['year_range'] = array('default' => '-3:+3');
$options['granularity'] = array('default' => 'month');
$options['default_argument_type'] = array('default' => 'date');
$options['add_delta'] = array('default' => '');
$options['use_fromto'] = array('default' => '');
$options['title_format'] = array('default' => '');
$options['title_format_custom'] = array('default' => '');
return $options;
}
/**
* Add a form element to select date_fields for this argument.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Add an option to control the format of the summary.
$options = array(
'' => t('Default format'),
'custom' => t('Custom format'),
);
$example_month = date_format_date(date_example_date(), 'custom', $this->date_handler->views_formats('month', 'display'));
$example_day = date_format_date(date_example_date(), 'custom', $this->date_handler->views_formats('day', 'display'));
$form['title_format'] = array(
'#type' => 'select',
'#title' => t('Date format options'),
'#default_value' => $this->options['title_format'],
'#options' => $options,
'#description' => t('The date format used in titles and summary links for this argument. The default format is based on the granularity of the filter, i.e. month: @example_month, day: @example_day.', array('@example_month' => $example_month, '@example_day' => $example_day)),
'#attributes' => array('class' => array('dependent-options')),
'#states' => array(
'visible' => array(
':input[name="options[default_action]"]' => array('value' => 'summary')
),
),
);
$form['title_format_custom'] = array(
'#type' => 'textfield',
'#title' => t('Custom summary date format'),
'#default_value' => $this->options['title_format_custom'],
'#description' => t("A custom format for the title and summary date format. Define a php date format string like 'm-d-Y H:i' (see <a href=\"@link\">http://php.net/date</a> for more details).", array('@link' => 'http://php.net/date')),
'#attributes' => array('class' => array('dependent-options')),
'#states' => array(
'visible' => array(
':input[name="options[title_format]"]' => array('value' => 'custom')
),
),
);
$options = $this->date_handler->date_parts();
unset($options['second'], $options['minute']);
$options += array('week' => t('Week', array(), array('context' => 'datetime')));
$form['granularity'] = array(
'#title' => t('Granularity'),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $this->options['granularity'],
'#multiple' => TRUE,
'#description' => t("Select the type of date value to be used in defaults, summaries, and navigation. For example, a granularity of 'month' will set the default date to the current month, summarize by month in summary views, and link to the next and previous month when using date navigation."),
);
$form['year_range'] = array(
'#title' => t('Date year range'),
'#type' => 'textfield',
'#default_value' => $this->options['year_range'],
'#description' => t("Set the allowable minimum and maximum year range for this argument, either a -X:+X offset from the current year, like '-3:+3' or an absolute minimum and maximum year, like '2005:2010' . When the argument is set to a date outside the range, the page will be returned as 'Page not found (404)' ."),
);
$form['use_fromto'] = array(
'#type' => 'radios',
'#title' => t('Dates to compare'),
'#default_value' => $this->options['use_fromto'],
'#options' => array('' => t('Start/End date range'), 'no' => t('Only this field')),
'#description' => t("If selected the view will check if any value starting with the 'Start' date and ending with the 'End' date matches the view criteria. Otherwise the view will be limited to the specifically selected fields. Comparing to the whole Start/End range is the recommended setting when using this filter in a Calendar. When using the Start/End option, it is not necessary to add both the Start and End fields to the filter, either one will do."),
);
$access = TRUE;
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
$access = $field['cardinality'] != 1;
}
$form['add_delta'] = array(
'#type' => 'radios',
'#title' => t('Add multiple value identifier'),
'#default_value' => $this->options['add_delta'],
'#options' => array('' => t('No'), 'yes' => t('Yes')),
'#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'),
// Only let mere mortals tweak this setting for multi-value fields
'#access' => $access,
);
}
function options_validate(&$form, &$form_state) {
// It is very important to call the parent function here:
parent::options_validate($form, $form_state);
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
/**
* Provide a link to the next level of the view from the summary.
*/
function summary_name($data) {
$value = $data->{$this->name_alias};
if (empty($value) && !empty($this->definition['empty field name'])) {
return $this->definition['empty field name'];
}
elseif (empty($value)) {
return $this->options['wildcard_substitution'];
}
$format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display');
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide a title for the view based on the argument value.
*/
function title() {
$format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display');
$range = $this->date_handler->arg_range($this->argument);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide the argument to use to link from the summary to the next level;
* this will be called once per row of a summary, and used as part of
* $view->get_url().
*
* @param $data
* The query results for the row.
*/
function summary_argument($data) {
$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$value = $data->{$this->name_alias};
if (empty($value)) {
return $this->options['exception']['value'];
}
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Inject a test for valid date range before the summary query.
*/
function summary_query() {
// @TODO The summary values are computed by the database. Unless the database has
// built-in timezone handling it will use a fixed offset, which will not be
// right for all dates. The only way I can see to make this work right is to
// store the offset for each date in the database so it can be added to the base
// date value before the database formats the result. Because this is a huge
// architectural change, it won't go in until we start a new branch.
$this->formula = $this->date_handler->sql_format($this->sql_format, $this->date_handler->sql_field("***table***.$this->real_field"));
$this->ensure_my_table();
// Now that our table is secure, get our formula.
$formula = $this->get_formula();
// Add the field, give it an alias that does NOT match the actual field name or grouping won't work right.
$this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field . '_summary');
$this->query->set_count_field(NULL, $formula, $this->field);
return $this->summary_basics(FALSE);
}
/**
* Inject a test for valid date range before the regular query.
* Override the parent query to be able to control the $group.
*/
function query($group_by = FALSE) {
// @TODO Not doing anything with $group_by yet, need to figure out what has to be done.
if ($this->date_forbid()) {
return;
}
// See if we need to reset granularity based on an argument value.
// Make sure we don't try to reset to some bogus value if someone has typed in an unexpected argument.
$granularity = $this->date_handler->arg_granularity($this->argument);
if (!empty($granularity)) {
$this->date_handler->granularity = $granularity;
$this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display');
$this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql');
}
$this->granularity = $this->date_handler->granularity;
$this->ensure_my_table();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0;
// If requested, add the delta field to the view so we can later find the value that matched our query.
if (!empty($this->options['add_delta']) && (substr($this->real_field, -6) == '_value' || substr($this->real_field, -7) == '_value2')) {
$this->query->add_field($this->table_alias, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($this->table_alias, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($this->table_alias, 'delta', 'date_delta_' . $real_field_name);
}
$format = $this->date_handler->granularity == 'week' ? DATE_FORMAT_DATETIME : $this->sql_format;
$view_min = date_format($this->min_date, $format);
$view_max = date_format($this->max_date, $format);
$view_min_placeholder = $this->placeholder();
$view_max_placeholder = $this->placeholder();
$this->date_handler->placeholders = array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max);
// Are we comparing this field only or the Start/End date range to the view criteria?
if (!empty($this->options['use_fromto'])) {
// The simple case, match the field to the view range.
$field = $this->date_handler->sql_field($this->table_alias . '.' . $this->real_field, NULL, $this->min_date);
$field = $this->date_handler->sql_format($format, $field);
$this->query->add_where_expression($group, "$field >= $view_min_placeholder AND $field <= $view_max_placeholder", array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max));
}
else {
// Look for the intersection of the range of the date field with the range of the view.
// Get the Start/End values for this field. Retrieve using the original table name.
// Swap the current table name (adjusted for relationships) into the query.
// @TODO We may be able to use Views substitutions here, investigate that later.
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$fromto = $fields[$this->original_table . '.' . $this->real_field]['fromto'];
$value_min = str_replace($this->original_table, $this->table_alias, $fromto[0]);
$value_max = str_replace($this->original_table, $this->table_alias, $fromto[1]);
$field_min = $this->date_handler->sql_field($value_min, NULL, $this->min_date);
$field_min = $this->date_handler->sql_format($format, $field_min);
$field_max = $this->date_handler->sql_field($value_max, NULL, $this->max_date);
$field_max = $this->date_handler->sql_format($format, $field_max);
$this->query->add_where_expression($group, "$field_max >= $view_min_placeholder AND $field_min <= $view_max_placeholder", array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max));
}
}
/**
* Add a callback to determine if we have moved outside the valid date range for this argument.
*/
function date_forbid() {
if (empty($this->argument)) {
return TRUE;
}
$this->date_range = $this->date_handler->arg_range($this->argument);
$this->min_date = $this->date_range[0];
$this->max_date = $this->date_range[1];
$this->limit = date_range_years($this->options['year_range']);
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0;
// See if we're outside the allowed date range for our argument.
if (date_format($this->min_date, 'Y') < $this->limit[0] || date_format($this->max_date, 'Y') > $this->limit[1]) {
$this->forbid = TRUE;
$this->view->build_info['fail'] = TRUE;
return TRUE;
}
return FALSE;
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* @file
* Helper for identifying Date API fields for views.
*/
/**
* Identify all potential date/timestamp fields.
*
* @return
* array with fieldname, type, and table.
* @see
* date_views_date_views_fields() which implements
* the hook_date_views_fields() for the core date fields.
*/
function _date_views_fields($base = 'node') {
// Make sure $base is never empty.
if (empty($base)) {
$base = 'node';
}
$cid = 'date_views_fields_' . $base;
cache_clear_all($cid, 'cache_views');
// We use fields that provide filter handlers as our universe of possible
// fields of interest.
$all_fields = date_views_views_fetch_fields($base, 'filter');
// Iterate over all the fields that Views knows about.
$fields = array();
foreach ((array) $all_fields as $alias => $val) {
// Set up some default values.
$granularity = array('year', 'month', 'day', 'hour', 'minute', 'second');
$tz_handling = 'site';
$related_fields = array();
$timezone_field = '';
$offset_field = '';
$rrule_field = '';
$delta_field = '';
$sql_type = DATE_UNIX;
$type = '';
$name = $alias;
$tmp = explode('.', $name);
$field_name = $tmp[1];
$table_name = $tmp[0];
// Unset the date filter to avoid ugly recursion and broken values.
if ($field_name == 'date_filter') {
continue;
}
$fromto = array($name, $name);
// If we don't have a filter handler, we don't need to do anything more.
if (!$handler = views_get_handler($table_name, $field_name, 'filter')) {
continue;
}
$handler = views_get_handler($table_name, $field_name, 'filter');
$handler_name = $handler->definition['handler'];
// We don't care about anything but date handlers
if (empty($handler->definition['is date'])) {
continue;
}
$is_field = FALSE;
// For Field module fields, get the date type.
$custom = array();
if (isset($handler->definition['field_name'])) {
$field = field_info_field($handler->definition['field_name']);
$is_field = TRUE;
switch ($field['type']) {
case 'date':
$sql_type = DATE_ISO;
break;
case 'datestamp':
break;
case 'datetime':
$sql_type = DATE_DATETIME;
break;
default:
// If this is not a date field, nothing more to do.
continue;
}
$revision = in_array($base, array('node_revision')) ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT;
$db_info = date_api_database_info($field, $revision);
$name = $table_name . "." . $field_name;
$granularity = !empty($field['granularity']) ? $field['granularity'] : array('year', 'month', 'day', 'hour', 'minute', 'second');
$fromto = array(
$table_name . '.' . $db_info['columns'][$table_name]['value'],
$table_name . '.' . (!empty($field['settings']['todate']) ? $db_info['columns'][$table_name]['value2'] : $db_info['columns'][$table_name]['value']),
);
if (isset($field['settings']['tz_handling'])) {
$tz_handling = $field['settings']['tz_handling'];
$db_info = date_api_database_info($field, $revision);
if ($tz_handling == 'date') {
$offset_field = $table_name . '.' . $db_info['columns'][$table_name]['offset'];
}
$related_fields = array(
$table_name . '.' . $db_info['columns'][$table_name]['value']
);
if (isset($db_info['columns'][$table_name]['value2'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['value2']));
}
if (isset($db_info['columns'][$table_name]['timezone'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['timezone']));
$timezone_field = $table_name . '.' . $db_info['columns'][$table_name]['timezone'];
}
if (isset($db_info['columns'][$table_name]['rrule'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['rrule']));
$rrule_field = $table_name . '.' . $db_info['columns'][$table_name]['rrule'];
}
}
// Get the delta value into the query.
if ($field['cardinality'] != 1) {
array_push($related_fields, "$table_name.delta");
$delta_field = $table_name . '_delta';
}
}
// Allow custom modules to provide date fields.
else {
foreach (module_implements('date_views_fields') as $module) {
$function = $module . '_date_views_fields';
if ($custom = $function("$table_name.$field_name")) {
$type = 'custom';
break;
}
}
}
// Don't do anything if this is not a date field we can handle.
if (!empty($type) || empty($custom)) {
$alias = str_replace('.', '_', $alias);
$fields['name'][$name] = array(
'is_field' => $is_field,
'sql_type' => $sql_type,
'label' => $val['group'] . ': ' . $val['title'],
'granularity' => $granularity,
'fullname' => $name,
'table_name' => $table_name,
'field_name' => $field_name,
'query_name' => $alias,
'fromto' => $fromto,
'tz_handling' => $tz_handling,
'offset_field' => $offset_field,
'timezone_field' => $timezone_field,
'rrule_field' => $rrule_field,
'related_fields' => $related_fields,
'delta_field' => $delta_field,
);
// Allow the custom fields to over-write values.
if (!empty($custom)) {
foreach ($custom as $key => $val) {
$fields['name'][$name][$key] = $val;
}
}
$fields['name'][$name]['real_field_name'] = $field_name;
$fields['alias'][$alias] = $fields['name'][$name];
}
}
cache_set($cid, $fields, 'cache_views');
return $fields;
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* @file
* A flexible, configurable date filter.
* This filter combines multiple date filters into a single filter
* where all fields are controlled by the same date and can be combined with either AND or OR.
*/
class date_views_filter_handler extends date_views_filter_handler_simple {
function init(&$view, &$options) {
parent::init($view, $options);
if (empty($this->view->date_info)) {
$this->view->date_info = new stdClass();
}
if (empty($this->view->date_info->date_fields)) {
$this->view->date_info->date_fields = array();
}
$this->view->date_info->date_fields = array_merge($this->view->date_info->date_fields, $this->options['date_fields']);
}
// Set default values for the date filter.
function option_definition() {
$options = parent::option_definition();
$options['date_fields'] = array('default' => array());
$options['date_method'] = array('default' => 'OR');
$options['date_group'] = array('default' => 'date');
return $options;
}
function op_between($field) {
$this->date_combine_conditions('op_between');
}
function op_simple($field) {
$this->date_combine_conditions('op_simple');
}
/**
* Combines multiple date WHERE expressions into a single WHERE expression.
*
* @param string $function
* The function name to use to add individual conditions. Either 'op_simple'
* or 'op_between'.
*/
protected function date_combine_conditions($function) {
$this->get_query_fields();
if (empty($this->query_fields)) {
return;
}
// Create a custom filter group for the conditions.
$this->query->set_where_group($this->options['date_method'], $this->options['date_group']);
// Add each condition to the custom filter group.
foreach ((array) $this->query_fields as $query_field) {
$field = $query_field['field'];
$this->date_handler = $query_field['date_handler'];
// Respect relationships when determining the table alias.
if ($field['table_name'] != $this->table || !empty($this->relationship)) {
$this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship);
}
$table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name'];
$field_name = $table_alias . '.' . $field['field_name'];
// Call the appropriate function, either 'op_between' or 'op_simple'.
parent::$function($field_name);
}
// Gather all of the condition strings and their placeholders.
$conditions = array();
$placeholders = array();
foreach ($this->query->where[$this->options['date_group']]['conditions'] as $condition) {
$conditions[] = $condition['field'];
$placeholders += $condition['value'];
}
// Remove the conditions from the custom filter group.
unset($this->query->where[$this->options['date_group']]);
// Combine all of the conditions into one string.
$conditions = implode(' ' . $this->options['date_method'] . ' ', $conditions);
// Add it to the filter group chosen in the Views UI.
$this->query->add_where_expression($this->options['group'], $conditions, $placeholders);
}
function extra_options_form(&$form, &$form_state) {
parent::extra_options_form($form, $form_state);
$fields = date_views_fields($this->base_table);
$options = array();
foreach ($fields['name'] as $name => $field) {
$options[$name] = $field['label'];
}
$form['date_fields'] = array(
'#title' => t('Date field(s)'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->options['date_fields'],
'#multiple' => FALSE,
'#description' => t('Select date field(s) to filter.'),
'#required' => TRUE,
);
$form['date_method'] = array(
'#title' => t('Method'),
'#type' => 'radios',
'#options' => array('OR' => t('OR'), 'AND' => t('AND')),
'#default_value' => $this->options['date_method'],
'#description' => t('Method of handling multiple date fields in the same query. Return items that have any matching date field (date = field_1 OR field_2), or only those with matches in all selected date fields (date = field_1 AND field_2).'),
);
}
function extra_options_validate($form, &$form_state) {
$check_fields = array_filter($form_state['values']['options']['date_fields']);
if (empty($check_fields)) {
form_error($form['date_fields'], t('You must select at least one date field for this filter.'));
}
}
function extra_options_submit($form, &$form_state) {
$form_state['values']['options']['date_fields'] = array_filter($form_state['values']['options']['date_fields']);
}
// Update the summary values to provide
// meaningful information for each option.
function admin_summary() {
if (empty($this->options['date_fields'])) {
return t('Missing date fields!');
}
$handler = $this->date_handler;
$fields = date_views_fields($this->view->base_table);
if (!empty($this->options['date_fields'])) {
$output = array();
foreach ($this->options['date_fields'] as $field) {
if (array_key_exists($field, $fields['name'])) {
$output[] = $fields['name'][$field]['label'];
}
}
}
$field = implode(' ' . $this->options['date_method'] . ' ', $output);
$output = "$field " . check_plain($this->operator) . ' ';
$parts = $handler->date_parts();
$widget_options = $this->widget_options();
// If the filter is exposed, display the granularity.
if ($this->options['exposed']) {
return t('(@field) <strong>Exposed</strong> @widget @format', array('@field' => $field, '@format' => $parts[$handler->granularity], '@widget' => $widget_options[$this->options['form_type']]));
}
// If not exposed, display the value.
if (in_array($this->operator, $this->operator_values(2))) {
$min = check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['min']);
$max = check_plain(!empty($this->options['default_to_date']) ? $this->options['default_to_date'] : $this->options['value']['max']);
$output .= t('@min and @max', array('@min' => $min, '@max' => $max));
}
else {
$output .= check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['value']);
}
return $output;
}
function get_query_fields() {
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$this->query_fields = array();
foreach ((array) $this->options['date_fields'] as $delta => $name) {
if (array_key_exists($name, $fields) && $field = $fields[$name]) {
$date_handler = new date_sql_handler($field['sql_type'], date_default_timezone());
$date_handler->granularity = $this->options['granularity'];
$date_handler->db_timezone = date_get_timezone_db($field['tz_handling']);
$date_handler->local_timezone = date_get_timezone($field['tz_handling']);
$this->query_fields[] = array('field' => $field, 'date_handler' => $date_handler);
}
}
}
}

View File

@@ -0,0 +1,494 @@
<?php
/**
* @file
* A standard Views filter for a single date field, using Date API form selectors and sql handling.
*/
class date_views_filter_handler_simple extends views_handler_filter_date {
var $date_handler = NULL;
var $offset = NULL;
function init(&$view, &$options) {
parent::init($view, $options);
module_load_include('inc', 'date_api', 'date_api_sql');
$this->date_handler = new date_sql_handler(DATE_UNIX);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
if (!empty($field) && !empty($field['type'])) {
$this->date_handler->date_type = $field['type'];
}
$this->date_handler->db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
$this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']);
}
$this->form_submitted = FALSE;
$this->date_handler->granularity = isset($options['granularity']) ? $options['granularity'] : 'day';
$this->format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
// Identify the base table for this field.
// It will be used to call for the right query field options.
$this->base_table = $this->table;
}
// Set default values for the date filter.
function option_definition() {
$options = parent::option_definition();
$options['granularity'] = array('default' => 'day');
$options['form_type'] = array('default' => 'date_select');
$options['default_date'] = array('default' => '');
$options['default_to_date'] = array('default' => '');
$options['year_range'] = array('default' => '-3:+3');
$options['add_delta'] = array('default' => '');
return $options;
}
/**
* Helper function to find a default value.
*/
function date_default_value($prefix, $options = NULL) {
$default_date = '';
if (empty($options)) {
$options = $this->options;
}
// If this is a remembered value, use the value from the SESSION.
if (!empty($this->options['expose']['remember'])) {
$display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
if (!empty($_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix])) {
return $_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix];
}
}
// This is a date that needs to be constructed from options like 'now' .
$default_option = $prefix == 'max' ? $options['default_to_date'] : $options['default_date'];
if (!empty($default_option)) {
str_replace('now', 'today', $default_option);
$date = date_create($default_option, date_default_timezone_object());
$default_date = !empty($date) ? $date->format($this->format) : '';
// The format for our filter is in ISO format, but the widget will need it in datetime format.
$default_date = str_replace('T', ' ', $default_date);
}
// This a fixed date.
else {
$default_date = $options['value'][$prefix];
}
return $default_date;
}
/**
* Helper function to see if we need to swap in the default value.
*
* Views exposed filters treat everything as submitted, so if it's an empty value we have to
* see if anything actually was submitted. If nothing has really been submitted, we need
* to swap in our default value logic.
*/
function get_filter_value($prefix, $input) {
// All our date widgets provide datetime values but we use ISO in our SQL
// for consistency between the way filters and arguments work (arguments
// cannot contain spaces).
if (empty($input)) {
if (empty($this->options['exposed'])) {
return str_replace(' ', 'T', $this->date_default_value($prefix));
}
elseif (isset($this->options['expose']['identifier']) && !isset($_GET[$this->options['expose']['identifier']])) {
return str_replace(' ', 'T', $this->date_default_value($prefix));
}
}
return str_replace(' ', 'T', $input);
}
function accept_exposed_input($input) {
if (!empty($this->options['exposed'])) {
$element_input = $input[$this->options['expose']['identifier']];
$element_input['value'] = $this->get_filter_value('value', !empty($element_input['value']) ? $element_input['value'] : '');
$element_input['min'] = $this->get_filter_value('min', !empty($element_input['min']) ? $element_input['min'] : '');
$element_input['max'] = $this->get_filter_value('max', !empty($element_input['max']) ? $element_input['max'] : '');
unset($element_input['default_date']);
unset($element_input['default_to_date']);
$input[$this->options['expose']['identifier']] = $element_input;
}
return parent::accept_exposed_input($input);
}
function op_between($field) {
// Add the delta field to the view so we can later find the value that matched our query.
list($table_name, $field_name) = explode('.', $field);
if (!empty($this->options['add_delta']) && (substr($field_name, -6) == '_value' || substr($field_name, -7) == '_value2')) {
$this->query->add_field($table_name, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($table_name, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($table_name, 'delta', 'date_delta_' . $real_field_name);
}
$min_value = $this->get_filter_value('min', $this->value['min']);
$min_comp_date = new DateObject($min_value, date_default_timezone(), $this->format);
$max_value = $this->get_filter_value('max', $this->value['max']);
$max_comp_date = new DateObject($max_value, date_default_timezone(), $this->format);
$field_min = $this->date_handler->sql_field($field, NULL, $min_comp_date);
$field_min = $this->date_handler->sql_format($this->format, $field_min);
$field_max = $this->date_handler->sql_field($field, NULL, $max_comp_date);
$field_max = $this->date_handler->sql_format($this->format, $field_max);
$placeholder_min = $this->placeholder();
$placeholder_max = $this->placeholder();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : $this->options['group'];
if ($this->operator == 'between') {
$this->query->add_where_expression($group, "$field_min >= $placeholder_min AND $field_max <= $placeholder_max", array($placeholder_min => $min_value, $placeholder_max => $max_value));
}
else {
$this->query->add_where_expression($group, "$field_min < $placeholder_min OR $field_max > $placeholder_max", array($placeholder_min => $min_value, $placeholder_max => $max_value));
}
}
function op_simple($field) {
// Add the delta field to the view so we can later find the value that matched our query.
list($table_name, $field_name) = explode('.', $field);
if (!empty($this->options['add_delta']) && (substr($field_name, -6) == '_value' || substr($field_name, -7) == '_value2')) {
$this->query->add_field($table_name, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($table_name, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($table_name, 'delta', 'date_delta_' . $real_field_name);
}
$value = $this->get_filter_value('value', $this->value['value']);
$comp_date = new DateObject($value, date_default_timezone(), $this->format);
$field = $this->date_handler->sql_field($field, NULL, $comp_date);
$field = $this->date_handler->sql_format($this->format, $field);
$placeholder = $this->placeholder();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : $this->options['group'];
$this->query->add_where_expression($group, "$field $this->operator $placeholder", array($placeholder => $value));
}
/**
* Set the granularity of the date parts to use in the filter.
*/
function has_extra_options() { return TRUE; }
/**
* Date selection options.
*/
function widget_options() {
$options = array(
'date_select' => t('Select'),
'date_text' => t('Text'),
'date_popup' => t('Popup'),
);
if (!module_exists('date_popup')) {
unset($options['date_popup']);
}
return $options;
}
function year_range() {
$year_range = explode(':', $this->options['year_range']);
if (substr($this->options['year_range'], 0, 1) == '-' || $year_range[0] < 0) {
$this_year = date_format(date_now(), 'Y');
$year_range[0] = $this_year + $year_range[0];
$year_range[1] = $this_year + $year_range[1];
}
return $year_range;
}
function extra_options_form(&$form, &$form_state) {
parent::extra_options_form($form, $form_state);
$form['form_type'] = array(
'#type' => 'radios',
'#title' => t('Date selection form element'),
'#default_value' => $this->options['form_type'],
'#options' => $this->widget_options(),
);
$form['granularity'] = $this->date_handler->granularity_form($this->options['granularity']);
$form['granularity']['#title'] = t('Filter granularity');
$form['year_range'] = array(
'#type' => 'date_year_range',
'#default_value' => $this->options['year_range'],
);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
}
$form['add_delta'] = array(
'#type' => 'radios',
'#title' => t('Add multiple value identifier'),
'#default_value' => $this->options['add_delta'],
'#options' => array('' => t('No'), 'yes' => t('Yes')),
'#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'),
// Only let mere mortals tweak this setting for multi-value fields
'#access' => !empty($field) ? $field['cardinality'] != 1 : 0,
);
}
function extra_options_validate($form, &$form_state) {
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
/**
* Add the selectors to the value form using the date handler.
*/
function value_form(&$form, &$form_state) {
// We use different values than the parent form, so we must
// construct our own form element.
$form['value'] = array();
$form['value']['#tree'] = TRUE;
// Below section copied from views_handler_filter_numeric.inc.
$which = 'all';
$source = '';
if (!empty($form['operator'])) {
$source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
}
$identifier = $this->options['expose']['identifier'];
if (!empty($form_state['exposed'])) {
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// exposed and locked.
$which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
}
else {
$source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
}
}
if ($which == 'all' || $which == 'value') {
$form['value'] += $this->date_parts_form($form_state, 'value', $source, $which, $this->operator_values(1), $identifier, 'default_date');
}
if ($which == 'all' || $which == 'minmax') {
$form['value'] += $this->date_parts_form($form_state, 'min', $source, $which, $this->operator_values(2), $identifier, 'default_date');
$form['value'] += $this->date_parts_form($form_state, 'max', $source, $which, $this->operator_values(2), $identifier, 'default_to_date');
}
// Add some extra validation for the select widget to be sure that
// the user inputs all parts of the date.
if ($this->options['form_type'] == 'date_select') {
$form['value']['#element_validate'] = array('date_views_select_validate');
}
}
/**
* A form element to select date part values.
*
* @param string $prefix
* A prefix for the date values, 'value', 'min', or 'max' .
* @param string $source
* The operator for this element.
* @param string $which
* Which element to provide, 'all', 'value', or 'minmax' .
* @param array $operator_values
* An array of the allowed operators for this element.
* @param array $identifier
* Identifier of the exposed element.
* @param array $relative_id
* Form element id to use for the relative date field.
*
* @return
* The form date part element for this instance.
*/
function date_parts_form($form_state, $prefix, $source, $which, $operator_values, $identifier, $relative_id) {
module_load_include('inc', 'date_api', 'date_api_elements');
switch ($prefix) {
case 'min':
$label = t('Start date');
$relative_label = t('Relative start date');
break;
case 'max':
$label = t('End date');
$relative_label = t('Relative end date');
break;
default:
$label = '';
$relative_label = t('Relative date');
break;
}
$type = $this->options['form_type'];
if ($type == 'date_popup' && !module_exists('date_popup')) {
$type = 'date_text';
}
$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$granularity = array_keys($this->date_handler->date_parts($this->options['granularity']));
$relative_value = ($prefix == 'max' ? $this->options['default_to_date'] : $this->options['default_date']);
if (!empty($form_state['exposed'])) {
// UI when the date selector is exposed.
$default_date = $this->date_default_value($prefix);
$id = 'edit-' . str_replace('_', '-', $this->field) . '-' . $prefix;
$form[$prefix] = array(
'#title' => check_plain($label),
'#type' => $type,
'#size' => 20,
'#default_value' => !empty($this->value[$prefix]) ? $this->value[$prefix] : $default_date,
'#date_format' => date_limit_format($format, $granularity),
'#date_label_position' => 'within',
'#date_year_range' => $this->options['year_range'],
'#process' => array($type . '_element_process'),
'#prefix' => '<div id="' . $id . '-wrapper"><div id="' . $id . '">',
'#suffix' => '</div></div>',
);
if ($which == 'all') {
$form[$prefix]['#pre_render'][] = 'ctools_dependent_pre_render';
$form[$prefix]['#dependency'] = array($source => $operator_values);
}
if (!isset($form_state['input'][$identifier][$prefix])) {
$form_state['input'][$identifier][$prefix] = $this->value[$prefix];
}
}
else {
// UI when the date selector is on the views configuration screen.
$default_date = '';
$id = 'edit-options-value-' . $prefix;
$form[$prefix . '_group'] = array(
'#type' => 'fieldset',
'#attributes' => array('class' => array('date-views-filter-fieldset')),
);
$form[$prefix . '_group'][$prefix . '_choose_input_type'] = array(
'#title' => check_plain($label),
'#type' => 'select',
'#options' => array('date' => t('Select a date'), 'relative' => ('Enter a relative date')),
'#attributes' => array('class' => array($prefix . '-choose-input-type')),
'#default_value' => !empty($relative_value) ? 'relative' : 'date',
);
$form[$prefix . '_group'][$prefix] = array(
'#title' => t('Select a date'),
'#type' => $type,
'#size' => 20,
'#default_value' => !empty($this->value[$prefix]) ? $this->value[$prefix] : $default_date,
'#date_format' => date_limit_format($format, $granularity),
'#date_label_position' => 'within',
'#date_year_range' => $this->options['year_range'],
'#process' => array($type . '_element_process'),
'#prefix' => '<div id="' . $id . '-wrapper"><div id="' . $id . '">',
'#suffix' => '</div></div>',
'#states' => array(
'visible' => array(
":input.{$prefix}-choose-input-type" => array('value' => 'date'),
),
),
);
$form[$prefix . '_group'][$relative_id] = array(
'#type' => 'textfield',
'#title' => check_plain($relative_label),
'#default_value' => $relative_value,
'#description' => t("Relative dates are computed when the view is displayed. Examples: now, now +1 day, 12AM today, Monday next week. <a href=\"@relative_format\">More examples of relative date formats in the PHP documentation</a>.", array('@relative_format' => 'http://www.php.net/manual/en/datetime.formats.relative.php')),
'#states' => array(
'visible' => array(
":input.{$prefix}-choose-input-type" => array('value' => 'relative'),
),
),
);
if ($which == 'all') {
$form[$prefix . '_group']['#pre_render'][] = 'ctools_dependent_pre_render';
$form[$prefix . '_group']['#dependency'] = array($source => $operator_values);
}
}
return $form;
}
/**
* Value validation.
*
* TODO add in more validation.
*
* We are setting an extra option using a value form
* because it makes more sense to set it there.
* That's not the normal method, so we have to manually
* transfer the selected value back to the option.
*/
function value_validate($form, &$form_state) {
$options = &$form_state['values']['options'];
if ($options['operator'] == 'between' || $options['operator'] == 'not between') {
if ($options['value']['min_group']['min_choose_input_type'] == 'relative') {
if (empty($options['value']['min_group']['default_date'])) {
form_set_error('options][value][min_group][default_date', t('Relative start date not specified.'));
}
else {
$this->options['default_date'] = $options['value']['min_group']['default_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['min_group']['min'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_date'] = '';
}
if ($options['value']['max_group']['max_choose_input_type'] == 'relative') {
if (empty($options['value']['max_group']['default_to_date'])) {
form_set_error('options][value][max_group][default_to_date', t('Relative end date not specified.'));
}
else {
$this->options['default_to_date'] = $options['value']['max_group']['default_to_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['max_group']['max'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_to_date'] = '';
}
}
elseif (in_array($options['operator'], array('<', '<=', '=', '!=', '>=', '>'))) {
if ($options['value']['value_group']['value_choose_input_type'] == 'relative') {
if (empty($options['value']['value_group']['default_date'])) {
form_set_error('options][value][value_group][default_date', t('Relative date not specified.'));
}
else {
$this->options['default_date'] = $options['value']['value_group']['default_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['value_group']['value'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_date'] = '';
}
}
// Flatten the form structure for views, so the values can be saved.
foreach (array('value', 'min', 'max') as $key) {
$options['value'][$key] = $options['value'][$key . '_group'][$key];
}
}
/**
* Validate that the time values convert to something usable.
*/
function validate_valid_time(&$form, $operator, $value) {
// Override the core date filter validation.
// Our date widgets do their own validation.
}
// Update the summary values to provide
// meaningful information for each option.
function admin_summary() {
$parts = $this->date_handler->date_parts();
$widget_options = $this->widget_options();
// If the filter is exposed, display the granularity.
if ($this->options['exposed']) {
return t('<strong>Exposed</strong> @widget @format', array('@format' => $parts[$this->date_handler->granularity], '@widget' => $widget_options[$this->options['form_type']]));
}
// If not exposed, display the value.
$output = '';
if (in_array($this->operator, $this->operator_values(2))) {
$min = check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['min']);
$max = check_plain(!empty($this->options['default_to_date']) ? $this->options['default_to_date'] : $this->options['value']['max']);
$output .= t('@min and @max', array('@min' => $min, '@max' => $max));
}
else {
$output .= check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['value']);
}
return $output;
}
}

View File

@@ -0,0 +1,233 @@
<?php
/**
* @file
* Date pager.
* Works with a Date argument, the argument filters the view and the pager provides back/next navigation.
*
* USER NOTES:
*
* To use this, add a pager to a view, and choose the option to 'Page by date'.
* There are several settings:
* - The pager id: Set an id to be used as the identifier in the url for pager values, defaults to 'date'.
* - Pager position: Choose whether to display the date pager above, below, or both above and below the content.
* - Link format: Choose whether the pager links will be in the simple 'calendar/2011-12' format or the
* more complex 'calendar/?date=2011-12' pager format. The second one is more likely to work correctly
* if the pager is used in blocks and panels.
*
* The pager works in combination with a Date argument and it will use the date fields and granularity
* set in that argument to create its back/next links. If the view has no Date argument, the pager can
* do nothing. The argument can either be a 'Date' argument that lets you select one or more date fields
* in the argument, or the simple 'Content' argument for an individual date field. It must be an
* argument that uses the date argument handler.
*
* DEVELOPER NOTES
*
* The pager could technically create a query of its own rather than depending on the date argument to
* set the query, but it has only a limited set of tools to work with because it is a plugin, not a handler:
* it has no knowledge about relationships, it cannot use the ensure_my_table() function,
* plugins are not even invoked in pre_query(), so can't do anything there.
*
* My conclusion was that the date pager simply is not powerful enough to create its own queries for
* date fields, which require very complex queries. Instead, we can combine this with a date argument and
* let the argument create the query and let the pager just provide the back/next links. If there is no
* date argument, the pager will do nothing.
*
* There are still other problems. The pager is not even initialized until after all the handlers
* have created their queries, so it has no chance to alter values ahead of that. And the argument
* has no knowledge of the pager, so it can't check for pager values before the query is created.
*
* The solution used here is to let the argument create the original query. The pager query
* runs after that, so the pager checks to see if there is a pager value that needs to be used in the query.
* The date argument has identified the placeholders it used in the query. So if a change is needed,
* we can swap the pager value into the query created by the date argument and adjust the
* $view->date_info values set by the argument accordingly so the theme will pick up the new information.
*/
/**
* Example plugin to handle paging by month.
*/
class date_views_plugin_pager extends views_plugin_pager {
/**
* This kind of pager does not need to count the number of records.
*/
function use_count_query() {
return FALSE;
}
/**
* Because we don't know how many pages there are, we never believe there are more records.
*/
function has_more_records() {
return FALSE;
}
/*
* Tell Views what this pager's setting is.
*/
function summary_title() {
return t("Position: @position, format: @format.", array('@position' => $this->options['pager_position'], '@format' => $this->options['link_format']));
}
/**
* Tell Views what options this plugin can store.
*/
function option_definition() {
$options = parent::option_definition();
$options['date_id'] = array('default' => 'date');
$options['pager_position'] = array('default' => 'top');
$options['link_format'] = array('default' => 'pager');
$options['date_argument'] = array('default' => 'Unknown');
$options['granularity'] = array('default' => 'Unknown');
return $options;
}
/*
* Provide the form for setting options.
*/
function options_form(&$form, &$form_state) {
$form['markup']['#markup'] = t('This pager works together with a Date or Content date field contextual filter. If a Date filter has been added to the view, this pager will provide back/next paging to match the granularity of that filter (i.e. paging by year, month, week, or day). The filter must also be configured to use a DATE default value. If there is no Date contextual filter on this view, or if it has not been set to use a default date, the pager will not appear.');
$form['date_id'] = array(
'#title' => t('Date identifier'),
'#type' => 'textfield',
'#description' => t('The query identifier to use when fetching date data from in the URL. Note that if you have more than one display in the same view that uses the date pager (like a page and a block), the pager id must be different for each one or both will change when the pager value changes.'),
'#default_value' => $this->options['date_id'],
'#required' => TRUE,
);
$form['pager_position'] = array(
'#title' => t('Pager position'),
'#type' => 'select',
'#options' => array('bottom' => t('Bottom'), 'top' => t('Top'), 'both' => t('Both')),
'#description' => t('Where to place the date pager, on the top, bottom, or both top and bottom of the content.'),
'#default_value' => $this->options['pager_position'],
'#required' => TRUE,
);
$form['link_format'] = array(
'#title' => t('Link format'),
'#type' => 'select',
'#options' => array('pager' => t('Pager'), 'clean' => t('Clean URL')),
'#description' => t("The format for pager link urls. With the Pager format, the links look like 'calendar/?date=2020-05'. The Clean URL format links look like 'calendar/2020-05'. The Clean format links look nicer but the Pager format links are likely to work better if the calendar is used in blocks or panels."),
'#default_value' => $this->options['link_format'],
'#required' => TRUE,
);
$form['date_argument']['#type'] = 'hidden';
$form['date_argument']['#value'] = $this->options['date_argument'];
$form['granularity']['#type'] = 'hidden';
$form['granularity']['#value'] = $this->options['granularity'];
}
/**
* Transfer date information from the argument to the view so the pager theme can use it
* and update the date argument value to whatever is set by the pager.
*/
function query() {
// By fetching our data from the exposed input, it is possible to
// feed pager data through some method other than $_GET.
$input = $this->view->get_exposed_input();
$value = NULL;
if (!empty($input) && !empty($input[$this->options['date_id']])) {
$value = $input[$this->options['date_id']];
}
// Bring the argument information into the view so our theme can access it.
$i = 0;
foreach ($this->view->argument as $id => &$argument) {
if (date_views_handler_is_date($argument, 'argument')) {
// If the argument is empty, nothing to do. This could be from
// an argument that does not set a default value.
if (empty($argument->argument) || empty($argument->date_handler)) {
continue;
}
// Storing this information in the pager so it's available for summary info.
// The view argument information is not otherwise accessible to the pager.
// Not working right yet, tho.
$date_handler = $argument->date_handler;
$this->options['date_argument'] = $id;
$this->options['granularity'] = $argument->date_handler->granularity;
// Reset values set by argument if pager requires it.
if (!empty($value)) {
$argument->argument = $value;
$argument->date_range = $argument->date_handler->arg_range($value);
$argument->min_date = $argument->date_range[0];
$argument->max_date = $argument->date_range[1];
// $argument->is_default works correctly for normal arguments, but does not
// work correctly if we are swapping in a new value from the pager.
$argument->is_default = FALSE;
}
// The pager value might move us into a forbidden range, so test it.
if ($this->date_forbid($argument)) {
$this->view->build_info['fail'] = TRUE;
return;
}
if (empty($this->view->date_info)) $this->view->date_info = new stdClass();
$this->view->date_info->granularity = $argument->date_handler->granularity;
$format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format;
$this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders;
$this->view->date_info->date_arg = $argument->argument;
$this->view->date_info->date_arg_pos = $i;
$this->view->date_info->year = date_format($argument->min_date, 'Y');
$this->view->date_info->month = date_format($argument->min_date, 'n');;
$this->view->date_info->day = date_format($argument->min_date, 'j');
$this->view->date_info->week = date_week(date_format($argument->min_date, DATE_FORMAT_DATE));
$this->view->date_info->date_range = $argument->date_range;
$this->view->date_info->min_date = $argument->min_date;
$this->view->date_info->max_date = $argument->max_date;
$this->view->date_info->limit = $argument->limit;
$this->view->date_info->url = $this->view->get_url();
$this->view->date_info->pager_id = $this->options['date_id'];
$this->view->date_info->date_pager_position = $this->options['pager_position'];
$this->view->date_info->date_pager_format = $this->options['link_format'];
}
$i++;
}
// Is this a view that needs to be altered based on a pager value?
// If there is pager input and the argument has set the placeholders,
// swap the pager value in for the placeholder set by the argument.
if (!empty($value) && !empty($this->view->date_info->placeholders)) {
$placeholders = $this->view->date_info->placeholders;
$count = count($placeholders);
foreach ($this->view->query->where as $group => $data) {
foreach ($data['conditions'] as $delta => $condition) {
if (array_key_exists('value', $condition) && is_array($condition['value'])) {
foreach ($condition['value'] as $placeholder => $placeholder_value) {
if (array_key_exists($placeholder, $placeholders)) {
// If we didn't get a match, this is a > $min < $max query that uses the view
// min and max dates as placeholders.
$date = ($count == 2) ? $this->view->date_info->min_date : $this->view->date_info->max_date;
$next_placeholder = array_shift($placeholders);
$this->view->query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format);
$count--;
}
}
}
}
}
}
}
/**
* Add a callback to determine if we have moved outside the valid date range for this argument.
*/
function date_forbid($argument) {
// See if we're outside the allowed date range for our argument.
$limit = date_range_years($argument->options['year_range']);
if (date_format($argument->min_date, 'Y') < $limit[0] || date_format($argument->max_date, 'Y') > $limit[1]) {
return TRUE;
}
return FALSE;
}
function render($input) {
// This adds all of our template suggestions based upon the view name and display id.
$pager_theme = views_theme_functions('date_views_pager', $this->view, $this->display);
return theme($pager_theme, array('plugin' => $this, 'input' => $input));
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* @file
* Template to display the Views date filter form.
*
* Values available vary depending on the operator. The availability
* of date vs adjustment depending on the filter settings. It can
* be date-only, date and adjustment, or adjustment only.
*
* If the operator is anything but 'Is between' or 'Is not between',
* a single date and adjustment field is available.
*
* $date
* $adjustment
*
* If the operator is 'Is between' or 'Is not between',
* two date and adjustment fields are available.
*
* $mindate
* $minadjustment
* $maxdate
* $maxadjustment
*
* A description field is also available.
*
* $description
*/
?>
<div class="date-views-filter-wrapper">
<div class="container-inline-date date-clear">
<?php if (!empty($date) || !empty($adjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $date; ?></div>
<div class="date-views-filter"><?php print $adjustment ?></div>
</div>
<?php endif; ?>
<?php if (!empty($mindate) || !empty($minadjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $mindate; ?></div>
<div class="date-views-filter"><?php print $minadjustment; ?></div>
</div>
<?php endif; ?>
<?php if (!empty($maxdate) || !empty($maxadjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $maxdate; ?></div>
<div class="date-views-filter"><?php print $maxadjustment; ?></div>
</div>
<?php endif; ?>
</div>
<div class="date-clear form-item"><div class="description">
<?php print $description; ?>
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
<?php
/**
* @file
* Template file for the example display.
*
* Variables available:
*
* $plugin: The pager plugin object. This contains the view.
*
* $plugin->view
* The view object for this navigation.
*
* $nav_title
* The formatted title for this view. In the case of block
* views, it will be a link to the full view, otherwise it will
* be the formatted name of the year, month, day, or week.
*
* $prev_url
* $next_url
* Urls for the previous and next calendar pages. The links are
* composed in the template to make it easier to change the text,
* add images, etc.
*
* $prev_options
* $next_options
* Query strings and other options for the links that need to
* be used in the l() function, including rel=nofollow.
*/
?>
<?php if (!empty($pager_prefix)) print $pager_prefix; ?>
<div class="date-nav-wrapper clearfix<?php if (!empty($extra_classes)) print $extra_classes; ?>">
<div class="date-nav item-list">
<div class="date-heading">
<h3><?php print $nav_title ?></h3>
</div>
<ul class="pager">
<?php if (!empty($prev_url)) : ?>
<li class="date-prev">
<?php print l('&laquo;' . ($mini ? '' : ' ' . t('Prev', array(), array('context' => 'date_nav'))), $prev_url, $prev_options); ?>
&nbsp;</li>
<?php endif; ?>
<?php if (!empty($next_url)) : ?>
<li class="date-next">&nbsp;
<?php print l(($mini ? '' : t('Next', array(), array('context' => 'date_nav')) . ' ') . '&raquo;', $next_url, $next_options); ?>
</li>
<?php endif; ?>
</ul>
</div>
</div>

View File

@@ -0,0 +1,212 @@
<?php
/**
* @file
* Theme files for Date Pager.
*/
/**
* Jump in and move the pager.
*/
function date_views_preprocess_views_view(&$vars) {
$view = $vars['view'];
if (!empty($view->date_info) && !empty($view->date_info->date_pager_position)) {
switch ($view->date_info->date_pager_position) {
case 'top':
$vars['header'] .= $vars['pager'];
$vars['pager'] = '';
break;
case 'both':
$vars['header'] .= $vars['pager'];
break;
default:
// Already on the bottom.
}
}
}
/**
* Preprocess function for Date pager template.
*/
function template_preprocess_date_views_pager(&$vars) {
ctools_add_css('date_views', 'date_views');
$plugin = $vars['plugin'];
$input = $vars['input'];
$view = $plugin->view;
$vars['nav_title'] = '';
$vars['next_url'] = '';
$vars['prev_url'] = '';
if (empty($view->date_info) || empty($view->date_info->min_date)) {
return;
}
$date_info = $view->date_info;
// Make sure we have some sort of granularity.
$granularity = !empty($date_info->granularity) ? $date_info->granularity : 'month';
$pos = $date_info->date_arg_pos;
if (!empty($input)) {
$id = $plugin->options['date_id'];
if (array_key_exists($id, $input) && !empty($input[$id])) {
$view->args[$pos] = $input[$id];
}
}
$next_args = $view->args;
$prev_args = $view->args;
$min_date = $date_info->min_date;
$max_date = $date_info->max_date;
// Set up the pager link format. Setting the block identifier
// will force pager style links.
if ((isset($date_info->date_pager_format) && $date_info->date_pager_format != 'clean') || !empty($date_info->mini)) {
if (empty($date_info->block_identifier)) {
$date_info->block_identifier = $date_info->pager_id;
}
}
if (empty($date_info->hide_nav)) {
$prev_date = clone($min_date);
date_modify($prev_date, '-1 ' . $granularity);
$next_date = clone($min_date);
date_modify($next_date, '+1 ' . $granularity);
$format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d');
switch ($granularity) {
case 'week':
$next_week = date_week(date_format($next_date, 'Y-m-d'));
$prev_week = date_week(date_format($prev_date, 'Y-m-d'));
$next_arg = date_format($next_date, 'Y-\W') . date_pad($next_week);
$prev_arg = date_format($prev_date, 'Y-\W') . date_pad($prev_week);
break;
default:
$next_arg = date_format($next_date, $format[$granularity]);
$prev_arg = date_format($prev_date, $format[$granularity]);
}
$next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url);
$prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url);
$next_args[$pos] = $next_arg;
$prev_args[$pos] = $prev_arg;
$vars['next_url'] = date_pager_url($view, NULL, $next_arg);
$vars['prev_url'] = date_pager_url($view, NULL, $prev_arg);
$vars['next_options'] = $vars['prev_options'] = array();
}
else {
$next_path = '';
$prev_path = '';
$vars['next_url'] = '';
$vars['prev_url'] = '';
$vars['next_options'] = $vars['prev_options'] = array();
}
// Check whether navigation links would point to
// a date outside the allowed range.
if (!empty($next_date) && !empty($vars['next_url']) && date_format($next_date, 'Y') > $date_info->limit[1]) {
$vars['next_url'] = '';
}
if (!empty($prev_date) && !empty($vars['prev_url']) && date_format($prev_date, 'Y') < $date_info->limit[0]) {
$vars['prev_url'] = '';
}
$vars['prev_options'] += array('attributes' => array());
$vars['next_options'] += array('attributes' => array());
$prev_title = '';
$next_title = '';
// Build next/prev link titles.
switch ($granularity) {
case 'year':
$prev_title = t('Navigate to previous year');
$next_title = t('Navigate to next year');
break;
case 'month':
$prev_title = t('Navigate to previous month');
$next_title = t('Navigate to next month');
break;
case 'week':
$prev_title = t('Navigate to previous week');
$next_title = t('Navigate to next week');
break;
case 'day':
$prev_title = t('Navigate to previous day');
$next_title = t('Navigate to next day');
break;
}
$vars['prev_options']['attributes'] += array('title' => $prev_title);
$vars['next_options']['attributes'] += array('title' => $next_title);
// Add nofollow for next/prev links.
$vars['prev_options']['attributes'] += array('rel' => 'nofollow');
$vars['next_options']['attributes'] += array('rel' => 'nofollow');
// Need this so we can use '&laquo;' or images in the links.
$vars['prev_options'] += array('html' => TRUE);
$vars['next_options'] += array('html' => TRUE);
$link = FALSE;
// Month navigation titles are used as links in the block view.
if (!empty($date_info->mini) && $granularity == 'month') {
$link = TRUE;
}
$params = array(
'granularity' => $granularity,
'view' => $view,
'link' => $link,
);
$nav_title = theme('date_nav_title', $params);
$vars['nav_title'] = $nav_title;
$vars['mini'] = !empty($date_info->mini);
}
/**
* Theme the calendar title
*/
function theme_date_nav_title($params) {
$granularity = $params['granularity'];
$view = $params['view'];
$date_info = $view->date_info;
$link = !empty($params['link']) ? $params['link'] : FALSE;
$format = !empty($params['format']) ? $params['format'] : NULL;
switch ($granularity) {
case 'year':
$title = $date_info->year;
$date_arg = $date_info->year;
break;
case 'month':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'F Y' : 'F');
$title = date_format_date($date_info->min_date, 'custom', $format);
$date_arg = $date_info->year . '-' . date_pad($date_info->month);
break;
case 'day':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'l, F j, Y' : 'l, F j');
$title = date_format_date($date_info->min_date, 'custom', $format);
$date_arg = $date_info->year . '-' . date_pad($date_info->month) . '-' . date_pad($date_info->day);
break;
case 'week':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'F j, Y' : 'F j');
$title = t('Week of @date', array('@date' => date_format_date($date_info->min_date, 'custom', $format)));
$date_arg = $date_info->year . '-W' . date_pad($date_info->week);
break;
}
if (!empty($date_info->mini) || $link) {
// Month navigation titles are used as links in the mini view.
$attributes = array('title' => t('View full page month'));
$url = date_pager_url($view, $granularity, $date_arg, TRUE);
return l($title, $url, array('attributes' => $attributes));
}
else {
return $title;
}
}
/**
* Preprocessor for Date Views filter form.
*/
function template_preprocess_date_views_filter_form(&$vars) {
$form = $vars['form'];
$vars['date'] = drupal_render($form['valuedate']);
$vars['mindate'] = drupal_render($form['mindate']);
$vars['maxdate'] = drupal_render($form['maxdate']);
$vars['adjustment'] = drupal_render($form['valueadjustment']);
$vars['minadjustment'] = drupal_render($form['minadjustment']);
$vars['maxadjustment'] = drupal_render($form['maxadjustment']);
$vars['description'] = drupal_render($form['description']) . drupal_render($form);
}