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