5 * Primarily Drupal hooks and global API functions to manipulate views.
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.
13 * Implementation of hook_theme(). Register views theming functions.
15 function views_theme() {
16 $path = drupal_get_path('module', 'views');
17 require_once
"./$path/theme/theme.inc";
19 // Some quasi clever array merging here.
21 'file' => 'theme.inc',
22 'path' => "$path/theme",
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()),
31 'display' => array('view' => NULL
),
32 'style' => array('view' => NULL
, 'options' => NULL
, 'rows' => NULL
),
33 'row' => array('view' => NULL
, 'options' => NULL
, 'row' => NULL
),
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
),
42 $plugins = views_fetch_plugin_data();
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],
54 if (!function_exists('theme_' .
$def['theme'])) {
55 $hooks[$def['theme']]['template'] = views_css_safe($def['theme']);
58 if (isset($def['additional themes'])) {
59 foreach ($def['additional themes'] as
$theme => $theme_type) {
60 if (empty($theme_type)) {
65 $hooks[$theme] = array(
66 'pattern' => $theme .
'__',
67 'file' => $def['file'],
68 'path' => $def['path'],
69 'arguments' => $arguments[$theme_type],
71 if (!function_exists('theme_' .
$theme)) {
72 $hooks[$theme]['template'] = views_css_safe($theme);
79 $hooks['views_exposed_form'] = $base + array(
80 'template' => 'views-exposed-form',
81 'pattern' => 'views_exposed_form__',
82 'arguments' => array('form' => NULL
),
85 $hooks['views_more'] = $base + array(
86 'template' => 'views-more',
87 'pattern' => 'views_more__',
88 'arguments' => array('more_url' => NULL
),
95 * Implementation of hook_menu().
97 function views_menu() {
98 // Any event which causes a menu_rebuild could potentially mean that the
99 // Views data is updated -- module changes, profile changes, etc.
100 views_invalidate_cache();
102 $items['views/ajax'] = array(
104 'page callback' => 'views_ajax',
105 'file' => 'includes/ajax.inc',
106 'access callback' => 'user_access',
107 'access arguments' => array('access content'),
108 'description' => 'Ajax callback for view loading.',
109 'type' => MENU_CALLBACK
,
115 * Implementation of hook_menu_alter().
117 function views_menu_alter(&$callbacks) {
118 $our_paths = array();
119 foreach (views_get_applicable_views('uses hook menu') as
$data) {
120 list($view, $display_id) = $data;
121 $result = $view->execute_hook_menu($display_id);
122 if (is_array($result)) {
123 // The menu system doesn't support having two otherwise
124 // identical paths with different placeholders. So we
125 // want to remove the existing items from the menu whose
126 // paths would conflict with ours.
128 // First, we must find any existing menu items that may
129 // conflict. We use a regular expression because we don't
130 // know what placeholders they might use. Note that we
131 // first construct the regex itself by replacing %views_arg
132 // in the display path, then we use this constructed regex
133 // (which will be something like '#^(foo/%[^/]*/bar)$#') to
134 // search through the existing paths.
135 $regex = '#^('.
preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) .
')$#';
136 $matches = preg_grep($regex, array_keys($callbacks));
138 // Remove any conflicting items that were found.
139 foreach ($matches as
$path) {
140 // Don't remove the paths we just added!
141 if (!isset($our_paths[$path])) {
142 unset($callbacks[$path]);
145 foreach ($result as
$path => $item) {
146 if (!isset($callbacks[$path])) {
147 // Add a new item, possibly replacing (and thus effectively
148 // overriding) one that we removed above.
149 $callbacks[$path] = $item;
152 // This item already exists, so it must be one that we added.
153 // We change the various callback arguments to pass an array
154 // of possible display IDs instead of a single ID.
155 $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
156 $callbacks[$path]['page arguments'][1][] = $display_id;
157 $callbacks[$path]['access arguments'][0][1] = (array)$callbacks[$path]['access arguments'][0][1];
158 $callbacks[$path]['access arguments'][0][1][] = $display_id;
159 $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
160 $callbacks[$path]['load arguments'][1][] = $display_id;
162 $our_paths[$path] = TRUE
;
169 * Helper function for menu loading. This will automatically be
170 * called in order to 'load' a views argument; primarily it
171 * will be used to perform validation.
174 * The actual value passed.
176 * The name of the view. This needs to be specified in the 'load function'
179 * The menu argument index. This counts from 1.
181 function views_arg_load($value, $name, $display_id, $index) {
182 if ($view = views_get_view($name)) {
183 $view->set_display($display_id);
184 $view->init_handlers();
186 $ids = array_keys($view->argument
);
189 $path = explode('/', $view->get_url());
190 foreach ($path as
$id => $piece) {
191 if ($piece == '%' && !empty($ids)) {
192 $indexes[$id] = array_shift($ids);
196 if (isset($indexes[$index])) {
197 if (isset($view->argument
[$indexes[$index]])) {
198 return $view->argument
[$indexes[$index]]['handler']->validate_argument($value) ?
$value : NULL
;
205 * Page callback entry point; requires a view and a display id, then
206 * passes control to the display handler.
208 function views_page() {
209 $args = func_get_args();
210 $name = array_shift($args);
211 $display_id = array_shift($args);
214 if ($view = views_get_view($name)) {
215 return $view->execute_display($display_id, $args);
218 // Fallback; if we get here no view was found or handler was not valid.
219 return drupal_not_found();
223 * Implementation of hook_block
225 function views_block($op = 'list', $delta = 0, $edit = array()) {
229 foreach (views_get_applicable_views('uses hook block') as
$data) {
230 list($view, $display_id) = $data;
231 $result = $view->execute_hook_block($display_id);
232 if (is_array($result)) {
233 $items = array_merge($items, $result);
237 // block.module has a delta length limit of 32, but our deltas can
238 // unfortunately be longer because view names can be 32 and display IDs
239 // can also be 32. So for very long deltas, change to md5 hashes.
242 // get the keys because we're modifying the array and we don't want to
243 // confuse PHP too much.
244 $keys = array_keys($items);
245 foreach ($keys as
$delta) {
246 if (strlen($delta) >= 32) {
248 $hashes[$hash] = $delta;
249 $items[$hash] = $items[$delta];
250 unset($items[$delta]);
254 variable_set('views_block_hashes', $hashes);
257 // if this is 32, this should be an md5 hash.
258 if (strlen($delta) == 32) {
259 $hashes = variable_get('views_block_hashes', array());
260 if (!empty($hashes[$delta])) {
261 $delta = $hashes[$delta];
265 list($name, $display_id) = explode('-', $delta);
267 if ($view = views_get_view($name)) {
268 if ($view->access($display_id)) {
269 return $view->execute_display($display_id);
277 * Implementation of hook_flush_caches().
279 function views_flush_caches() {
280 return array('cache_views');
284 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
286 function views_invalidate_cache() {
287 cache_clear_all('*', 'cache_views', true
);
291 * Determine if the given user has access to the view + display.
294 * May be a view object, or an array with the view name and the display ID,
295 * or a string to use as the view name.
297 * An optional account to use; if left off, the current user will be used.
299 function views_access($view, $account = NULL
) {
300 if (is_array($view)) {
301 list($name, $display_id) = $view;
302 $view = views_get_view($name);
307 elseif (is_string($view)) {
308 $view = views_get_view($view);
312 $display_id = 'default';
315 // Clone the view to prevent problems.
316 $view = $view->clone_view();
317 $display_id = isset($view->current_display
) ?
$view->current_display
: 'default';
320 return $view->access($display_id, $account);
323 // ------------------------------------------------------------------
324 // Functions to help identify views that are running or ran
327 * Set the current 'page view' that is being displayed so that it is easy
328 * for other modules or the theme to identify.
330 function &views_set_page_view($view = NULL
) {
331 static
$cache = NULL
;
340 * Find out what, if any, page view is currently in use. Please note that
341 * this returns a reference, so be careful! You can unintentionally modify the
344 function &views_get_page_view() {
345 return views_set_page_view();
349 * Set the current 'current view' that is being built/rendered so that it is
350 * easy for other modules or items in drupal_eval to identify
352 function &views_set_current_view($view = NULL
) {
353 static
$cache = NULL
;
362 * Find out what, if any, current view is currently in use. Please note that
363 * this returns a reference, so be careful! You can unintentionally modify the
366 function &views_get_current_view() {
367 return views_set_current_view();
370 // ------------------------------------------------------------------
371 // Include file helpers
374 * Include views .inc files as necessary.
376 function views_include($file) {
377 static
$used = array();
378 if (!isset($used[$file])) {
379 require_once
'./' .
drupal_get_path('module', 'views') .
"/includes/$file.inc";
386 * Load views files on behalf of modules.
388 function views_module_include($file) {
389 $views_path = drupal_get_path('module', 'views') .
'/modules';
390 foreach (module_list() as
$module) {
391 $module_path = drupal_get_path('module', $module);
392 if (file_exists("$module_path/$module.$file")) {
393 require_once
"./$module_path/$module.$file";
395 else if (file_exists("$module_path/includes/$module.$file")) {
396 require_once
"./$module_path/includes/$module.$file";
398 else if (file_exists("$views_path/$module.$file")) {
399 require_once
"./$views_path/$module.$file";
405 * Include views .css files.
407 function views_add_css($file) {
408 drupal_add_css(drupal_get_path('module', 'views') .
"/css/$file.css");
412 * Include views .js files.
414 function views_add_js($file) {
417 drupal_add_js(drupal_get_path('module', 'views') .
"/js/base.js");
419 drupal_add_js(drupal_get_path('module', 'views') .
"/js/$file.js");
423 * Load views files on behalf of modules.
425 function views_include_handlers() {
426 static
$finished = FALSE
;
427 // Ensure this only gets run once.
432 views_include('handlers');
433 views_include('cache');
434 views_include('plugins');
435 _views_include_handlers();
440 * Load default views files on behalf of modules.
442 function views_include_default_views() {
443 static
$finished = FALSE
;
444 // Ensure this only gets run once.
449 // Default views hooks may be in the normal handler file,
450 // or in a separate views_default file at the discretion of
451 // the module author.
452 views_include_handlers();
454 _views_include_default_views();
458 // -----------------------------------------------------------------------
459 // Views handler functions
462 * Fetch a handler from the data cache.
464 function views_get_handler($table, $field, $key) {
465 $data = views_fetch_data($table);
466 if (isset($data[$field][$key])) {
467 return _views_prepare_handler($data[$field][$key], $data, $field);
469 // DEBUG -- identify missing handlers
470 vpr("Missing handler: $table $field $key");
474 * Fetch Views' data from the cache
476 function views_fetch_data($table = NULL
) {
477 views_include('cache');
478 return _views_fetch_data($table);
481 // -----------------------------------------------------------------------
482 // Views plugin functions
485 * Fetch the plugin data from cache.
487 function views_fetch_plugin_data($type = NULL
, $plugin = NULL
) {
488 views_include('cache');
489 return _views_fetch_plugin_data($type, $plugin);
493 * Get a handler for a plugin
495 function views_get_plugin($type, $plugin) {
496 $definition = views_fetch_plugin_data($type, $plugin);
497 if (!empty($definition)) {
498 return _views_create_handler($definition);
502 // -----------------------------------------------------------------------
503 // Views database functions
506 * Get a view from the default views defined by modules.
508 * Default views are cached per-language. This function will rescan the
509 * default_views hook if necessary.
512 * The name of the view to load.
514 * A view object or NULL if it is not available.
516 function &views_get_default_view($view_name) {
518 $cache = views_discover_default_views();
520 if (isset($cache[$view_name])) {
521 return $cache[$view_name];
527 * Create an empty view to work with.
530 * A fully formed, empty $view object. This object must be populated before
531 * it can be successfully saved.
533 function views_new_view() {
534 views_include('view');
537 $view->add_display('default');
543 * Scan all modules for default views and rebuild the default views cache.
545 * @return An associative array of all known default views.
547 function views_discover_default_views() {
548 static
$cache = array();
551 views_include('cache');
552 $cache = _views_discover_default_views();
558 * Return a list of all views and display IDs that have a particular
559 * setting in their display's plugin settings.
564 * array($view, $display_id),
565 * array($view, $display_id),
569 function views_get_applicable_views($type) {
570 // @todo: Use a smarter flagging system so that we don't have to
571 // load every view for this.
573 $views = views_get_all_views();
575 foreach ($views as
$view) {
576 // Skip disabled views.
577 if (!empty($view->disabled
)) {
581 if (empty($view->display
)) {
582 // Skip this view as it is broken.
583 vsm(t("Skipping broken view @view", array('@view' => $view->name
)));
587 // Loop on array keys because something seems to muck with $view->display
589 foreach (array_keys($view->display
) as
$id) {
590 $plugin = views_fetch_plugin_data('display', $view->display
[$id]->display_plugin
);
591 if (!empty($plugin[$type])) {
592 // This view uses hook menu. Clone it so that different handlers
593 // don't trip over each other, and add it to the list.
594 $v = $view->clone_view();
595 if ($v->set_display($id)) {
596 $result[] = array($v, $id);
598 // In PHP 4.4.7 and presumably earlier, if we do not unset $v
599 // here, we will find that it actually overwrites references
600 // possibly due to shallow copying issues.
609 * Return an array of all views as fully loaded $view objects.
611 function views_get_all_views() {
612 static
$views = array();
615 // First, get all applicable views.
616 views_include('view');
617 $views = view
::load_views();
619 // Get all default views.
620 $status = variable_get('views_defaults', array());
622 foreach (views_discover_default_views() as
$view) {
623 // Determine if default view is enabled or disabled.
624 if (isset($status[$view->name
])) {
625 $view->disabled
= $status[$view->name
];
628 // If overridden, also say so.
629 if (!empty($views[$view->name
])) {
630 $views[$view->name
]->type
= t('Overridden');
633 $view->type
= t('Default');
634 $views[$view->name
] = $view;
643 * Get a view from the database or from default views.
645 * This function is just a static wrapper around views::load(). This function
646 * isn't called 'views_load()' primarily because it might get a view
647 * from the default views which aren't technically loaded from the database.
650 * The name of the view.
652 * If TRUE, reset this entry in the load cache.
654 * A reference to the $view object. Use $reset if you're sure you want
657 function views_get_view($name, $reset = FALSE
) {
658 views_include('view');
659 $view = view
::load($name, $reset);
660 $default_view = views_get_default_view($name);
662 if (empty($view) && empty($default_view)) {
665 elseif (empty($view) && !empty($default_view)) {
666 $default_view->type
= t('Default');
667 return $default_view->clone_view();
669 elseif (!empty($view) && !empty($default_view)) {
670 $view->type
= t('Overridden');
673 return $view->clone_view();
677 * Basic definition for many views objects
681 * Views handlers use a special construct function so that we can more
682 * easily construct them with variable arguments.
684 function construct() { }
687 * Let the handler know what its full definition is.
689 function set_definition($definition) {
690 $this->definition
= $definition;
691 if (isset($definition['field'])) {
692 $this->real_field
= $definition['field'];
697 // ------------------------------------------------------------------
698 // Views debug helper functions
701 * Provide debug output for Views. This relies on devel.module
703 function views_debug($message) {
704 if (module_exists('devel') && variable_get('views_devel_output', FALSE
)) {
705 drupal_set_content(variable_get('views_devel_region', 'footer'), dpr($message, TRUE
));
710 * Shortcut to views_debug()
712 function vpr($message) {
713 views_debug($message);
719 function vsm($message) {
720 if (module_exists('devel')) {
725 function views_trace() {
727 foreach (debug_backtrace() as
$item) {
728 if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
729 $message .
= basename($item['file']) .
": " .
(empty($item['class']) ?
'' : ($item['class'] .
'->')) .
"$item[function] line $item[line]" .
"\n";
735 function vsm_trace() {
739 function vpr_trace() {
743 // ------------------------------------------------------------------
744 // Exposed widgets form
747 * Form builder for the exposed widgets form.
749 * Be sure that $view and $display are references.
751 function views_exposed_form(&$form_state) {
752 $view = &$form_state['view'];
753 $display = &$form_state['display'];
754 // Fill our input either from $_GET or from something previously set on the
756 if (empty($view->exposed_input
)) {
758 // unset items that are definitely not our input:
759 unset($input['page']);
761 // If we have no input at all, check for remembered input via session.
762 if (empty($input) && !empty($_SESSION['views'][$view->name
][$view->current_display
])) {
763 $input = $_SESSION['views'][$view->name
][$view->current_display
];
765 $form['#post'] = $input;
768 $form['#post'] = $view->exposed_input
;
771 // Let form plugins know this is for exposed widgets.
772 $form_state['exposed'] = TRUE
;
774 $form['#info'] = array();
776 if (!variable_get('clean_url', FALSE
)) {
779 '#value' => $view->get_url(),
783 // Go through each filter and let it generate its info.
784 foreach ($view->filter as
$id => $filter) {
785 $filter['handler']->exposed_form($form, $form_state);
786 if ($info = $filter['handler']->exposed_info()) {
787 $form['#info']['filter-' .
$id] = $info;
791 // @todo deal with exposed sorts
793 $form['submit'] = array(
794 '#name' => '', // prevent from showing up in $_GET.
796 '#value' => t('Apply'),
799 $form['#action'] = url($view->get_url());
800 $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
802 // If using AJAX, we need the form plugin.
803 if ($view->use_ajax
) {
804 drupal_add_js('misc/jquery.form.js');
806 views_add_js('dependent');
811 * Validate handler for exposed filters
813 function views_exposed_form_validate(&$form, &$form_state) {
814 foreach (array('field', 'filter') as
$type) {
815 $var = &$form_state['view']->$type;
816 foreach ($var as
$key => $info) {
817 $var[$key]['handler']->exposed_validate($form, $form_state);
823 * Submit handler for exposed filters
825 function views_exposed_form_submit(&$form, &$form_state) {
826 foreach (array('field', 'filter') as
$type) {
827 $var = &$form_state['view']->$type;
828 foreach ($var as
$key => $info) {
829 $var[$key]['handler']->exposed_submit($form, $form_state);
832 $form_state['view']->exposed_data
= $form_state['values'];
833 $form_state['view']->exposed_raw_input
= array();
835 foreach ($form_state['values'] as
$key => $value) {
836 if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token'))) {
837 $form_state['view']->exposed_raw_input
[$key] = $value;
842 // ------------------------------------------------------------------
846 * Build a list of theme function names for use most everywhere.
848 function views_theme_functions($hook, $view, $display = NULL
) {
849 require_once
'./' .
drupal_get_path('module', 'views') .
"/theme/theme.inc";
850 return _views_theme_functions($hook, $view, $display);
854 * Views' replacement for drupal_get_form so that we can do more with
857 * Items that can be set on the form_state include:
858 * - input: The source of input. If unset this will be $_POST.
859 * - no_redirect: Absolutely do not redirect the form even if instructed
861 * - rerender: If no_redirect is set and the form was successfully submitted,
862 * rerender the form. Otherwise it will just return.
865 function drupal_build_form($form_id, &$form_state) {
866 views_include('form');
867 return _drupal_build_form($form_id, $form_state);
871 * Substitute current time; this works with cached queries.
873 function views_views_query_substitutions($view) {
876 '***CURRENT_TIME***' => time(),
877 '***CURRENT_LANGUAGE***' => $language->language
,
878 '***NO_LANGUAGE***' => '',
883 * Embed a view using a PHP snippet.
885 * This function is meant to be called from PHP snippets, should one wish to
886 * embed a view in a node or something. It's meant to provide the simplest
887 * solution and doesn't really offer a lot of options, but breaking the function
888 * apart is pretty easy, and this provides a worthwhile guide to doing so.
891 * The name of the view to embed.
893 * The display id to embed. If unsure, use 'default', as it will always be
894 * valid. But things like 'page' or 'block' should work here.
896 * Any additional parameters will be passed as arguments.
898 function views_embed_view($name, $display_id = 'default') {
899 $args = func_get_args();
900 array_shift($args); // remove $name
902 array_shift($args); // remove $display_id
905 $view = views_get_view($name);
910 return $view->preview($display_id, $args);
916 function views_var_export($var, $prefix = '') {
917 if (is_array($var)) {
922 $output = "array(\n";
923 foreach ($var as
$key => $value) {
924 $output .
= " '$key' => " .
views_var_export($value, ' ') .
",\n";
929 else if (is_bool($var)) {
930 $output = $var ?
'TRUE' : 'FALSE';
933 $output = var_export($var, TRUE
);
937 $output = str_replace("\n", "\n$prefix", $output);
944 * Prepare the specified string for use as a CSS identifier.
946 function views_css_safe($string) {
947 return str_replace('_', '-', $string);