Various fixes, including making sure that exposed filter settings pass through to...
[project/views.git] / views.module
1 <?php
2 // $Id$
3 /**
4 * @file
5 * Primarily Drupal hooks and global API functions to manipulate views.
6 *
7 * This is the main module file for Views. The main entry points into
8 * this module are views_page() and views_block(), where it handles
9 * incoming page and block requests.
10 */
11
12 /**
13 * Implementation of hook_theme(). Register views theming functions.
14 */
15 function views_theme() {
16 $path = drupal_get_path('module', 'views');
17 require_once "./$path/theme/theme.inc";
18
19 // Some quasi clever array merging here.
20 $base = array(
21 'file' => 'theme.inc',
22 'path' => "$path/theme",
23 );
24
25 // Our extra version of pager from pager.inc
26 $hooks['views_mini_pager'] = $base + array(
27 'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
28 );
29
30 $arguments = array(
31 'display' => array('view' => NULL),
32 'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL),
33 'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
34 );
35
36 // Default view themes
37 $hooks['views_view_field'] = $base + array(
38 'pattern' => 'views_view_field__',
39 'arguments' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
40 );
41
42 $plugins = views_fetch_plugin_data();
43
44 // Register theme functions for all style plugins
45 foreach ($plugins as $type => $info) {
46 foreach ($info as $plugin => $def) {
47 if (isset($def['theme'])) {
48 $hooks[$def['theme']] = array(
49 'pattern' => $def['theme'] . '__',
50 'file' => $def['file'],
51 'path' => $def['path'],
52 'arguments' => $arguments[$type],
53 );
54 if (!function_exists('theme_' . $def['theme'])) {
55 $hooks[$def['theme']]['template'] = views_css_safe($def['theme']);
56 }
57 }
58 if (isset($def['additional themes'])) {
59 foreach ($def['additional themes'] as $theme => $theme_type) {
60 if (empty($theme_type)) {
61 $theme = $theme_type;
62 $theme_type = $type;
63 }
64
65 $hooks[$theme] = array(
66 'pattern' => $theme . '__',
67 'file' => $def['file'],
68 'path' => $def['path'],
69 'arguments' => $arguments[$theme_type],
70 );
71 if (!function_exists('theme_' . $theme)) {
72 $hooks[$theme]['template'] = views_css_safe($theme);
73 }
74 }
75 }
76 }
77 }
78
79 $hooks['views_exposed_form'] = $base + array(
80 'template' => 'views-exposed-form',
81 'pattern' => 'views_exposed_form__',
82 'arguments' => array('form' => NULL),
83 );
84
85 $hooks['views_more'] = $base + array(
86 'template' => 'views-more',
87 'pattern' => 'views_more__',
88 'arguments' => array('more_url' => NULL),
89 );
90
91 return $hooks;
92 }
93
94 /**
95 * Implementation of hook_help().
96 */
97 function views_help($path, $arg) {
98 switch ($path) {
99 case 'admin/help#views':
100 $output = '<p>'. t('The views module allows administrators and site designers to create, manage, and display lists of content. Each list managed by the views module is known as a "view", and the output of a view is known as a "display". Displays are provided in either block or page form, and a single view may have multiple displays. Optional navigation aids, including a system path and menu item, can be set for each page-based display of a view. By default, views may be created that list content (a <em>Node</em> view type), content revisions (a <em>Node revisions</em> view type) or users (a <em>User</em> view type). A view may be restricted to members of specific user roles, and may be added, edited or deleted at the <a href="@views-administration">views administration page</a>.', array('@views-api' => 'http://drupal.org/handbook/modules/views/api', '@views-administration' => url('admin/build/views'))) .'</p>';
101 $output .= '<p>'. t('The "building block" design of the views system provides power and flexibility, allowing parameters to be specified only when needed. While an advanced view may use all of available parameters to create complex and highly interactive applications, a simple content listing may specify only a few options. All views rely on a conceptual framework that includes:') .'</p>';
102 $output .= '<ul>';
103 $output .= '<li>'. t('<em>fields</em>, or the individual pieces of data being displayed. Adding the fields <em>Node: Title</em>, <em>Node: Type</em>, and <em>Node: Post date</em> to a node view, for example, includes the title, content type and creation date in the displayed results.') .'</li>';
104 $output .= '<li>'. t('<em>relationships</em>, or information about how data elements relate to one another. If relationship data is available, like that provided by a CCK <em>nodereference</em> field, items from a related node may be included in the view.') .'</li>';
105 $output .= '<li>'. t('<em>arguments</em>, or additional parameters that dynamically refine the view results, passed as part of the path. Adding an argument of <em>Node: Type</em> to a node view with a path of "content", for example, dynamically filters the displayed items by content type. In this example (shown with Clean URLs enabled), accessing the view through the path "http://www.example.com/content/page" displays all posts of the type "page", the path "http://www.example.com/content/story" displays all posts of the type "story", and "http://www.example.com/content" displays all posts regardless of type.') .'</li>';
106 $output .= '<li>'. t('<em>sort criteria</em>, which determines the order of items displayed in the view results. Adding the sort criteria <em>Node: Post date</em> (in descending order) to a node <em>view</em>, for example, sorts the displayed posts in descending order by creation date.') .'</li>';
107 $output .= '<li>'. t('<em>filters</em>, which limit items displayed in the results. Adding the filter <em>Node: Published</em> (and setting it equal to "Published") to a node view, for example, prevents unpublished items from being displayed.') .'</li>';
108 $output .= '</ul>';
109 $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@handbook-views">Views</a> or the <a href="@project-views">Views project page</a>.', array('@handbook-views' => 'http://drupal.org/handbook/modules/views', '@project-views' => 'http://drupal.org/project/views')) .'</p>';
110 return $output;
111 }
112 }
113
114 /**
115 * Implementation of hook_menu().
116 */
117 function views_menu() {
118 $items = array();
119 $items['views/ajax'] = array(
120 'title' => 'Views',
121 'page callback' => 'views_ajax',
122 'access callback' => 'user_access',
123 'access arguments' => array('access content'),
124 'description' => 'Ajax callback for view loading.',
125 'type' => MENU_CALLBACK,
126 );
127 return $items;
128 }
129
130 /**
131 * Implementation of hook_menu_alter().
132 */
133 function views_menu_alter(&$callbacks) {
134 $our_paths = array();
135 foreach (views_get_page_views() as $data) {
136 list($view, $display_id) = $data;
137 $result = $view->execute_hook_menu($display_id);
138 if (is_array($result)) {
139 // The menu system doesn't support having two otherwise
140 // identical paths with different placeholders. So we
141 // want to remove the existing items from the menu whose
142 // paths would conflict with ours.
143
144 // First, we must find any existing menu items that may
145 // conflict. We use a regular expression because we don't
146 // know what placeholders they might use. Note that we
147 // first construct the regex itself by replacing %views_arg
148 // in the display path, then we use this constructed regex
149 // (which will be something like '#^(foo/%[^/]*/bar)$#') to
150 // search through the existing paths.
151 $regex = '#^('. preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) .')$#';
152 $matches = preg_grep($regex, array_keys($callbacks));
153
154 // Remove any conflicting items that were found.
155 foreach ($matches as $path) {
156 // Don't remove the paths we just added!
157 if (!isset($our_paths[$path])) {
158 unset($callbacks[$path]);
159 }
160 }
161 foreach ($result as $path => $item) {
162 if (!isset($callbacks[$path])) {
163 // Add a new item, possibly replacing (and thus effectively
164 // overriding) one that we removed above.
165 $callbacks[$path] = $item;
166 }
167 else {
168 // This item already exists, so it must be one that we added.
169 // We change the various callback arguments to pass an array
170 // of possible display IDs instead of a single ID.
171 $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
172 $callbacks[$path]['page arguments'][1][] = $display_id;
173 $callbacks[$path]['access arguments'][0][1] = (array)$callbacks[$path]['access arguments'][0][1];
174 $callbacks[$path]['access arguments'][0][1][] = $display_id;
175 $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
176 $callbacks[$path]['load arguments'][1][] = $display_id;
177 }
178 $our_paths[$path] = TRUE;
179 }
180 }
181 }
182 }
183
184 /**
185 * Helper function for menu loading. This will automatically be
186 * called in order to 'load' a views argument; primarily it
187 * will be used to perform validation.
188 *
189 * @param $value
190 * The actual value passed.
191 * @param $name
192 * The name of the view. This needs to be specified in the 'load function'
193 * of the menu entry.
194 * @param $index
195 * The menu argument index. This counts from 1.
196 */
197 function views_arg_load($value, $name, $display_id, $index) {
198 if ($view = views_get_view($name)) {
199 $view->set_display($display_id);
200 $view->init_handlers();
201
202 $ids = array_keys($view->argument);
203
204 $indexes = array();
205 $path = explode('/', $view->get_url());
206 foreach ($path as $id => $piece) {
207 if ($piece == '%' && !empty($ids)) {
208 $indexes[$id] = array_shift($ids);
209 }
210 }
211
212 if (isset($indexes[$index])) {
213 if (isset($view->argument[$indexes[$index]])) {
214 return $view->argument[$indexes[$index]]['handler']->validate_argument($value) ? $value : NULL;
215 }
216 }
217 }
218 }
219
220 /**
221 * Page callback entry point; requires a view and a display id, then
222 * passes control to the display handler.
223 */
224 function views_page() {
225 $args = func_get_args();
226 $name = array_shift($args);
227 $display_id = array_shift($args);
228
229 // Load the view
230 if ($view = views_get_view($name)) {
231 return $view->execute_display($display_id, $args);
232 }
233
234 // Fallback; if we get here no view was found or handler was not valid.
235 return drupal_not_found();
236 }
237
238 /**
239 * Implementation of hook_block
240 */
241 function views_block($op = 'list', $delta = 0, $edit = array()) {
242 switch ($op) {
243 case 'list':
244 $items = array();
245 foreach (views_get_block_views() as $data) {
246 list($view, $display_id) = $data;
247 $result = $view->execute_hook_block($display_id);
248 if (is_array($result)) {
249 $items = array_merge($items, $result);
250 }
251 }
252 return $items;
253 case 'view':
254 list($name, $display_id) = explode('-', $delta);
255 // Load the view
256 if ($view = views_get_view($name)) {
257 if ($view->access($display_id)) {
258 return $view->execute_display($display_id);
259 }
260 }
261 break;
262 }
263 }
264
265 /**
266 * Implementation of hook_flush_caches().
267 */
268 function views_flush_caches() {
269 return array('cache_views');
270 }
271
272 /**
273 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
274 */
275 function views_invalidate_cache() {
276 cache_clear_all('*', 'cache_views', true);
277 }
278
279 /**
280 * Determine if the given user has access to the view + display.
281 *
282 * @param $view
283 * May be a view object, or an array with the view name and the display ID,
284 * or a string to use as the view name.
285 * @param $account
286 * An optional account to use; if left off, the current user will be used.
287 */
288 function views_access($view, $account = NULL) {
289 if (is_array($view)) {
290 list($name, $display_id) = $view;
291 $view = views_get_view($name);
292 if (!$view) {
293 return FALSE;
294 }
295 }
296 elseif (is_string($view)) {
297 $view = views_get_view($view);
298 if (!$view) {
299 return FALSE;
300 }
301 $display_id = 'default';
302 }
303 else {
304 // Clone the view to prevent problems.
305 $view = $view->clone_view();
306 $display_id = isset($view->current_display) ? $view->current_display : 'default';
307 }
308
309 return $view->access($display_id, $account);
310 }
311
312 /**
313 * Menu callback to load a view via AJAX.
314 */
315 function views_ajax() {
316 if (isset($_REQUEST['view_name']) && isset($_REQUEST['view_display_id'])) {
317 $name = $_REQUEST['view_name'];
318 $display_id = $_REQUEST['view_display_id'];
319 $args = isset($_REQUEST['view_args']) ? explode('/', $_REQUEST['view_args']) : array();
320 $path = isset($_REQUEST['view_path']) ? $_REQUEST['view_path'] : NULL;
321 views_include('ajax');
322 $object = new stdClass();
323
324 $object->status = FALSE;
325 $object->display = '';
326
327 // Load the view.
328 if ($view = views_get_view($name)) {
329 if ($view->access($display_id)) {
330
331 // Fix 'q' for paging.
332 if (!empty($path)) {
333 $_GET['q'] = $path;
334 }
335
336 $errors = $view->validate();
337 if ($errors === TRUE) {
338 $object->status = TRUE;
339 $object->title = $view->get_title();
340 $object->display .= $view->preview($display_id, $args);
341 }
342 else {
343 foreach ($errors as $error) {
344 drupal_set_message($error, 'error');
345 }
346 }
347 // Register the standard JavaScript callback.
348 $object->__callbacks = array('Drupal.Views.Ajax.ajaxViewResponse');
349 // Allow other modules to extend the data returned.
350 drupal_alter('ajax_data', $object, 'views', $view);
351 }
352 }
353 $messages = theme('status_messages');
354 $object->messages = $messages ? '<div class="views-messages">'. $messages .'</div>' : '';
355
356 views_ajax_render($object);
357 }
358 }
359
360 /**
361 * Set the current 'page view' that is being displayed so that it is easy
362 * for other modules or the theme to identify.
363 */
364 function &views_set_page_view($view = NULL) {
365 static $cache = NULL;
366 if (isset($view)) {
367 $cache = $view;
368 }
369
370 return $cache;
371 }
372
373 /**
374 * Find out what, if any, page view is currently in use. Please note that
375 * this returns a reference, so be careful! You can unintentionally modify the
376 * $view object.
377 */
378 function &views_get_page_view() {
379 return views_set_page_view();
380 }
381
382 /**
383 * Set the current 'current view' that is being built/rendered so that it is
384 * easy for other modules or items in drupal_eval to identify
385 */
386 function &views_set_current_view($view = NULL) {
387 static $cache = NULL;
388 if (isset($view)) {
389 $cache = $view;
390 }
391
392 return $cache;
393 }
394
395 /**
396 * Find out what, if any, current view is currently in use. Please note that
397 * this returns a reference, so be careful! You can unintentionally modify the
398 * $view object.
399 */
400 function &views_get_current_view() {
401 return views_set_current_view();
402 }
403
404 /**
405 * Include views .inc files as necessary.
406 */
407 function views_include($file) {
408 static $used = array();
409 if (!isset($used[$file])) {
410 require_once './' . drupal_get_path('module', 'views') . "/includes/$file.inc";
411 }
412
413 $used[$file] = TRUE;
414 }
415
416 /**
417 * Load views files on behalf of modules.
418 */
419 function views_module_include($file) {
420 $views_path = drupal_get_path('module', 'views') . '/modules';
421 foreach (module_list() as $module) {
422 $module_path = drupal_get_path('module', $module);
423 if (file_exists("$module_path/$module.$file")) {
424 require_once "./$module_path/$module.$file";
425 }
426 else if (file_exists("$module_path/includes/$module.$file")) {
427 require_once "./$module_path/includes/$module.$file";
428 }
429 else if (file_exists("$views_path/$module.$file")) {
430 require_once "./$views_path/$module.$file";
431 }
432 }
433 }
434
435 /**
436 * Include views .css files.
437 */
438 function views_add_css($file) {
439 drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css");
440 }
441
442 /**
443 * Include views .js files.
444 */
445 function views_add_js($file) {
446 static $base = TRUE;
447 if ($base) {
448 drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
449 }
450 drupal_add_js(drupal_get_path('module', 'views') . "/js/$file.js");
451 }
452
453 /**
454 * Prepare the specified string for use as a CSS identifier.
455 */
456 function views_css_safe($string) {
457 return str_replace('_', '-', $string);
458 }
459
460 // -----------------------------------------------------------------------
461 // Views handler functions
462
463 /**
464 * Load views files on behalf of modules.
465 */
466 function views_include_handlers() {
467 static $finished = FALSE;
468 // Ensure this only gets run once.
469 if ($finished) {
470 return;
471 }
472
473 views_include('handlers');
474 views_include('cache');
475 views_include('plugins');
476 _views_include_handlers();
477 $finished = TRUE;
478 }
479
480 /**
481 * Load default views files on behalf of modules.
482 */
483 function views_include_default_views() {
484 static $finished = FALSE;
485 // Ensure this only gets run once.
486 if ($finished) {
487 return;
488 }
489
490 // Default views hooks may be in the normal handler file,
491 // or in a separate views_default file at the discretion of
492 // the module author.
493 views_include_handlers();
494
495 _views_include_default_views();
496 $finished = TRUE;
497 }
498
499 /**
500 * Fetch a handler from the data cache.
501 */
502 function views_get_handler($table, $field, $key) {
503 $data = views_fetch_data($table);
504 if (isset($data[$field][$key])) {
505 return _views_prepare_handler($data[$field][$key], $data, $field);
506 }
507 // DEBUG -- identify missing handlers
508 vpr("Missing handler: $table $field $key");
509 }
510
511 /**
512 * Fetch Views' data from the cache
513 */
514 function views_fetch_data($table = NULL) {
515 views_include('cache');
516 return _views_fetch_data($table);
517 }
518
519 function _views_weight_sort($a, $b) {
520 if ($a['weight'] != $b['weight']) {
521 return $a['weight'] < $b['weight'] ? -1 : 1;
522 }
523 if ($a['title'] != $b['title']) {
524 return $a['title'] < $b['title'] ? -1 : 1;
525 }
526
527 return 0;
528 }
529
530 /**
531 * Fetch a list of all base tables available
532 *
533 * @return
534 * A keyed array of in the form of 'base_table' => 'Description'.
535 */
536 function views_fetch_base_tables() {
537 static $base_tables = array();
538 if (empty($base_tables)) {
539 $weights = array();
540 $tables = array();
541 $data = views_fetch_data();
542 foreach ($data as $table => $info) {
543 if (!empty($info['table']['base'])) {
544 $tables[$table] = array(
545 'title' => $info['table']['base']['title'],
546 'description' => $info['table']['base']['help'],
547 'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0,
548 );
549 }
550 }
551 uasort($tables, '_views_weight_sort');
552 $base_tables = $tables;
553 }
554
555 return $base_tables;
556 }
557
558 function _views_sort_types($a, $b) {
559 if ($a['group'] != $b['group']) {
560 return $a['group'] < $b['group'] ? -1 : 1;
561 }
562
563 if ($a['title'] != $b['title']) {
564 return $a['title'] < $b['title'] ? -1 : 1;
565 }
566
567 return 0;
568 }
569
570 /**
571 * Fetch a list of all fields available for a given base type.
572 *
573 * @return
574 * A keyed array of in the form of 'base_table' => 'Description'.
575 */
576 function views_fetch_fields($base, $type) {
577 static $fields = array();
578 if (empty($fields)) {
579 $data = views_fetch_data();
580 $start = microtime();
581 // This constructs this ginormous multi dimensional array to
582 // collect the important data about fields. In the end,
583 // the structure looks a bit like this (using nid as an example)
584 // $strings['nid']['filter']['title'] = 'string'.
585 //
586 // This is constructed this way because the above referenced strings
587 // can appear in different places in the actual data structure so that
588 // the data doesn't have to be repeated a lot. This essentially lets
589 // each field have a cheap kind of inheritance.
590
591 foreach ($data as $table => $table_data) {
592 $bases = array();
593 $strings = array();
594 foreach ($table_data as $field => $info) {
595 // Collect table data from this table
596 if ($field == 'table') {
597 // calculate what tables this table can join to.
598 if (!empty($info['join'])) {
599 $bases = array_keys($info['join']);
600 }
601 // And if this table IS a base table it obviously joins to itself.
602 if (!empty($info['base'])) {
603 $bases[] = $table;
604 }
605 continue;
606 }
607 foreach (array('field', 'sort', 'filter', 'argument', 'relationship') as $key) {
608 if (!empty($info[$key])) {
609 foreach (array('title', 'group', 'help', 'base') as $string) {
610 // First, try the lowest possible level
611 if (!empty($info[$key][$string])) {
612 $strings[$field][$key][$string] = $info[$key][$string];
613 }
614 // Then try the field level
615 elseif (!empty($info[$string])) {
616 $strings[$field][$key][$string] = $info[$string];
617 }
618 // Finally, try the table level
619 elseif (!empty($table_data['table'][$string])) {
620 $strings[$field][$key][$string] = $table_data['table'][$string];
621 }
622 else {
623 if ($string != 'base') {
624 $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
625 }
626 }
627 }
628 }
629 }
630 }
631 foreach ($bases as $base_name) {
632 foreach ($strings as $field => $field_strings) {
633 foreach ($field_strings as $type_name => $type_strings) {
634 $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
635 }
636 }
637 }
638 }
639 // vsm('Views UI data build time: ' . (microtime() - $start) * 1000 . ' ms');
640 }
641
642 // If we have an array of base tables available, go through them
643 // all and add them together. Duplicate keys will be lost and that's
644 // Just Fine.
645 if (is_array($base)) {
646 $strings = array();
647 foreach ($base as $base_table) {
648 if (isset($fields[$base_table][$type])) {
649 $strings += $fields[$base_table][$type];
650 }
651 }
652 uasort($strings, '_views_sort_types');
653 return $strings;
654 }
655
656 if (isset($fields[$base][$type])) {
657 uasort($fields[$base][$type], '_views_sort_types');
658 return $fields[$base][$type];
659 }
660 return array();
661 }
662
663 // -----------------------------------------------------------------------
664 // Views plugin functions
665
666 /**
667 * Fetch the plugin data from cache.
668 */
669 function views_fetch_plugin_data($type = NULL, $plugin = NULL) {
670 views_include('cache');
671 return _views_fetch_plugin_data($type, $plugin);
672 }
673
674 /**
675 * Get a handler for a plugin
676 */
677 function views_get_plugin($type, $plugin) {
678 $definition = views_fetch_plugin_data($type, $plugin);
679 if (!empty($definition)) {
680 return _views_create_handler($definition);
681 }
682 }
683
684 /**
685 * Fetch a list of all base tables available
686 *
687 * @param $type
688 * Either 'display', 'style' or 'row'
689 * @param $key
690 * For style plugins, this is an optional type to restrict to. May be 'normal',
691 * 'summary', 'feed' or others based on the neds of the display.
692 *
693 * @return
694 * A keyed array of in the form of 'base_table' => 'Description'.
695 */
696 function views_fetch_plugin_names($type, $key = NULL) {
697 $data = views_fetch_plugin_data();
698
699 $plugins[$type] = array();
700
701 foreach ($data[$type] as $id => $plugin) {
702 // Skip plugins that don't conform to our key.
703 if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
704 continue;
705 }
706 if (empty($plugin['no ui'])) {
707 $plugins[$type][$id] = $plugin['title'];
708 }
709 }
710
711 if (!empty($plugins[$type])) {
712 asort($plugins[$type]);
713 return $plugins[$type];
714 }
715 // fall-through
716 return array();
717 }
718
719 // -----------------------------------------------------------------------
720 // Views database functions
721
722 /**
723 * Get a view from the default views defined by modules.
724 *
725 * Default views are cached per-language. This function will rescan the
726 * default_views hook if necessary.
727 *
728 * @param $view_name
729 * The name of the view to load.
730 * @return
731 * A view object or NULL if it is not available.
732 */
733 function &views_get_default_view($view_name) {
734 $null = NULL;
735 $cache = views_discover_default_views();
736
737 if (isset($cache[$view_name])) {
738 return $cache[$view_name];
739 }
740 return $null;
741 }
742
743 /**
744 * Create an empty view to work with.
745 *
746 * @return
747 * A fully formed, empty $view object. This object must be populated before
748 * it can be successfully saved.
749 */
750 function views_new_view() {
751 views_include('view');
752 $view = new view();
753 $view->vid = 'new';
754 $view->add_display('default');
755
756 return $view;
757 }
758
759 /**
760 * Scan all modules for default views and rebuild the default views cache.
761 *
762 * @return An associative array of all known default views.
763 */
764 function views_discover_default_views() {
765 static $cache = array();
766
767 if (empty($cache)) {
768 views_include('cache');
769 $cache = _views_discover_default_views();
770 }
771 return $cache;
772 }
773
774 /**
775 * Get a list of all views and the display plugins that provide
776 * page support to the Drupal menu system. Since views can appear
777 * in this list multiple times, the return of this function is an
778 * array of arrays.
779 *
780 * @return
781 * @code
782 * array(
783 * array($view, $display_id),
784 * array($view, $display_id),
785 * );
786 * @endcode
787 */
788 function views_get_page_views() {
789 return views_get_applicable_views('uses hook menu');
790 }
791
792 /**
793 * Get a list of all views and the display plugins that provide
794 * themselves to the Drupal block system. Since views can appear
795 * in this list multiple times, the return of this function is an
796 * array of arrays.
797 *
798 * @return
799 * @code
800 * array(
801 * array($view, $display_id),
802 * array($view, $display_id),
803 * );
804 * @endcode
805 */
806 function views_get_block_views() {
807 return views_get_applicable_views('uses hook block');
808 }
809
810 /**
811 * Return a list of all views and display IDs that have a particular
812 * setting in their display's plugin settings.
813 *
814 * @return
815 * @code
816 * array(
817 * array($view, $display_id),
818 * array($view, $display_id),
819 * );
820 * @endcode
821 */
822 function views_get_applicable_views($type) {
823 // @todo: Use a smarter flagging system so that we don't have to
824 // load every view for this.
825 $result = array();
826 $views = views_get_all_views();
827
828 foreach ($views as $view) {
829 // Skip disabled views.
830 if (!empty($view->disabled)) {
831 continue;
832 }
833
834 if (empty($view->display)) {
835 // Skip this view as it is broken.
836 vsm(t("Skipping broken view @view", array('@view' => $view->name)));
837 continue;
838 }
839
840 // Loop on array keys because something seems to muck with $view->display
841 // a bit in PHP4.
842 foreach (array_keys($view->display) as $id) {
843 $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
844 if (!empty($plugin[$type])) {
845 // This view uses hook menu. Clone it so that different handlers
846 // don't trip over each other, and add it to the list.
847 $v = $view->clone_view();
848 if ($v->set_display($id)) {
849 $result[] = array($v, $id);
850 }
851 // In PHP 4.4.7 and presumably earlier, if we do not unset $v
852 // here, we will find that it actually overwrites references
853 // possibly due to shallow copying issues.
854 unset($v);
855 }
856 }
857 }
858 return $result;
859 }
860
861 /**
862 * Return an array of all views as fully loaded $view objects.
863 */
864 function views_get_all_views() {
865 static $views = array();
866
867 if (empty($views)) {
868 // First, get all applicable views.
869 views_include('view');
870 $views = view::load_views();
871
872 // Get all default views.
873 $status = variable_get('views_defaults', array());
874
875 foreach (views_discover_default_views() as $view) {
876 // Determine if default view is enabled or disabled.
877 if (isset($status[$view->name])) {
878 $view->disabled = $status[$view->name];
879 }
880
881 // If overridden, also say so.
882 if (!empty($views[$view->name])) {
883 $views[$view->name]->type = t('Overridden');
884 }
885 else {
886 $view->type = t('Default');
887 $views[$view->name] = $view;
888 }
889 }
890
891 }
892 return $views;
893 }
894
895 /**
896 * Get a view from the database or from default views.
897 *
898 * This function is just a static wrapper around views::load(). This function
899 * isn't called 'views_load()' primarily because it might get a view
900 * from the default views which aren't technically loaded from the database.
901 *
902 * @param $name
903 * The name of the view.
904 * @param $reset
905 * If TRUE, reset this entry in the load cache.
906 * @return $view
907 * A reference to the $view object. Use $reset if you're sure you want
908 * a fresh one.
909 */
910 function views_get_view($name, $reset = FALSE) {
911 views_include('view');
912 $view = view::load($name, $reset);
913 $default_view = views_get_default_view($name);
914
915 if (empty($view) && empty($default_view)) {
916 return;
917 }
918 elseif (empty($view) && !empty($default_view)) {
919 $default_view->type = t('Default');
920 return $default_view->clone_view();
921 }
922 elseif (!empty($view) && !empty($default_view)) {
923 $view->type = t('Overridden');
924 }
925
926 return $view->clone_view();
927 }
928
929 /**
930 * Basic definition for many views objects
931 */
932 class views_object {
933 /**
934 * Views handlers use a special construct function so that we can more
935 * easily construct them with variable arguments.
936 */
937 function construct() { }
938
939 /**
940 * Let the handler know what its full definition is.
941 */
942 function set_definition($definition) {
943 $this->definition = $definition;
944 if (isset($definition['field'])) {
945 $this->real_field = $definition['field'];
946 }
947 }
948 }
949
950 /**
951 * Provide debug output for Views. This relies on devel.module
952 */
953 function views_debug($message) {
954 if (module_exists('devel')) {
955 drupal_set_content('footer', dpr($message, TRUE));
956 }
957 }
958
959 /**
960 * Shortcut to views_debug()
961 */
962 function vpr($message) {
963 views_debug($message);
964 }
965
966 /**
967 * Debug messages
968 */
969 function vsm($message) {
970 if (module_exists('devel')) {
971 dsm($message);
972 }
973 }
974
975 function views_trace() {
976 $message = '';
977 foreach (debug_backtrace() as $item) {
978 if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
979 $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
980 }
981 }
982 return $message;
983 }
984
985 function vsm_trace() {
986 vsm(views_trace());
987 }
988
989 function vpr_trace() {
990 dpr(views_trace());
991 }
992
993 /**
994 * Figure out what timezone we're in; needed for some date manipulations.
995 */
996 function views_get_timezone() {
997 global $user;
998 if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
999 $timezone = $user->timezone;
1000 }
1001 else {
1002 $timezone = variable_get('date_default_timezone', 0);
1003 }
1004
1005 // set up the database timezone
1006 if (in_array($GLOBALS['db_type'], array('mysql', 'mysqli'))) {
1007 static $already_set = false;
1008 if (!$already_set) {
1009 if ($GLOBALS['db_type'] == 'mysqli' || version_compare(mysql_get_server_info(), '4.1.3', '>=')) {
1010 db_query("SET @@session.time_zone = '+00:00'");
1011 }
1012 $already_set = true;
1013 }
1014 }
1015
1016 return $timezone;
1017 }
1018
1019 /**
1020 * Helper function to create cross-database SQL dates.
1021 *
1022 * @param $field
1023 * The real table and field name, like 'tablename.fieldname'.
1024 * @param $field_type
1025 * The type of date field, 'int' or 'datetime'.
1026 * @param $set_offset
1027 * The name of a field that holds the timezone offset or a fixed timezone
1028 * offset value. If not provided, the normal Drupal timezone handling
1029 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1030 * @return
1031 * An appropriate SQL string for the db type and field type.
1032 */
1033 function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
1034 $db_type = $GLOBALS['db_type'];
1035 $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
1036 switch ($db_type) {
1037 case 'mysql':
1038 case 'mysqli':
1039 switch ($field_type) {
1040 case 'int':
1041 $field = "FROM_UNIXTIME($field)";
1042 break;
1043 case 'datetime':
1044 break;
1045 }
1046 if (!empty($offset)) {
1047 $field = "($field + INTERVAL $offset SECOND)";
1048 }
1049 return $field;
1050 case 'pgsql':
1051 switch ($field_type) {
1052 case 'int':
1053 $field = "$field::ABSTIME";
1054 break;
1055 case 'datetime':
1056 break;
1057 }
1058 if (!empty($offset)) {
1059 $field = "($field + 'INTERVAL $offset SECONDS')";
1060 }
1061 return $field;
1062 }
1063 }
1064
1065 /**
1066 * Helper function to create cross-database SQL date formatting.
1067 *
1068 * @param $format
1069 * A format string for the result, like 'Y-m-d H:i:s'.
1070 * @param $field
1071 * The real table and field name, like 'tablename.fieldname'.
1072 * @param $field_type
1073 * The type of date field, 'int' or 'datetime'.
1074 * @param $set_offset
1075 * The name of a field that holds the timezone offset or a fixed timezone
1076 * offset value. If not provided, the normal Drupal timezone handling
1077 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1078 * @return
1079 * An appropriate SQL string for the db type and field type.
1080 */
1081 function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
1082 $db_type = $GLOBALS['db_type'];
1083 $field = views_date_sql_field($field, $field_type, $set_offset);
1084 switch ($db_type) {
1085 case 'mysql':
1086 case 'mysqli':
1087 $replace = array(
1088 'Y' => '%Y',
1089 'm' => '%m',
1090 'd' => '%%d',
1091 'H' => '%H',
1092 'i' => '%i',
1093 's' => '%s',
1094 );
1095 $format = strtr($format, $replace);
1096 return "DATE_FORMAT($field, '$format')";
1097 case 'pgsql':
1098 $replace = array(
1099 'Y' => 'YY',
1100 'm' => 'MM',
1101 'd' => 'DD',
1102 'H' => 'HH24',
1103 'i' => 'MI',
1104 's' => 'SS',
1105 );
1106 $format = strtr($format, $replace);
1107 return "TO_CHAR($field, '$format')";
1108 }
1109 }
1110
1111 /**
1112 * Helper function to create cross-database SQL date extraction.
1113 *
1114 * @param $extract_type
1115 * The type of value to extract from the date, like 'MONTH'.
1116 * @param $field
1117 * The real table and field name, like 'tablename.fieldname'.
1118 * @param $field_type
1119 * The type of date field, 'int' or 'datetime'.
1120 * @param $set_offset
1121 * The name of a field that holds the timezone offset or a fixed timezone
1122 * offset value. If not provided, the normal Drupal timezone handling
1123 * will be used, i.e. $set_offset = 0 will make no timezone adjustment.
1124 * @return
1125 * An appropriate SQL string for the db type and field type.
1126 */
1127 function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
1128 $db_type = $GLOBALS['db_type'];
1129 $field = views_date_sql_field($field, $field_type, $set_offset);
1130
1131 // Note there is no space after FROM to avoid db_rewrite problems
1132 // see http://drupal.org/node/79904.
1133 switch ($extract_type) {
1134 case('DATE'):
1135 return $field;
1136 case('YEAR'):
1137 return "EXTRACT(YEAR FROM($field))";
1138 case('MONTH'):
1139 return "EXTRACT(MONTH FROM($field))";
1140 case('DAY'):
1141 return "EXTRACT(DAY FROM($field))";
1142 case('HOUR'):
1143 return "EXTRACT(HOUR FROM($field))";
1144 case('MINUTE'):
1145 return "EXTRACT(MINUTE FROM($field))";
1146 case('SECOND'):
1147 return "EXTRACT(SECOND FROM($field))";
1148 case('WEEK'): // ISO week number for date
1149 switch ($db_type) {
1150 case('mysql'):
1151 case('mysqli'):
1152 // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
1153 return "WEEK($field, 3)";
1154 case('pgsql'):
1155 return "EXTRACT(WEEK FROM($field))";
1156 }
1157 case('DOW'):
1158 switch ($db_type) {
1159 case('mysql'):
1160 case('mysqli'):
1161 // mysql returns 1 for Sunday through 7 for Saturday
1162 // php date functions and postgres use 0 for Sunday and 6 for Saturday
1163 return "INTEGER(DAYOFWEEK($field) - 1)";
1164 case('pgsql'):
1165 return "EXTRACT(DOW FROM($field))";
1166 }
1167 case('DOY'):
1168 switch ($db_type) {
1169 case('mysql'):
1170 case('mysqli'):
1171 return "DAYOFYEAR($field)";
1172 case('pgsql'):
1173 return "EXTRACT(DOY FROM($field))";
1174 }
1175 }
1176
1177 }
1178
1179 /**
1180 * Form builder for the exposed widgets form.
1181 *
1182 * Be sure that $view and $display are references.
1183 */
1184 function views_exposed_form(&$form_state) {
1185 $view = &$form_state['view'];
1186 $display = &$form_state['display'];
1187 // Fill our input either from $_GET or from something previously set on the
1188 // view.
1189 if (empty($view->exposed_input)) {
1190 $input = $_GET;
1191 // unset items that are definitely not our input:
1192 unset($input['page']);
1193 unset($input['q']);
1194 // If we have no input at all, check for remembered input via session.
1195 if (empty($input) && !empty($_SESSION['views'][$view->name][$view->current_display])) {
1196 $input = $_SESSION['views'][$view->name][$view->current_display];
1197 }
1198 $form['#post'] = $input;
1199 }
1200 else {
1201 $form['#post'] = $view->exposed_input;
1202 }
1203
1204 // Let form plugins know this is for exposed widgets.
1205 $form_state['exposed'] = TRUE;
1206
1207 $form['#info'] = array();
1208
1209 if (!variable_get('clean_url', FALSE)) {
1210 $form['q'] = array(
1211 '#type' => 'hidden',
1212 '#value' => $view->get_url(),
1213 );
1214 }
1215
1216 // Go through each filter and let it generate its info.
1217 foreach ($view->filter as $id => $filter) {
1218 $filter['handler']->exposed_form($form, $form_state);
1219 if ($info = $filter['handler']->exposed_info()) {
1220 $form['#info']['filter-' . $id] = $info;
1221 }
1222 }
1223
1224 // @todo deal with exposed sorts
1225
1226 $form['submit'] = array(
1227 '#name' => '', // prevent from showing up in $_GET.
1228 '#type' => 'submit',
1229 '#value' => t('Apply'),
1230 );
1231
1232
1233 $form['#action'] = url($view->get_url());
1234 $form['#process'] = array('views_exposed_process');
1235
1236 $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
1237
1238 // If using AJAX, we need the form plugin.
1239 if ($view->use_ajax) {
1240 drupal_add_js('misc/jquery.form.js');
1241 }
1242 views_add_js('dependent');
1243 return $form;
1244 }
1245
1246
1247 /**
1248 * Validate handler for exposed filters
1249 */
1250 function views_exposed_form_validate(&$form, &$form_state) {
1251 foreach (array('field', 'filter') as $type) {
1252 $var = &$form_state['view']->$type;
1253 foreach ($var as $key => $info) {
1254 $var[$key]['handler']->exposed_validate($form, $form_state);
1255 }
1256 }
1257 }
1258
1259 /**
1260 * Submit handler for exposed filters
1261 */
1262 function views_exposed_form_submit(&$form, &$form_state) {
1263 foreach (array('field', 'filter') as $type) {
1264 $var = &$form_state['view']->$type;
1265 foreach ($var as $key => $info) {
1266 $var[$key]['handler']->exposed_submit($form, $form_state);
1267 }
1268 }
1269 $form_state['view']->exposed_data = $form_state['values'];
1270 $form_state['view']->exposed_raw_input = array();
1271
1272 foreach ($form_state['values'] as $key => $value) {
1273 if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token'))) {
1274 $form_state['view']->exposed_raw_input[$key] = $value;
1275 }
1276 }
1277 }
1278
1279 /**
1280 * Build a list of theme function names for use most everywhere.
1281 */
1282 function views_theme_functions($hook, $view, $display = NULL) {
1283 require_once './' . drupal_get_path('module', 'views') . "/theme/theme.inc";
1284 return _views_theme_functions($hook, $view, $display);
1285 }
1286
1287 /**
1288 * Views' replacement for drupal_get_form so that we can do more with
1289 * less.
1290 *
1291 * Items that can be set on the form_state include:
1292 * - input: The source of input. If unset this will be $_POST.
1293 * - no_redirect: Absolutely do not redirect the form even if instructed
1294 * to do so.
1295 * - rerender: If no_redirect is set and the form was successfully submitted,
1296 * rerender the form. Otherwise it will just return.
1297 *
1298 */
1299 function drupal_build_form($form_id, &$form_state) {
1300 views_include('form');
1301 return _drupal_build_form($form_id, $form_state);
1302 }
1303
1304 /**
1305 * Substitute current time; this works with cached queries.
1306 */
1307 function views_views_query_substitutions($view) {
1308 global $language;
1309 return array('***CURRENT_TIME***' => time(), '***CURRENT_LANGUAGE***' => $language->language);
1310 }
1311
1312 /**
1313 * Embed a view using a PHP snippet.
1314 *
1315 * This function is meant to be called from PHP snippets, should one wish to
1316 * embed a view in a node or something. It's meant to provide the simplest
1317 * solution and doesn't really offer a lot of options, but breaking the function
1318 * apart is pretty easy, and this provides a worthwhile guide to doing so.
1319 *
1320 * @param $name
1321 * The name of the view to embed.
1322 * @param $display_id
1323 * The display id to embed. If unsure, use 'default', as it will always be
1324 * valid. But things like 'page' or 'block' should work here.
1325 * @param ...
1326 * Any additional parameters will be passed as arguments.
1327 */
1328 function views_embed_view($name, $display_id = 'default') {
1329 $args = func_get_args();
1330 array_shift($args); // remove $name
1331 if (count($args)) {
1332 array_shift($args); // remove $display_id
1333 }
1334
1335 $view = views_get_view($name);
1336 if (!$view) {
1337 return;
1338 }
1339
1340 return $view->preview($display_id, $args);
1341 }