#1172714 by dereine, mstrelan: Allow to add a display of the same view in an area...
[project/views.git] / views.module
CommitLineData
31dd0540 1<?php
31dd0540
TGGM
2/**
3 * @file
4 * Primarily Drupal hooks and global API functions to manipulate views.
5 *
6 * This is the main module file for Views. The main entry points into
7 * this module are views_page() and views_block(), where it handles
8 * incoming page and block requests.
9 */
10
11/**
12 * Advertise the current views api version
13 */
14function views_api_version() {
edff66bb 15 return '3.0';
a8f0683e
DW
16}
17
18/**
28161496
DW
19 * Implements hook_forms().
20 *
21 * To provide distinct form IDs for Views forms, the View name and
22 * specific display name are appended to the base ID,
23 * views_form_views_form. When such a form is built or submitted, this
24 * function will return the proper callback function to use for the given form.
25 */
26function views_forms($form_id, $args) {
27 if (strpos($form_id, 'views_form_') === 0) {
28 return array(
29 $form_id => array(
30 'callback' => 'views_form',
31 ),
32 );
33 }
34}
35
36/**
37 * Returns a form ID for a Views form using the name and display of the View.
38 */
2f011518 39function views_form_id($view) {
28161496
DW
40 $parts = array(
41 'views_form',
42 $view->name,
43 $view->current_display,
44 );
45
46 return implode('_', $parts);
47}
48
49/**
a8f0683e
DW
50 * Views will not load plugins advertising a version older than this.
51 */
52function views_api_minimum_version() {
53 return '2';
31dd0540
TGGM
54}
55
56/**
2dd76fe0 57 * Implement hook_theme(). Register views theming functions.
31dd0540 58 */
9cab79ea 59function views_theme($existing, $type, $theme, $path) {
31dd0540 60 $path = drupal_get_path('module', 'views');
2dd76fe0 61 include_once $path . '/theme/theme.inc';
31dd0540
TGGM
62
63 // Some quasi clever array merging here.
64 $base = array(
65 'file' => 'theme.inc',
2dd76fe0 66 'path' => $path . '/theme',
31dd0540
TGGM
67 );
68
69 // Our extra version of pager from pager.inc
70 $hooks['views_mini_pager'] = $base + array(
ac9dea05 71 'variables' => array('tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()),
31dd0540
TGGM
72 'pattern' => 'views_mini_pager__',
73 );
74
de509319 75 $variables = array(
613984f1
DR
76 // For displays, we pass in a dummy array as the first parameter, since
77 // $view is an object but the core contextual_preprocess() function only
78 // attaches contextual links when the primary theme argument is an array.
79 'display' => array('view_array' => array(), 'view' => NULL),
31dd0540
TGGM
80 'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
81 'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
06592a30
DW
82 'exposed_form' => array('view' => NULL, 'options' => NULL),
83 'pager' => array(
84 'view' => NULL, 'options' => NULL,
85 'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
86 ),
31dd0540
TGGM
87 );
88
89 // Default view themes
90 $hooks['views_view_field'] = $base + array(
91 'pattern' => 'views_view_field__',
3c45ad76 92 'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
31dd0540
TGGM
93 );
94
95 $plugins = views_fetch_plugin_data();
96
97 // Register theme functions for all style plugins
98 foreach ($plugins as $type => $info) {
99 foreach ($info as $plugin => $def) {
03810048 100 if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
31dd0540
TGGM
101 $hooks[$def['theme']] = array(
102 'pattern' => $def['theme'] . '__',
103 'file' => $def['theme file'],
104 'path' => $def['theme path'],
de509319 105 'variables' => $variables[$type],
31dd0540
TGGM
106 );
107
2dd76fe0 108 $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
31dd0540
TGGM
109 if (file_exists($include)) {
110 require_once $include;
111 }
112
113 if (!function_exists('theme_' . $def['theme'])) {
5be494e5 114 $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
31dd0540
TGGM
115 }
116 }
117 if (isset($def['additional themes'])) {
118 foreach ($def['additional themes'] as $theme => $theme_type) {
119 if (empty($theme_type)) {
120 $theme = $theme_type;
121 $theme_type = $type;
122 }
123
124 $hooks[$theme] = array(
125 'pattern' => $theme . '__',
126 'file' => $def['theme file'],
127 'path' => $def['theme path'],
de509319 128 'variables' => $variables[$theme_type],
31dd0540
TGGM
129 );
130
131 if (!function_exists('theme_' . $theme)) {
5be494e5 132 $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
31dd0540
TGGM
133 }
134 }
135 }
136 }
137 }
138
28161496
DW
139 $hooks['views_form_views_form'] = $base + array(
140 'render element' => 'form',
141 );
142
31dd0540
TGGM
143 $hooks['views_exposed_form'] = $base + array(
144 'template' => 'views-exposed-form',
145 'pattern' => 'views_exposed_form__',
3c45ad76 146 'render element' => 'form',
31dd0540
TGGM
147 );
148
149 $hooks['views_more'] = $base + array(
150 'template' => 'views-more',
151 'pattern' => 'views_more__',
0a9f6a19 152 'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
31dd0540 153 );
9cab79ea
DW
154
155 // Add theme suggestions which are part of modules.
156 foreach (views_get_module_apis() as $info) {
157 if (isset($info['template path'])) {
158 $hooks += _views_find_module_templates($hooks, $info['template path']);
159 }
160 }
31dd0540
TGGM
161 return $hooks;
162}
163
164/**
9cab79ea
DW
165 * Scans a directory of a module for template files.
166 *
167 * @param $cache
168 * The existing cache of theme hooks to test against.
169 * @param $path
170 * The path to search.
ced80ad3 171 *
9cab79ea
DW
172 * @see drupal_find_theme_templates
173 */
174function _views_find_module_templates($cache, $path) {
bfc83fe2 175 $templates = array();
ce243f57 176 $regex = '/' . '\.tpl\.php' . '$' . '/';
9cab79ea
DW
177
178 // Because drupal_system_listing works the way it does, we check for real
179 // templates separately from checking for patterns.
180 $files = drupal_system_listing($regex, $path, 'name', 0);
9cab79ea
DW
181 foreach ($files as $template => $file) {
182 // Chop off the remaining extensions if there are any. $template already
183 // has the rightmost extension removed, but there might still be more,
184 // such as with .tpl.php, which still has .tpl in $template at this point.
185 if (($pos = strpos($template, '.')) !== FALSE) {
186 $template = substr($template, 0, $pos);
187 }
188 // Transform - in filenames to _ to match function naming scheme
189 // for the purposes of searching.
190 $hook = strtr($template, '-', '_');
191 if (isset($cache[$hook])) {
192 $templates[$hook] = array(
193 'template' => $template,
194 'path' => dirname($file->filename),
ce243f57 195 'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
9cab79ea
DW
196 );
197 }
198 // Ensure that the pattern is maintained from base themes to its sub-themes.
199 // Each sub-theme will have their templates scanned so the pattern must be
200 // held for subsequent runs.
201 if (isset($cache[$hook]['pattern'])) {
202 $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
203 }
204 }
205
206 $patterns = array_keys($files);
207
9cab79ea
DW
208 foreach ($cache as $hook => $info) {
209 if (!empty($info['pattern'])) {
210 // Transform _ in pattern to - to match file naming scheme
211 // for the purposes of searching.
212 $pattern = strtr($info['pattern'], '_', '-');
213
214 $matches = preg_grep('/^'. $pattern .'/', $patterns);
215 if ($matches) {
216 foreach ($matches as $match) {
217 $file = substr($match, 0, strpos($match, '.'));
218 // Put the underscores back in for the hook name and register this pattern.
9cab79ea
DW
219 $templates[strtr($file, '-', '_')] = array(
220 'template' => $file,
b3bca25c 221 'path' => dirname($files[$match]->uri),
26525633
DW
222 'variables' => isset($info['variables']) ? $info['variables'] : NULL,
223 'render element' => isset($info['render element']) ? $info['render element'] : NULL,
d0fdb348 224 'base hook' => $hook,
ce243f57 225 'includes' => isset($info['includes']) ? $info['includes'] : NULL,
9cab79ea
DW
226 );
227 }
228 }
229 }
230 }
231
232 return $templates;
233}
234
235/**
31dd0540
TGGM
236 * A theme preprocess function to automatically allow view-based node
237 * templates if called from a view.
238 *
239 * The 'modules/node.views.inc' file is a better place for this, but
240 * we haven't got a chance to load that file before Drupal builds the
241 * node portion of the theme registry.
242 */
243function views_preprocess_node(&$vars) {
4e8f216a 244 // The 'view' attribute of the node is added in views_preprocess_node()
31dd0540 245 if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
4e8f216a 246 $vars['view'] = $vars['node']->view;
742335fc 247 $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
2dd76fe0 248 if (!empty($vars['node']->view->current_display)) {
742335fc 249 $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
bb5cf8f9
EM
250
251 // If a node is being rendered in a view, and the view does not have a path,
252 // prevent drupal from accidentally setting the $page variable:
253 if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
254 $vars['page'] = FALSE;
255 }
31dd0540 256 }
4e8f216a 257 }
bb5cf8f9 258
4e8f216a
DW
259 // Allow to alter comments and links based on the settings in the row plugin.
260 if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
261 node_row_node_view_preprocess_node($vars);
31dd0540
TGGM
262 }
263}
264
265/**
266 * A theme preprocess function to automatically allow view-based node
267 * templates if called from a view.
268 */
269function views_preprocess_comment(&$vars) {
270 // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
271 if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
2f011518 272 $vars['view'] = &$vars['node']->view;
742335fc 273 $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
2dd76fe0 274 if (!empty($vars['node']->view->current_display)) {
742335fc 275 $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
31dd0540
TGGM
276 }
277 }
278}
279
280/*
2dd76fe0 281 * Implement hook_permission().
31dd0540 282 */
2dd76fe0
EM
283function views_permission() {
284 return array(
285 'administer views' => array(
286 'title' => t('Administer views'),
287 'description' => t('Access the views administration pages.'),
288 ),
289 'access all views' => array(
8a1ee587 290 'title' => t('Bypass views access control'),
2dd76fe0
EM
291 'description' => t('Bypass access control when accessing views.'),
292 ),
293 );
31dd0540
TGGM
294}
295
296/**
2dd76fe0 297 * Implement hook_menu().
31dd0540
TGGM
298 */
299function views_menu() {
300 // Any event which causes a menu_rebuild could potentially mean that the
301 // Views data is updated -- module changes, profile changes, etc.
302 views_invalidate_cache();
303 $items = array();
304 $items['views/ajax'] = array(
305 'title' => 'Views',
306 'page callback' => 'views_ajax',
48336fba 307 'theme callback' => 'ajax_base_page_theme',
7552acbb 308 'delivery callback' => 'ajax_deliver',
31dd0540
TGGM
309 'access callback' => TRUE,
310 'description' => 'Ajax callback for view loading.',
31dd0540 311 'type' => MENU_CALLBACK,
7552acbb 312 'file' => 'includes/ajax.inc',
31dd0540 313 );
2dd76fe0 314 // Path is not admin/structure/views due to menu complications with the wildcards from
31dd0540
TGGM
315 // the generic ajax callback.
316 $items['admin/views/ajax/autocomplete/user'] = array(
317 'page callback' => 'views_ajax_autocomplete_user',
48336fba 318 'theme callback' => 'ajax_base_page_theme',
31dd0540
TGGM
319 'access callback' => 'user_access',
320 'access arguments' => array('access content'),
31dd0540 321 'type' => MENU_CALLBACK,
2dd76fe0 322 'file' => 'includes/ajax.inc',
31dd0540 323 );
bd3ad3ed
DW
324 // Define another taxonomy autocomplete because the default one of drupal
325 // does not support a vid a argument anymore
326 $items['admin/views/ajax/autocomplete/taxonomy'] = array(
327 'page callback' => 'views_ajax_autocomplete_taxonomy',
48336fba 328 'theme callback' => 'ajax_base_page_theme',
bd3ad3ed
DW
329 'access callback' => 'user_access',
330 'access arguments' => array('access content'),
331 'type' => MENU_CALLBACK,
332 'file' => 'includes/ajax.inc',
333 );
31dd0540
TGGM
334 return $items;
335}
336
337/**
2dd76fe0 338 * Implement hook_menu_alter().
31dd0540
TGGM
339 */
340function views_menu_alter(&$callbacks) {
341 $our_paths = array();
342 $views = views_get_applicable_views('uses hook menu');
343 foreach ($views as $data) {
344 list($view, $display_id) = $data;
58f55929 345 $result = $view->execute_hook_menu($display_id, $callbacks);
31dd0540
TGGM
346 if (is_array($result)) {
347 // The menu system doesn't support having two otherwise
348 // identical paths with different placeholders. So we
349 // want to remove the existing items from the menu whose
350 // paths would conflict with ours.
351
352 // First, we must find any existing menu items that may
353 // conflict. We use a regular expression because we don't
354 // know what placeholders they might use. Note that we
355 // first construct the regex itself by replacing %views_arg
356 // in the display path, then we use this constructed regex
357 // (which will be something like '#^(foo/%[^/]*/bar)$#') to
358 // search through the existing paths.
359 $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
360 $matches = preg_grep($regex, array_keys($callbacks));
361
362 // Remove any conflicting items that were found.
363 foreach ($matches as $path) {
364 // Don't remove the paths we just added!
365 if (!isset($our_paths[$path])) {
366 unset($callbacks[$path]);
367 }
368 }
369 foreach ($result as $path => $item) {
370 if (!isset($callbacks[$path])) {
371 // Add a new item, possibly replacing (and thus effectively
372 // overriding) one that we removed above.
373 $callbacks[$path] = $item;
374 }
375 else {
376 // This item already exists, so it must be one that we added.
377 // We change the various callback arguments to pass an array
378 // of possible display IDs instead of a single ID.
379 $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
380 $callbacks[$path]['page arguments'][1][] = $display_id;
381 $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
382 $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
383 $callbacks[$path]['load arguments'][1][] = $display_id;
384 }
385 $our_paths[$path] = TRUE;
386 }
387 }
388 }
389
390 // Save memory: Destroy those views.
391 foreach ($views as $data) {
392 list($view, $display_id) = $data;
393 $view->destroy();
394 }
395}
396
397/**
398 * Helper function for menu loading. This will automatically be
399 * called in order to 'load' a views argument; primarily it
400 * will be used to perform validation.
401 *
402 * @param $value
403 * The actual value passed.
404 * @param $name
405 * The name of the view. This needs to be specified in the 'load function'
406 * of the menu entry.
f9c4cee0
EM
407 * @param $display_id
408 * The display id that will be loaded for this menu item.
31dd0540
TGGM
409 * @param $index
410 * The menu argument index. This counts from 1.
411 */
412function views_arg_load($value, $name, $display_id, $index) {
f9c4cee0
EM
413 static $views = array();
414
415 // Make sure we haven't already loaded this views argument for a similar menu
416 // item elsewhere.
417 $key = $name . ':' . $display_id . ':' . $value . ':' . $index;
418 if (isset($views[$key])) {
419 return $views[$key];
420 }
421
31dd0540
TGGM
422 if ($view = views_get_view($name)) {
423 $view->set_display($display_id);
424 $view->init_handlers();
425
426 $ids = array_keys($view->argument);
427
428 $indexes = array();
429 $path = explode('/', $view->get_path());
430
431 foreach ($path as $id => $piece) {
432 if ($piece == '%' && !empty($ids)) {
433 $indexes[$id] = array_shift($ids);
434 }
435 }
436
437 if (isset($indexes[$index])) {
438 if (isset($view->argument[$indexes[$index]])) {
439 $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
440 $view->destroy();
f9c4cee0
EM
441
442 // Store the output in case we load this same menu item again.
443 $views[$key] = $arg;
31dd0540
TGGM
444 return $arg;
445 }
446 }
447 $view->destroy();
448 }
449}
450
451/**
452 * Page callback entry point; requires a view and a display id, then
453 * passes control to the display handler.
454 */
455function views_page() {
456 $args = func_get_args();
457 $name = array_shift($args);
458 $display_id = array_shift($args);
459
60115937 460 // Load the view and render it.
31dd0540 461 if ($view = views_get_view($name)) {
60115937 462 return $view->execute_display($display_id, $args);
31dd0540
TGGM
463 }
464
465 // Fallback; if we get here no view was found or handler was not valid.
466 return drupal_not_found();
467}
468
469/**
613984f1
DR
470 * Implements hook_page_alter().
471 */
472function views_page_alter(&$page) {
60115937 473 // If the main content of this page contains a view, attach its contextual
613984f1
DR
474 // links to the overall page array. This allows them to be rendered directly
475 // next to the page title.
60115937
DW
476 $view = views_get_page_view();
477 if (!empty($view)) {
6ff79b80
EM
478 // If a module is still putting in the display like we used to, catch that.
479 if (is_subclass_of($view, 'views_plugin_display')) {
480 $view = $view->view;
481 }
482
60115937 483 views_add_contextual_links($page, 'page', $view, $view->current_display);
613984f1
DR
484 }
485}
486
487/**
488 * Implements MODULE_preprocess_HOOK().
489 */
490function views_preprocess_html(&$variables) {
491 // If the page contains a view as its main content, contextual links may have
492 // been attached to the page as a whole; for example, by views_page_alter().
493 // This allows them to be associated with the page and rendered by default
494 // next to the page title (which we want). However, it also causes the
495 // Contextual Links module to treat the wrapper for the entire page (i.e.,
496 // the <body> tag) as the HTML element that these contextual links are
497 // associated with. This we don't want; for better visual highlighting, we
498 // prefer a smaller region to be chosen. The region we prefer differs from
499 // theme to theme and depends on the details of the theme's markup in
500 // page.tpl.php, so we can only find it using JavaScript. We therefore remove
501 // the "contextual-links-region" class from the <body> tag here and add
502 // JavaScript that will insert it back in the correct place.
503 if (!empty($variables['page']['#views_contextual_links_info'])) {
504 $key = array_search('contextual-links-region', $variables['classes_array']);
505 if ($key !== FALSE) {
506 unset($variables['classes_array'][$key]);
507 // Add the JavaScript, with a group and weight such that it will run
508 // before modules/contextual/contextual.js.
509 drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
510 }
511 }
512}
513
514/**
515 * Implements hook_contextual_links_view_alter().
516 */
517function views_contextual_links_view_alter(&$element, $items) {
518 // If we are rendering views-related contextual links attached to the overall
519 // page array, add a class to the list of contextual links. This will be used
520 // by the JavaScript added in views_preprocess_html().
521 if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
522 $element['#attributes']['class'][] = 'views-contextual-links-page';
523 }
524}
525
526/**
ea9088df 527 * Implement hook_block_info().
31dd0540 528 */
ea9088df 529function views_block_info() {
9f1018b7
EM
530 // Try to avoid instantiating all the views just to get the blocks info.
531 views_include('cache');
532 $cache = views_cache_get('views_block_items', TRUE);
533 if ($cache && is_array($cache->data)) {
534 return $cache->data;
535 }
536
2dd76fe0
EM
537 $items = array();
538 $views = views_get_all_views();
539 foreach ($views as $view) {
540 // disabled views get nothing.
541 if (!empty($view->disabled)) {
542 continue;
543 }
31dd0540 544
2dd76fe0
EM
545 $view->init_display();
546 foreach ($view->display as $display_id => $display) {
31dd0540 547
2dd76fe0
EM
548 if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
549 $result = $display->handler->execute_hook_block_list();
550 if (is_array($result)) {
551 $items = array_merge($items, $result);
31dd0540
TGGM
552 }
553 }
554
2dd76fe0
EM
555 if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
556 $result = $display->handler->get_special_blocks();
557 if (is_array($result)) {
558 $items = array_merge($items, $result);
31dd0540
TGGM
559 }
560 }
2dd76fe0
EM
561 }
562 }
31dd0540 563
2dd76fe0
EM
564 // block.module has a delta length limit of 32, but our deltas can
565 // unfortunately be longer because view names can be 32 and display IDs
566 // can also be 32. So for very long deltas, change to md5 hashes.
567 $hashes = array();
568
569 // get the keys because we're modifying the array and we don't want to
570 // confuse PHP too much.
571 $keys = array_keys($items);
572 foreach ($keys as $delta) {
573 if (strlen($delta) >= 32) {
574 $hash = md5($delta);
575 $hashes[$hash] = $delta;
576 $items[$hash] = $items[$delta];
577 unset($items[$delta]);
578 }
579 }
31dd0540 580
593e6ae6
DW
581 // Only save hashes if they have changed.
582 $old_hashes = variable_get('views_block_hashes', array());
583 if ($hashes != $old_hashes) {
584 variable_set('views_block_hashes', $hashes);
585 }
2dd76fe0
EM
586 // Save memory: Destroy those views.
587 foreach ($views as $view) {
588 $view->destroy();
589 }
31dd0540 590
9f1018b7
EM
591 views_cache_set('views_block_items', $items, TRUE);
592
2dd76fe0
EM
593 return $items;
594}
595
596/**
597 * Implement hook_block_view().
598 */
599function views_block_view($delta) {
600 $start = microtime(TRUE);
601 // if this is 32, this should be an md5 hash.
602 if (strlen($delta) == 32) {
603 $hashes = variable_get('views_block_hashes', array());
604 if (!empty($hashes[$delta])) {
605 $delta = $hashes[$delta];
606 }
607 }
31dd0540 608
2dd76fe0
EM
609 // This indicates it's a special one.
610 if (substr($delta, 0, 1) == '-') {
611 list($nothing, $type, $name, $display_id) = explode('-', $delta);
612 // Put the - back on.
613 $type = '-' . $type;
614 if ($view = views_get_view($name)) {
615 if ($view->access($display_id)) {
616 $view->set_display($display_id);
617 if (isset($view->display_handler)) {
618 $output = $view->display_handler->view_special_blocks($type);
613984f1
DR
619 // Before returning the block output, convert it to a renderable
620 // array with contextual links.
44e4a371 621 views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
31dd0540
TGGM
622 $view->destroy();
623 return $output;
624 }
31dd0540 625 }
2dd76fe0
EM
626 $view->destroy();
627 }
628 }
629
7cd1a3d5
DW
630 // If the delta doesn't contain valid data return nothing.
631 $explode = explode('-', $delta);
632 if (count($explode) != 2) {
633 return;
634 }
635 list($name, $display_id) = $explode;
2dd76fe0
EM
636 // Load the view
637 if ($view = views_get_view($name)) {
638 if ($view->access($display_id)) {
639 $output = $view->execute_display($display_id);
613984f1
DR
640 // Before returning the block output, convert it to a renderable array
641 // with contextual links.
642 views_add_block_contextual_links($output, $view, $display_id);
2dd76fe0
EM
643 $view->destroy();
644 return $output;
645 }
646 $view->destroy();
31dd0540
TGGM
647 }
648}
649
650/**
613984f1
DR
651 * Converts Views block content to a renderable array with contextual links.
652 *
653 * @param $block
654 * An array representing the block, with the same structure as the return
655 * value of hook_block_view(). This will be modified so as to force
656 * $block['content'] to be a renderable array, containing the optional
657 * '#contextual_links' property (if there are any contextual links associated
658 * with the block).
659 * @param $view
660 * The view that was used to generate the block content.
661 * @param $display_id
662 * The ID of the display within the view that was used to generate the block
663 * content.
44e4a371
DW
664 * @param $block_type
665 * The type of the block. If it's block it's a regular views display,
666 * but 'special_block_-exp' exist as well.
613984f1 667 */
2f011518 668function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
613984f1
DR
669 // Do not add contextual links to an empty block.
670 if (!empty($block['content'])) {
671 // Contextual links only work on blocks whose content is a renderable
672 // array, so if the block contains a string of already-rendered markup,
673 // convert it to an array.
674 if (is_string($block['content'])) {
675 $block['content'] = array('#markup' => $block['content']);
676 }
677 // Add the contextual links.
44e4a371 678 views_add_contextual_links($block['content'], $block_type, $view, $display_id);
613984f1
DR
679 }
680}
681
682/**
683 * Adds contextual links associated with a view display to a renderable array.
684 *
685 * This function should be called when a view is being rendered in a particular
686 * location and you want to attach the appropriate contextual links (e.g.,
687 * links for editing the view) to it.
688 *
689 * The function operates by checking the view's display plugin to see if it has
690 * defined any contextual links that are intended to be displayed in the
691 * requested location; if so, it attaches them. The contextual links intended
692 * for a particular location are defined by the 'contextual links' and
693 * 'contextual links locations' properties in hook_views_plugins() and
694 * hook_views_plugins_alter(); as a result, these hook implementations have
695 * full control over where and how contextual links are rendered for each
696 * display.
697 *
698 * In addition to attaching the contextual links to the passed-in array (via
699 * the standard #contextual_links property), this function also attaches
700 * additional information via the #views_contextual_links_info property. This
701 * stores an array whose keys are the names of each module that provided
702 * views-related contextual links (same as the keys of the #contextual_links
703 * array itself) and whose values are themselves arrays whose keys ('location',
704 * 'view_name', and 'view_display_id') store the location, name of the view,
705 * and display ID that were passed in to this function. This allows you to
706 * access information about the contextual links and how they were generated in
707 * a variety of contexts where you might be manipulating the renderable array
708 * later on (for example, alter hooks which run later during the same page
709 * request).
710 *
711 * @param $render_element
712 * The renderable array to which contextual links will be added. This array
713 * should be suitable for passing in to drupal_render() and will normally
714 * contain a representation of the view display whose contextual links are
715 * being requested.
716 * @param $location
717 * The location in which the calling function intends to render the view and
718 * its contextual links. The core system supports three options for this
719 * parameter:
720 * - 'block': Used when rendering a block which contains a view. This
721 * retrieves any contextual links intended to be attached to the block
722 * itself.
723 * - 'page': Used when rendering the main content of a page which contains a
724 * view. This retrieves any contextual links intended to be attached to the
725 * page itself (for example, links which are displayed directly next to the
726 * page title).
727 * - 'view': Used when rendering the view itself, in any context. This
728 * retrieves any contextual links intended to be attached directly to the
729 * view.
730 * If you are rendering a view and its contextual links in another location,
731 * you can pass in a different value for this parameter. However, you will
732 * also need to use hook_views_plugins() or hook_views_plugins_alter() to
733 * declare, via the 'contextual links locations' array key, which view
734 * displays support having their contextual links rendered in the location
735 * you have defined.
736 * @param $view
737 * The view whose contextual links will be added.
738 * @param $display_id
739 * The ID of the display within $view whose contextual links will be added.
740 *
741 * @see hook_views_plugins()
742 * @see views_block_view()
743 * @see views_page_alter()
744 * @see template_preprocess_views_view()
745 */
2f011518 746function views_add_contextual_links(&$render_element, $location, $view, $display_id) {
613984f1
DR
747 // Do not do anything if the view is configured to hide its administrative
748 // links.
749 if (empty($view->hide_admin_links)) {
750 // Also do not do anything if the display plugin has not defined any
751 // contextual links that are intended to be displayed in the requested
752 // location.
753 $plugin = views_fetch_plugin_data('display', $view->display[$display_id]->display_plugin);
9298e365
DW
754 // If contextual links locations are not set, provide a sane default. (To
755 // avoid displaying any contextual links at all, a display plugin can still
756 // set 'contextual links locations' to, e.g., an empty array.)
757 $plugin += array('contextual links locations' => array('view'));
44e4a371
DW
758 // On exposed_forms blocks contextual links should always be visible.
759 $plugin['contextual links locations'][] = 'special_block_-exp';
613984f1
DR
760 $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual links locations']);
761 if ($has_links && in_array($location, $plugin['contextual links locations'])) {
762 foreach ($plugin['contextual links'] as $module => $link) {
763 $args = array();
764 $valid = TRUE;
765 if (!empty($link['argument properties'])) {
766 foreach ($link['argument properties'] as $property) {
767 // If the plugin is trying to create an invalid contextual link
768 // (for example, "path/to/{$view->property}", where $view->property
769 // does not exist), we cannot construct the link, so we skip it.
770 if (!property_exists($view, $property)) {
771 $valid = FALSE;
772 break;
773 }
774 else {
775 $args[] = $view->{$property};
776 }
777 }
778 }
779 // If the link was valid, attach information about it to the renderable
780 // array.
781 if ($valid) {
782 $render_element['#contextual_links'][$module] = array($link['parent path'], $args);
783 $render_element['#views_contextual_links_info'][$module] = array(
784 'location' => $location,
785 'view_name' => $view->name,
786 'view_display_id' => $display_id,
787 );
788 }
789 }
790 }
791 }
792}
793
794/**
bf687b81 795 * Returns an array of language names.
3cd25ee7
DW
796 *
797 * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
798 *
799 * @param $field
800 * 'name' => names in current language, localized
801 * 'native' => native names
802 * @param $all
803 * Boolean to return all languages or only enabled ones
804 *
805 * @see locale_language_list
806 */
807function views_language_list($field = 'name', $all = FALSE) {
808 if ($all) {
809 $languages = language_list();
810 }
811 else {
812 $languages = language_list('enabled');
813 $languages = $languages[1];
814 }
815 $list = array();
816 foreach ($languages as $language) {
817 $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
818 }
819 return $list;
820}
821
822/**
50de0ded 823 * Implements hook_flush_caches().
31dd0540
TGGM
824 */
825function views_flush_caches() {
826 return array('cache_views', 'cache_views_data');
827}
828
829/**
50de0ded
DW
830 * Implements hook_field_create_instance.
831 */
832function views_field_create_instance($instance) {
833 cache_clear_all('*', 'cache_views', TRUE);
834 cache_clear_all('*', 'cache_views_data', TRUE);
835}
836
837/**
838 * Implements hook_field_update_instance.
839 */
840function views_field_update_instance($instance, $prior_instance) {
841 cache_clear_all('*', 'cache_views', TRUE);
842 cache_clear_all('*', 'cache_views_data', TRUE);
843}
844
845/**
846 * Implements hook_field_delete_instance.
847 */
848function views_field_delete_instance($instance) {
849 cache_clear_all('*', 'cache_views', TRUE);
850 cache_clear_all('*', 'cache_views_data', TRUE);
851}
852
853/**
31dd0540
TGGM
854 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
855 */
856function views_invalidate_cache() {
2dd76fe0 857 cache_clear_all('*', 'cache_views', TRUE);
31dd0540
TGGM
858}
859
860/**
2b31a2d7
EM
861 * Access callback to determine if the user can import Views.
862 *
863 * View imports require an additional access check because they are PHP
864 * code and PHP is more locked down than administer views.
865 */
866function views_import_access() {
0ef49a5b 867 return user_access('administer views') && user_access('use PHP for settings');
2b31a2d7
EM
868}
869
870/**
31dd0540
TGGM
871 * Determine if the logged in user has access to a view.
872 *
873 * This function should only be called from a menu hook or some other
874 * embedded source. Each argument is the result of a call to
875 * views_plugin_access::get_access_callback() which is then used
876 * to determine if that display is accessible. If *any* argument
877 * is accessible, then the view is accessible.
878 */
879function views_access() {
31dd0540
TGGM
880 $args = func_get_args();
881 foreach ($args as $arg) {
882 if ($arg === TRUE) {
883 return TRUE;
884 }
885
886 if (!is_array($arg)) {
887 continue;
888 }
889
890 list($callback, $arguments) = $arg;
5e2c1962 891 $arguments = $arguments ? $arguments : array();
0e0ae1a6
DW
892 // Bring dynamic arguments to the access callback.
893 foreach ($arguments as $key => $value) {
894 if (is_int($value) && isset($args[$value])) {
895 $arguments[$key] = $args[$value];
896 }
897 }
31dd0540
TGGM
898 if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
899 return TRUE;
900 }
901 }
902
903 return FALSE;
904}
905
906/**
5eb98d56 907 * Access callback for the views_plugin_access_perm access plugin.
31dd0540 908 *
5eb98d56
EM
909 * Determine if the specified user has access to a view on the basis of
910 * permissions. If the $account argument is omitted, the current user
911 * is used.
912 */
913function views_check_perm($perm, $account = NULL) {
914 return user_access($perm, $account) || user_access('access all views', $account);
915}
916
917/**
918 * Access callback for the views_plugin_access_role access plugin.
919
920 * Determine if the specified user has access to a view on the basis of any of
921 * the requested roles. If the $account argument is omitted, the current user
922 * is used.
31dd0540 923 */
5eb98d56 924function views_check_roles($rids, $account = NULL) {
31dd0540 925 global $user;
5eb98d56
EM
926 $account = isset($account) ? $account : $user;
927 $roles = array_keys($account->roles);
928 $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
929 return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
31dd0540
TGGM
930}
931// ------------------------------------------------------------------
932// Functions to help identify views that are running or ran
933
934/**
935 * Set the current 'page view' that is being displayed so that it is easy
936 * for other modules or the theme to identify.
937 */
938function &views_set_page_view($view = NULL) {
939 static $cache = NULL;
940 if (isset($view)) {
941 $cache = $view;
942 }
943
944 return $cache;
945}
946
947/**
948 * Find out what, if any, page view is currently in use. Please note that
949 * this returns a reference, so be careful! You can unintentionally modify the
950 * $view object.
f50dcc09
DW
951 *
952 * @return view
19e2d166 953 * A fully formed, empty $view object.
31dd0540
TGGM
954 */
955function &views_get_page_view() {
956 return views_set_page_view();
957}
958
959/**
960 * Set the current 'current view' that is being built/rendered so that it is
961 * easy for other modules or items in drupal_eval to identify
962 */
963function &views_set_current_view($view = NULL) {
964 static $cache = NULL;
965 if (isset($view)) {
966 $cache = $view;
967 }
968
969 return $cache;
970}
971
972/**
973 * Find out what, if any, current view is currently in use. Please note that
974 * this returns a reference, so be careful! You can unintentionally modify the
975 * $view object.
976 */
977function &views_get_current_view() {
978 return views_set_current_view();
979}
980
981// ------------------------------------------------------------------
982// Include file helpers
983
984/**
985 * Include views .inc files as necessary.
986 */
987function views_include($file) {
344b79a6 988 ctools_include($file, 'views');
31dd0540
TGGM
989}
990
991/**
992 * Load views files on behalf of modules.
993 */
ce174eb6
EM
994function views_module_include($api, $reset = FALSE) {
995 if ($reset) {
996 $cache = &drupal_static('ctools_plugin_api_info');
997 if (isset($cache['views']['views'])) {
998 unset($cache['views']['views']);
31dd0540
TGGM
999 }
1000 }
ce174eb6
EM
1001 ctools_include('plugins');
1002 return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
31dd0540
TGGM
1003}
1004
1005/**
1006 * Get a list of modules that support the current views API.
1007 */
ce174eb6
EM
1008function views_get_module_apis($api = 'views', $reset = FALSE) {
1009 if ($reset) {
1010 $cache = &drupal_static('ctools_plugin_api_info');
1011 if (isset($cache['views']['views'])) {
1012 unset($cache['views']['views']);
31dd0540
TGGM
1013 }
1014 }
ce174eb6
EM
1015 ctools_include('plugins');
1016 return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
31dd0540
TGGM
1017}
1018
1019/**
1020 * Include views .css files.
1021 */
1022function views_add_css($file) {
1023 // We set preprocess to FALSE because we are adding the files conditionally,
1024 // and we don't want to generate duplicate cache files.
1025 // TODO: at some point investigate adding some files unconditionally and
1026 // allowing preprocess.
2dd76fe0 1027 drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
31dd0540
TGGM
1028}
1029
1030/**
1031 * Include views .js files.
1032 */
1033function views_add_js($file) {
1034 // If javascript has been disabled by the user, never add js files.
1035 if (variable_get('views_no_javascript', FALSE)) {
1036 return;
1037 }
2dd76fe0 1038 static $base = TRUE, $ajax = TRUE;
31dd0540
TGGM
1039 if ($base) {
1040 drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
1041 $base = FALSE;
1042 }
7552acbb
EM
1043 if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
1044 drupal_add_library('system', 'drupal.ajax');
1045 drupal_add_library('system', 'jquery.form');
1046 $ajax = FALSE;
2dd76fe0 1047 }
7552acbb 1048 ctools_add_js($file, 'views');
31dd0540
TGGM
1049}
1050
1051/**
1052 * Load views files on behalf of modules.
1053 */
8e218614 1054function views_include_handlers($reset = FALSE) {
31dd0540
TGGM
1055 static $finished = FALSE;
1056 // Ensure this only gets run once.
8e218614 1057 if ($finished && !$reset) {
31dd0540
TGGM
1058 return;
1059 }
1060
1061 views_include('base');
1062 views_include('handlers');
1063 views_include('cache');
1064 views_include('plugins');
ce174eb6 1065 views_module_include('views', $reset);
31dd0540
TGGM
1066 $finished = TRUE;
1067}
1068
1069// -----------------------------------------------------------------------
1070// Views handler functions
1071
1072/**
1073 * Fetch a handler from the data cache.
1074 *
1075 * @param $table
1076 * The name of the table this handler is from.
1077 * @param $field
1078 * The name of the field this handler is from.
1079 * @param $key
1080 * The type of handler. i.e, sort, field, argument, filter, relationship
a8f0683e
DW
1081 * @param $override
1082 * Override the actual handler object with this class. Used for
1083 * aggregation when the handler is redirected to the aggregation
1084 * handler.
31dd0540 1085 *
f509a867 1086 * @return views_handler
31dd0540
TGGM
1087 * An instance of a handler object. May be views_handler_broken.
1088 */
a8f0683e 1089function views_get_handler($table, $field, $key, $override = NULL) {
7539062c
DW
1090 static $recursion_protection = array();
1091
2a54f588 1092 $data = views_fetch_data($table, FALSE);
a8f0683e
DW
1093 $handler = NULL;
1094
273e8e92 1095 // Support old views_data entries conversion.
2a54f588 1096
08d87a06
DW
1097 // Support conversion on table level.
1098 if (isset($data['moved to'])) {
1099 $moved = array($data['moved to'], $field);
1100 }
1101 // Support conversion on datafield level.
273e8e92
DW
1102 if (isset($data[$field]['moved to'])) {
1103 $moved = $data[$field]['moved to'];
1104 }
08d87a06 1105 // Support conversion on handler level.
273e8e92
DW
1106 if (isset($data[$field][$key]['moved to'])) {
1107 $moved = $data[$field][$key]['moved to'];
1108 }
2a54f588 1109
3dcacbc8 1110 if (isset($data[$field][$key]) || !empty($moved)) {
62ebe49d
DW
1111 if (!empty($moved)) {
1112 list($moved_table, $moved_field) = $moved;
7539062c
DW
1113 if (!empty($recursion_protection[$moved_table][$moved_field])) {
1114 // recursion detected!
1115 return NULL;
1116 }
2a54f588 1117
7539062c
DW
1118 $recursion_protection[$moved_table][$moved_field] = TRUE;
1119 $handler = views_get_handler($moved_table, $moved_field, $key, $override);
1120 $recursion_protection = array();
1121 if ($handler) {
1122 // store these values so we know what we were originally called.
1123 $handler->original_table = $table;
1124 $handler->original_field = $field;
1125 if (empty($handler->actual_table)) {
1126 $handler->actual_table = $moved_table;
1127 $handler->actual_field = $moved_field;
1128 }
1129 }
1130 return $handler;
1131 }
1132
31dd0540
TGGM
1133 // Set up a default handler:
1134 if (empty($data[$field][$key]['handler'])) {
1135 $data[$field][$key]['handler'] = 'views_handler_' . $key;
1136 }
a8f0683e
DW
1137
1138 if ($override) {
1139 $data[$field][$key]['override handler'] = $override;
1140 }
1141
6e9e2144 1142 $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
31dd0540 1143 }
a8f0683e
DW
1144
1145 if ($handler) {
1146 return $handler;
1147 }
1148
31dd0540 1149 // DEBUG -- identify missing handlers
7b79d410 1150 vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
31dd0540
TGGM
1151 $broken = array(
1152 'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
1153 'handler' => 'views_handler_' . $key . '_broken',
1154 'table' => $table,
1155 'field' => $field,
1156 );
cca2fbe7 1157 return _views_create_handler($broken, 'handler', $key);
31dd0540
TGGM
1158}
1159
1160/**
1161 * Fetch Views' data from the cache
1162 */
3dcacbc8 1163function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
31dd0540 1164 views_include('cache');
3dcacbc8 1165 return _views_fetch_data($table, $move, $reset);
31dd0540
TGGM
1166}
1167
1168// -----------------------------------------------------------------------
1169// Views plugin functions
1170
1171/**
1172 * Fetch the plugin data from cache.
1173 */
8e218614 1174function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
31dd0540 1175 views_include('cache');
8e218614 1176 return _views_fetch_plugin_data($type, $plugin, $reset);
31dd0540
TGGM
1177}
1178
1179/**
05a920ec
KS
1180 * Fetch a list of all base tables available
1181 *
1182 * @param $type
1183 * Either 'display', 'style' or 'row'
1184 * @param $key
1185 * For style plugins, this is an optional type to restrict to. May be 'normal',
1186 * 'summary', 'feed' or others based on the neds of the display.
1187 * @param $base
1188 * An array of possible base tables.
1189 *
1190 * @return
1191 * A keyed array of in the form of 'base_table' => 'Description'.
1192 */
1193function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
1194 $data = views_fetch_plugin_data();
1195
1196 $plugins[$type] = array();
1197
1198 foreach ($data[$type] as $id => $plugin) {
1199 // Skip plugins that don't conform to our key.
1200 if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
1201 continue;
1202 }
1203 if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
1204 $plugins[$type][$id] = $plugin['title'];
1205 }
1206 }
1207
1208 if (!empty($plugins[$type])) {
1209 asort($plugins[$type]);
1210 return $plugins[$type];
1211 }
1212 // fall-through
1213 return array();
1214}
1215
1216/**
31dd0540 1217 * Get a handler for a plugin
f509a867
DW
1218 *
1219 * @return views_plugin
1220 *
1221 * The created plugin object.
31dd0540 1222 */
8e218614 1223function views_get_plugin($type, $plugin, $reset = FALSE) {
2dd76fe0 1224 views_include('handlers');
8e218614 1225 $definition = views_fetch_plugin_data($type, $plugin, $reset);
31dd0540
TGGM
1226 if (!empty($definition)) {
1227 return _views_create_handler($definition, $type);
1228 }
1229}
1230
0c68bb6e
DW
1231/**
1232 * Load the current enabled localization plugin.
1233 *
1234 * @return The name of the localization plugin.
1235 */
1236function views_get_localization_plugin() {
1237 $plugin = variable_get('views_localization_plugin', '');
1238 // Provide sane default values for the localization plugin.
1239 if (empty($plugin)) {
1240 if (module_exists('locale')) {
1241 $plugin = 'core';
1242 }
1243 else {
1244 $plugin = 'none';
1245 }
1246 }
1247
1248 return $plugin;
1249}
1250
31dd0540
TGGM
1251// -----------------------------------------------------------------------
1252// Views database functions
1253
1254/**
ce174eb6 1255 * Get all view templates.
31dd0540 1256 *
ce174eb6
EM
1257 * Templates are special in-code views that are never active, but exist only
1258 * to be cloned into real views as though they were templates.
31dd0540 1259 */
ce174eb6
EM
1260function views_get_all_templates() {
1261 $templates = array();
1262 $modules = views_module_include('views_template');
1263
1264 foreach ($modules as $module => $info) {
1265 $function = $module . '_views_templates';
1266 if (function_exists($function)) {
1267 $new = $function();
1268 if ($new && is_array($new)) {
1269 $templates = array_merge($new, $templates);
1270 }
0dc1ea54 1271 }
7ba86678
EM
1272 }
1273
ce174eb6 1274 return $templates;
31dd0540
TGGM
1275}
1276
1277/**
1278 * Create an empty view to work with.
1279 *
f509a867 1280 * @return view
31dd0540
TGGM
1281 * A fully formed, empty $view object. This object must be populated before
1282 * it can be successfully saved.
1283 */
1284function views_new_view() {
1285 views_include('view');
1286 $view = new view();
1287 $view->vid = 'new';
1288 $view->add_display('default');
1289
1290 return $view;
1291}
1292
1293/**
31dd0540
TGGM
1294 * Return a list of all views and display IDs that have a particular
1295 * setting in their display's plugin settings.
1296 *
1297 * @return
1298 * @code
1299 * array(
1300 * array($view, $display_id),
1301 * array($view, $display_id),
1302 * );
1303 * @endcode
1304 */
1305function views_get_applicable_views($type) {
1306 // @todo: Use a smarter flagging system so that we don't have to
1307 // load every view for this.
1308 $result = array();
5ef1fa25 1309 $views = views_get_all_views();
31dd0540
TGGM
1310
1311 foreach ($views as $view) {
1312 // Skip disabled views.
1313 if (!empty($view->disabled)) {
1314 continue;
1315 }
1316
1317 if (empty($view->display)) {
1318 // Skip this view as it is broken.
1319 vsm(t("Skipping broken view @view", array('@view' => $view->name)));
1320 continue;
1321 }
1322
1323 // Loop on array keys because something seems to muck with $view->display
1324 // a bit in PHP4.
1325 foreach (array_keys($view->display) as $id) {
1326 $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
1327 if (!empty($plugin[$type])) {
1328 // This view uses hook menu. Clone it so that different handlers
1329 // don't trip over each other, and add it to the list.
1330 $v = $view->clone_view();
447f87cd 1331 if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
31dd0540
TGGM
1332 $result[] = array($v, $id);
1333 }
1334 // In PHP 4.4.7 and presumably earlier, if we do not unset $v
1335 // here, we will find that it actually overwrites references
1336 // possibly due to shallow copying issues.
1337 unset($v);
1338 }
1339 }
1340 }
1341 return $result;
1342}
1343
1344/**
1345 * Return an array of all views as fully loaded $view objects.
1346 *
1347 * @param $reset
1348 * If TRUE, reset the static cache forcing views to be reloaded.
1349 */
1350function views_get_all_views($reset = FALSE) {
29576778 1351 ctools_include('export');
237529c4 1352 return ctools_export_crud_load_all('views_view', $reset);
5ed42115 1353}
31dd0540 1354
5ed42115 1355/**
1b649ff6
DR
1356 * Returns an array of all enabled views, as fully loaded $view objects.
1357 */
1358function views_get_enabled_views() {
1359 $views = views_get_all_views();
1360 return array_filter($views, 'views_view_is_enabled');
1361}
31dd0540 1362
1b649ff6
DR
1363/**
1364 * Returns an array of all disabled views, as fully loaded $view objects.
1365 */
1366function views_get_disabled_views() {
1367 $views = views_get_all_views();
1368 return array_filter($views, 'views_view_is_disabled');
1369}
31dd0540 1370
1b649ff6 1371/**
6a2708a3
DW
1372 * Return an array of view as options array, that can be used by select,
1373 * checkboxes and radios as #options.
1374 *
1375 * @param bool $views_only
1376 * If TRUE, only return views, not displays.
1377 * @param string $filter
1378 * Filters the views on status. Can either be 'all' (default), 'enabled' or
1379 * 'disabled'
1380 * @param mixed $exclude_view
1381 * view or current display to exclude
1382 * either a
1383 * - views object (containing $exclude_view->name and $exclude_view->current_display)
1384 * - views name as string: e.g. my_view
1385 * - views name and display id (separated by ':'): e.g. my_view:default
1386 * @param bool $optgroup
1387 * If TRUE, returns an array with optgroups for each view (will be ignored for
1388 * $views_only = TRUE). Can be used by select
1389 *
1390 * @return array
1391 * an associative array for use in select.
1392 * - key: view name and display id separated by ':', or the view name only
1393 */
1394function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE) {
1395
1396 // Filter the big views array.
1397 switch ($filter) {
1398 case 'all':
1399 case 'disabled':
1400 case 'enabled':
1401 $func = "views_get_{$filter}_views";
1402 $views = $func();
1403 break;
1404 default:
1405 return array();
1406 }
1407
1408 // Prepare exclude view strings for comparison.
1409 if (empty($exclude_view)) {
1410 $exclude_view_name = '';
1411 $exclude_view_display = '';
1412 }
1413 elseif (is_object($exclude_view)) {
1414 $exclude_view_name = $exclude_view->name;
1415 $exclude_view_display = $exclude_view->current_display;
1416 }
1417 else {
1418 list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
1419 }
1420
1421 $options = array();
1422 foreach ($views as $view) {
1423 // Return only views.
1424 if ($views_only && $view->name != $exclude_view_name) {
32b24aff 1425 $options[$view->name] = $view->name;
6a2708a3
DW
1426 }
1427 // Return views with display ids.
1428 else {
1429 foreach ($view->display as $display_id => $display) {
c6135e13 1430 if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
6a2708a3
DW
1431 if ($optgroup) {
1432 $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
1433 }
1434 else {
1435 $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
1436 }
1437 }
1438 }
1439 }
1440 }
1441 return $options;
1442}
1443
1444/**
1b649ff6
DR
1445 * Returns TRUE if a view is enabled, FALSE otherwise.
1446 */
1447function views_view_is_enabled($view) {
1448 return empty($view->disabled);
1449}
31dd0540 1450
1b649ff6
DR
1451/**
1452 * Returns TRUE if a view is disabled, FALSE otherwise.
1453 */
2f011518 1454function views_view_is_disabled($view) {
1b649ff6 1455 return !empty($view->disabled);
31dd0540
TGGM
1456}
1457
1458/**
1459 * Get a view from the database or from default views.
1460 *
1461 * This function is just a static wrapper around views::load(). This function
1462 * isn't called 'views_load()' primarily because it might get a view
1463 * from the default views which aren't technically loaded from the database.
1464 *
1465 * @param $name
1466 * The name of the view.
1467 * @param $reset
1468 * If TRUE, reset this entry in the load cache.
2476ae8c 1469 * @return view
31dd0540
TGGM
1470 * A reference to the $view object. Use $reset if you're sure you want
1471 * a fresh one.
1472 */
1473function views_get_view($name, $reset = FALSE) {
29576778
EM
1474 if ($reset) {
1475 $cache = &drupal_static('ctools_export_load_object');
1476 if (isset($cache['views_view'][$name])) {
1477 unset($cache['views_view'][$name]);
5ed42115 1478 }
5ed42115 1479 }
31dd0540 1480
29576778
EM
1481 ctools_include('export');
1482 $view = ctools_export_crud_load('views_view', $name);
1483 if ($view) {
37a4f648 1484 $view->update();
29576778 1485 return $view->clone_view();
31dd0540 1486 }
5ed42115
KS
1487}
1488
ced80ad3 1489/**
d5462b32 1490 * Find the real location of a table.
37a4f648
EM
1491 *
1492 * If a table has moved, find the new name of the table so that we can
1493 * change its name directly in options where necessary.
1494 */
1495function views_move_table($table) {
1496 $data = views_fetch_data($table, FALSE);
1497 if (isset($data['moved to'])) {
1498 $table = $data['moved to'];
1499 }
1500
1501 return $table;
1502}
1503
1504/**
ced80ad3
EM
1505 * Export callback to load the view subrecords, which are the displays.
1506 */
1507function views_load_display_records(&$views) {
1508 // Get vids from the views.
1509 $names = array();
1510 foreach ($views as $view) {
1511 if (empty($view->display)) {
1512 $names[$view->vid] = $view->name;
aaef8fac 1513 }
31dd0540 1514 }
ced80ad3
EM
1515
1516 if (empty($names)) {
1517 return;
1518 }
1519
1520 foreach (view::db_objects() as $key) {
1521 $object_name = "views_$key";
1522 $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
1523 array(':vids' => array_keys($names)));
1524
1525 foreach ($result as $data) {
1526 $object = new $object_name(FALSE);
1527 $object->load_row($data);
1528
1529 // Because it can get complicated with this much indirection,
1530 // make a shortcut reference.
1531 $location = &$views[$names[$object->vid]]->$key;
1532
1533 // If we have a basic id field, load the item onto the view based on
1534 // this ID, otherwise push it on.
1535 if (!empty($object->id)) {
1536 $location[$object->id] = $object;
1537 }
1538 else {
1539 $location[] = $object;
1540 }
1541 }
31dd0540 1542 }
ced80ad3 1543}
31dd0540 1544
ced80ad3
EM
1545/**
1546 * Export CRUD callback to save a view.
1547 */
2f011518 1548function views_save_view(&$view) {
ced80ad3
EM
1549 return $view->save();
1550}
1551
1552/**
d9d2f291 1553 * Export CRUD callback to delete a view.
ced80ad3 1554 */
2f011518 1555function views_delete_view(&$view) {
ced80ad3
EM
1556 return $view->delete(TRUE);
1557}
1558
1559/**
1560 * Export CRUD callback to export a view.
1561 */
2f011518 1562function views_export_view(&$view, $indent = '') {
ced80ad3 1563 return $view->export($indent);
31dd0540
TGGM
1564}
1565
31dd0540
TGGM
1566// ------------------------------------------------------------------
1567// Views debug helper functions
1568
1569/**
7b79d410
DW
1570 * Provide debug output for Views.
1571 *
1572 * This relies on devel.module
1573 * or on the debug() function if you use a simpletest.
1574 *
1575 * @param $message
1576 * The message/variable which should be debugged.
1577 * This either could be
1578 * * an array/object which is converted to pretty output
1579 * * a translation source string which is used together with the parameter placeholders.
1580 *
1581 * @param $placeholder
1582 * The placeholders which are used for the translation source string.
e5814af7 1583 */
7b79d410
DW
1584function views_debug($message, $placeholders = array()) {
1585 if (!is_string($message)) {
1586 $output = '<pre>' . var_export($message, TRUE) . '</pre>';
1587 }
e5814af7 1588 if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
49f2a529
DW
1589 $devel_region = variable_get('views_devel_region', 'footer');
1590 if ($devel_region == 'watchdog') {
1591 $output = $message;
1592 watchdog('views_logging', $output, $placeholders);
1593 }
1594 else if ($devel_region == 'message') {
7b79d410 1595 $output = empty($output) ? t($message, $placeholders) : $output;
49f2a529
DW
1596 dpm($output);
1597 }
1598 else if ($devel_region == 'drupal_debug') {
1599 $output = empty($output) ? t($message, $placeholders) : $output;
1600 dd($output);
e5814af7
DW
1601 }
1602 else {
49f2a529 1603 drupal_add_region_content($devel_region, $output);
e5814af7
DW
1604 }
1605 }
7aa85602 1606 elseif (isset($GLOBALS['drupal_test_info'])) {
7b79d410
DW
1607 $output = empty($output) ? t($message, $placeholders) : $output;
1608 debug($output);
7aa85602 1609 }
e5814af7
DW
1610}
1611
1612/**
1613 * Shortcut to views_debug()
1614 */
7b79d410
DW
1615function vpr($message, $placeholders = array()) {
1616 views_debug($message, $placeholders);
e5814af7
DW
1617}
1618
1619/**
31dd0540
TGGM
1620 * Debug messages
1621 */
1622function vsm($message) {
1623 if (module_exists('devel')) {
d0457231 1624 dpm($message);
31dd0540
TGGM
1625 }
1626}
1627
1628function views_trace() {
1629 $message = '';
1630 foreach (debug_backtrace() as $item) {
1631 if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
1632 $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
1633 }
1634 }
1635 return $message;
1636}
1637
1638function vsm_trace() {
1639 vsm(views_trace());
1640}
1641
1642function vpr_trace() {
1643 dpr(views_trace());
1644}
1645
1646// ------------------------------------------------------------------
28161496
DW
1647// Views form (View with form elements)
1648
1649/**
e8d754f5
DW
1650 * Returns TRUE if the passed-in view contains handlers with views form
1651 * implementations, FALSE otherwise.
1652 */
2f011518 1653function views_view_has_form_elements($view) {
e8d754f5
DW
1654 foreach ($view->field as $field) {
1655 if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
1656 return TRUE;
1657 }
1658 }
1659 $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1660 $empty = empty($view->result);
1661 foreach ($area_handlers as $area) {
1662 if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1663 return TRUE;
1664 }
1665 }
1666 return FALSE;
1667}
1668
1669/**
28161496
DW
1670 * This is the entry function. Just gets the form for the current step.
1671 * The form is always assumed to be multistep, even if it has only one
89d3750f 1672 * step (the default 'views_form_views_form' step). That way it is actually
28161496
DW
1673 * possible for modules to have a multistep form if they need to.
1674 */
2f011518 1675function views_form($form, &$form_state, $view, $output) {
28161496
DW
1676 $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
1677
1678 $form = array();
8effb341
DW
1679 $query = drupal_get_query_parameters($_GET, array('q'));
1680 $form['#action'] = url($view->get_url(), array('query' => $query));
28161496
DW
1681 $form['#validate'] = array('views_form_validate');
1682 $form['#submit'] = array('views_form_submit');
1683 // Tell the preprocessor whether it should hide the header, footer, pager...
1684 $form['show_view_elements'] = array(
1685 '#type' => 'value',
1686 '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
1687 );
1688
1689 $form = $form_state['step']($form, $form_state, $view, $output);
1690 return $form;
1691}
1692
1693/**
1694 * The basic form validate handler.
1695 * Fires the hook_views_form_validate() function.
1696 */
1697function views_form_validate($form, &$form_state) {
1698 // Fire the hook. Doesn't use module_invoke_all() because $form_state needs
1699 // to be passed by reference.
1700 foreach (module_implements('views_form_validate') as $module) {
1701 $function = $module . '_views_form_validate';
1702 $function($form, $form_state);
1703 }
1704}
1705
1706/**
1707 * The basic form submit handler.
1708 * Fires the hook_views_form_submit() function.
1709 */
1710function views_form_submit($form, &$form_state) {
1711 // Fire the hook. Doesn't use module_invoke_all() because $form_state needs
1712 // to be passed by reference.
1713 foreach (module_implements('views_form_submit') as $module) {
1714 $function = $module . '_views_form_submit';
1715 $function($form, $form_state);
1716 }
1717}
1718
1719/**
1720 * Callback for the main step of a Views form.
1721 * Invoked by views_form().
1722 */
2f011518 1723function views_form_views_form($form, &$form_state, $view, $output) {
28161496
DW
1724 $form['#prefix'] = '<div class="views-form">';
1725 $form['#suffix'] = '</div>';
1726 $form['#theme'] = 'views_form_views_form';
1727 $form['#validate'][] = 'views_form_views_form_validate';
1728 $form['#submit'][] = 'views_form_views_form_submit';
1729
1730 // Add the output markup to the form array so that it's included when the form
1731 // array is passed to the theme function.
1732 $form['output'] = array(
1733 '#type' => 'markup',
1734 '#markup' => $output,
1735 // This way any additional form elements will go before the view
1736 // (below the exposed widgets).
1737 '#weight' => 50,
1738 );
1739
1740 $substitutions = array();
1741 foreach ($view->field as $field_name => $field) {
1742 // If the field provides a views form, allow it to modify the $form array.
1743 $has_form = FALSE;
1744 if (property_exists($field, 'views_form_callback')) {
1745 $callback = $field->views_form_callback;
1746 $callback($view, $field, $form, $form_state);
1747 $has_form = TRUE;
1748 }
1749 elseif (method_exists($field, 'views_form')) {
1750 $field->views_form($form, $form_state);
1751 $has_form = TRUE;
1752 }
1753
1754 // Build the substitutions array for use in the theme function.
1755 if ($has_form) {
1756 foreach ($view->result as $row_id => $row) {
1757 $substitutions[] = array(
1758 'placeholder' => '<!--form-item-' . $field_name . '--' . $row_id . '-->',
1759 'field_name' => $field_name,
1760 'row_id' => $row_id,
1761 );
1762 }
1763 }
1764 }
1765
e8d754f5
DW
1766 // Give the area handlers a chance to extend the form.
1767 $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1768 $empty = empty($view->result);
1769 foreach ($area_handlers as $area) {
1770 if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1771 $area->views_form($form, $form_state);
1772 }
1773 }
1774
28161496
DW
1775 $form['#substitutions'] = array(
1776 '#type' => 'value',
1777 '#value' => $substitutions,
1778 );
1779 $form['actions'] = array(
1780 '#type' => 'container',
1781 '#attributes' => array('class' => array('form-actions')),
1782 '#weight' => 100,
1783 );
28161496
DW
1784 $form['actions']['submit'] = array(
1785 '#type' => 'submit',
1786 '#value' => t('Save'),
1787 );
1788
1789 return $form;
1790}
1791
1792/**
1793 * Validate handler for the first step of the views form.
1794 * Calls any existing views_form_validate functions located
1795 * on the views fields.
1796 */
1797function views_form_views_form_validate($form, &$form_state) {
1798 $view = $form_state['build_info']['args'][0];
1799
1800 // Call the validation method on every field handler that has it.
1801 foreach ($view->field as $field_name => $field) {
1802 if (method_exists($field, 'views_form_validate')) {
1803 $field->views_form_validate($form, $form_state);
1804 }
1805 }
a9ab2cfb
DW
1806
1807 // Call the validate method on every area handler that has it.
1808 foreach (array('header', 'footer') as $area) {
1809 foreach ($view->{$area} as $area_name => $area_handler) {
1810 if (method_exists($area_handler, 'views_form_validate')) {
1811 $area_handler->views_form_validate($form, $form_state);
1812 }
1813 }
1814 }
28161496
DW
1815}
1816
1817/**
1818 * Submit handler for the first step of the views form.
1819 * Calls any existing views_form_submit functions located
1820 * on the views fields.
1821 */
1822function views_form_views_form_submit($form, &$form_state) {
1823 $view = $form_state['build_info']['args'][0];
1824
1825 // Call the submit method on every field handler that has it.
1826 foreach ($view->field as $field_name => $field) {
1827 if (method_exists($field, 'views_form_submit')) {
1828 $field->views_form_submit($form, $form_state);
1829 }
1830 }
a9ab2cfb
DW
1831
1832 // Call the submit method on every area handler that has it.
1833 foreach (array('header', 'footer') as $area) {
1834 foreach ($view->{$area} as $area_name => $area_handler) {
1835 if (method_exists($area_handler, 'views_form_submit')) {
1836 $area_handler->views_form_submit($form, $form_state);
1837 }
1838 }
1839 }
28161496
DW
1840}
1841
1842// ------------------------------------------------------------------
31dd0540
TGGM
1843// Exposed widgets form
1844
1845/**
1846 * Form builder for the exposed widgets form.
1847 *
1848 * Be sure that $view and $display are references.
1849 */
2dd76fe0 1850function views_exposed_form($form, &$form_state) {
31dd0540 1851 // Don't show the form when batch operations are in progress.
4f09d3fd 1852 if ($batch = batch_get() && isset($batch['current_set'])) {
31dd0540
TGGM
1853 return array(
1854 // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
1855 '#theme' => '',
1856 );
1857 }
1858
1859 // Make sure that we validate because this form might be submitted
1860 // multiple times per page.
1861 $form_state['must_validate'] = TRUE;
2f011518
DW
1862 $view = &$form_state['view'];
1863 $display = &$form_state['display'];
31dd0540
TGGM
1864
1865 $form_state['input'] = $view->get_exposed_input();
1866
1867 // Let form plugins know this is for exposed widgets.
1868 $form_state['exposed'] = TRUE;
1869 // Check if the form was already created
1870 if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
1871 return $cache;
1872 }
1873
1874 $form['#info'] = array();
1875
1876 if (!variable_get('clean_url', FALSE)) {
1877 $form['q'] = array(
1878 '#type' => 'hidden',
1879 '#value' => $view->get_url(),
1880 );
1881 }
1882
2dd76fe0
EM
1883 // Go through each handler and let it generate its exposed widget.
1884 foreach ($view->display_handler->handlers as $type => $value) {
1885 foreach ($view->$type as $id => $handler) {
1886 if ($handler->can_expose() && $handler->is_exposed()) {
1887 $handler->exposed_form($form, $form_state);
1888 if ($info = $handler->exposed_info()) {
1889 $form['#info']["$type-$id"] = $info;
1890 }
1891 }
31dd0540
TGGM
1892 }
1893 }
1894
31dd0540
TGGM
1895 $form['submit'] = array(
1896 '#name' => '', // prevent from showing up in $_GET.
1897 '#type' => 'submit',
9d4cb3d4 1898 '#value' => t('Apply'),
2dd76fe0 1899 '#id' => drupal_html_id('edit-submit-' . $view->name),
31dd0540
TGGM
1900 );
1901
1902 $form['#action'] = url($view->get_url());
1903 $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
5be494e5 1904 $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
31dd0540
TGGM
1905// $form['#attributes']['class'] = array('views-exposed-form');
1906
1907 // If using AJAX, we need the form plugin.
1908 if ($view->use_ajax) {
254f4306 1909 drupal_add_library('system', 'jquery.form');
31dd0540 1910 }
3981ce08 1911 ctools_include('dependent');
31dd0540 1912
652ea18e
DW
1913 $exposed_form_plugin = $form_state['exposed_form_plugin'];
1914 $exposed_form_plugin->exposed_form_alter($form, $form_state);
1915
31dd0540
TGGM
1916 // Save the form
1917 views_exposed_form_cache($view->name, $view->current_display, $form);
1918
1919 return $form;
1920}
1921
1922/**
45d17c76
EM
1923 * Implement hook_form_alter for the exposed form.
1924 *
1925 * Since the exposed form is a GET form, we don't want it to send a wide
1926 * variety of information.
1927 */
1928function views_form_views_exposed_form_alter(&$form, &$form_state) {
1929 $form['form_build_id']['#access'] = FALSE;
1930 $form['form_token']['#access'] = FALSE;
1931 $form['form_id']['#access'] = FALSE;
1932}
1933
1934/**
31dd0540
TGGM
1935 * Validate handler for exposed filters
1936 */
1937function views_exposed_form_validate(&$form, &$form_state) {
1938 foreach (array('field', 'filter') as $type) {
1939 $handlers = &$form_state['view']->$type;
1940 foreach ($handlers as $key => $handler) {
1941 $handlers[$key]->exposed_validate($form, $form_state);
1942 }
1943 }
652ea18e
DW
1944 $exposed_form_plugin = $form_state['exposed_form_plugin'];
1945 $exposed_form_plugin->exposed_form_validate($form, $form_state);
31dd0540
TGGM
1946}
1947
1948/**
1949 * Submit handler for exposed filters
1950 */
1951function views_exposed_form_submit(&$form, &$form_state) {
1952 foreach (array('field', 'filter') as $type) {
1953 $handlers = &$form_state['view']->$type;
1954 foreach ($handlers as $key => $info) {
1955 $handlers[$key]->exposed_submit($form, $form_state);
1956 }
1957 }
1958 $form_state['view']->exposed_data = $form_state['values'];
1959 $form_state['view']->exposed_raw_input = array();
1960
652ea18e 1961
1cd44be0 1962 $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
652ea18e
DW
1963 $exposed_form_plugin = $form_state['exposed_form_plugin'];
1964 $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
ced80ad3 1965
31dd0540 1966 foreach ($form_state['values'] as $key => $value) {
652ea18e 1967 if (!in_array($key, $exclude)) {
31dd0540
TGGM
1968 $form_state['view']->exposed_raw_input[$key] = $value;
1969 }
1970 }
1971}
1972
1973/**
1974 * Save the Views exposed form for later use.
1975 *
1976 * @param $views_name
1977 * String. The views name.
1978 * @param $display_name
1979 * String. The current view display name.
1980 * @param $form_output
1981 * Array (optional). The form structure. Only needed when inserting the value.
1982 * @return
1983 * Array. The form structure, if any. Otherwise, return FALSE.
1984 */
1985function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
1986 static $views_exposed;
1987
1988 // Save the form output
1989 if (!empty($form_output)) {
1990 $views_exposed[$views_name][$display_name] = $form_output;
1991 return;
1992 }
1993
1994 // Return the form output, if any
1995 return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
1996}
1997
1998// ------------------------------------------------------------------
1999// Misc helpers
2000
2001/**
2002 * Build a list of theme function names for use most everywhere.
2003 */
2f011518 2004function views_theme_functions($hook, $view, $display = NULL) {
2dd76fe0 2005 require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
31dd0540
TGGM
2006 return _views_theme_functions($hook, $view, $display);
2007}
2008
2009/**
31dd0540
TGGM
2010 * Substitute current time; this works with cached queries.
2011 */
2f011518 2012function views_views_query_substitutions($view) {
27a708ad 2013 global $language_content;
31dd0540 2014 return array(
dfb4b281
DW
2015 '***CURRENT_VERSION***' => VERSION,
2016 '***CURRENT_TIME***' => REQUEST_TIME,
27a708ad 2017 '***CURRENT_LANGUAGE***' => $language_content->language,
dfb4b281 2018 '***DEFAULT_LANGUAGE***' => language_default('language'),
31dd0540
TGGM
2019 );
2020}
2021
2022/**
dfb4b281
DW
2023 * Implements hook_query_TAG_alter().
2024 *
2025 * This is the hook_query_alter() for queries tagged by Views and is used to
2026 * add in substitutions from hook_views_query_substitutions().
2027 */
2028function views_query_views_alter(QueryAlterableInterface $query) {
2029 $substitutions = $query->getMetaData('views_substitutions');
2030 $tables =& $query->getTables();
2031 $where =& $query->conditions();
2032
2033 // Replaces substitions in tables.
2034 foreach ($tables as $table_name => $table_metadata) {
2035 foreach ($table_metadata['arguments'] as $replacement_key => $value) {
3eb76e0c 2036 if (isset($substitutions[$value])) {
dfb4b281
DW
2037 $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
2038 }
2039 }
2040 }
2041
2042 // Replaces substitions in filter criterias.
35175e08 2043 _views_query_tag_alter_condition($query, $where, $substitutions);
dfb4b281
DW
2044}
2045
2046/**
2047 * Replaces the substitutions recursive foreach condition.
2048 */
35175e08 2049function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
dfb4b281
DW
2050 foreach ($conditions as $condition_id => &$condition) {
2051 if (is_numeric($condition_id)) {
2052 if (is_string($condition['field'])) {
2053 $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
2054 }
35175e08 2055 elseif (is_object($condition['field'])) {
dfb4b281 2056 $sub_conditions =& $condition['field']->conditions();
35175e08
DW
2057 _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
2058 }
2059 // $condition['value'] is a subquery so alter the subquery recursive.
2060 // Therefore take sure to get the metadata of the main query.
2061 if (is_object($condition['value'])) {
2062 $subquery = $condition['value'];
2063 $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
2064 views_query_views_alter($condition['value']);
2065 }
2066 elseif (isset($condition['value'])) {
2067 $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
dfb4b281
DW
2068 }
2069 }
2070 }
2071}
2072
2073/**
31dd0540
TGGM
2074 * Embed a view using a PHP snippet.
2075 *
2076 * This function is meant to be called from PHP snippets, should one wish to
2077 * embed a view in a node or something. It's meant to provide the simplest
2078 * solution and doesn't really offer a lot of options, but breaking the function
2079 * apart is pretty easy, and this provides a worthwhile guide to doing so.
2080 *
2081 * Note that this function does NOT display the title of the view. If you want
2082 * to do that, you will need to do what this function does manually, by
2083 * loading the view, getting the preview and then getting $view->get_title().
2084 *
2085 * @param $name
2086 * The name of the view to embed.
2087 * @param $display_id
2088 * The display id to embed. If unsure, use 'default', as it will always be
2089 * valid. But things like 'page' or 'block' should work here.
2090 * @param ...
2091 * Any additional parameters will be passed as arguments.
2092 */
2093function views_embed_view($name, $display_id = 'default') {
2094 $args = func_get_args();
2095 array_shift($args); // remove $name
2096 if (count($args)) {
2097 array_shift($args); // remove $display_id
2098 }
2099
2100 $view = views_get_view($name);
2101 if (!$view || !$view->access($display_id)) {
2102 return;
2103 }
2104
2105 return $view->preview($display_id, $args);
2106}
2107
2108/**
5ee9b0c7
EM
2109* Get the result of a view.
2110*
2111* @param string $name
2112* The name of the view to retrieve the data from.
2113* @param string $display_id
2114* The display id. On the edit page for the view in question, you'll find
86e0d16d 2115* a list of displays at the left side of the control area. "Master"
5ee9b0c7
EM
2116* will be at the top of that list. Hover your cursor over the name of the
2117* display you want to use. An URL will appear in the status bar of your
2118* browser. This is usually at the bottom of the window, in the chrome.
2119* Everything after #views-tab- is the display ID, e.g. page_1.
2120 * @param ...
2121 * Any additional parameters will be passed as arguments.
2122* @return
2123* array
2124* An array containing an object for each view item.
2125*/
2126function views_get_view_result($name, $display_id = NULL) {
2127 $args = func_get_args();
2128 array_shift($args); // remove $name
2129 if (count($args)) {
2130 array_shift($args); // remove $display_id
2131 }
2132
2133 $view = views_get_view($name);
2134 if (is_object($view)) {
2135 if (is_array($args)) {
2136 $view->set_arguments($args);
2137 }
2138 if (is_string($display_id)) {
2139 $view->set_display($display_id);
2140 }
2141 else {
2142 $view->init_display();
2143 }
2144 $view->pre_execute();
2145 $view->execute();
2146 return $view->result;
2147 }
2148 else {
2149 return array();
2150 }
2151}
2152
2153/**
31dd0540
TGGM
2154 * Export a field.
2155 */
2156function views_var_export($var, $prefix = '', $init = TRUE) {
2157 if (is_array($var)) {
2158 if (empty($var)) {
2159 $output = 'array()';
2160 }
2161 else {
2162 $output = "array(\n";
2163 foreach ($var as $key => $value) {
638dc155 2164 $output .= " " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, ' ', FALSE) . ",\n";
31dd0540
TGGM
2165 }
2166 $output .= ')';
2167 }
2168 }
2dd76fe0 2169 elseif (is_bool($var)) {
31dd0540
TGGM
2170 $output = $var ? 'TRUE' : 'FALSE';
2171 }
2dd76fe0 2172 elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
31dd0540
TGGM
2173 // Replace line breaks in strings with a token for replacement
2174 // at the very end. This protects multi-line strings from
2175 // unintentional indentation.
2176 $var = str_replace("\n", "***BREAK***", $var);
2177 $output = var_export($var, TRUE);
2178 }
2179 else {
2180 $output = var_export($var, TRUE);
2181 }
2182
2183 if ($prefix) {
2184 $output = str_replace("\n", "\n$prefix", $output);
2185 }
2186
2187 if ($init) {
2188 $output = str_replace("***BREAK***", "\n", $output);
2189 }
2190
2191 return $output;
2192}
2193
2194/**
be7dcc39
DW
2195 * Prepare a string for use as a valid CSS identifier (element, class or ID name).
2196 * This function is similar to a core version but with more sane filter values.
2197 *
2198 * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
2199 * CSS identifiers (including element names, classes, and IDs in selectors.)
2200 *
2201 * @param $identifier
2202 * The identifier to clean.
2203 * @param $filter
2204 * An array of string replacements to use on the identifier.
2205 * @return
2206 * The cleaned identifier.
2207 *
2208 * @see drupal_clean_css_identifier
2209 */
2210function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
2211 // By default, we filter using Drupal's coding standards.
2212 $identifier = strtr($identifier, $filter);
2213
2214 // Valid characters in a CSS identifier are:
2215 // - the hyphen (U+002D)
2216 // - a-z (U+0030 - U+0039)
2217 // - A-Z (U+0041 - U+005A)
2218 // - the underscore (U+005F)
2219 // - 0-9 (U+0061 - U+007A)
2220 // - ISO 10646 characters U+00A1 and higher
2221 // We strip out any character not in the above list.
2222 $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
2223
2224 return $identifier;
2225}
2226
2227/**
2dd76fe0 2228 * Implement hook_views_exportables().
31dd0540
TGGM
2229 */
2230function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
2231 $all_views = views_get_all_views();
2232 if ($op == 'list') {
2233
2234 foreach ($all_views as $name => $view) {
2235 // in list, $views is a list of tags.
2236 if (empty($views) || in_array($view->tag, $views)) {
2237 $return[$name] = array(
2238 'name' => check_plain($name),
2239 'desc' => check_plain($view->description),
2240 'tag' => check_plain($view->tag)
2241 );
2242 }
2243 }
2244 return $return;
2245 }
2246
2247 if ($op == 'export') {
2248 $code = "/**\n";
2dd76fe0 2249 $code .= " * Implement hook_views_default_views().\n";
31dd0540
TGGM
2250 $code .= " */\n";
2251 $code .= "function " . $name . "_views_default_views() {\n";
2252 foreach ($views as $view => $truth) {
2253 $code .= " /*\n";
2dd76fe0 2254 $code .= " * View " . var_export($all_views[$view]->name, TRUE) . "\n";
31dd0540
TGGM
2255 $code .= " */\n";
2256 $code .= $all_views[$view]->export(' ');
2257 $code .= ' $views[$view->name] = $view;' . "\n\n";
2258 }
2259 $code .= " return \$views;\n";
2260 $code .= "}\n";
2261
2262 return $code;
2263 }
2264}
2265
2266/**
28244380
DW
2267 * #process callback to see if we need to check_plain() the options.
2268 *
2269 * Since FAPI is inconsistent, the #options are sanitized for you in all cases
2270 * _except_ checkboxes. We have form elements that are sometimes 'select' and
2271 * sometimes 'checkboxes', so we need decide late in the form rendering cycle
2272 * if the options need to be sanitized before they're rendered. This callback
2273 * inspects the type, and if it's still 'checkboxes', does the sanitation.
2274 */
0f2586f9 2275function views_process_check_options($element, &$form_state) {
28244380
DW
2276 if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
2277 $element['#options'] = array_map('check_plain', $element['#options']);
2278 }
2279 return $element;
2280}
2f5e3034
EM
2281
2282/**
2283 * Trim the field down to the specified length.
2284 *
2285 * @param $alter
2286 * - max_length: Maximum lenght of the string, the rest gets truncated.
2287 * - word_boundary: Trim only on a word boundary.
2288 * - ellipsis: Trim only on a word boundary.
2289 * - html: Take sure that the html is correct.
2290 *
2291 * @param $value
2292 * The string which should be trimmed.
2293 */
3cf55a10 2294function views_trim_text($alter, $value) {
2f5e3034
EM
2295 if (drupal_strlen($value) > $alter['max_length']) {
2296 $value = drupal_substr($value, 0, $alter['max_length']);
1f3724e1 2297 // TODO: replace this with cleanstring of ctools
2f5e3034 2298 if (!empty($alter['word_boundary'])) {
1f3724e1
EM
2299 $regex = "(.*)\b.+";
2300 if (function_exists('mb_ereg')) {
2301 mb_regex_encoding('UTF-8');
2302 $found = mb_ereg($regex, $value, $matches);
2303 }
2304 else {
2305 $found = preg_match("/$regex/us", $value, $matches);
2306 }
2307 if ($found) {
2f5e3034
EM
2308 $value = $matches[1];
2309 }
2310 }
2311 // Remove scraps of HTML entities from the end of a strings
2312 $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
2313
2314 if (!empty($alter['ellipsis'])) {
87feb37e 2315 $value .= t('...');
2f5e3034
EM
2316 }
2317 }
2318 if (!empty($alter['html'])) {
2319 $value = _filter_htmlcorrector($value);
2320 }
2321
2322 return $value;
2323}
ced80ad3 2324
ce174eb6
EM
2325/**
2326 * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
2327 */
2328function views_ctools_plugin_api_hook_name() {
2329 return 'views_api';
2330}
2331
ced80ad3
EM
2332// Declare API compatibility on behalf of core modules:
2333
2334/**
2335 * Implements hook_views_api().
2336 *
2337 * This one is used as the base to reduce errors when updating.
2338 */
2339function views_views_api() {
2340 return array(
2341 // in your modules do *not* use views_api_version()!!!
2342 'api' => views_api_version(),
2343 'path' => drupal_get_path('module', 'views') . '/modules',
2344 );
2345}
2346
f35fdf46
TP
2347if (!function_exists('aggregator_views_api')) {
2348 function aggregator_views_api() { return views_views_api(); }
2349}
ced80ad3 2350
f35fdf46
TP
2351if (!function_exists('book_views_api')) {
2352 function book_views_api() { return views_views_api(); }
2353}
ced80ad3 2354
f35fdf46
TP
2355if (!function_exists('comment_views_api')) {
2356 function comment_views_api() { return views_views_api(); }
2357}
ced80ad3 2358
1df49fae
DW
2359if (!function_exists('field_views_api')) {
2360 function field_views_api() { return views_views_api(); }
e1adbdc2
DW
2361}
2362
75dbbdd4
DW
2363if (!function_exists('file_views_api')) {
2364 function file_views_api() { return views_views_api(); }
2365}
2366
f35fdf46
TP
2367if (!function_exists('filter_views_api')) {
2368 function filter_views_api() { return views_views_api(); }
2369}
ced80ad3 2370
75dbbdd4
DW
2371if (!function_exists('image_views_api')) {
2372 function image_views_api() { return views_views_api(); }
2373}
2374
2375if (!function_exists('locale_views_api')) {
2376 function locale_views_api() { return views_views_api(); }
2377}
2378
f35fdf46
TP
2379if (!function_exists('node_views_api')) {
2380 function node_views_api() { return views_views_api(); }
2381}
ced80ad3 2382
f35fdf46
TP
2383if (!function_exists('poll_views_api')) {
2384 function poll_views_api() { return views_views_api(); }
2385}
ced80ad3 2386
f35fdf46
TP
2387if (!function_exists('profile_views_api')) {
2388 function profile_views_api() { return views_views_api(); }
2389}
ced80ad3 2390
f35fdf46
TP
2391if (!function_exists('search_views_api')) {
2392 function search_views_api() { return views_views_api(); }
2393}
ced80ad3 2394
f35fdf46
TP
2395if (!function_exists('statistics_views_api')) {
2396 function statistics_views_api() { return views_views_api(); }
2397}
ced80ad3 2398
f35fdf46
TP
2399if (!function_exists('system_views_api')) {
2400 function system_views_api() { return views_views_api(); }
2401}
ced80ad3 2402
f35fdf46
TP
2403if (!function_exists('taxonomy_views_api')) {
2404 function taxonomy_views_api() { return views_views_api(); }
2405}
ced80ad3 2406
f35fdf46
TP
2407if (!function_exists('translation_views_api')) {
2408 function translation_views_api() { return views_views_api(); }
2409}
ced80ad3 2410
f35fdf46
TP
2411if (!function_exists('upload_views_api')) {
2412 function upload_views_api() { return views_views_api(); }
2413}
ced80ad3 2414
f35fdf46
TP
2415if (!function_exists('user_views_api')) {
2416 function user_views_api() { return views_views_api(); }
2417}
ced80ad3 2418
f35fdf46
TP
2419if (!function_exists('contact_views_api')) {
2420 function contact_views_api() { return views_views_api(); }
2421}