Minor fix to caching and validating.
[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 * Advertise the current views api version
14 */
15 function views_api_version() {
16 return 2.0;
17 }
18
19 /**
20 * Implementation of hook_theme(). Register views theming functions.
21 */
22 function views_theme() {
23 $path = drupal_get_path('module', 'views');
24 require_once "./$path/theme/theme.inc";
25
26 // Some quasi clever array merging here.
27 $base = array(
28 'file' => 'theme.inc',
29 'path' => "$path/theme",
30 );
31
32 // Our extra version of pager from pager.inc
33 $hooks['views_mini_pager'] = $base + array(
34 'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
35 'pattern' => 'views_mini_pager__',
36 );
37
38 $arguments = array(
39 'display' => array('view' => NULL),
40 'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
41 'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
42 );
43
44 // Default view themes
45 $hooks['views_view_field'] = $base + array(
46 'pattern' => 'views_view_field__',
47 'arguments' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
48 );
49
50 $plugins = views_fetch_plugin_data();
51
52 // Register theme functions for all style plugins
53 foreach ($plugins as $type => $info) {
54 foreach ($info as $plugin => $def) {
55 if (isset($def['theme'])) {
56 $hooks[$def['theme']] = array(
57 'pattern' => $def['theme'] . '__',
58 'file' => $def['theme file'],
59 'path' => $def['theme path'],
60 'arguments' => $arguments[$type],
61 );
62
63 $include = './' . $def['theme path'] . '/' . $def['theme file'];
64 if (file_exists($include)) {
65 require_once $include;
66 }
67
68 if (!function_exists('theme_' . $def['theme'])) {
69 $hooks[$def['theme']]['template'] = views_css_safe($def['theme']);
70 }
71 }
72 if (isset($def['additional themes'])) {
73 foreach ($def['additional themes'] as $theme => $theme_type) {
74 if (empty($theme_type)) {
75 $theme = $theme_type;
76 $theme_type = $type;
77 }
78
79 $hooks[$theme] = array(
80 'pattern' => $theme . '__',
81 'file' => $def['theme file'],
82 'path' => $def['theme path'],
83 'arguments' => $arguments[$theme_type],
84 );
85
86 if (!function_exists('theme_' . $theme)) {
87 $hooks[$theme]['template'] = views_css_safe($theme);
88 }
89 }
90 }
91 }
92 }
93
94 $hooks['views_exposed_form'] = $base + array(
95 'template' => 'views-exposed-form',
96 'pattern' => 'views_exposed_form__',
97 'arguments' => array('form' => NULL),
98 );
99
100 $hooks['views_more'] = $base + array(
101 'template' => 'views-more',
102 'pattern' => 'views_more__',
103 'arguments' => array('more_url' => NULL, 'link_text' => 'more'),
104 );
105 return $hooks;
106 }
107
108 /**
109 * A theme preprocess function to automatically allow view-based node
110 * templates if called from a view.
111 *
112 * The 'modules/node.views.inc' file is a better place for this, but
113 * we haven't got a chance to load that file before Drupal builds the
114 * node portion of the theme registry.
115 */
116 function views_preprocess_node(&$vars) {
117 // The 'view' attribute of the node is added in template_preprocess_views_view_row_node()
118 if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
119 $vars['view'] = &$vars['node']->view;
120 $vars['template_files'][] = 'node-view-' . $vars['node']->view->name;
121 if(!empty($vars['node']->view->current_display)) {
122 $vars['template_files'][] = 'node-view-' . $vars['node']->view->name . '-' . $vars['node']->view->current_display;
123 }
124 }
125 }
126
127 /**
128 * A theme preprocess function to automatically allow view-based node
129 * templates if called from a view.
130 */
131 function views_preprocess_comment(&$vars) {
132 // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
133 if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
134 $vars['view'] = &$vars['node']->view;
135 $vars['template_files'][] = 'comment-view-' . $vars['node']->view->name;
136 if(!empty($vars['node']->view->current_display)) {
137 $vars['template_files'][] = 'comment-view-' . $vars['node']->view->name . '-' . $vars['node']->view->current_display;
138 }
139 }
140 }
141
142 /*
143 * Implementation of hook_perm()
144 */
145 function views_perm() {
146 return array('access all views', 'administer views');
147 }
148
149 /**
150 * Implementation of hook_menu().
151 */
152 function views_menu() {
153 // Any event which causes a menu_rebuild could potentially mean that the
154 // Views data is updated -- module changes, profile changes, etc.
155 views_invalidate_cache();
156 $items = array();
157 $items['views/ajax'] = array(
158 'title' => 'Views',
159 'page callback' => 'views_ajax',
160 'access callback' => 'user_access',
161 'access arguments' => array('access content'),
162 'description' => 'Ajax callback for view loading.',
163 'file' => 'includes/ajax.inc',
164 'type' => MENU_CALLBACK,
165 );
166 // Path is not admin/build/views due to menu complications with the wildcards from
167 // the generic ajax callback.
168 $items['admin/views/ajax/autocomplete/user'] = array(
169 'page callback' => 'views_ajax_autocomplete_user',
170 'access callback' => 'user_access',
171 'access arguments' => array('access content'),
172 'file' => 'includes/ajax.inc',
173 'type' => MENU_CALLBACK,
174 );
175 return $items;
176 }
177
178 /**
179 * Implementation of hook_menu_alter().
180 */
181 function views_menu_alter(&$callbacks) {
182 $our_paths = array();
183 $views = views_get_applicable_views('uses hook menu');
184 foreach ($views as $data) {
185 list($view, $display_id) = $data;
186 $result = $view->execute_hook_menu($display_id);
187 if (is_array($result)) {
188 // The menu system doesn't support having two otherwise
189 // identical paths with different placeholders. So we
190 // want to remove the existing items from the menu whose
191 // paths would conflict with ours.
192
193 // First, we must find any existing menu items that may
194 // conflict. We use a regular expression because we don't
195 // know what placeholders they might use. Note that we
196 // first construct the regex itself by replacing %views_arg
197 // in the display path, then we use this constructed regex
198 // (which will be something like '#^(foo/%[^/]*/bar)$#') to
199 // search through the existing paths.
200 $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
201 $matches = preg_grep($regex, array_keys($callbacks));
202
203 // Remove any conflicting items that were found.
204 foreach ($matches as $path) {
205 // Don't remove the paths we just added!
206 if (!isset($our_paths[$path])) {
207 unset($callbacks[$path]);
208 }
209 }
210 foreach ($result as $path => $item) {
211 if (!isset($callbacks[$path])) {
212 // Add a new item, possibly replacing (and thus effectively
213 // overriding) one that we removed above.
214 $callbacks[$path] = $item;
215 }
216 else {
217 // This item already exists, so it must be one that we added.
218 // We change the various callback arguments to pass an array
219 // of possible display IDs instead of a single ID.
220 $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
221 $callbacks[$path]['page arguments'][1][] = $display_id;
222 $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
223 $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
224 $callbacks[$path]['load arguments'][1][] = $display_id;
225 }
226 $our_paths[$path] = TRUE;
227 }
228 }
229 }
230
231 // Save memory: Destroy those views.
232 foreach ($views as $data) {
233 list($view, $display_id) = $data;
234 $view->destroy();
235 }
236 }
237
238 /**
239 * Helper function for menu loading. This will automatically be
240 * called in order to 'load' a views argument; primarily it
241 * will be used to perform validation.
242 *
243 * @param $value
244 * The actual value passed.
245 * @param $name
246 * The name of the view. This needs to be specified in the 'load function'
247 * of the menu entry.
248 * @param $index
249 * The menu argument index. This counts from 1.
250 */
251 function views_arg_load($value, $name, $display_id, $index) {
252 if ($view = views_get_view($name)) {
253 $view->set_display($display_id);
254 $view->init_handlers();
255
256 $ids = array_keys($view->argument);
257
258 $indexes = array();
259 $path = explode('/', $view->get_path());
260
261 foreach ($path as $id => $piece) {
262 if ($piece == '%' && !empty($ids)) {
263 $indexes[$id] = array_shift($ids);
264 }
265 }
266
267 if (isset($indexes[$index])) {
268 if (isset($view->argument[$indexes[$index]])) {
269 $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
270 $view->destroy();
271 return $arg;
272 }
273 }
274 $view->destroy();
275 }
276 }
277
278 /**
279 * Page callback entry point; requires a view and a display id, then
280 * passes control to the display handler.
281 */
282 function views_page() {
283 $args = func_get_args();
284 $name = array_shift($args);
285 $display_id = array_shift($args);
286
287 // Load the view
288 if ($view = views_get_view($name)) {
289 return $view->execute_display($display_id, $args);
290 }
291
292 // Fallback; if we get here no view was found or handler was not valid.
293 return drupal_not_found();
294 }
295
296 /**
297 * Implementation of hook_block
298 */
299 function views_block($op = 'list', $delta = 0, $edit = array()) {
300 switch ($op) {
301 case 'list':
302 $items = array();
303 $views = views_get_all_views();
304 foreach ($views as $view) {
305 // disabled views get nothing.
306 if (!empty($view->disabled)) {
307 continue;
308 }
309
310 $view->init_display();
311 foreach ($view->display as $display_id => $display) {
312
313 if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
314 $result = $display->handler->execute_hook_block();
315 if (is_array($result)) {
316 $items = array_merge($items, $result);
317 }
318 }
319
320 if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
321 $result = $display->handler->get_special_blocks();
322 if (is_array($result)) {
323 $items = array_merge($items, $result);
324 }
325 }
326 }
327 }
328
329 // block.module has a delta length limit of 32, but our deltas can
330 // unfortunately be longer because view names can be 32 and display IDs
331 // can also be 32. So for very long deltas, change to md5 hashes.
332 $hashes = array();
333
334 // get the keys because we're modifying the array and we don't want to
335 // confuse PHP too much.
336 $keys = array_keys($items);
337 foreach ($keys as $delta) {
338 if (strlen($delta) >= 32) {
339 $hash = md5($delta);
340 $hashes[$hash] = $delta;
341 $items[$hash] = $items[$delta];
342 unset($items[$delta]);
343 }
344 }
345
346 variable_set('views_block_hashes', $hashes);
347 // Save memory: Destroy those views.
348 foreach ($views as $view) {
349 $view->destroy();
350 }
351
352 return $items;
353 case 'view':
354 $start = views_microtime();
355 // if this is 32, this should be an md5 hash.
356 if (strlen($delta) == 32) {
357 $hashes = variable_get('views_block_hashes', array());
358 if (!empty($hashes[$delta])) {
359 $delta = $hashes[$delta];
360 }
361 }
362
363 // This indicates it's a special one.
364 if (substr($delta, 0, 1) == '-') {
365 list($nothing, $type, $name, $display_id) = explode('-', $delta);
366 // Put the - back on.
367 $type = '-' . $type;
368 if ($view = views_get_view($name)) {
369 if ($view->access($display_id)) {
370 $view->set_display($display_id);
371 if (isset($view->display_handler)) {
372 $output = $view->display_handler->view_special_blocks($type);
373 $view->destroy();
374 return $output;
375 }
376 }
377 $view->destroy();
378 }
379 }
380
381 list($name, $display_id) = explode('-', $delta);
382 // Load the view
383 if ($view = views_get_view($name)) {
384 if ($view->access($display_id)) {
385 $output = $view->execute_display($display_id);
386 vpr("Block $view->name execute time: " . (views_microtime() - $start) * 1000 . "ms");
387 $view->destroy();
388 return $output;
389 }
390 $view->destroy();
391 }
392 break;
393 }
394 }
395
396 /**
397 * Implementation of hook_flush_caches().
398 */
399 function views_flush_caches() {
400 return array('cache_views', 'cache_views_data');
401 }
402
403 /**
404 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
405 */
406 function views_invalidate_cache() {
407 cache_clear_all('*', 'cache_views', true);
408 }
409
410 /**
411 * Determine if the logged in user has access to a view.
412 *
413 * This function should only be called from a menu hook or some other
414 * embedded source. Each argument is the result of a call to
415 * views_plugin_access::get_access_callback() which is then used
416 * to determine if that display is accessible. If *any* argument
417 * is accessible, then the view is accessible.
418 */
419 function views_access() {
420 if (user_access('access all views')) {
421 return TRUE;
422 }
423
424 $args = func_get_args();
425 foreach ($args as $arg) {
426 if ($arg === TRUE) {
427 return TRUE;
428 }
429
430 if (!is_array($arg)) {
431 continue;
432 }
433
434 list($callback, $arguments) = $arg;
435 if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
436 return TRUE;
437 }
438 }
439
440 return FALSE;
441 }
442
443 /**
444 * Access callback to determine if the logged in user has any of the
445 * requested roles.
446 *
447 * This must be in views.module as it is called by menu access callback
448 * and can be called often.
449 */
450 function views_check_roles($rids) {
451 global $user;
452 $roles = array_keys($user->roles);
453 $roles[] = $user->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
454 return array_intersect(array_filter($rids), $roles);
455 }
456 // ------------------------------------------------------------------
457 // Functions to help identify views that are running or ran
458
459 /**
460 * Set the current 'page view' that is being displayed so that it is easy
461 * for other modules or the theme to identify.
462 */
463 function &views_set_page_view($view = NULL) {
464 static $cache = NULL;
465 if (isset($view)) {
466 $cache = $view;
467 }
468
469 return $cache;
470 }
471
472 /**
473 * Find out what, if any, page view is currently in use. Please note that
474 * this returns a reference, so be careful! You can unintentionally modify the
475 * $view object.
476 */
477 function &views_get_page_view() {
478 return views_set_page_view();
479 }
480
481 /**
482 * Set the current 'current view' that is being built/rendered so that it is
483 * easy for other modules or items in drupal_eval to identify
484 */
485 function &views_set_current_view($view = NULL) {
486 static $cache = NULL;
487 if (isset($view)) {
488 $cache = $view;
489 }
490
491 return $cache;
492 }
493
494 /**
495 * Find out what, if any, current view is currently in use. Please note that
496 * this returns a reference, so be careful! You can unintentionally modify the
497 * $view object.
498 */
499 function &views_get_current_view() {
500 return views_set_current_view();
501 }
502
503 // ------------------------------------------------------------------
504 // Include file helpers
505
506 /**
507 * Include views .inc files as necessary.
508 */
509 function views_include($file) {
510 static $used = array();
511 if (!isset($used[$file])) {
512 require_once './' . drupal_get_path('module', 'views') . "/includes/$file.inc";
513 }
514
515 $used[$file] = TRUE;
516 }
517
518 /**
519 * Load views files on behalf of modules.
520 */
521 function views_module_include($file) {
522 foreach (views_get_module_apis() as $module => $info) {
523 if (file_exists("./$info[path]/$module.$file")) {
524 require_once "./$info[path]/$module.$file";
525 }
526 }
527 }
528
529 /**
530 * Get a list of modules that support the current views API.
531 */
532 function views_get_module_apis() {
533 static $cache = NULL;
534 if (!isset($cache)) {
535 $cache = array();
536 foreach (module_implements('views_api') as $module) {
537 $function = $module . '_views_api';
538 $info = $function();
539 if (isset($info['api']) && $info['api'] == 2.000) {
540 if (!isset($info['path'])) {
541 $info['path'] = drupal_get_path('module', $module);
542 }
543 $cache[$module] = $info;
544 }
545 }
546 }
547
548 return $cache;
549 }
550
551 /**
552 * Include views .css files.
553 */
554 function views_add_css($file) {
555 // We set preprocess to FALSE because we are adding the files conditionally,
556 // and we don't want to generate duplicate cache files.
557 // TODO: at some point investigate adding some files unconditionally and
558 // allowing preprocess.
559 drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", 'module', 'all', FALSE);
560 }
561
562 /**
563 * Include views .js files.
564 */
565 function views_add_js($file) {
566 // If javascript has been disabled by the user, never add js files.
567 if (variable_get('views_no_javascript', FALSE)) {
568 return;
569 }
570
571 static $base = TRUE;
572 if ($base) {
573 drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
574 $base = FALSE;
575 }
576 drupal_add_js(drupal_get_path('module', 'views') . "/js/$file.js");
577 }
578
579 /**
580 * Load views files on behalf of modules.
581 */
582 function views_include_handlers() {
583 static $finished = FALSE;
584 // Ensure this only gets run once.
585 if ($finished) {
586 return;
587 }
588
589 views_include('base');
590 views_include('handlers');
591 views_include('cache');
592 views_include('plugins');
593 _views_include_handlers();
594 $finished = TRUE;
595 }
596
597 /**
598 * Load default views files on behalf of modules.
599 */
600 function views_include_default_views() {
601 static $finished = FALSE;
602 // Ensure this only gets run once.
603 if ($finished) {
604 return;
605 }
606
607 // Default views hooks may be in the normal handler file,
608 // or in a separate views_default file at the discretion of
609 // the module author.
610 views_include_handlers();
611
612 _views_include_default_views();
613 $finished = TRUE;
614 }
615
616 // -----------------------------------------------------------------------
617 // Views handler functions
618
619 /**
620 * Fetch a handler from the data cache.
621 *
622 * @param $table
623 * The name of the table this handler is from.
624 * @param $field
625 * The name of the field this handler is from.
626 * @param $key
627 * The type of handler. i.e, sort, field, argument, filter, relationship
628 *
629 * @return
630 * An instance of a handler object. May be views_handler_broken.
631 */
632 function views_get_handler($table, $field, $key) {
633 $data = views_fetch_data($table);
634 if (isset($data[$field][$key])) {
635 // Set up a default handler:
636 if (empty($data[$field][$key]['handler'])) {
637 $data[$field][$key]['handler'] = 'views_handler_' . $key;
638 }
639 return _views_prepare_handler($data[$field][$key], $data, $field);
640 }
641 // DEBUG -- identify missing handlers
642 vpr("Missing handler: $table $field $key");
643 $broken = array(
644 'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
645 'handler' => 'views_handler_' . $key . '_broken',
646 'table' => $table,
647 'field' => $field,
648 );
649 return _views_create_handler($broken);
650 }
651
652 /**
653 * Fetch Views' data from the cache
654 */
655 function views_fetch_data($table = NULL) {
656 views_include('cache');
657 return _views_fetch_data($table);
658 }
659
660 // -----------------------------------------------------------------------
661 // Views plugin functions
662
663 /**
664 * Fetch the plugin data from cache.
665 */
666 function views_fetch_plugin_data($type = NULL, $plugin = NULL) {
667 views_include('cache');
668 return _views_fetch_plugin_data($type, $plugin);
669 }
670
671 /**
672 * Get a handler for a plugin
673 */
674 function views_get_plugin($type, $plugin) {
675 $definition = views_fetch_plugin_data($type, $plugin);
676 if (!empty($definition)) {
677 return _views_create_handler($definition, $type);
678 }
679 }
680
681 // -----------------------------------------------------------------------
682 // Views database functions
683
684 /**
685 * Get a view from the default views defined by modules.
686 *
687 * Default views are cached per-language. This function will rescan the
688 * default_views hook if necessary.
689 *
690 * @param $view_name
691 * The name of the view to load.
692 * @return
693 * A view object or NULL if it is not available.
694 */
695 function &views_get_default_view($view_name) {
696 $null = NULL;
697 $cache = views_discover_default_views();
698
699 if (isset($cache[$view_name])) {
700 return $cache[$view_name];
701 }
702 return $null;
703 }
704
705 /**
706 * Create an empty view to work with.
707 *
708 * @return
709 * A fully formed, empty $view object. This object must be populated before
710 * it can be successfully saved.
711 */
712 function views_new_view() {
713 views_include('view');
714 $view = new view();
715 $view->vid = 'new';
716 $view->add_display('default');
717
718 return $view;
719 }
720
721 /**
722 * Scan all modules for default views and rebuild the default views cache.
723 *
724 * @return An associative array of all known default views.
725 */
726 function views_discover_default_views() {
727 static $cache = array();
728
729 if (empty($cache)) {
730 views_include('cache');
731 $cache = _views_discover_default_views();
732 }
733 return $cache;
734 }
735
736 /**
737 * Return a list of all views and display IDs that have a particular
738 * setting in their display's plugin settings.
739 *
740 * @return
741 * @code
742 * array(
743 * array($view, $display_id),
744 * array($view, $display_id),
745 * );
746 * @endcode
747 */
748 function views_get_applicable_views($type) {
749 // @todo: Use a smarter flagging system so that we don't have to
750 // load every view for this.
751 $result = array();
752 $views = views_get_all_views();
753
754 foreach ($views as $view) {
755 // Skip disabled views.
756 if (!empty($view->disabled)) {
757 continue;
758 }
759
760 if (empty($view->display)) {
761 // Skip this view as it is broken.
762 vsm(t("Skipping broken view @view", array('@view' => $view->name)));
763 continue;
764 }
765
766 // Loop on array keys because something seems to muck with $view->display
767 // a bit in PHP4.
768 foreach (array_keys($view->display) as $id) {
769 $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
770 if (!empty($plugin[$type])) {
771 // This view uses hook menu. Clone it so that different handlers
772 // don't trip over each other, and add it to the list.
773 $v = $view->clone_view();
774 if ($v->set_display($id)) {
775 $result[] = array($v, $id);
776 }
777 // In PHP 4.4.7 and presumably earlier, if we do not unset $v
778 // here, we will find that it actually overwrites references
779 // possibly due to shallow copying issues.
780 unset($v);
781 }
782 }
783 }
784 return $result;
785 }
786
787 /**
788 * Return an array of all views as fully loaded $view objects.
789 *
790 * @param $reset
791 * If TRUE, reset the static cache forcing views to be reloaded.
792 */
793 function views_get_all_views($reset = FALSE) {
794 static $views = array();
795
796 if (empty($views) || $reset) {
797 $views = array();
798
799 // First, get all applicable views.
800 views_include('view');
801 $views = view::load_views();
802
803 // Get all default views.
804 $status = variable_get('views_defaults', array());
805
806 foreach (views_discover_default_views() as $view) {
807 // Determine if default view is enabled or disabled.
808 if (isset($status[$view->name])) {
809 $view->disabled = $status[$view->name];
810 }
811
812 // If overridden, also say so.
813 if (!empty($views[$view->name])) {
814 $views[$view->name]->type = t('Overridden');
815 }
816 else {
817 $view->type = t('Default');
818 $views[$view->name] = $view;
819 }
820 }
821
822 }
823 return $views;
824 }
825
826 /**
827 * Get a view from the database or from default views.
828 *
829 * This function is just a static wrapper around views::load(). This function
830 * isn't called 'views_load()' primarily because it might get a view
831 * from the default views which aren't technically loaded from the database.
832 *
833 * @param $name
834 * The name of the view.
835 * @param $reset
836 * If TRUE, reset this entry in the load cache.
837 * @return $view
838 * A reference to the $view object. Use $reset if you're sure you want
839 * a fresh one.
840 */
841 function views_get_view($name, $reset = FALSE) {
842 views_include('view');
843 $view = view::load($name, $reset);
844 $default_view = views_get_default_view($name);
845
846 if (empty($view) && empty($default_view)) {
847 return;
848 }
849 elseif (empty($view) && !empty($default_view)) {
850 $default_view->type = t('Default');
851 return $default_view->clone_view();
852 }
853 elseif (!empty($view) && !empty($default_view)) {
854 $view->type = t('Overridden');
855 }
856
857 return $view->clone_view();
858 }
859
860
861 // ------------------------------------------------------------------
862 // Views debug helper functions
863
864 /**
865 * Provide debug output for Views. This relies on devel.module
866 */
867 function views_debug($message) {
868 if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
869 if (is_string($message)) {
870 $output = $message;
871 }
872 else {
873 $output = var_export($message, TRUE);
874 }
875 if (variable_get('views_devel_region', 'footer') != 'watchdog') {
876 drupal_set_content(variable_get('views_devel_region', 'footer'), '<pre>' . $output . '</pre>');
877 }
878 else {
879 watchdog('views_logging', '<pre>' . $output . '</pre>');
880 }
881 }
882 }
883
884 /**
885 * Shortcut to views_debug()
886 */
887 function vpr($message) {
888 views_debug($message);
889 }
890
891 /**
892 * Debug messages
893 */
894 function vsm($message) {
895 if (module_exists('devel')) {
896 dsm($message);
897 }
898 }
899
900 function views_trace() {
901 $message = '';
902 foreach (debug_backtrace() as $item) {
903 if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
904 $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
905 }
906 }
907 return $message;
908 }
909
910 function vsm_trace() {
911 vsm(views_trace());
912 }
913
914 function vpr_trace() {
915 dpr(views_trace());
916 }
917
918 // ------------------------------------------------------------------
919 // Exposed widgets form
920
921 /**
922 * Form builder for the exposed widgets form.
923 *
924 * Be sure that $view and $display are references.
925 */
926 function views_exposed_form(&$form_state) {
927 // Don't show the form when batch operations are in progress.
928 $batch =& batch_get();
929 if (!empty($batch)) {
930 return array(
931 // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
932 '#theme' => '',
933 );
934 }
935
936 // Make sure that we validate because this form might be submitted
937 // multiple times per page.
938 $form_state['must_validate'] = TRUE;
939 $view = &$form_state['view'];
940 $display = &$form_state['display'];
941
942 $form_state['input'] = $view->get_exposed_input();
943
944 // Let form plugins know this is for exposed widgets.
945 $form_state['exposed'] = TRUE;
946 // Check if the form was already created
947 if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
948 return $cache;
949 }
950
951 $form['#info'] = array();
952
953 if (!variable_get('clean_url', FALSE)) {
954 $form['q'] = array(
955 '#type' => 'hidden',
956 '#value' => $view->get_url(),
957 );
958 }
959
960 // Go through each filter and let it generate its info.
961 foreach ($view->filter as $id => $filter) {
962 $view->filter[$id]->exposed_form($form, $form_state);
963 if ($info = $view->filter[$id]->exposed_info()) {
964 $form['#info']['filter-' . $id] = $info;
965 }
966 }
967
968 // @todo deal with exposed sorts
969
970 $form['submit'] = array(
971 '#name' => '', // prevent from showing up in $_GET.
972 '#type' => 'submit',
973 '#value' => t('Apply'),
974 );
975
976 $form['#action'] = url($view->get_url());
977 $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
978 $form['#id'] = views_css_safe('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
979 // $form['#attributes']['class'] = array('views-exposed-form');
980
981 // If using AJAX, we need the form plugin.
982 if ($view->use_ajax) {
983 drupal_add_js('misc/jquery.form.js');
984 }
985 views_add_js('dependent');
986
987 // Save the form
988 views_exposed_form_cache($view->name, $view->current_display, $form);
989
990 return $form;
991 }
992
993 /**
994 * Validate handler for exposed filters
995 */
996 function views_exposed_form_validate(&$form, &$form_state) {
997 foreach (array('field', 'filter') as $type) {
998 $handlers = &$form_state['view']->$type;
999 foreach ($handlers as $key => $handler) {
1000 $handlers[$key]->exposed_validate($form, $form_state);
1001 }
1002 }
1003 }
1004
1005 /**
1006 * Submit handler for exposed filters
1007 */
1008 function views_exposed_form_submit(&$form, &$form_state) {
1009 foreach (array('field', 'filter') as $type) {
1010 $handlers = &$form_state['view']->$type;
1011 foreach ($handlers as $key => $info) {
1012 $handlers[$key]->exposed_submit($form, $form_state);
1013 }
1014 }
1015 $form_state['view']->exposed_data = $form_state['values'];
1016 $form_state['view']->exposed_raw_input = array();
1017
1018 foreach ($form_state['values'] as $key => $value) {
1019 if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token', ''))) {
1020 $form_state['view']->exposed_raw_input[$key] = $value;
1021 }
1022 }
1023 }
1024
1025 /**
1026 * Save the Views exposed form for later use.
1027 *
1028 * @param $views_name
1029 * String. The views name.
1030 * @param $display_name
1031 * String. The current view display name.
1032 * @param $form_output
1033 * Array (optional). The form structure. Only needed when inserting the value.
1034 * @return
1035 * Array. The form structure, if any. Otherwise, return FALSE.
1036 */
1037 function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
1038 static $views_exposed;
1039
1040 // Save the form output
1041 if (!empty($form_output)) {
1042 $views_exposed[$views_name][$display_name] = $form_output;
1043 return;
1044 }
1045
1046 // Return the form output, if any
1047 return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
1048 }
1049
1050 // ------------------------------------------------------------------
1051 // Misc helpers
1052
1053 /**
1054 * Build a list of theme function names for use most everywhere.
1055 */
1056 function views_theme_functions($hook, $view, $display = NULL) {
1057 require_once './' . drupal_get_path('module', 'views') . "/theme/theme.inc";
1058 return _views_theme_functions($hook, $view, $display);
1059 }
1060
1061 /**
1062 * Views' replacement for drupal_get_form so that we can do more with
1063 * less.
1064 *
1065 * Items that can be set on the form_state include:
1066 * - input: The source of input. If unset this will be $_POST.
1067 * - no_redirect: Absolutely do not redirect the form even if instructed
1068 * to do so.
1069 * - rerender: If no_redirect is set and the form was successfully submitted,
1070 * rerender the form. Otherwise it will just return.
1071 *
1072 */
1073 function drupal_build_form($form_id, &$form_state) {
1074 views_include('form');
1075 return _drupal_build_form($form_id, $form_state);
1076 }
1077
1078 /**
1079 * Substitute current time; this works with cached queries.
1080 */
1081 function views_views_query_substitutions($view) {
1082 global $language;
1083 return array(
1084 '***CURRENT_VERSION***' => VERSION,
1085 '***CURRENT_TIME***' => time(),
1086 '***CURRENT_LANGUAGE***' => $language->language,
1087 '***DEFAULT_LANGUAGE***' => language_default('language'),
1088 '***NO_LANGUAGE***' => '',
1089 );
1090 }
1091
1092 /**
1093 * Embed a view using a PHP snippet.
1094 *
1095 * This function is meant to be called from PHP snippets, should one wish to
1096 * embed a view in a node or something. It's meant to provide the simplest
1097 * solution and doesn't really offer a lot of options, but breaking the function
1098 * apart is pretty easy, and this provides a worthwhile guide to doing so.
1099 *
1100 * Note that this function does NOT display the title of the view. If you want
1101 * to do that, you will need to do what this function does manually, by
1102 * loading the view, getting the preview and then getting $view->get_title().
1103 *
1104 * @param $name
1105 * The name of the view to embed.
1106 * @param $display_id
1107 * The display id to embed. If unsure, use 'default', as it will always be
1108 * valid. But things like 'page' or 'block' should work here.
1109 * @param ...
1110 * Any additional parameters will be passed as arguments.
1111 */
1112 function views_embed_view($name, $display_id = 'default') {
1113 $args = func_get_args();
1114 array_shift($args); // remove $name
1115 if (count($args)) {
1116 array_shift($args); // remove $display_id
1117 }
1118
1119 $view = views_get_view($name);
1120 if (!$view) {
1121 return;
1122 }
1123
1124 return $view->preview($display_id, $args);
1125 }
1126
1127 /**
1128 * Export a field.
1129 */
1130 function views_var_export($var, $prefix = '', $init = TRUE) {
1131 if (is_array($var)) {
1132 if (empty($var)) {
1133 $output = 'array()';
1134 }
1135 else {
1136 $output = "array(\n";
1137 foreach ($var as $key => $value) {
1138 $output .= " '$key' => " . views_var_export($value, ' ', FALSE) . ",\n";
1139 }
1140 $output .= ')';
1141 }
1142 }
1143 else if (is_bool($var)) {
1144 $output = $var ? 'TRUE' : 'FALSE';
1145 }
1146 else if (is_string($var) && strpos($var, "\n") !== FALSE) {
1147 // Replace line breaks in strings with a token for replacement
1148 // at the very end. This protects multi-line strings from
1149 // unintentional indentation.
1150 $var = str_replace("\n", "***BREAK***", $var);
1151 $output = var_export($var, TRUE);
1152 }
1153 else {
1154 $output = var_export($var, TRUE);
1155 }
1156
1157 if ($prefix) {
1158 $output = str_replace("\n", "\n$prefix", $output);
1159 }
1160
1161 if ($init) {
1162 $output = str_replace("***BREAK***", "\n", $output);
1163 }
1164
1165 return $output;
1166 }
1167
1168 /**
1169 * Prepare the specified string for use as a CSS identifier.
1170 */
1171 function views_css_safe($string) {
1172 return str_replace('_', '-', $string);
1173 }
1174
1175 /**
1176 * Implementation of hook_views_exportables().
1177 */
1178 function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
1179 $all_views = views_get_all_views();
1180 if ($op == 'list') {
1181
1182 foreach ($all_views as $name => $view) {
1183 // in list, $views is a list of tags.
1184 if (empty($views) || in_array($view->tag, $views)) {
1185 $return[$name] = array(
1186 'name' => check_plain($name),
1187 'desc' => check_plain($view->description),
1188 'tag' => check_plain($view->tag)
1189 );
1190 }
1191 }
1192 return $return;
1193 }
1194
1195 if ($op == 'export') {
1196 $code = "/**\n";
1197 $code .= " * Implementation of hook_views_default_views().\n";
1198 $code .= " */\n";
1199 $code .= "function " . $name . "_views_default_views() {\n";
1200 foreach ($views as $view => $truth) {
1201 $code .= " /*\n";
1202 $code .= " * View ". var_export($all_views[$view]->name, TRUE) ."\n";
1203 $code .= " */\n";
1204 $code .= $all_views[$view]->export(' ');
1205 $code .= ' $views[$view->name] = $view;' . "\n\n";
1206 }
1207 $code .= " return \$views;\n";
1208 $code .= "}\n";
1209
1210 return $code;
1211 }
1212 }
1213
1214 /**
1215 * Microtime helper function to return a float time value (php4 & php5 safe).
1216 */
1217 function views_microtime() {
1218 list($usec, $sec) = explode(' ', microtime());
1219 return (float)$sec + (float)$usec;
1220 }