View complete documentation at !link.
", array('!link' => 'http://drupal.org/node/120710')); } } /** * Implementation of hook_menu(). */ function calendar_menu($may_cache) { include_once(CALENDAR_PATH .'/calendar.theme'); $items = array(); if (!$may_cache) { foreach (calendar_views('page') as $view_name) { $view = views_get_view($view_name); $items[] = array('path' => $view->url . '/view', 'title' => t('View'), 'callback' => 'views_view_page', 'callback arguments' => $args, 'access' => user_access('access content'), 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK); $items[] = array( 'path' => $view->url .'/setup', 'title' => t('Setup'), 'description' => t('Calendar setup.'), 'access' => user_access('administer content'), 'callback' => 'drupal_get_form', 'callback arguments' => array('calendar_setup_form', $view), 'type' => MENU_LOCAL_TASK, 'weight' => 5, ); } } return $items; } /** * Implementation of hook_views_style_plugins() */ function calendar_views_style_plugins() { $plugins = array(); $types = calendar_view_types(); foreach ($types as $name => $type) { $plugins[$name] = array( 'name' => $type, 'theme' => 'calendar_display', 'summary_theme' => 'calendar_display', 'validate' => 'calendar_views_validate', 'needs_fields' => TRUE, 'needs_table_header' => FALSE, ); } return $plugins; } /** * Validate a view. */ function calendar_views_validate($type, $view, $form) { // list (and table) modes require there to be at least 1 field active. if (is_array($view['field'])) { $fields = array_filter(array_keys($view['field']), 'is_numeric'); } if (!$fields) { form_error($form["$type-info"][$type . '_type'], t('The Calendar View requires at least one field.')); } if (isset($view['field']['count'])) { $defaultsort = false; for ($i = 0; $i < $view['field']['count']; $i++) { if ($view['field'][$i]['defaultsort']) { if ($defaultsort) { form_error($form['field'][$i]['defaultsort'], t('You can only set on Default Sort on one field.')); break; } $defaultsort = true; } } } // Make sure all arguments are set to 'Display all values'. $arg_types = array(); $cal_args = array('calendar_year', 'calendar_month', 'calendar_day', 'calendar_week'); foreach ($view['argument'] as $delta => $argument) { if (in_array($argument['type'], $cal_args)) { $view['argument'][$delta]['argdefault'] = 2; if ($argument['argdefault'] != 2) { form_error($form['argument'][$delta]['argdefault'], t('Calendar arguments must be set to \'Display All Values\'.')); } $arg_types[] = $argument['type']; } } // Must have Year, Month, and Day or Year and Week calendar arguments. if (!in_array('calendar_year', $arg_types) && ((!in_array('calendar_month', $arg_types) && !in_array('calendar_day', $arg_types) || !in_array('calendar_week', $arg_types)))) { form_error($form['argument'], t('The Calendar requres as arguments Calendar: Year, Calendar: Month, and Calendar: Day, or Calendar: Year and Calendar: Week')); } // CCK date fields cannot use grouped handler. $calendar_fields = array_keys(calendar_fields()); foreach ($view['field'] as $delta => $field) { if (in_array($field['field'], $calendar_fields) && $field['handler'] == 'content_views_field_handler_group') { form_error($form['field'][$delta]['handler'], t('Calendar CCK Date fields must be set to \'Do not group multiple values\'.')); } } } /** * Possible calendar views page display types */ function calendar_view_types() { return array('calendar' => t('Calendar')); } /** * All views that are of the type 'calendar'; * */ function calendar_views($build_type = 'page', $reset = FALSE) { //static $calendar_views; if ($reset || !isset($calendar_views[$build_type])) { $calendar_views = array(); $result = db_query("SELECT vid FROM {view_view} ORDER BY name"); while ($row = db_fetch_array($result)) { $view = views_get_view($row['vid']); $view->build_type = $build_type; if (calendar_is_calendar($view)) { $calendar_views[$build_type][] = $view->name; } } } if (empty($calendar_views) || !array_key_exists($build_type, $calendar_views)) { $calendar_views = array($build_type => array()); } return $calendar_views[$build_type]; } /** * Identify all potential date/timestamp fields * @return an array with fieldname, type, and table */ function calendar_fields() { // TODO add caching // iterate through all the views fields $delta = 0; foreach (_views_get_fields() as $name => $val) { $timestamp_fromto = array(); $string_fromto = array(); $type = ''; // for cck fields, get the date type if ($val['content_field']['type'] == 'date' || $val['content_field']['type'] == 'datestamp') { $type = $val['content_field']['type'] == 'date' ? 'cck_string' : 'cck_timestamp'; } // all other fields that use the views date handler are timestamps elseif ($val['handler'] == views_handler_field_dates()) { $type = 'timestamp'; } // don't do anything if this is not a date field if ($type) { // dates with from and to dates need to handle both fields as one // add the from and to dates to the first one found and ignore the second // Handling for event fromto dates if (module_exists('event') && !$event_field_processed && ($name == 'event.event_start' || $name == 'event.event_end')) { $timestamp_fromto = array('event.event_start', 'event.event_end'); $offset_field = 'event.timezone'; $tz_handling = variable_get('event_timezone_display', 'site'); $event_field_processed = TRUE; } elseif ($val['content_field']['tz_handling']) { $tz_handling = $val['content_field']['tz_handling']; if ($tz_handling == 'date') $offset_field = $val['table'] .'.'. $val['content_db_info']['columns']['offset']['column']; } else { $tz_handling = 'site'; } // Handling for cck fromto dates if (!$event_field_processed && $val['content_field']['todate']) { switch ($val['content_field']['type']) { case 'datestamp': $timestamp_fromto = array($val['table'] .'.'. $val['content_field']['field_name'] .'_value', $val['table'] .'.'. $val['content_field']['field_name'] .'_value2'); break; case 'date': $string_fromto = array($val['table'] .'.'. $val['content_field']['field_name'] .'_value', $val['table'] .'.'. $val['content_field']['field_name'] .'_value2'); break; } $event_field_processed == TRUE; } // skip this step on second occurance of fromto date fields, if more than one exists in view if (!$event_field_processed || $timestamp_fromto || $string_fromto) { // cck fields append a column name to the field, others do not // need a real field_name with no column name appended for cck date formatters $tmp = explode('.', $name); $field_name = $val['content_field']['field_name'] ? $val['content_field']['field_name'] : $tmp[1]; $fields[$tmp[1]] = array( 'type' => $type, 'delta' => $delta, 'label' => $val['name'], 'granularity' => is_array($val['content_field']['granularity']) ? (array) array_keys($val['content_field']['granularity']) : array('Y', 'M', 'D', 'H', 'N'), 'fullname' => $name, 'table' => $tmp[0], 'field' => $tmp[1], 'field_name' => $field_name, 'query_name' => str_replace('.', '_', $name), 'timestamp_fromto' => $timestamp_fromto, 'string_fromto' => $string_fromto, 'tz_handling' => $tz_handling, 'offset_field' => $offset_field, ); } } } return $fields; } /** * Calendar Views plugin theme, overrides default views theme */ function theme_calendar_display(&$view, &$items, $type) { drupal_add_css(CALENDAR_PATH .'/calendar.css'); $title = ($view->build_type == 'page' ? '' : ($view->subtitle ? $view->subtitle : $view->page_type)); $results = calendar_get_nodes($view, $items, $type); $nodes = $results['nodes']; $params = $results['params']; foreach (module_implements('calendar_add_items') as $module) { $function = $module .'_calendar_add_items'; $nodes += $function($view); } return theme('calendar_show_nav', $view) . calendar_get_calendar($view->calendar_type, $nodes, 'calendar', $title, $params); } /** * The workhorse function that takes the beginning array of items and alters it * to an array of calendar nodes that the theme can handle. */ function calendar_get_nodes(&$view, &$items, $type) { if (!is_array($items)) { $items = array(); } if ($view->build_type == 'page') { // bail out here to display regular views views switch($_GET['view']) { case ('table') : $view->table_header = _views_construct_header($view, _views_get_fields()); return theme('views_view_table', $view, $items, $type); case ('teasers') : return theme('views_view_teasers', $view, $items, $type); case ('nodes') : return theme('views_view_nodes', $view, $items, $type); case ('list') : return theme('views_view_list', $view, $items, $type); } } include_once(CALENDAR_PATH .'/calendar_api.inc'); include_once(DATE_API_PATH .'/date.inc'); $view->nodes_per_page = 0; $type_names = node_get_types('names'); unset($params['limit']); if ($type == 'block' || $view->calendar_type == 'year') $params['mini'] = TRUE; $fields = calendar_fields(); $calendar_fields = (array) array_keys($fields); $nodes = array(); $i = 0; $items_in = $items; // explode out field and format info from the view foreach ($view->field as $delta => $data) { if (in_array($data['field'], $calendar_fields)) { $option = $fields[$data['field']]; $field_type = strstr($option['type'], 'string') ? 'string' : 'timestamp'; $field_function = strstr($option['type'], 'cck') ? 'content_format' : $data['handler']; $field_formatter = $data['options']; $field_field = $option['query_name']; $field_end = $field_field .'2'; $field_field_name = $option['field_name']; $timestamp_fromto = $option['timestamp_fromto']; $string_fromto = $option['string_fromto']; $field_id = $delta; $tz_handling = $option['tz_handling']; $offset_field = $option['offset_field']; $label = $data['label']; $granularity = $option['granularity']; $view_fields = _views_get_fields(); // iterate through the $items array returned by the query and create date or pseudo date nodes foreach ($items as $delta => $item) { // we have to serialize and unserialize to force a completely new copy of $item when duplicate fields use the same node // without doing this, values added to the item in later iterations get applied to earlier ones $node = unserialize(serialize($item)); $node->title = $node->node_title; $node->label = $label; foreach ($view->field as $field) { if (!in_array($field['field'], $calendar_fields) && $field['field'] != 'title') { if ($view_fields[$field['id']]['visible'] !== FALSE) { $node->fields[$field['queryname']] = views_theme_field('views_handle_field', $field['queryname'], $view_fields, $field, $node, $view); } } } // If we're dealing with an event node, join the start and to dates together in one node and get rid of the other if (module_exists('event') && ($field_field == 'event_event_start' || $field_field == 'event_event_end') && !$event_field_processed[$item->nid]) { if ($node->event_event_start > 0) { $node->calendar_start = $node->event_event_start; $node->calendar_end = $node->event_event_end; $event_field_processed[$item->nid] = TRUE; } } elseif (module_exists('event') && ($field_field == 'event_event_start' || $field_field == 'event_event_end') && $event_field_processed[$item->nid]) { // if more than one event field was added to the view (like start and end dates) // don't process it more than once unset($node); } if (isset($node) && !isset($node->calendar_start) && !isset($item->$field_field)) { // if no date for the node and no date in the item // there is no way to display it on the calendar unset($node); } if (isset($node) && !$node->calendar_start && $item->$field_field) { // if calendar_start field holds a numeric value, treat it as a unix timestamp // if string, convert to timestamp using strtotime if ($field_type == 'timestamp') { $node->calendar_start = $item->$field_field; $node->calendar_end = $item->$field_end ? $item->$field_end : $item->$field_field; } else { // get the timestamp value for this date, use UTC to make sure no timezone conversion gets done on it $node->calendar_start = strtotime(str_replace('T', ' ', $item->$field_field) .' UTC'); $node->calendar_end = $item->$field_end ? strtotime(str_replace('T', ' ', $item->$field_end) .' UTC') : strtotime(str_replace('T', ' ', $item->$field_field) .' UTC'); } } if (isset($node)) { // get appropriate timezone offset switch ($tz_handling) { case 'user' : global $user; $node->start_offset = $node->end_offset = $user->timezone; break; case 'none': case 'GMT' : $node->start_offset = $node->end_offset = 0; break; case 'date' : $node->start_offset = $node->end_offset = $offset_field; break; case 'event' : include_once(DATE_TIMEZONES); $node->start_offset = event_get_offset($node->event_timezone, $node->event_event_start); $node->end_offset = event_get_offset($node->event_timezone, $node->event_event_end); break; default : $node->start_offset = $node->end_offset = variable_get('date_default_timezone', 0); break; } } if (isset($node) && function_exists($field_function) && $node->calendar_start && $item->$field_field) { if ($field_function == 'content_format') { // force the original value for this field into the array that content_format expects $node->start_format = content_format($field_field_name, array('value' => $item->$field_field, 'value2' => $item->$field_field), $field_formatter); if ($node->calendar_end) $node->end_format = content_format($field_field_name, array('value' => $item->$field_end, 'value2' => $item->$field_end), $field_formatter); } else { // or call date format function if (!$node->start_format) { $node->start_format = $field_function(NULL, NULL, $item->$field_field, NULL); if ($node->calendar_end && !$node->end_format) $node->end_format = $field_function(NULL, NULL, $node->calendar_end, NULL); } } // format a time-only display for the month calendar for dates that have time elements if (array_intersect($granularity, array('H', 'N', 'S'))) { $node->start_time_format = gmdate(variable_get('calendar_time_format_'. $view->name, 'H:i'), intval($node->calendar_start + $node->start_offset)); if ($node->calendar_end) $node->end_time_format = gmdate(variable_get('calendar_time_format_'. $view->name, 'H:i'), intval($node->calendar_end + $node->end_offset)); } else { $node->start_time_format = $node->start_format; $node->end_time_format = $node->end_format; } if ($node) { // we can have multiple representations with the same nid, like multi-day values // or different fields that refer to the same node // create a unique id so it gets displayed in the calendar // Add delta to key to make multiple value CCK fields display as separate items. if (strstr($option['type'], 'cck')) { $id = $item->nid .':'. $delta .':'. $field_field; } else { $id = $item->nid .':0:'. $field_field; } $node->nid = $id; if ($view->build_type == 'page' && $view->calendar_type != 'year') { $node->stripe = calendar_node_stripe($view, $node, $option['query_name'], $field_field); } $nodes[$id] = $node; unset($node); } } } } } // make sure there is at least one item in $nodes to force the calendar to display // set the hour to 12 to minimize timezone adjustments that might push it to previous or next day if ($view->calendar_type == 'year') { // for the year view to work, must have at least one node in each month for ($i=1; $i<13; $i++) { $nodes[] = _calendar_make_node(NULL, NULL, _views_get_timezone(), $view->year, $i, 1, 12, 0); } } elseif ($view->calendar_type == 'week') { // make sure at least one node is created for the current week // add both start and end of week in case week crosses from one month to next $week_range = calendar_week_range($view->year, $view->week); $nodes[] = _calendar_make_node(NULL, NULL, _views_get_timezone(), $view->year, date('m', $week_range[0]), date('j', $week_range[0]), 12, 0); $nodes[] = _calendar_make_node(NULL, NULL, _views_get_timezone(), $view->year, date('m', $week_range[1]), date('j', $week_range[1]), 12, 0); } elseif (sizeof($nodes) == 0) { // otherwise add a blank node for the current day $nodes = array(_calendar_make_node(NULL, NULL, _views_get_timezone(), $view->year, $view->month, $view->day, 12, 0)); } if (_calendar_is_valid($view->year, 'year')) { // valid year is a test that indicates if arguments were available to establish a date for the calendar // a full view with an argument date will display a single month, day or week calendar page // with navigation that mimics regular calendar // trim off date values that are outside the selected range to prevent display of incomplete extra calendars $params['limit'] = _calendar_limit_nodes($nodes, $view->calendar_type, $view->year, $view->month, $view->day, $view->week, _views_get_timezone()); // hide the intermediate header rows created by the event api and // push title and navigation into calendar header $params['hide_header'] = $view->calendar_type == 'week' ? false : true; //$title = theme('calendar_nav_wrapper', calendar_nav($view, $params['mini']), array()); // standard api displays a whole month instead of a single week // adjust here for single week display if ($view->calendar_type == 'week' && $view->week) { $params['force_week'] = $view->week; } } else { // if this is a summary page or any view that doesn't use arguments // view results may contain multiple calendar months, days or weeks // the standard calendar api (not using the $params and $title above) // will display multiple calendar months, weeks, or days with no navigation // make no change to api, just set title $title = $view->subtitle ? $view->subtitle : $view->page_type; } // use calendar_get_calendar api to draw the calendar $params['url'] = calendar_get_url($view, $view->args); $params['append'] = calendar_url_append($view); $params['stripe'] = 'stripe'; $params['with_weekno'] = $view->build_type == 'block' || $view->calendar_type == 'year' ? FALSE : TRUE; return array('nodes' => $nodes, 'params' => $params); } /** * Function to construct back and next navigation from views arguments */ function calendar_nav($view, $mini = FALSE) { if (!_calendar_is_valid($view->year, 'year')) { return $view->subtitle; } if ($view->calendar_type == 'week' && _calendar_is_valid($view->week, 'week')) { $range = calendar_week_range($view->year, $view->week + 1); $cur_stamp = $range[0]; } else { $cur_stamp = gmmktime(0, 0, 0, ($view->month ? $view->month : 1), ($view->day ? $view->day : 1), ($view->year ? $view->year : gmdate("Y", time()))); } // make the navigation into a header, with prev and next links // use the calendar_nav themes to mimic standard calendar navigation $paths = calendar_get_paths($cur_stamp, $view); $prev_path = implode('/', array_reverse($paths[0])); $next_path = implode('/', array_reverse($paths[1])); $prev_query = $next_query = array(); if ($_GET['view']) { $prev_query[] = 'view='. $_GET['view']; $next_query[] = 'view='. $_GET['view']; } // for the mini calendar in a block, treat the url as a querystring to avoid actually changing the page if ($mini && $view->calendar_type == 'month') { $prev_query[] = 'mini='. $prev_path; $prev_path = $_GET['q']; $next_query[] = 'mini='. $next_path; $next_path = $_GET['q']; } $prev_query[] = calendar_url_append($view); $next_query[] = calendar_url_append($view); $header = array(); $header[] = array('data' => theme('calendar_nav_prev', $prev_path, $mini ? FALSE : TRUE, implode('&', $prev_query)), 'class' => 'prev'); $header[] = array('data' => $view->subtitle, 'class' => 'heading', 'colspan' => 5); $header[] = array('data' => theme('calendar_nav_next', $next_path, $mini ? FALSE : TRUE, implode('&', $next_query)), 'class' => 'next'); return $header; } function calendar_get_paths($cur_stamp, $view) { $path = array(); // build an array of the current path and its parts $i = 0; $path[$i] = array( 'path' => $view->real_url, 'type' => 'url', ); foreach ($view->argument as $delta => $arg) { if ($view->args[$delta]) { $i++; $pathtype = str_replace('calendar_', '', $arg['type']); $path[$i] = array( 'path' => $view->$pathtype, 'type' => $pathtype, ); } } // if there are other arguments after the view arguments, add them to the navigation links while($i < sizeof($view->args)) { $i++; $path[$i] = array( 'path' => $view->args[intval($i - 1)], 'type' => '', ); } // reverse through the path, creating a $nextpath and $prevpath arrays for ($x = $i; $x >= 0; $x--) { switch ($path[$x]['type']) { case ('day'): $day = $path[$x]['path']; $next_stamp = $cur_stamp + (86400); $prev_stamp = $cur_stamp - (86400); $nextpath[$x] = gmdate('j', $next_stamp); $prevpath[$x] = gmdate('j', $prev_stamp); break; case ('week'): include_once(drupal_get_path('module', 'date_api') .'/date.inc'); $week = $path[$x]['path']; $year = $view->year; if (!$next_stamp) { // ISO weeks will not match calendar week if week starts any day but Monday. // Need to find prev and next ISO week for proper navigation. $cal_range = calendar_week_range($year, $week); $iso_range = date_iso_week_range($year, $week); $next_stamp = max($iso_range[1], $cal_range[1]) + 1; $prev_stamp = min($iso_range[0], $cal_range[1]) - 1; } $nextpath[$x] = 'W'. gmdate('W', $next_stamp); $prevpath[$x] = 'W'. gmdate('W', $prev_stamp); break; case ('month'): $month = $path[$x]['path']; $year = $view->year; if (!$next_stamp) { $next_stamp = gmmktime(0, 0, 0, ($month < 12 ? $month + 1 : 1), 1, ($month < 12 ? $year : $year + 1)); $prev_stamp = gmmktime(0, 0, 0, ($month > 1 ? $month - 1 : 12), 1, ($month > 1 ? $year : $year - 1)); } $nextpath[$x] = gmdate('n', $next_stamp); $prevpath[$x] = gmdate('n', $prev_stamp); break; case ('year'): $year = $view->year; if (!$next_stamp) { $next_stamp = gmmktime(0, 0, 0, 1, 1, ($year + 1)); $prev_stamp = gmmktime(0, 0, 0, 12, 1, ($year - 1)); } $nextpath[$x] = gmdate('Y', $next_stamp); $prevpath[$x] = gmdate('Y', $prev_stamp); break; default: // all other arguments are just passed through if ($path[$x]['path']) { $nextpath[$x] = $path[$x]['path']; $prevpath[$x] = $path[$x]['path']; } break; } } return array($prevpath, $nextpath); } /** * A function to create a blank date to force a calendar display when there is no data */ function _calendar_make_node($node = NULL, $timestamp = NULL, $offset = NULL, $year = NULL, $month = NULL, $day = NULL, $hour = NULL, $minute = NULL) { $offset = $offset ? $offset : _views_get_timezone(); if (!$timestamp) { $year = _calendar_is_valid($year, 'year') ? $year : gmdate('Y', time()); $month = _calendar_is_valid($month, 'month') ? $month : gmdate('m', time()); $day = _calendar_is_valid($day, 'day') ? $day : gmdate('j', time()); $hour = _calendar_is_valid($hour, 'hour') ? $hour : gmdate('H', time()); $minute = _calendar_is_valid($minute, 'minute') ? $minute : gmdate('i', time()); while (!checkdate($month, $day, $year)) $day--; // Account for days that don't exist $timestamp = gmmktime($hour, $minute, 0, $month, $day, $year); } if (!$node) { $node = new stdClass(); $node->nid = 0; } $node->calendar_start = $timestamp; $node->start_offset = $offset; $node->calendar_end = $timestamp; $node->end_offset = $offset; return $node; } /** * A function to adjust node values to slice off times before and after the selected view * used for calendars that span days, months, or years since the calendar api * automatically creates additional calendars for calendars that extend into another time period * and the additional calendars will be incomplete (only containing cross-over calendars) */ function _calendar_limit_nodes($nodes, $type, $year, $month, $day, $week, $offset) { if (!_calendar_is_valid($day, 'day')) $day = 1; if (!_calendar_is_valid($month, 'month')) $month = date('m', time()); if (!_calendar_is_valid($year, 'year')) $year = date('Y', time()); switch ($type) { case ('day'): $min_date = mktime(0, 0, 0, $month, $day, $year); $max_date = $min_date + 86400 - 1; break; case ('week'): return calendar_week_range($year, $week); case ('month'); $min_date = mktime(0, 0, 0, $month, 1, $year); // find the first day of the next month and subtract one day if ($month < 12) { $max_date = mktime(0, 0, 0, intval($month + 1), 1, $year) - 1; } else { $max_date = mktime(0, 0, 0, 1, 1, intval($year + 1)) - 1; } break; case ('year'): $min_date = mktime(0, 0, 0, 1, 1, $year); $max_date = mktime(23, 59, 59, 12, 31, $year); } return array($min_date, $max_date); } /** * TODO need to identify type of timezone handling needed for each date field */ function calendar_offset($field_name) { $default_offset = variable_get('date_default_timezone', 0); $configurable_zones = variable_get('configurable_timezones', 1); } /** * Implementation of hook_views_arguments() */ function calendar_views_arguments() { $arguments = array( 'calendar_year' => array( 'name' => t('Calendar: Year'), 'handler' => 'calendar_handler_arg_year', 'help' => t('Filter by the calendar year (YYYY).'), ), 'calendar_month' => array( 'name' => t('Calendar: Month'), 'handler' => 'calendar_handler_arg_month', 'help' => t("Filter by the calendar month (1-12). Place this argument after a 'Year' argument."), ), 'calendar_day' => array( 'name' => t('Calendar: Day'), 'handler' => 'calendar_handler_arg_day', 'help' => t("Filter by the calendar day (1-31). Place this argument after a 'Year' and a 'Month' argument."), ), 'calendar_week' => array( 'name' => t('Calendar: Week'), 'handler' => 'calendar_handler_arg_week', 'help' => t("Filter by the week number (1-52). Place this argument after a 'Year' argument and use a 'W' in front of the week number in the url."), ), ); return $arguments; } /** * Custom views handlers for the calendars arguments */ function calendar_handler_arg_year($op, &$query, $argtype, $arg = '') { return calendar_handler_arg_type($op, $query, $argtype, $arg, 'calendar_year', 'YEAR'); } function calendar_handler_arg_month($op, &$query, $argtype, $arg = '') { return calendar_handler_arg_type($op, $query, $argtype, $arg, 'calendar_month', 'MONTH'); } function calendar_handler_arg_day($op, &$query, $argtype, $arg = '') { return calendar_handler_arg_type($op, $query, $argtype, $arg, 'calendar_day', 'DAY'); } function calendar_handler_arg_week($op, &$query, $argtype, $arg = '') { return calendar_handler_arg_type($op, $query, $argtype, $arg, 'calendar_week', 'WEEK'); } function calendar_handler_arg_type($op, &$query, $argtype, $arg, $field_name, $field_type) { // arguments don't actually do anything except tell us what period we're in // and make sure views knows that a page with this argument must be processed by views // the individual argument provides too little info to set up filters // filtering is done in hook_query_alter since we don't have complete view info until then $query->field_name[] = $field_name; $query->field_type[] = $field_type; $query->field_args[] = $arg; return; } /** * Implementation of hook_views_query() * Insert filters into the query based on the current calendar view and the selected fields */ function calendar_views_query_alter(&$query, &$view) { if (!calendar_is_calendar($view)) return; include_once(CALENDAR_PATH .'/calendar_api.inc'); include_once(DATE_API_PATH .'/date.inc'); // make sure block views default to the current month // and make sure day view is not selected if ($view->build_type == 'block') { // if the real view values were reset in the page, reset them if ($view->reset_argument) { $view->argument = $view->reset_argument; $view->args = $view->reset_args; unset($view->reset_argument); unset($view->reset_args); } $view->calendar_type = 'month'; $view->args = explode('/', str_replace($view->url .'/', '', $_GET['mini'])); foreach ($view->argument as $delta => $argument) { if ($argument['type'] == 'calendar_month' && !$view->args[$delta]) { $view->args[$delta] = calendar_user_date('month'); } elseif ($argument['type'] == 'calendar_day') { unset($view->args[$delta]); } } } // either a month or a week argument could occupy the second position of the group // this is done so that a single view has the capability to switch between all calendar layouts // to make this work we must make some adjustments to the view if ($view->build_type == 'page') { if (empty($view->args)) { foreach ($view->argument as $delta => $argument) { if ($argument['type'] == 'calendar_month' && !$view->args[$delta]) { $view->args[$delta] = calendar_user_date('month'); } elseif ($argument['type'] == 'calendar_year' && !$view->args[$delta]) { $view->args[$delta] = calendar_user_date('year'); } } } $GLOBALS['calendar_is_calendar'] = TRUE; foreach ($view->argument as $delta => $argument) { if ($argument['type'] == 'calendar_week' || $argument['type'] == 'calendar_month') { // see if this is a week view and reconstruct view info as needed // the difference between a calendar_month arg and a calendar_week arg is the preceeding 'W' if (strstr($view->args[$delta], 'W') && $view->build_type == 'page') { // keep track of the original values so blocks rendered on the same page don't get wrong values $view->reset_argument = $view->argument; $view->reset_args = $view->args; $view->argument[$delta]['type'] = 'calendar_week'; $view->argument[$delta]['id'] = 'calendar_week'; $view->argument[$delta]['title'] = t('Week'); $view->args[$delta] = str_replace('W', '', $view->args[1]); } // if this is not a week argument and view was created with a week argument, change it back elseif (!strstr($view->args[$delta], 'W') && $view->build_type == 'page' && $view->argument[$delta]['type'] == 'calendar_week') { $view->argument[$delta]['type'] = 'calendar_month'; $view->argument[$delta]['id'] = 'calendar_month'; $view->argument[$delta]['title'] = t('Month'); if ($view->args[$delta + 1]) { $view->argument[$delta + 1]['type'] = 'calendar_day'; $view->argument[$delta + 1]['id'] = 'calendar_day'; $view->argument[$delta + 1]['title'] = t('Day'); } } } } } foreach ($view->argument as $delta => $argument) { // there might be non-calendar arguments in the view, so we must ignore those if (in_array($argument['type'], array('calendar_year', 'calendar_month', 'calendar_day', 'calendar_week'))) { // make sure 'display all values' is selected for the calendar arguments // summary views are meaningless and create errors in this context $view->argument[$delta]['argdefault'] = 2; $view->real_url = calendar_get_url($view, $view->args); if ($argument['type'] == 'calendar_year') { $view->year = _calendar_is_valid($view->args[$delta], 'year') ? $view->args[$delta] : calendar_user_date('year'); $query_min = $view->year; $query_max = $view->year; $view->subtitle = l($view->year, $view->real_url .'/'. $view->year, array(), calendar_url_append($view)); // make sure we have at least a year available as an argument if (!$view->args[$delta]) { $view->args[$delta] = $view->year; } $view->calendar_type = str_replace('calendar_', '', $argument['type']); $view->delta['year'] = $delta; } if ($argument['type'] == 'calendar_month' && $view->args[$delta]) { $view->month = _calendar_is_valid($view->args[$delta], 'month') ? $view->args[$delta] : calendar_user_date('month'); $query_min .= '-'. sprintf('%02d', $view->month); $query_max .= '-'. sprintf('%02d', $view->month); // use the second day of the month because gm functions sometimes return the previous month $format = $view->build_type == 'block' || $view->calendar_type == 'year' ? 'M Y' : 'F Y'; $iso = $view->year .'-'. sprintf('%02d', $view->month) .'-02'; $view->subtitle = l(gmdate($format, strtotime($iso)), $view->real_url .'/'. $view->year .'/'. $view->month, array(), calendar_url_append($view)); $view->calendar_type = str_replace('calendar_', '', $argument['type']); $view->delta['month'] = $delta; } if ($argument['type'] == 'calendar_day' && $view->args[$delta]) { $view->day = _calendar_is_valid($view->args[$delta], 'day') ? $view->args[$delta] : calendar_user_date('day'); $query_min .= '-'. sprintf('%02d', $view->day); $query_max .= '-'. sprintf('%02d', $view->day); $iso = $view->year .'-'. sprintf('%02d', $view->month) .'-'. sprintf('%02d', $view->day) .'T12:00:00'; $view->subtitle = l(gmdate('l, F j Y', strtotime($iso)), $view->real_url .'/'. $view->year .'/'. $view->month .'/'. $view->day, array(), calendar_url_append($view)); $view->calendar_type = str_replace('calendar_', '', $argument['type']); $view->delta['week'] = $delta; } if ($argument['type'] == 'calendar_week' && $view->args[$delta]) { $view->week = _calendar_is_valid($view->args[$delta], 'week') ? $view->args[$delta] : 1; // use strtotime to find first day of requested week $week = $view->args[$delta]; $view->week = $week; $query_min = calendar_week('start_year', $view, $week) .'-'. sprintf('%02d', calendar_week('start_month', $view, $week)) .'-'. calendar_week('start_day', $view, $week) .'T00:00:00'; $query_max = calendar_week('end_year', $view, $week) .'-'. sprintf('%02d', calendar_week('end_month', $view, $week)) .'-'. calendar_week('end_day', $view, $week) .'T23:59:59'; $view->subtitle = l(t("Week of !date", array('!date' => gmdate('F j Y', calendar_week('start_timestamp', $view, $week)))), $view->real_url .'/'. $view->year .'/W'. $view->week, array(), calendar_url_append($view)); $view->calendar_type = str_replace('calendar_', '', $argument['type']); $view->month = calendar_week('start_month', $view, $week); $view->day = calendar_week('start_day', $view, $week); $view->delta['day'] = $delta; } } } if (!isset($view->week)) $view->week = calendar_week('week', $view); if (!$query_min) { return; } // Add elements to the query min and max values to create a complete date value // for the minimum and maximum once all the View's args have been used. $minmax = array( 'year' => array('-01-01 00:00:00', "-12-31 23:59:59"), 'month' => array('-01 00:00:00', '-'. ($view->month > 0 ? sprintf("%02d", date_last_day_of_month($view->month, $view->year)) : '12') .' 23:59:59'), 'day' => array(' 00:00:00', ' 23:59:59'), 'hour' => array(':00:00', ':59:59'), 'minute' => array(':00', ':59'), ); $query_min .= $minmax[$view->calendar_type][0]; $query_max .= $minmax[$view->calendar_type][1]; $query_timestamp_min = strtotime($query_min); $query_timestamp_max = strtotime($query_max); // find all datetime fields in this view and add filters for them to the query $fields = calendar_fields(); foreach ($view->field as $delta => $field) { $field_name = $field['field']; $this_field = $fields[$field_name]; $view_fields[] = $field_name; if (array_key_exists($field_name, $fields)) { $query->ensure_table($this_field['table'], $this_field['table']); $tz_handling = $this_field['tz_handling']; $offset_field = $this_field['offset_field']; $field_type = strstr($this_field['type'], 'string') ? 'iso' : 'int'; // get appropriate timezone offset switch ($tz_handling) { case 'user' : global $user; $start_offset = $end_offset = $user->timezone; break; case 'none': case 'GMT': $start_offset = $end_offset = 0; break; case 'date' : $start_offset = $end_offset = $offset_field; break; case 'event' : // event-specific timezones won't work right here because no offset is stored in the database // the best we can do is treat it the same as if it was a site timezone default : $start_offset = $end_offset = variable_get('date_default_timezone', 0); break; } // handling for from and to date ranges if ($this_field['timestamp_fromto']) { $queries[] = "(". date_sql('DATE', $this_field['timestamp_fromto'][1], $field_type, $end_offset) ." >='$query_min' AND ". date_sql('DATE', $this_field['timestamp_fromto'][0], $field_type, $start_offset) ." <='$query_max')"; $event_field_processed = TRUE; } elseif ($this_field['string_fromto']) { $queries[] = "(". date_sql('DATE', $this_field['string_fromto'][1], $field_type, $end_offset) ." >='$query_min' AND ". date_sql('DATE', $this_field['string_fromto'][0], $field_type, $start_offset) ." <='$query_max')"; $event_field_processed = TRUE; } // handling for single day dates elseif ($this_field['type'] == 'cck_string') { $queries[] = "(". date_sql('DATE', $this_field['fullname'], $field_type, $start_offset) .">='$query_min' AND ". date_sql('DATE', $field['fullname'], $field_type, $end_offset) ."<='$query_max')"; } elseif (strstr($this_field['type'], 'timestamp')) { $queries[] = "(". date_sql('DATE', $this_field['fullname'], $field_type, $start_offset) .">='$query_min' AND ". date_sql('DATE', $this_field['fullname'], $field_type, $end_offset) ."<='$query_max')"; } } } // bring the node type into the query so we can use it in the theme $query->add_field('type', 'node'); if ($queries) $query->add_where(implode(" OR ", $queries)); return; } /** * Implementation of hook_views_pre_view() */ function calendar_views_pre_view(&$view, &$items) { if (!calendar_is_calendar($view)) return; include_once(CALENDAR_PATH .'/calendar_api.inc'); drupal_add_css(CALENDAR_PATH .'/calendar.css'); // add this so empty calendar results will produce blank calendar page // needed for smooth prev/next transitions and to make sure at least // a blank calendar is displayed in calendar block if (array_key_exists($view->page_type, calendar_view_types())) { if (!$items && $view->build_type == 'page' && $view->year) { $view->page_empty = $view->page_header . $view->page_empty . theme('calendar_display', $view, array(), 'page'); $view->page_empty_format = 3; } } if (array_key_exists($view->block_type, calendar_view_types())) { if (!$items && $view->build_type == 'block' && $view->year) { $view->block_empty = $view->block_header . $view->block_empty . theme('calendar_display', $view, array(), 'block'); $view->block_empty_format = 3; } } } /** * Get the start and end datestamp for a week. * * Use strtotime to find first day of requested week. * Strtotime uses Monday as first day of week, so adjust if necessary. */ function calendar_week_range($year, $week) { $min_date = strtotime('Jan 1, '.$year.' + '.intval($week).' weeks UTC'); // Adjust the minimum date based on whether the site first day of week is earlier // or later than the ISO first day of week (Monday). switch (variable_get('date_first_day', 0)) { case(0): $min_date -= 604800 + 86400; break; case(1): $min_date -= 604800; break; default: $min_date -= (2 * 604800) - intval(86400 * (variable_get('date_first_day', 0))); } $max_date = $min_date + (604800) - 1; return array($min_date, $max_date); } /** * Handle a lot of messy week calculations all in one place to make maintenance easier */ function calendar_week($op, $view, $week = 0) { if ($week == 0) { $date = strtotime($view->year .'-'. sprintf('%02d', $view->month ? $view->month : calendar_user_date('month')) .'-'. sprintf('%02d', $view->day ? $view->day : calendar_user_date('day'))); $week = date('W', $date); if ($op == 'week') return $week; } $range = calendar_week_range($view->year, $week); $week_start = $range[0]; $week_end = $range[1]; switch ($op) { case ('start_year'): return date('Y', $week_start); case ('end_year'): return date('Y', $week_end); case ('start_month'): return date('n', $week_start); case ('end_month'): return date('n', $week_end); case ('start_day'): return date('d', $week_start); case ('end_day'): return date('d', $week_end); default: return $week_start; } } /** * A function to test the validity of various date parts */ function _calendar_is_valid($value, $type) { $value = intval($value); if ($value <= 0) return false; switch ($type) { case ('year'): if ($value < 1970) return false; break; case ('month'): if ($value < 0 || $value > 12) return false; break; case ('day'): if ($value < 0 || $value > 31) return false; break; case ('week'): if ($value < 0 || $value > 53) return false; } return true; } /** * Function to test whether this is a view that the calendar module should touch */ function calendar_is_calendar($view) { $is_calendar = FALSE; if ($view->build_type == 'page' && !array_key_exists($view->page_type, calendar_view_types())) { return FALSE; } if ($view->build_type == 'block' && !array_key_exists($view->block_type, calendar_view_types())) { return FALSE; } foreach ($view->argument as $delta => $argument) { if (in_array($argument['type'], array('calendar_year', 'calendar_month', 'calendar_day', 'calendar_week'))) { return TRUE; } } return FALSE; } function calendar_views_default_views() { $view = new stdClass(); $view->name = t('calendar'); $view->description = t('Calendar view of any date field'); $view->access = array (); $view->view_args_php = ''; $view->page = TRUE; $view->page_title = t('Calendar'); $view->page_header = ""; $view->page_header_format = '1'; $view->page_footer = ""; $view->page_footer_format = '1'; $view->page_empty = ""; $view->page_empty_format = '1'; $view->page_type = 'calendar'; $view->url = t('calendar'); $view->use_pager = TRUE; $view->nodes_per_page = '99'; $view->block = TRUE; $view->block_title = t('Calendar'); $view->block_header = ""; $view->block_header_format = '1'; $view->block_footer = ""; $view->block_footer_format = '1'; $view->block_empty = ""; $view->block_empty_format = '1'; $view->block_type = 'calendar'; $view->nodes_per_block = '99'; $view->block_more = '1'; $view->block_use_page_header = FALSE; $view->block_use_page_footer = FALSE; $view->block_use_page_empty = FALSE; $view->sort = array ( array ( 'tablename' => 'node', 'field' => 'changed', 'sortorder' => 'ASC', 'options' => '', ), ); $view->argument = array ( array ( 'type' => 'calendar_year', 'argdefault' => '2', 'title' => 'Year', 'options' => '', 'wildcard' => '', 'wildcard_substitution' => '', ), array ( 'type' => 'calendar_month', 'argdefault' => '2', 'title' => 'Month', 'options' => '', 'wildcard' => '', 'wildcard_substitution' => '', ), array ( 'type' => 'calendar_day', 'argdefault' => '2', 'title' => 'Day', 'options' => '', 'wildcard' => '', 'wildcard_substitution' => '', ), ); $view->field = array ( array ( 'tablename' => 'node', 'field' => 'title', 'label' => 'Title:', 'handler' => 'views_handler_field_nodelink', 'options' => 'link', ), array ( 'tablename' => 'node', 'field' => 'changed', 'label' => 'Updated:', 'handler' => 'views_handler_field_date_small', ), ); $view->filter = array ( array ( 'tablename' => 'node', 'field' => 'status', 'operator' => '=', 'options' => '', 'value' => '1', ), ); $view->exposed_filter = array ( ); $view->requires = array(node); $views[$view->name] = $view; return $views; } /** * implementation of hook_block() */ function calendar_block($op = 'list', $delta = 0) { switch ($op) { case 'list' : $blocks[0]['info'] = t('Calendar Legend.'); $blocks[1]['info'] = t('Switch Calendar.'); return $blocks; break; case 'view' : switch ($delta) { case 0: $block['subject'] = t('Calendar Legend'); $block['content'] = $GLOBALS['calendar_stripe_labels'] ? '