date_views.module 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. <?php
  2. /**
  3. * Implements hook_menu().
  4. */
  5. function date_views_menu() {
  6. // Used to import files from a local filesystem into Drupal.
  7. $items['admin/config/regional/date-time/date-views'] = array(
  8. 'title' => 'Date views',
  9. 'description' => 'Configure settings for date views.',
  10. 'page callback' => 'drupal_get_form',
  11. 'page arguments' => array('date_views_settings'),
  12. 'access arguments' => array('administer site configuration '),
  13. 'type' => MENU_LOCAL_TASK,
  14. );
  15. return $items;
  16. }
  17. /**
  18. * Form callback for date views settings.
  19. */
  20. function date_views_settings($form, &$form_state) {
  21. $form['date_views_month_format_with_year'] = array(
  22. '#type' => 'textfield',
  23. '#title' => t('Date views month format with year'),
  24. '#size' => 10,
  25. '#default_value' => variable_get('date_views_month_format_with_year', 'F Y'),
  26. '#description' => t('Date views month format with year, default value : F Y'),
  27. );
  28. $form['date_views_month_format_without_year'] = array(
  29. '#type' => 'textfield',
  30. '#title' => t('Date views month format without year'),
  31. '#size' => 10,
  32. '#default_value' => variable_get('date_views_month_format_without_year', 'F'),
  33. '#description' => t('Date views month format without year, default value : F'),
  34. );
  35. $form['date_views_day_format_with_year'] = array(
  36. '#type' => 'textfield',
  37. '#title' => t('Date views day format with year'),
  38. '#size' => 10,
  39. '#default_value' => variable_get('date_views_day_format_with_year', 'l, F j, Y'),
  40. '#description' => t('Date views day format with year, default value : l, F j, Y'),
  41. );
  42. $form['date_views_day_format_without_year'] = array(
  43. '#type' => 'textfield',
  44. '#title' => t('Date views day format without year'),
  45. '#size' => 10,
  46. '#default_value' => variable_get('date_views_day_format_without_year', 'l, F j'),
  47. '#description' => t('Date views day format without year, default value : l, F j'),
  48. );
  49. $form['date_views_week_format_with_year'] = array(
  50. '#type' => 'textfield',
  51. '#title' => t('Date views week format with year'),
  52. '#size' => 10,
  53. '#default_value' => variable_get('date_views_week_format_with_year', 'F j, Y'),
  54. '#description' => t('Date views week format with year, default value : F j, Y'),
  55. );
  56. $form['date_views_week_format_without_year'] = array(
  57. '#type' => 'textfield',
  58. '#title' => t('Date views week format without year'),
  59. '#size' => 10,
  60. '#default_value' => variable_get('date_views_week_format_without_year', 'F j'),
  61. '#description' => t('Date views week format without year, default value : F j'),
  62. );
  63. return system_settings_form($form);
  64. }
  65. /**
  66. * Implements hook_views_api().
  67. *
  68. * This one is used as the base to reduce errors when updating.
  69. */
  70. function date_views_theme() {
  71. $path = drupal_get_path('module', 'date_views');
  72. $base = array(
  73. 'file' => 'theme.inc',
  74. 'path' => "$path/theme",
  75. );
  76. return array(
  77. 'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)),
  78. 'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
  79. 'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
  80. 'date_views_pager' => $base + array(
  81. 'variables' => array('plugin' => NULL, 'input' => NULL),
  82. // Register a pattern so that it can work like all views templates.
  83. 'pattern' => 'date_views_pager__',
  84. 'template' => 'date-views-pager',
  85. ),
  86. );
  87. }
  88. function date_views_views_api() {
  89. return array(
  90. 'api' => 3,
  91. 'path' => drupal_get_path('module', 'date_views') . '/includes',
  92. );
  93. }
  94. /**
  95. * Wrapper function to make sure this function will always work.
  96. */
  97. function date_views_views_fetch_fields($base, $type) {
  98. if (!module_exists('views')) {
  99. return array();
  100. }
  101. module_load_include('inc', 'views', 'includes/admin');
  102. return views_fetch_fields($base, $type);
  103. }
  104. /**
  105. * Identify all potential date/timestamp fields and cache the data.
  106. */
  107. function date_views_fields($base = 'node', $reset = FALSE) {
  108. static $fields = array();
  109. $empty = array('name' => array(), 'alias' => array());
  110. module_load_include('inc', 'date_views', 'includes/date_views_fields');
  111. if (empty($fields[$base]) || $reset) {
  112. $cid = 'date_views_fields_' . $base;
  113. if (!$reset && $cached = cache_get($cid, 'cache_views')) {
  114. $fields[$base] = $cached->data;
  115. }
  116. else {
  117. $fields[$base] = _date_views_fields($base);
  118. }
  119. }
  120. // Make sure that empty values will be arrays in he expected format.
  121. return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
  122. }
  123. /**
  124. * Implements hook_date_views_entities().
  125. * Map extra Views tables to the entity that holds its date fields,
  126. * needed for Views tables other than the primary tables identified in entity_info().
  127. */
  128. function date_views_date_views_extra_tables() {
  129. return array(
  130. 'node_revision' => 'node',
  131. );
  132. }
  133. /**
  134. * Helper function to map entity types to the Views base table they use,
  135. * to make it easier to infer the entity type from a base table.
  136. *
  137. * Views has a new handler called views_handler_field_entity() that loads
  138. * entities, and you can use something like the following to get the
  139. * entity type from a view, but not all our base tables contain the
  140. * entity information we need, (i.e. revisions) so it won't work here
  141. * and we resort to creating information from entity_get_info().
  142. *
  143. * // A method to get the entity type for a base table.
  144. * $table_data = views_fetch_data($base_table);
  145. * if (!isset($table_data['table']['base']['entity type'])) {
  146. * return FALSE;
  147. * }
  148. * $entity_type = $table_data['table']['base']['entity type'];
  149. */
  150. function date_views_base_tables() {
  151. $base_tables = &drupal_static(__FILE__, array());
  152. if (empty($base_tables)) {
  153. // First we get the base tables we can learn about from entity_info.
  154. $entity_info = entity_get_info();
  155. foreach ($entity_info as $entity_type => $info) {
  156. if (!empty($info['base table'])) {
  157. $base_tables[$info['base table']] = $entity_type;
  158. }
  159. if (!empty($info['revision table'])) {
  160. $base_tables[$info['revision table']] = $entity_type;
  161. }
  162. }
  163. // Then we let other modules tell us about other entity tables that hold date fields.
  164. $base_tables += module_invoke_all('date_views_extra_tables');
  165. }
  166. return $base_tables;
  167. }
  168. /**
  169. * Implements hook_date_views_fields().
  170. *
  171. * All modules that create custom fields that use the
  172. * 'views_handler_field_date' handler can provide
  173. * additional information here about the type of
  174. * date they create so the date can be used by
  175. * the Date API views date argument and date filter.
  176. */
  177. function date_views_date_views_fields($field) {
  178. $values = array(
  179. // The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
  180. 'sql_type' => DATE_UNIX,
  181. // Timezone handling options: 'none', 'site', 'date', 'utc' .
  182. 'tz_handling' => 'site',
  183. // Needed only for dates that use 'date' tz_handling.
  184. 'timezone_field' => '',
  185. // Needed only for dates that use 'date' tz_handling.
  186. 'offset_field' => '',
  187. // Array of "table.field" values for related fields that should be
  188. // loaded automatically in the Views SQL.
  189. 'related_fields' => array(),
  190. // Granularity of this date field's db data.
  191. 'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
  192. );
  193. switch ($field) {
  194. case 'users.created':
  195. case 'users.access':
  196. case 'users.login':
  197. case 'node.created':
  198. case 'node.changed':
  199. case 'node_revision.timestamp':
  200. case 'file_managed.timestamp':
  201. case 'comment.timestamp':
  202. return $values;
  203. }
  204. }
  205. /**
  206. * A version of date_real_url that formats links correctly for the new Date pager.
  207. */
  208. function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_url = FALSE, $absolute = TRUE) {
  209. // If someone adds a pager without a matching argument, there is not date information to work with.
  210. if (empty($view->date_info) || !isset($view->date_info->date_arg_pos)) {
  211. return '';
  212. }
  213. $args = $view->args;
  214. $pos = $view->date_info->date_arg_pos;
  215. // The View arguments array is indexed numerically but is not necessarily
  216. // in numerical order. Sort the arguments to ensure the correct order.
  217. ksort($args);
  218. // If there are empty arguments before the date argument,
  219. // pad them with the wildcard so the date argument will be in
  220. // the right position.
  221. if (count($args) < $pos) {
  222. foreach ($view->argument as $name => $argument) {
  223. if ($argument->position == $pos) {
  224. break;
  225. }
  226. $args[] = $argument->options['exception']['value'];
  227. }
  228. }
  229. if (!empty($date_type)) {
  230. switch ($date_type) {
  231. case 'year':
  232. $args[$pos] = date_pad($view->date_info->year, 4);
  233. break;
  234. case 'week':
  235. $args[$pos] = date_pad($view->date_info->year, 4) . '-W' . date_pad($view->date_info->week);
  236. break;
  237. case 'day':
  238. $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month) . '-' . date_pad($view->date_info->day);
  239. break;
  240. default:
  241. $args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month);
  242. break;
  243. }
  244. }
  245. elseif (!empty($date_arg)) {
  246. $args[$pos] = $date_arg;
  247. }
  248. else {
  249. $args = $view->args;
  250. }
  251. // Is this an embedded or a block view?
  252. // Return the pager query value.
  253. if (!$force_view_url &&
  254. (!empty($view->preview) || !empty($view->date_info->block_identifier))) {
  255. $url = $args[$pos];
  256. $key = date_block_identifier($view);
  257. if (!empty($key)) {
  258. return url($_GET['q'], array(
  259. 'query' => date_views_querystring($view, array($key => $url)),
  260. 'absolute' => $absolute));
  261. }
  262. }
  263. // Normal views may need querystrings appended to them
  264. // if they use exposed filters.
  265. return url($view->get_url($args), array(
  266. 'query' => date_views_querystring($view),
  267. 'absolute' => $absolute));
  268. }
  269. function date_block_identifier($view) {
  270. if (!empty($view->block_identifier)) {
  271. return $view->block_identifier;
  272. }
  273. return isset($view->date_info->block_identifier) ? $view->date_info->block_identifier : NULL;
  274. }
  275. /**
  276. * Implements hook_field_views_data_alter().
  277. *
  278. * Create a Views field for each date column we care about
  279. * to supplement the generic 'entity_id' and 'revision_id'
  280. * fields that are automatically created.
  281. *
  282. * Also use friendlier labels to distinguish the start date
  283. * and end date in listings (for fields that use both).
  284. */
  285. function date_views_field_views_data_alter(&$result, $field, $module) {
  286. if ($module == 'date') {
  287. $has_end_date = !empty($field['settings']['todate']);
  288. if ($has_end_date) {
  289. $labels = field_views_field_label($field['field_name']);
  290. $label = array_shift($labels);
  291. }
  292. foreach ($result as $table => $data) {
  293. $additional = array();
  294. $field_name = $field['field_name'];
  295. foreach ($data as $column => $value) {
  296. // The old 'entity_id' and 'revision_id' values got rewritten in Views.
  297. // The old values are still there with a 'moved to' key, so ignore them.
  298. if (array_key_exists('field', $value) && !array_key_exists('moved to', $value['field'])) {
  299. $result[$table][$column]['field']['is date'] = TRUE;
  300. // Not sure yet if we still need a custom field handler in D7 now that custom formatters are available.
  301. // Might still need it to handle grouping of multiple value dates.
  302. //$result[$table][$column]['field']['handler'] = 'date_handler_field_date';
  303. //$result[$table][$column]['field']['add fields to query'] = TRUE;
  304. }
  305. // For filters, arguments, and sorts, determine if this column is for
  306. // the start date ('value') or the end date ('value2').
  307. $this_column = NULL;
  308. foreach (array_keys($field['columns']) as $candidate_column) {
  309. if ($column == $field['field_name'] . '_' . $candidate_column) {
  310. $this_column = $candidate_column;
  311. break;
  312. }
  313. }
  314. // Only alter the date fields, not timezone, rrule, offset, etc.
  315. if ($this_column != 'value' && $this_column != 'value2') {
  316. continue;
  317. }
  318. // We will replace the label with a friendlier name in the case of
  319. // arguments, filters, and sorts (but only if this field uses an end
  320. // date).
  321. $replace_label = FALSE;
  322. if (array_key_exists('argument', $value)) {
  323. $result[$table][$column]['argument']['handler'] = 'date_views_argument_handler_simple';
  324. $result[$table][$column]['argument']['empty field name'] = t('Undated');
  325. $result[$table][$column]['argument']['is date'] = TRUE;
  326. $replace_label = $has_end_date;
  327. }
  328. if (array_key_exists('filter', $value)) {
  329. $result[$table][$column]['filter']['handler'] = 'date_views_filter_handler_simple';
  330. $result[$table][$column]['filter']['empty field name'] = t('Undated');
  331. $result[$table][$column]['filter']['is date'] = TRUE;
  332. $replace_label = $has_end_date;
  333. }
  334. if (array_key_exists('sort', $value)) {
  335. $result[$table][$column]['sort']['is date'] = TRUE;
  336. $replace_label = $has_end_date;
  337. }
  338. if ($replace_label) {
  339. // Determine if this column is for the start date ('value') or the
  340. // end date ('value2').
  341. $this_column = NULL;
  342. foreach (array_keys($field['columns']) as $candidate_column) {
  343. if ($column == $field['field_name'] . '_' . $candidate_column) {
  344. $this_column = $candidate_column;
  345. break;
  346. }
  347. }
  348. // Insert the phrase "start date" or "end date" after the label, so
  349. // users can distinguish them in listings (compared to the default
  350. // behavior of field_views_field_default_views_data(), which only
  351. // uses the 'value2' column name to distinguish them).
  352. switch ($this_column) {
  353. case 'value':
  354. // Insert a deliberate double space before 'start date' in the
  355. // translatable string. This is a hack to get it to appear right
  356. // before 'end date' in the listing (i.e., in a non-alphabetical,
  357. // but more user friendly, order).
  358. $result[$table][$column]['title'] = t('@label - start date (!name)', array('@label' => $label, '!name' => $field['field_name']));
  359. $result[$table][$column]['title short'] = t('@label - start date', array('@label' => $label));
  360. break;
  361. case 'value2':
  362. $result[$table][$column]['title'] = t('@label - end date (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $this_column));
  363. $result[$table][$column]['title short'] = t('@label - end date:!column', array('@label' => $label, '!column' => $this_column));
  364. break;
  365. }
  366. }
  367. }
  368. }
  369. }
  370. }
  371. /**
  372. * Implements hook_form_FORM_ID_alter() for views_ui_edit_form().
  373. */
  374. function date_views_form_views_ui_edit_form_alter(&$form, &$form_state, $form_id) {
  375. // This CSS is needed for the configuration form provided by the Date filter
  376. // (date_views_filter_handler_simple), but we have to add it here so that
  377. // it's already on the edit form the first time a Date filter is being added
  378. // to the View. See http://drupal.org/node/1239228#comment-4885288.
  379. $form['#attached']['css'][] = drupal_get_path('module', 'date_views') . '/css/date_views.css';
  380. }
  381. /**
  382. * The instanceof function makes this work for any handler that was derived
  383. * from 'views_handler_filter_date' or 'views_handler_argument_date',
  384. * which includes core date fields like the node updated field.
  385. *
  386. * The test for $handler->min_date tells us that this is an argument that
  387. * not only is derived from the views date handler but also has been processed
  388. * by the Date Views filter or argument code.
  389. */
  390. function date_views_handler_is_date($handler, $type = 'argument') {
  391. switch ($type) {
  392. case 'filter':
  393. return $handler instanceof views_handler_filter_date && !empty($handler->min_date);
  394. case 'argument':
  395. return $handler instanceof views_handler_argument_date && !empty($handler->min_date);
  396. }
  397. return FALSE;
  398. }
  399. /**
  400. * Validation hook for exposed filters that use the select widget.
  401. * This is to ensure the the user completes all parts of the date
  402. * not just some parts. Only needed for the select widget.
  403. */
  404. function date_views_select_validate(&$form, &$form_state) {
  405. // If there are no values just return.
  406. if (empty($form['value']) && empty($form['min'])) {
  407. return;
  408. }
  409. $granularity = (!empty($form['min']['#date_format'])) ? date_format_order($form['min']['#date_format']) : date_format_order($form['value']['#date_format']);
  410. $filled = array();
  411. $value = drupal_array_get_nested_value($form_state['input'], $form['#parents']);
  412. foreach ($granularity as $part) {
  413. if (!empty($value['value'][$part])) {
  414. $filled[] = $part;
  415. }
  416. }
  417. if (!empty($filled) && count($filled) != count($granularity)) {
  418. $unfilled = array_diff($granularity, $filled);
  419. foreach ($unfilled as $part) {
  420. switch ($part) {
  421. case 'year':
  422. form_error($form['value'][$part], t('Please choose a year.'), $form_state);
  423. break;
  424. case 'month':
  425. form_error($form['value'][$part], t('Please choose a month.'), $form_state);
  426. break;
  427. case 'day':
  428. form_error($form['value'][$part], t('Please choose a day.'), $form_state);
  429. break;
  430. case 'hour':
  431. form_error($form['value'][$part], t('Please choose an hour.'), $form_state);
  432. break;
  433. case 'minute':
  434. form_error($form['value'][$part], t('Please choose a minute.'), $form_state);
  435. break;
  436. case 'second':
  437. form_error($form['value'][$part], t('Please choose a second.'), $form_state);
  438. break;
  439. }
  440. }
  441. }
  442. }
  443. /**
  444. * Implements hook_date_formatter_view_alter().
  445. *
  446. * If we are displaying a date from a view, see if we have information about
  447. * which multiple value to display. If so, set the date_id in the entity.
  448. */
  449. function date_views_date_formatter_pre_view_alter(&$entity, &$variables) {
  450. // Some views have no row index.
  451. if (!empty($entity->view) && isset($entity->view->row_index)) {
  452. $field = $variables['field'];
  453. $date_id = 'date_id_' . $field['field_name'];
  454. $date_delta = 'date_delta_' . $field['field_name'];
  455. $date_item = $entity->view->result[$entity->view->row_index];
  456. if (!empty($date_item->$date_id)) {
  457. $entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0';
  458. }
  459. }
  460. }