View complete documentation at !link.

", array('!link' => 'http://drupal.org/node/120710')); } } /** * Implementation of hook_menu(). */ function calendar_menu($may_cache) { $items = array(); if (!$may_cache) { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar.theme'); drupal_add_css('./'. drupal_get_path('module', 'calendar') .'/calendar.css'); define('CALENDAR_EMPTY_ARG', variable_get('calendar_empty_arg', 'all')); foreach (calendar_info() as $view_name => $view) { $parts = explode('/', $view['url']); foreach ($parts as $delta => $part) { if (in_array($part, array('$arg', '$node', '$user', '$group'))) { $parts[$delta] = arg($delta); } } $items[] = array( 'path' => implode('/', $parts) .'/setup', 'title' => t('Setup'), 'description' => t('Calendar setup.'), 'access' => user_access('administer views'), 'callback' => 'drupal_get_form', 'callback arguments' => array('calendar_setup_form', $view_name), 'type' => MENU_LOCAL_TASK, 'weight' => 5, ); } } return $items; } /** * Implementation of hook_form_alter(). * Make sure calendar_info() and calendar_fields() get updated. */ function calendar_form_alter($form_id, &$form) { if ($form_id == 'views_edit_view') { $form['#submit'] = array_merge($form['#submit'], array('calendar_clear_all' => array())); } } /** * Implementation of hook_views_tabs(). */ function calendar_views_tabs($op) { switch ($op) { case 'names': return array('setup'); } } /** * Helper function for loading date_api. */ function calendar_load_date_api() { include_once(drupal_get_path('module', 'date_api') .'/date.inc'); include_once(drupal_get_path('module', 'date_api') .'/date_timezones.inc'); } /** * Helper function for loading calendar_api. */ function calendar_load_calendar_api() { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_api.inc'); } /** * Call hooks in other modules to add other items to a calendar view. * * @param object $view * @param string $type - display type, 'table', 'teasers', 'nodes', 'list', 'calendar' * @return array of additional items to add. */ function calendar_add_items($view, $type) { $new_items = array(); foreach (module_implements('calendar_add_items') as $module) { $function = $module .'_calendar_add_items'; if (function_exists($function)) { $feeds = $function($view); // Calendar items are just added as objects, themeing will be done by the calendar api. // For other types, theme the items and add them. foreach ($feeds as $feed) { if ($type == 'calendar') { $new_items[] = $feed; } else { switch ($type) { case 'table': $output = array('data' => array(theme('calendar_node_month', $feed))); break; case 'nodes': case 'teasers': case 'list': $output = theme('calendar_node_day', $feed); break; } if (!empty($output)) $new_items[] = $output; } } } } return $new_items; } /** * 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) { calendar_load_calendar_api(); calendar_load_date_api(); $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 = str_replace('.', '_', $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 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 = date_iso2unix($item->$field_field); $node->calendar_end = $item->$field_end ? date_iso2unix($item->$field_end) : date_iso2unix($item->$field_field); } } 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 = $node->$offset_field; break; case 'event' : $node->start_offset = date_get_offset($node->event_timezone, $node->event_event_start); $node->end_offset = date_get_offset($node->event_timezone, $node->event_event_end); break; default : $timezone = variable_get('date_default_timezone_name', 'UTC'); $node->start_offset = date_offset(date_unix2array($node->calendar_start), $timezone); $node->end_offset = date_offset(date_unix2array($node->calendar_end), $timezone); 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 = date_format_date(variable_get('calendar_time_format_'. $view->name, 'H:i'), intval($node->calendar_start + $node->start_offset)); if ($node->calendar_end) $node->end_time_format = date_format_date(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(), date_format_date('Y', $week_range[0]), date_format_date('m', $week_range[0]), date_format_date('j', $week_range[0]), 12, 0); $nodes[] = _calendar_make_node(NULL, NULL, _views_get_timezone(), date_format_date('Y', $week_range[1]), date_format_date('m', $week_range[1]), date_format_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_part_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; } } // use calendar_get_calendar api to draw the calendar $params['url'] = calendar_real_url($view, $view->args); $params['append'] = calendar_url_append($view); $params['stripe'] = 'stripe'; $params['with_weekno'] = $view->build_type == 'block' || $view->calendar_type == 'year' || $view->year < 1970 ? 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_part_is_valid($view->year, 'year')) { return $view->subtitle; } calendar_load_date_api(); if ($view->calendar_type == 'week' && calendar_part_is_valid($view->week, 'week')) { $range = calendar_week_range($view->year, $view->week + 1); $cur_stamp = $range[0]; } else { $cur_stamp = date_gmmktime(array('mon' => $view->month ? $view->month : 1, 'mday' => $view->day ? $view->day : 1, 'year' => $view->year ? $view->year : date_format_date("Y", (date_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) { calendar_load_date_api(); $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->args[$delta] != CALENDAR_EMPTY_ARG ? $view->$pathtype : CALENDAR_EMPTY_ARG, '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--) { if ($path[$x]['path'] != CALENDAR_EMPTY_ARG) { switch ($path[$x]['type']) { case ('day'): $day = $path[$x]['path']; $next_stamp = $cur_stamp + (86400); $prev_stamp = $cur_stamp - (86400); $nextpath[$x] = date_format_date('j', $next_stamp); $prevpath[$x] = date_format_date('j', $prev_stamp); break; case ('week'): $week = $path[$x]['path']; $year = $view->year; if (!$next_stamp) { $cal_range = calendar_week_range($year, $week); $next_stamp = $cal_range[1] + 1; $prev_stamp = $cal_range[0] - 1; } $week_year = calendar_week_year($next_stamp); $next_year = $week_year[1]; $nextpath[$x] = 'W'. sprintf("%02d", $week_year[0]); $week_year = calendar_week_year($prev_stamp); $prev_year = $week_year[1]; $prevpath[$x] = 'W'. sprintf("%02d", $week_year[0]); break; case ('month'): $month = $path[$x]['path']; $year = $view->year; $next_year = $month < 12 ? $year : $year + 1; $prev_year = $month > 1 ? $year : $year - 1; if (!$next_stamp) { $next_stamp = date_gmmktime(array('mon' => $month < 12 ? $month + 1 : 1, 'mday' => 1, 'year' => $next_year)); $prev_stamp = date_gmmktime(array('mon' => $month > 1 ? $month - 1 : 12, 'mday' => 1, 'year' => $prev_year)); } $nextpath[$x] = date_format_date('n', $next_stamp); $prevpath[$x] = date_format_date('n', $prev_stamp); break; case ('year'): $year = $view->year; if (!$next_stamp) { $next_stamp = date_gmmktime(array('mon' => 1, 'mday' => 1, 'year' => $next_year ? $next_year : $year + 1)); $prev_stamp = date_gmmktime(array('mon' => 12, 'mday' => 1, 'year' => $prev_year ? $prev_year : $year - 1)); } $nextpath[$x] = date_format_date('Y', $next_stamp); $prevpath[$x] = date_format_date('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; } } else { $nextpath[$x] = $path[$x]['path']; $prevpath[$x] = $path[$x]['path']; } } 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) { calendar_load_date_api(); $offset = $offset ? $offset : _views_get_timezone(); if (!$timestamp) { $year = calendar_part_is_valid($year, 'year') ? $year : date_format_date('Y', date_time()); $month = calendar_part_is_valid($month, 'month') ? $month : date_format_date('m', date_time()); $day = calendar_part_is_valid($day, 'day') ? $day : date_format_date('j', date_time()); $hour = calendar_part_is_valid($hour, 'hour') ? $hour : date_format_date('H', date_time()); $minute = calendar_part_is_valid($minute, 'minute') ? $minute : date_format_date('i', date_time()); while (!checkdate($month, $day, $year)) $day--; // Account for days that don't exist $timestamp = date_gmmktime(array('hours' => $hour, 'minutes' => $minute, 'mon' => $month, 'mday' => $day, 'year' => $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) { calendar_load_date_api(); if (!calendar_part_is_valid($day, 'day')) $day = 1; if (!calendar_part_is_valid($month, 'month')) $month = date_format_date('m', date_time()); if (!calendar_part_is_valid($year, 'year')) $year = date_format_date('Y', date_time()); switch ($type) { case ('day'): $min_date = date_gmmktime(array('mon' => $month, 'mday' => $day, 'year' => $year)); $max_date = $min_date + 86400 - 1; break; case ('week'): return calendar_week_range($year, $week); case ('month'); $min_date = date_gmmktime(array('mon' => $month, 'mday' => 1, 'year' => $year)); // find the first day of the next month and subtract one day if (intval($month) < 12) { $max_date = date_gmmktime(array('mon' => intval($month + 1), 'day' => 1, 'year' => $year, 'hours' => 23, 'minutes' => 59, 'seconds' => 59)); } else { $max_date = date_gmmktime(array('mon' => 1, 'mday' => 1, 'year' => intval($year + 1), 'hours' => 23, 'minutes' => 59, 'seconds' => 59)); } $max_date -= 86400; break; case ('year'): $min_date = date_gmmktime(array('hours' => 0, 'minutes' => 0, 'seconds' => 0, 'mon' => 1, 'mday' => 1, 'year' => $year)); $max_date = date_gmmktime(array('hours' => 23, 'minutes' => 59, 'seconds' => 59, 'mon' => 12, 'mday' => 31, 'year' => $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); } /** * Custom views handler for the year argument. */ function calendar_handler_arg_year($op, &$query, $argtype, $arg = '') { if ($op == 'filter' && !empty($arg) && $arg != CALENDAR_EMPTY_ARG) { calendar_filter_year($query, $arg); } return calendar_handler_arg_type($op, $query, $argtype, $arg, 'YEAR'); } /** * Callback for year filter. * Build year, month, day, min, and max into query object. * * @param object $query * @param integer $arg */ function calendar_filter_year(&$query, $arg) { calendar_load_calendar_api(); $query->calendar_type = 'year'; $query->year = calendar_part_is_valid($arg, 'year') ? $arg : calendar_user_date('year'); $query->month = CALENDAR_EMPTY_ARG; $query->day = CALENDAR_EMPTY_ARG; $query->min = $query->year; $query->max = $query->year; } /** * Custom views handler for the month argument. */ function calendar_handler_arg_month($op, &$query, $argtype, $arg = '') { if ($op == 'filter' && !empty($arg) && $arg != CALENDAR_EMPTY_ARG) { calendar_filter_month($query, $arg); } return calendar_handler_arg_type($op, $query, $argtype, $arg, 'MONTH'); } /** * Callback for month filter. * Build year, month, day, min, and max into query object. * * @param object $query * @param integer $arg */ function calendar_filter_month(&$query, $arg) { calendar_load_calendar_api(); $query->calendar_type = 'month'; if (!isset($query->year)) { calendar_filter_year($query, calendar_user_date('year')); } $query->month = calendar_part_is_valid($arg, 'month') ? $arg : calendar_user_date('month'); $query->day = CALENDAR_EMPTY_ARG; $query->min .= '-'. sprintf('%02d', $query->month); $query->max .= '-'. sprintf('%02d', $query->month); } /** * Custom views handler for the day argument. */ function calendar_handler_arg_day($op, &$query, $argtype, $arg = '') { if ($op == 'filter' && !empty($arg) && $arg != CALENDAR_EMPTY_ARG) { calendar_filter_day($query, $arg); } return calendar_handler_arg_type($op, $query, $argtype, $arg, 'DAY'); } /** * Callback for day filter. * Build year, month, day, min, and max into query object. * * @param object $query * @param integer $arg */ function calendar_filter_day(&$query, $arg) { calendar_load_calendar_api(); if (!isset($query->month)) { calendar_filter_month($query, calendar_user_date('month')); } $query->calendar_type = 'day'; $query->day = calendar_part_is_valid($arg, 'day') ? $arg : calendar_user_date('day'); $query->min .= '-'. sprintf('%02d', $query->day); $query->max .= '-'. sprintf('%02d', $query->day); } /** * Custom views handlers for the week argument. */ function calendar_handler_arg_week($op, &$query, $argtype, $arg = '') { if ($op == 'filter' && !empty($arg) && $arg != CALENDAR_EMPTY_ARG) { calendar_filter_week($query, $arg); } return calendar_handler_arg_type($op, $query, $argtype, $arg, 'WEEK'); } /** * Callback for week filter. * Build year, month, day, min, and max into query object. * * @param object $query * @param integer $arg */ function calendar_filter_week(&$query, $arg) { calendar_load_calendar_api(); if (!isset($query->year)) { calendar_filter_year($query, calendar_user_date('year')); } $arg = str_replace('W', '', $arg); $query->calendar_type = 'week'; $query->week = calendar_part_is_valid($arg, 'week') ? $arg : calendar_user_date('week'); $query->month = calendar_week('start_month', $query, $query->week); $query->day = calendar_week('start_day', $query, $query->week); $query->min = calendar_week('start_year', $query, $query->week) .'-'. sprintf('%02d', calendar_week('start_month', $query, $query->week)) .'-'. calendar_week('start_day', $query, $query->week) .' 00:00:00'; $query->max = calendar_week('end_year', $query, $query->week) .'-'. sprintf('%02d', calendar_week('end_month', $query, $query->week)) .'-'. calendar_week('end_day', $query, $query->week) .' 23:59:59'; } /** * Custom views handler for all calendar arguments. */ function calendar_handler_arg_type($op, &$query, $argtype, $arg, $field_type) { calendar_load_date_api(); switch ($op) { case 'summary': case 'link': // The query to do summaries when date ranges can include multiple days, months, and years // is extremely complex and has been omitted, so summary views with these arguments just will not work. // TODO add some kind of validation or warning to keep people from trying to use summary views. break; case 'filter': // Figure out which will be the final calendar argument in this view so we know when to insert the query. $view = $GLOBALS['current_view']; if ($argtype['type'] == calendar_is_last_arg($view)) { $query->calendar_finished = TRUE; calendar_build_filter($query, $view); } break; case 'title': // Set titles for each argument. $value = intval(str_replace('W', '', $arg ? $arg : $query)); return theme('calendar_arg_title', $field_type, $value, $query); } return; } /** * Compile the filter query for this view. * * @param object $query * @param object $view */ function calendar_build_filter(&$query, &$view) { if (!isset($query->week)) { $query->week = calendar_week('week', $query); } 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', '-'. sprintf("%02d", date_last_day_of_month($query->month, $query->year)) .' 23:59:59'), 'day' => array(' 00:00:00', ' 23:59:59'), 'hour' => array(':00:00', ':59:59'), 'minute' => array(':00', ':59'), ); $query->min .= $minmax[$query->calendar_type][0]; $query->max .= $minmax[$query->calendar_type][1]; // find all datetime fields in this view and add filters for them to the query $queries = array(); foreach ($view->field as $delta => $field) { $query_strings = calendar_build_field_query($query, $field); if (!empty($query_strings)) $queries = array_merge($queries, $query_strings); } // 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; } /** * Build a filtering query for an individual date field * * @param object $query - the query object * @param array $field - the view field array */ function calendar_build_field_query(&$query, $field) { $queries = array(); $fields = calendar_fields(); $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 ."')"; } } return $queries; } /** * Implementation of hook_views_query() * Insert filters into the query based on the current calendar view and the selected fields * Used when the actual view arguments don't provide enough info to construct the query. * i.e. on a view with no arguments or one with partial arguments like year or year/month. * * @param object $query * @param object $view */ function calendar_views_query_alter(&$query, &$view) { $view->real_args = $view->args; $view->real_url = calendar_real_url($view, $view->args); if (!calendar_has_calendar_args($view) || (empty($view->args) && !calendar_is_calendar_arg($view) && $view->argument[0]['argdefault'] != 2)) { return; } calendar_load_calendar_api(); calendar_load_date_api(); // make sure block views default to the current month // and make sure day view is not selected if ($view->build_type == 'block') { $query->calendar_type = 'month'; $view->args = explode('/', str_replace($view->url .'/', '', $_GET['mini'])); foreach ($view->argument as $delta => $argument) { // Special handling for OG gid argument. // Find a default value for the gid when used in a block. if ($argument['type'] == 'gid') { $groupnodes = calendar_og_groups($view); $view->args[$delta] = $groupnodes[0]; $query->ensure_table('og_ancestry'); $query->add_where("og_ancestry.group_nid IN (%d)", implode(',', $groupnodes)); } if ($argument['type'] == 'calendar_year') { if (!$view->args[$delta]) $view->args[$delta] = calendar_user_date('year'); $query->year = $view->args[$delta]; calendar_filter_year($query, $query->year); } elseif (($argument['type'] == 'calendar_month' || $argument['type'] == 'calendar_week')) { if (!$view->args[$delta]) $view->args[$delta] = calendar_user_date('month'); $query->month = $view->args[$delta]; calendar_filter_month($query, $query->month); } elseif ($argument['type'] == 'calendar_day') { $query->day = CALENDAR_EMPTY_ARG; $view->args[$delta] = CALENDAR_EMPTY_ARG; } } } // 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') { $GLOBALS['calendar_is_calendar'] = TRUE; if (empty($view->args) || !calendar_is_calendar_arg($view)) { // If no arguments are provided, default to current month view. $query->calendar_type = 'month'; foreach ($view->argument as $delta => $argument) { if ($argument['type'] == 'calendar_year' && !$view->args[$delta]) { $view->args[$delta] = calendar_user_date('year'); calendar_filter_year($query, calendar_user_date('year')); } elseif ($argument['type'] == 'calendar_month' && !$view->args[$delta]) { $view->args[$delta] = calendar_user_date('month'); calendar_filter_month($query, calendar_user_date('month')); } elseif ($argument['type'] == 'calendar_day' && !$view->args[$delta]) { $view->args[$delta] = CALENDAR_EMPTY_ARG; } else { $view->args[$delta] = $view->real_args[$delta]; } } } foreach ($view->argument as $delta => $argument) { if (in_array($argument['type'], calendar_args())) { // 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; // Pad any unused values in the view arguments with CALENDAR_EMPTY_ARG to indicate all values. if (empty($view->args[$delta])) { $view->args[$delta] = CALENDAR_EMPTY_ARG; } } // Calendar_week and Calendar_month can swap positions as the second arg in the url. // Do some work here to make sure we know which is which and swap view data to match it. // the difference between a calendar_month arg and a calendar_week arg is the preceeding 'W' if ($argument['type'] == 'calendar_week' || $argument['type'] == 'calendar_month') { if (strstr($view->args[$delta], 'W')) { calendar_filter_week($query, $view->args[$delta]); $view->argument[$delta]['type'] = 'calendar_week'; $view->argument[$delta]['id'] = 'calendar_week'; $view->argument[$delta + 1]['type'] = 'calendar_day'; $view->argument[$delta + 1]['id'] = 'calendar_day'; // Make sure that there is no day set for the week view. $view->args[$delta + 1] = CALENDAR_EMPTY_ARG; } // 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') { calendar_filter_month($query, $view->args[$delta]); $view->argument[$delta]['type'] = 'calendar_month'; $view->argument[$delta]['id'] = 'calendar_month'; $view->argument[$delta + 1]['type'] = 'calendar_day'; $view->argument[$delta + 1]['id'] = 'calendar_day'; } } } } // Make sure the calendar query gets inserted. May not have finished yet on views like year or year/month. if (!$query->calendar_finished) { calendar_build_filter($query, $view); } $view->calendar_type = $query->calendar_type; $view->year = $query->year; $view->month = $query->month; $view->day = $query->day; $view->week = $query->week; } /** * Implementation of hook_views_pre_view() */ function calendar_views_pre_view(&$view, &$items) { // If no part of this view has calendar elements, exit. if ((!calendar_is_calendar($view) || !calendar_has_calendar_args($view))) { return; } // Construct a formatted title for the view from the last calendar argument encountered. switch ($view->calendar_type) { case 'year': $view->subtitle = theme('calendar_nav_title', 'YEAR', $view); break; case 'month': $view->subtitle = theme('calendar_nav_title', 'MONTH', $view); break; case 'day': $view->subtitle = theme('calendar_nav_title', 'DAY', $view); break; case 'week': $view->subtitle = theme('calendar_nav_title', 'WEEK', $view); break; } // If this is a view with calendar arguments but not a calendar view, // add navigation to the top of the view and return. if (!calendar_is_calendar($view) && calendar_has_calendar_args($view)) { return theme('calendar_show_nav', $view, $view->build_type == 'block', calendar_is_calendar($view)); } // If this is a calendar plugin theme view, make sure empty results // will produce blank calendar page if (array_key_exists($view->page_type, calendar_view_types())) { if (!$items && $view->build_type == 'page' && $view->year) { $view->page_empty = check_markup($view->page_header, $view->page_header_format) . check_markup($view->page_empty, $view->page_empty_format) . theme('calendar_display', $view, array(), 'page') . check_markup($view->page_footer, $view->page_footer_format); $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 = check_markup($view->block_header, $view->block_header_format) . check_markup($view->block_empty, $view->block_empty_format) . theme('calendar_display', $view, array(), 'block') . check_markup($view->block_footer, $view->block_footer_format); $view->block_empty_format = 3; } } } /** * Implementation of hook_views_post_view(). * * Views automatically sets the page title to the value of the last argument. * The calendar module has already created a proper title within the * calendar, so override Views to set the page title to match the View title. */ function calendar_views_post_view(&$view, $items, $output) { // If no part of this view has calendar elements, exit. if ($view->build_type != 'page' || !calendar_is_calendar($view) || !calendar_has_calendar_args($view)) { return; } $title = theme('calendar_page_title', $view, $items, $output); drupal_set_title($title); } /** * Get the start and end datestamp for a calendar week. */ function calendar_week_range($year, $week) { calendar_load_date_api(); // Get timestamp for January 1 of the requested year. $min_date = date_gmmktime(array('mon' => 1, 'mday' => 1, 'year' => $year)); // Adjust back or forward to the first day of the calendar week for the specified first day of week. $dow = date_format_date('w', $min_date); $first_day = variable_get('date_first_day', 0); $diff = -((7 - $first_day + $dow) % 7); $min_date += $diff * 86400; // Add the requested number of weeks to that date. $min_date += intval(($week - 1) * 604800); // Find the end date, which is one week later, less one second. $max_date = $min_date + 604800 - 1; return array($min_date, $max_date); } /** * Find the calendar week number and year for a date. * * This is complicated by the fact that the calendar week does not match the ISO week, * since the ISO week starts on Monday for the first week that has 4 or more days in the new year * while the calendar week starts on the site's preferred first day of the week regardless * of the number of days that are in the first week or the year. * * @param unknown_type $timestamp * @return array of calendar week number and year */ function calendar_week_year($timestamp) { $first_day = variable_get('date_first_day', 0); $iso_week = intval(date_format_date('W', $timestamp)); $year = date_format_date('Y', $timestamp); // Where does this timestamp fall within the range for the iso week number. $range = calendar_week_range($year, $iso_week); // If the timestamp is in the range, the ISO week number is correct. if ($timestamp >= $range[0] && $timestamp <= $range[1]) { return array($iso_week, $year); } // If the timestamp is earlier, the calendar week is earlier than the ISO week. elseif ($timestamp < $range[1]) { if ($iso_week >= 2) { return array(intval($iso_week - 1), $year); } else { return array(1, $year); } } // If the timestamp is later, the calendar week is later than the ISO week. elseif ($timestamp > $range[0]) { if ($iso_week < 52) { return array(intval($iso_week + 1), $year); } else { return array(1, intval($year + 1)); } } } /** * Handle a lot of messy week calculations all in one place to make maintenance easier */ function calendar_week($op, $view, $week = 0) { calendar_load_date_api(); if ($week == 0) { $day = !empty($view->day) && $view->day != CALENDAR_EMPTY_ARG ? $view->day : calendar_user_date('day'); $month = !empty($view->month) && $view->month != CALENDAR_EMPTY_ARG ? $view->month : calendar_user_date('month'); $isodate = $view->year .'-'. sprintf('%02d', $month) .'-'. sprintf('%02d', $day) .'T00:00:00'; $week_year = calendar_week_year(date_iso2unix($isodate)); if ($op == 'week') return $week_year[0]; } $range = calendar_week_range($view->year, $week); $week_start = $range[0]; $week_end = $range[1]; switch ($op) { case ('start_year'): return date_format_date('Y', $week_start); case ('end_year'): return date_format_date('Y', $week_end); case ('start_month'): return date_format_date('n', $week_start); case ('end_month'): return date_format_date('n', $week_end); case ('start_day'): return date_format_date('d', $week_start); case ('end_day'): return date_format_date('d', $week_end); default: return $week_start; } } /** * A function to test the validity of various date parts */ function calendar_part_is_valid($value, $type) { if ( !preg_match('/^[0-9]*$/', $value) ) { return false; } calendar_load_date_api(); $value = intval($value); if ($value <= 0) return false; switch ($type) { case ('year'): if ($value < DATE_MIN_YEAR) 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; } /** * 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'] ? '
'. theme('calendar_stripe_legend', $GLOBALS['calendar_stripe_labels']) .'
' : ''; return $block; case 1: $block['subject'] = t('Switch Calendar'); $block['content'] = $GLOBALS['calendar_is_calendar'] ? drupal_get_form('calendar_switch_view') : ''; return $block; } } } /** * A block with a drop-down list that allows the user to switch between views of the current period */ function calendar_switch_view() { $options[''] = t('Calendar'); $options['list'] = t('List'); $options['teasers'] = t('Teasers'); $options['nodes'] = t('Nodes'); $options['table'] = t('Table'); $form = array( '#method' => 'GET', 'view' => array( '#type' => 'select', '#default_value' => $_GET['view'], '#options' => $options, ), 'q' => array( '#type' => 'hidden', '#value' => $_GET['q'], ), 'submit' => array('#type' => 'submit', '#value' => t('Switch')), ); return $form; } /** * Implementation of hook_calendar_node() from the calendar_get_calendar() api * calendar api is expecting a function for each calendar type but * all of them need the same processing, so run them through a single function * instead of duplicating the processing for each one */ function calendar_calendar_node_month($node) { return calendar_calendar_node($node, 'calendar_node_month'); } function calendar_calendar_node_day($node) { return calendar_calendar_node($node, 'calendar_node_day'); } function calendar_calendar_node_week($node) { return calendar_calendar_node($node, 'calendar_node_week'); } function calendar_calendar_node($node, $type) { calendar_load_calendar_api(); if ($node->nid && $node->nid !== 0 && $node->calendar_start) { // this is a real calendar, go ahead and display it if (!$node->remote) { // switch our psuedo nids back to the right values $tmp = explode(':', $node->nid); $node->nid = $tmp[0]; $node->instance = $tmp[1]; } if (isset($node->calendar_node_theme)) { return theme($node->calendar_node_theme, $node, $type); } else { return theme('calendar_calendar_node', $node, $type); } } else { // surpress display of psuedo calendars added to force display of a blank calendar // have to return some value for blank day so not overridden by normal calendar node theme // a blank space seems to be sufficient to do that return ' '; } } /** * Valid calendar arguments. */ function calendar_args() { return array('calendar_year', 'calendar_week', 'calendar_month', 'calendar_day'); } /** * Figure out what the URL of the calendar view we're currently looking at is. */ function calendar_real_url($view, $args) { // Add non-calendar arguments to the base url. $parts = explode('/', $view->url); $bump = 0; foreach ($parts as $delta => $part) { // If one of the args is buried in the url, add it here and adjust // the delta values we'll compare the calendar arg positions to. if (in_array($part, array('$arg', '$node', '$user', '$group'))) { $parts[$delta] = array_shift($args); $bump++; } } foreach ($args as $delta => $arg) { if (!in_array($delta + $bump, calendar_arg_positions($view)) && !empty($arg)) { array_push($parts, $arg); } } return implode('/', $parts); } /** * Pick up filter and sort info from url. */ function calendar_url_append($view) { if ($view->build_type == 'page') { foreach ($_GET as $key => $val) { if ($key != 'q' && $key != 'view') { if (!is_array($val)) { $url .= '&'. $key .'='. $val; } else { foreach ($val as $v) { $url .= '&'. $key .'[]='. $v; } } } } } return $url; } /** * Function to test whether this is a view that uses the calendar plugin theme. */ function calendar_is_calendar($view) { $calendar_info = calendar_info(); return $calendar_info[$view->name][$view->build_type]; } /** * Function to test whether any calendar args are used in this view. */ function calendar_has_calendar_args($view, $reset = FALSE) { $calendar_info = calendar_info(); if (count($calendar_info[$view->name]['args']) > 0) { return TRUE; } else { return FALSE; } } /** * The positions in the view that hold calendar arguments. */ function calendar_arg_positions($view) { $calendar_info = calendar_info(); if (array_key_exists($view->name, $calendar_info)) { return array_keys($calendar_info[$view->name]['args']); } else { return array(); } } /** * Is the current argument a calendar argument. * Used to sort out whether or not to display the calendar at each point. */ function calendar_is_calendar_arg($view) { if (empty($view->real_args)) { $delta = 0; } else { $delta = max(array_keys($view->real_args)); } if (in_array($delta, calendar_arg_positions($view))) { return TRUE; } return FALSE; } /** * Identify the final calendar argument in this view. * Needed because we can't construct a query until we know all the calendar elements. * Used to tell when to add the filter to the query object. * * @param object $view * @return string $argtype */ function calendar_is_last_arg($view, $reset = FALSE) { foreach ($view->argument as $argument) { if (in_array($argument['id'], calendar_args())) { $calendar_arg = $argument['id']; } $max = $argument['id']; } return $max < $calendar_arg; } /** * Helper function to find the display formats * for each part of this view. */ function calendar_get_formats($view) { return variable_get('calendar_display_format_'. $view->name, array( 'year' => 'calendar', 'month' => 'calendar', 'week' => 'calendar', 'day' => 'calendar', 'block' => 'calendar')); } /** * create a stripe id from a combination of the field and content types * and store value for legend * formula tries to create a unique id for each possible combination * * @param $node - the node object * @param $query_name - the views queryname for this date field * @param $delta - the delta for this field, used to distinguish fields that appear more than once in the calendar * @param $label - the label to give this stripe. */ function calendar_node_stripe($view, $node, $query_name, $delta, $stripe = NULL, $label = '') { $type_names = node_get_types('names'); if (!$label) { $label = $view->field[$delta]['label'] .' ('. $type_names[$node->type] .')'; } if (!$stripe) { $i = 1; foreach ($type_names as $k => $v) { if ($k == $node->type) { break; } $i++; } $stripe = intval((20 * $delta) + $i); } $GLOBALS['calendar_stripe_labels'][$stripe] = $label; return $stripe; } /** * Moved the following infrequently-used functions to separate file * so the code is not parsed on every page. */ /** * Implementation of hook_views_style_plugins() */ function calendar_views_style_plugins() { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_views_style_plugins(); } /** * Implementation of hook_views_default_views() */ function calendar_views_default_views() { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_views_default_views(); } /** * Implementation of hook_views_arguments() */ function calendar_views_arguments() { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_views_arguments(); } /** * Function to return all possible calendar views page display types. */ function calendar_view_types($reset = FALSE) { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_view_types($reset); } /** * Function to get information about all views that have calendar components. */ function calendar_info($reset = FALSE) { static $calendar_views; if (empty($calendar_views) || $reset) { $cid = 'calendar_views'; if (!$reset && $cached = cache_get($cid, 'cache_views')) { $calendar_views = unserialize($cached->data); } else { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); $calendar_views = _calendar_info(); } } return $calendar_views; } /** * Identify all potential date/timestamp fields */ function calendar_fields($reset = FALSE) { static $fields; if (empty($fields) || $reset) { $cid = 'calendar_fields'; if (!$reset && $cached = cache_get($cid, 'cache_views')) { $fields = unserialize($cached->data); } else { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); $fields = _calendar_fields(); } } return $fields; } /** * Validate a view during Views administration. */ function calendar_views_validate($type, $view, $form) { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_views_validate($type, $view, $form); } /** * Setup Calendar parameters in the Setup Tab. */ function calendar_setup_form($view_name) { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_setup_form($view_name); } /** * Save Setup values. */ function calendar_setup_form_submit($form_id, $form_values) { include_once('./'. drupal_get_path('module', 'calendar') .'/calendar_admin.inc'); return _calendar_setup_form_submit($form_id, $form_values); } /** * Empty or reset cached values. * * @param $remove * whether or not to completely remove the caches. */ function calendar_clear_all($remove = FALSE) { if ($remove) { cache_clear_all('calendar_views', 'cache_views'); cache_clear_all('calendar_fields', 'cache_views'); } else { calendar_fields(TRUE); calendar_info(TRUE); } } /** * Helper function to figure out a group gid to use in blocks. * * @return an array of group nodes that are relevant. * @todo this may need more work. */ function calendar_og_groups($view) { if (!$groupnode = og_get_group_context()) { global $user; $groupnodes = array_keys($user->og_groups); } else { $groupnodes = array($groupnode->nid); } return $groupnodes; }