#119463: Double check_plain on breadcrumbs for Views titles.
[project/views.git] / views.module
1 <?php
2 // $Id$
3 // $Name$
4
5 // ---------------------------------------------------------------------------
6 // Drupal Hooks
7
8 /**
9 * Implementation of hook_help()
10 */
11 function views_help($section) {
12 switch ($section) {
13 case 'admin/help#views':
14 return t('The views module creates customized views of node lists. You may need to activate the Views UI module to get to the user administration pages.');
15 }
16 }
17
18 /**
19 * Implementation of hook_menu()
20 */
21 function views_menu($may_cache) {
22 $items = array();
23
24 if ($may_cache) {
25 views_load_cache();
26 // Invalidate the views cache to ensure that views data gets rebuilt.
27 // This is the best way to tell that module configuration has changed.
28 if (arg(0) == 'admin' && arg(2) == 'modules') {
29 views_invalidate_cache();
30 }
31
32 views_menu_standard_items($items);
33 }
34 else {
35 views_menu_inline_items($items);
36 }
37 return $items;
38 }
39
40 /**
41 * Add the menu items for all non-inline views to the menu
42 */
43 function views_menu_standard_items(&$items) {
44 $result = db_query("SELECT * FROM {view_view} WHERE page = 1");
45
46 while ($view = db_fetch_object($result)) {
47 // This happens before the next check; even if it's put off for later
48 // it is still used.
49 $used[$view->name] = true;
50
51 // Skip views with inline arguments.
52 if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
53 continue;
54 }
55
56 // unpack the array
57 $view->access = ($view->access ? explode(', ', $view->access) : array());
58
59 _views_create_menu_item($items, $view, $view->url);
60 }
61 $default_views = _views_get_default_views();
62 $views_status = variable_get('views_defaults', array());
63
64 // Process default views
65 foreach ($default_views as $name => $view) {
66 if ($view->page && !$used[$name] &&
67 ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
68
69 // skip views with inline args
70 if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
71 continue;
72 }
73
74 _views_create_menu_item($items, $view, $view->url);
75 }
76 }
77 }
78
79 function views_menu_inline_items(&$items) {
80 // I don't think we gain anything by caching these, there should never
81 // be all that many, and caching == a database hit.
82 $tokens = module_invoke_all('views_url_tokens');
83
84 $args = explode('/', $_GET['q']);
85 $urls = views_get_all_urls();
86 foreach ($urls as $view_name => $url) {
87 if ($url{0} != '$' && strpos($url, '/$') === FALSE) {
88 if (module_exists('views_ui') && user_access('administer views')) {
89 $view_args = $args;
90
91 foreach (explode('/', $url) as $num => $element) {
92 if ($element != $args[$num]) {
93 continue 2;
94 }
95 unset($view_args[$num]);
96 }
97 views_menu_admin_items($items, $view_name, $view_args, $args);
98 }
99 }
100 else {
101 // Do substitution on args.
102 $use_view = $use_menu = FALSE;
103 $menu_args = $view_args = $menu_path = array();
104
105 foreach (explode('/', $url) as $num => $element) {
106 if ($element{0} == '$') {
107 // If we pass the token check, this view is definitely being used.
108 list($token, $argument) = explode('-', $element);
109 if ($tokens[$token] && function_exists($tokens[$token])) {
110 if (!($use_view = $use_menu = $tokens[$token]($element, $argument, arg($num)))) {
111 break;
112 }
113 }
114 $menu_path[] = $view_args[] = arg($num);
115 }
116 else {
117 unset($menu_args[$num]);
118 $menu_path[] = $element;
119 if ($element != arg($num)) {
120 $use_menu = FALSE;
121 }
122 }
123 // we are only using views that match our URL, up to the
124 // point where we hit an inline arg.
125 if (!$use_view && $element != arg($num)) {
126 break;
127 }
128 }
129 if ($use_view) {
130 $path = implode('/', $menu_path);
131 $view = views_get_view($view_name);
132 $view->args = $view_args;
133 _views_create_menu_item($items, $view, $path, MENU_CALLBACK, $view_args);
134 }
135
136 if (module_exists('views_ui') && user_access('administer views') && $use_menu) {
137 views_menu_admin_items($items, $view_name, $menu_args, $args);
138 }
139 }
140 }
141 }
142
143 /**
144 * Add the adminstrative items to a view.
145 */
146 function views_menu_admin_items(&$items, $view_name, $view_args, $args) {
147 // See what the last arg is.
148 $last_arg = array_pop($args);
149 if (in_array($last_arg, array('edit', 'view', 'clone', 'export'))) {
150 array_pop($view_args);
151 }
152 else {
153 $args[] = $last_arg;
154 }
155
156 $path = implode('/', $args);
157 $view = views_get_view($view_name);
158 views_ui_add_menu_items($items, $view, $path, $path != $_GET['q'] && !empty($view_args), $view_args);
159 }
160
161 /**
162 * Load all of the URLs we use; this is cached in a special manner
163 * in an attempt to make the menu system both flexible and yet not
164 * overly intensive.
165 */
166 function views_get_all_urls() {
167 $cache = cache_get("views_urls", 'cache_views');
168 if ($cache == 0) {
169 $views = array();
170 $used = array();
171 $result = db_query("SELECT name, url FROM {view_view} WHERE page = 1");
172
173 while ($view = db_fetch_object($result)) {
174 $used[$view->name] = TRUE;
175 $views[$view->name] = $view->url;
176 }
177
178 views_load_cache();
179 $default_views = _views_get_default_views();
180 $views_status = variable_get('views_defaults', array());
181
182 foreach ($default_views as $name => $view) {
183 if ($view->page && !$used[$name] && ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
184 if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
185 $views[$view->name] = $view->url;
186 }
187 }
188 }
189 cache_set("views_urls", 'cache_views', serialize($views));
190 }
191 else {
192 $views = unserialize($cache->data);
193 }
194
195 return $views;
196 }
197
198 /**
199 * Helper function to add a menu item for a view.
200 */
201 function _views_create_menu_item(&$items, $view, $path, $local_task_type = MENU_NORMAL_ITEM, $args = array()) {
202 static $roles = NULL;
203 if ($roles === NULL) {
204 global $user;
205 $roles = array_keys($user->roles);
206 if ($user->uid) {
207 $roles[] = DRUPAL_AUTHENTICATED_RID;
208 }
209 else {
210 $roles[] = DRUPAL_ANONYMOUS_RID;
211 }
212 }
213 $title = filter_xss_admin(views_get_title($view, 'menu'));
214 $type = _views_menu_type($view);
215 if ($type == MENU_LOCAL_TASK || $type == MENU_DEFAULT_LOCAL_TASK) {
216 $weight = $view->menu_tab_weight;
217 }
218 $access = user_access('access all views') || !$view->access || array_intersect($view->access, $roles);
219 $items[] = _views_menu_item($path, $title, $view, $args, $access, $type, $weight);
220
221 if ($type == MENU_DEFAULT_LOCAL_TASK) {
222 $items[] = _views_menu_item(dirname($path), $title, $view, $args, $access, $local_task_type, $weight);
223 }
224 }
225
226 /**
227 * Helper function to create a menu item for a view.
228 */
229 function _views_menu_item($path, $title, $view, $args, $access, $type, $weight = NULL) {
230 array_unshift($args, $view->name);
231 $retval = array('path' => $path,
232 'title' => $title,
233 'callback' => 'views_view_page',
234 'callback arguments' => $args,
235 'access' => user_access('access content') && $access,
236 'type' => $type,
237 );
238 if ($weight !== NULL) {
239 $retval['weight'] = $weight;
240 }
241 return $retval;
242 }
243
244 /**
245 * Determine what menu type a view needs to use.
246 */
247 function _views_menu_type($view) {
248 if ($view->menu) {
249 if ($view->menu_tab_default) {
250 $type = MENU_DEFAULT_LOCAL_TASK;
251 }
252 else if ($view->menu_tab) {
253 $type = MENU_LOCAL_TASK;
254 }
255 else {
256 $type = MENU_NORMAL_ITEM;
257 }
258 }
259 else {
260 $type = MENU_CALLBACK;
261 }
262 return $type;
263 }
264
265 /**
266 * Implementation of hook_perm
267 */
268 function views_perm() {
269 return array('access all views');
270 }
271
272 /**
273 * Implementation of hook_block()
274 */
275 function views_block($op = 'list', $delta = 0) {
276 $block = array();
277 if ($op == 'list') {
278 views_load_cache();
279 // Grab views from the database and provide them as blocks.
280 $result = db_query("SELECT vid, block_title, page_title, name FROM {view_view} WHERE block = 1");
281 while ($view = db_fetch_object($result)) {
282 $block[$view->name]['info'] = filter_xss_admin(views_get_title($view, 'block-info'));
283 }
284
285 $default_views = _views_get_default_views();
286 $views_status = variable_get('views_defaults', array());
287
288 foreach ($default_views as $name => $view) {
289 if (!isset($block[$name]) && $view->block &&
290 ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
291 $title = filter_xss_admin(views_get_title($view, 'block'));
292 $block[$name]['info'] = empty($title) ? $name : $title;
293 }
294 }
295 return $block;
296 }
297 else if ($op == 'view') {
298 return views_view_block($delta);
299 }
300 }
301
302 // ---------------------------------------------------------------------------
303 // View Construction
304
305 /**
306 * Ensure that all the arrays in a view exist so we don't run into array
307 * operations on a non-array error.
308 */
309 function _views_check_arrays(&$view) {
310 $fields = array('field', 'sort', 'argument', 'filter', 'exposed_filter', 'access');
311
312 foreach($fields as $field) {
313 if (!is_array($view->$field)) {
314 $view->$field = array();
315 }
316 }
317 return $view;
318 }
319
320 /**
321 * This function loads a view by name or vid; if not found in db, it looks
322 * for a default view by that name.
323 */
324 function views_get_view($view_name) {
325 views_load_cache();
326 $view = _views_load_view($view_name);
327 if ($view) {
328 return $view;
329 }
330
331 if (is_int($view_name)) {
332 return; // don't bother looking if view_name is an int!
333 }
334
335 $default_views = _views_get_default_views();
336
337 if (isset($default_views[$view_name])) {
338 $view = $default_views[$view_name];
339 $view->is_cacheable = _views_is_cacheable($view);
340 return $view;
341 }
342 }
343
344 /**
345 * This views a view by page, and should only be used as a callback.
346 *
347 * @param $view_name
348 * The name or id of the view to load
349 * @param $args
350 * The arguments from the end of the url. Usually passed from the menu system.
351 *
352 * @return
353 * The view page.
354 */
355 function views_view_page() {
356 $args = func_get_args();
357 $view_name = array_shift($args);
358 $view = views_get_view($view_name);
359
360 if (!$view) {
361 drupal_not_found();
362 exit;
363 }
364
365 $output = views_build_view('page', $view, $args, $view->use_pager, $view->nodes_per_page);
366 if ($output === FALSE) {
367 drupal_not_found();
368 exit;
369 }
370
371 return $output;
372 }
373
374 /**
375 * This views a view by block. Can be used as a callback or programmatically.
376 */
377 function views_view_block($vid) {
378 views_load_cache();
379 $view = views_get_view($vid);
380
381 if (!$view || !$view->block) {
382 return NULL;
383 }
384
385 global $user;
386 if (!$user->roles) {
387 return NULL;
388 }
389
390 $roles = array_keys($user->roles);
391 if ($view->access && !array_intersect($roles, $view->access)) {
392 return NULL;
393 }
394
395 $content = views_build_view('block', $view, array(), false, $view->nodes_per_block);
396 if ($content) {
397 $block['content'] = $content;
398 $block['subject'] = filter_xss_admin(views_get_title($view, 'block'));
399 return $block;
400 }
401 else {
402 return NULL;
403 }
404 }
405
406 function &views_set_current_view(&$view) {
407 static $current_view = NULL;
408 if ($view !== NULL) {
409 unset($current_view);
410 $current_view = &$view;
411 unset($GLOBALS['current_view']);
412 $GLOBALS['current_view'] = &$view;
413 }
414 return $current_view;
415 }
416
417 function &views_get_current_view() {
418 $dummy = NULL;
419 return views_set_current_view($dummy);
420 }
421
422 /**
423 * This builds the basic view.
424 * @param $type
425 * 'page' -- Produce output as a page, sent through theme.
426 * The only real difference between this and block is that
427 * a page uses drupal_set_title to change the page title.
428 * 'block' -- Produce output as a block, sent through theme.
429 * 'embed' -- Use this if you want to embed a view onto another page,
430 * and don't want any block or page specific things to happen to it.
431 * 'result' -- return an $info array. The array contains:
432 * query: The actual query ran.
433 * countquery: The count query that would be run if limiting was required.
434 * summary: True if an argument was missing and a summary was generated.
435 * level: What level the missing argument was at.
436 * result: Database object you can use db_fetch_object on.
437 * 'items' -- return info array as above, except instead of result,
438 * items: An array of objects containing the results of the query.
439 * 'queries' -- returns an array, summarizing the queries, but does not
440 * run them.
441 * @param $view
442 * The actual view object. Use views_get_view() if you only have the name or
443 * vid.
444 * @param $args
445 * args taken from the URL. Not relevant for many views. Can be null.
446 * @param $use_pager
447 * If set, use a pager. Set this to the pager id you want it
448 * to use if you plan on using multiple pagers on a page. To go with the
449 * default setting, set to $view->use_pager. Note that the pager element
450 * id will be decremented in order to have the IDs start at 0.
451 * @param $limit
452 * Required if $use_pager is set; if $limit is set and $use_pager is
453 * not, this will be the maximum number of records returned. This is ignored
454 * if using a view set to return a random result. To go with the default
455 * setting set to $view->nodes_per_page or $view->nodes_per_block. If
456 * $use_pager is set and this field is not, you'll get a SQL error. Don't
457 * do that!
458 * @param $page
459 * $use_pager is false, and $limit is !0, $page tells it what page to start
460 * on, in case for some reason a particular section of view is needed,
461 * @param $offset
462 * If $use_pager == false, skip the first $offset results. Does not work
463 * with pager.
464 * without paging on.
465 */
466 function views_build_view($type, &$view, $args = array(), $use_pager = false, $limit = 0, $page = 0, $offset = 0) {
467 views_load_cache();
468
469 // Fix a number of annoying whines when NULL is passed in..
470 if ($args == NULL) {
471 $args = array();
472 }
473
474 views_set_current_view($view);
475
476 $view->build_type = $type;
477 $view->type = ($type == 'block' ? $view->block_type : $view->page_type);
478
479 if ($view->view_args_php) {
480 ob_start();
481 $result = eval($view->view_args_php);
482 if (is_array($result)) {
483 $args = $result;
484 }
485 ob_end_clean();
486 }
487
488 // Call a hook that'll let modules modify the view query before it is created
489 foreach (module_implements('views_pre_query') as $module) {
490 $function = $module .'_views_pre_query';
491 $output .= $function($view);
492 }
493
494 $info = _views_get_query($view, $args);
495
496 if ($info['fail']) {
497 return FALSE;
498 }
499
500 if ($type == 'queries') {
501 return $info;
502 }
503
504 $query = db_rewrite_sql($info['query'], 'node');
505
506 $items = array();
507 if ($query) {
508 if ($use_pager) {
509 $cquery = db_rewrite_sql($info['countquery'], 'node', 'nid', $info['rewrite_args']);
510 $result = pager_query($query, $limit, $use_pager - 1, $cquery, $info['args']);
511 $view->total_rows = $GLOBALS['pager_total_items'][$use_pager - 1];
512 }
513 else {
514 $result = ($limit ? db_query_range($query, $info['args'], $page * $limit + $offset, $limit) : db_query($query, $info['args']));
515 }
516 $view->num_rows = db_num_rows($result);
517 if ($type == 'result') {
518 $info['result'] = $result;
519 return $info;
520 }
521
522 while ($item = db_fetch_object($result)) {
523 $items[] = $item;
524 }
525 }
526
527 if ($type == 'items') {
528 $info['items'] = $items;
529 return $info;
530 }
531
532 // Call a hook that'll let modules modify the view just before it is displayed.
533 foreach (module_implements('views_pre_view') as $module) {
534 $function = $module .'_views_pre_view';
535 $output .= $function($view, $items);
536 }
537
538 $view->real_url = views_get_url($view, $args);
539
540 $view->use_pager = $use_pager;
541 $view->pager_limit = $limit;
542 $output .= views_theme('views_view', $view, $type, $items, $info['level'], $args);
543
544 // Call a hook that'll let modules modify the view just after it is displayed.
545 foreach (module_implements('views_post_view') as $module) {
546 $function = $module .'_views_post_view';
547 $output .= $function($view, $items, $output);
548 }
549
550 return $output;
551 }
552
553 // ---------------------------------------------------------------------------
554 // Utility
555
556 /**
557 * Load the cache sub-module
558 */
559 function views_load_cache() {
560 $path = drupal_get_path('module', 'views');
561 require_once("./$path/views_cache.inc");
562 }
563
564 /**
565 * Load the query sub-module
566 */
567 function views_load_query() {
568 $path = drupal_get_path('module', 'views');
569 require_once("./$path/views_query.inc");
570 }
571
572 /**
573 * Easily theme any item to a view.
574 * @param $function
575 * The name of the function to call.
576 * @param $view
577 * The view being themed.
578 */
579 function views_theme() {
580 $args = func_get_args();
581 $function = array_shift($args);
582 $view = $args[0];
583
584 if (!($func = theme_get_function($function . "_" . $view->name))) {
585 $func = theme_get_function($function);
586 }
587
588 if ($func) {
589 return call_user_func_array($func, $args);
590 }
591 }
592
593 /**
594 * Easily theme any item to a field name.
595 * field name will be in the format of TABLENAME_FIELDNAME
596 * You have to understand a bit about the views data to utilize this.
597 *
598 * @param $function
599 * The name of the function to call.
600 * @param $field_name
601 * The field being themed.
602 */
603 function views_theme_field() {
604 $args = func_get_args();
605 $function = array_shift($args);
606 $field_name = array_shift($args);
607 $view = array_pop($args);
608
609 if (!($func = theme_get_function($function . '_' . $view->name . '_' . $field_name))) {
610 if (!($func = theme_get_function($function . '_' . $field_name))) {
611 $func = theme_get_function($function);
612 }
613 }
614
615 if ($func) {
616 return call_user_func_array($func, $args);
617 }
618 }
619
620 /**
621 * Figure out what timezone we're in; needed for some date manipulations.
622 */
623 function _views_get_timezone() {
624 global $user;
625 if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
626 $timezone = $user->timezone;
627 }
628 else {
629 $timezone = variable_get('date_default_timezone', 0);
630 }
631
632 // set up the database timezone
633 if (in_array($GLOBALS['db_type'], array('mysql', 'mysqli'))) {
634 static $already_set = false;
635 if (!$already_set) {
636 if ($GLOBALS['db_type'] == 'mysqli' || version_compare(mysql_get_server_info(), '4.1.3', '>=')) {
637 db_query("SET @@session.time_zone = '+00:00'");
638 }
639 $already_set = true;
640 }
641 }
642
643 return $timezone;
644 }
645
646 /**
647 * Figure out what the URL of the view we're currently looking at is.
648 */
649 function views_get_url($view, $args) {
650 $url = $view->url;
651
652 if (!empty($url)) {
653 $where = 1;
654 foreach ($args as $arg) {
655 // This odd construct prevents us from strposing once there is no
656 // longer an $arg to replace.
657 if ($where && $where = strpos($url, '$arg')) {
658 $url = substr_replace($url, $arg, $where, 4);
659 }
660 else {
661 $url .= "/$arg";
662 }
663 }
664 }
665
666 return $url;
667 }
668
669 /**
670 * Figure out what the title of a view should be.
671 */
672 function views_get_title($view, $context = 'menu', $args = NULL) {
673 if (($context == 'menu' || $context == 'admin') && $view->menu_title)
674 return $view->menu_title;
675
676 if ($context == 'block-info') {
677 return $view->description ? $view->description : $view->name;
678 }
679
680 if ($args === NULL) {
681 $args = $view->args;
682 }
683 // Grab the title from the highest argument we got. If there is no such
684 // title, track back until we find a title.
685
686 if (is_array($args)) {
687 $rargs = array_reverse(array_keys($args));
688 foreach ($rargs as $arg_id) {
689 if ($title = $view->argument[$arg_id]['title']) {
690 break;
691 }
692 }
693 }
694
695 if (!$title && ($context == 'menu' || $context == 'page' || $context == 'admin')) {
696 $title = $view->page_title;
697 }
698
699 if (!$title && ($context == 'block' || $context == 'admin')) {
700 $title = $view->block_title;
701 }
702
703 if (!$view->argument) {
704 return $title;
705 }
706
707 views_load_cache();
708 $arginfo = _views_get_arguments();
709 foreach ($view->argument as $i => $arg) {
710 if (!isset($args[$i])) {
711 break;
712 }
713 $argtype = $arg['type'];
714 if ($arg['wildcard'] == $args[$i] && $arg['wildcard_substitution'] != '') {
715 $title = str_replace("%" . ($i + 1), $arg['wildcard_substitution'], $title);
716 }
717 else if (function_exists($arginfo[$argtype]['handler'])) {
718 // call the handler
719 $rep = $arginfo[$argtype]['handler']('title', $args[$i], $argtype);
720 $title = str_replace("%" . ($i + 1), $rep, $title);
721 }
722 }
723 return $title;
724 }
725
726 /**
727 * Determine whether or not a view is cacheable. A view is not cacheable if
728 * there is some kind of user input or data required. For example, views
729 * that need to restrict to the 'current' user, or any views that require
730 * arguments or allow click-sorting are not cacheable.
731 */
732 function _views_is_cacheable(&$view) {
733 // views with arguments are immediately not cacheable.
734 if (!empty($view->argument) || !empty($view->exposed_filter) || !empty($view->no_cache)) {
735 return false;
736 }
737
738 views_load_cache();
739 $filters = _views_get_filters();
740
741 foreach ($view->filter as $i => $filter) {
742 if ($filters[$filter['field']]['cacheable'] == 'no') {
743 return false;
744 }
745 }
746
747 foreach ($view->field as $i => $field) {
748 if ($field['sortable']) {
749 return false;
750 }
751 }
752 return true;
753 }
754
755 /**
756 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
757 */
758 function views_invalidate_cache() {
759 cache_clear_all('*', 'cache_views', true);
760 }
761
762 // ---------------------------------------------------------------------------
763 // Database functions
764
765 /**
766 * Provide all the fields in a view.
767 */
768 function _views_view_fields() {
769 return array('vid', 'name', 'description', 'access', 'page', 'page_title', 'page_header', 'page_header_format', 'page_footer', 'page_footer_format', 'page_empty', 'page_empty_format', 'page_type', 'use_pager', 'nodes_per_page', 'url', 'menu', 'menu_tab', 'menu_tab_default', 'menu_tab_weight', 'menu_title', 'block', 'block_title', 'block_use_page_header', 'block_header', 'block_header_format', 'block_use_page_footer', 'block_footer', 'block_footer_format', 'block_use_page_empty', 'block_empty', 'block_empty_format', 'block_type', 'nodes_per_block', 'block_more', 'url', 'breadcrumb_no_home', 'changed', 'view_args_php', 'is_cacheable');
770 }
771
772 /**
773 * Delete a view from the database.
774 */
775 function _views_delete_view($view) {
776 $view->vid = intval($view->vid);
777 if (!$view->vid) {
778 return;
779 }
780
781 db_query("DELETE FROM {view_view} where vid=%d", $view->vid);
782 db_query("DELETE FROM {view_sort} where vid=%d", $view->vid);
783 db_query("DELETE FROM {view_argument} where vid=%d", $view->vid);
784 db_query("DELETE FROM {view_tablefield} where vid=%d", $view->vid);
785
786 cache_clear_all('views_query:' . $view->name, 'cache_views');
787 }
788
789 /**
790 * Load a view from the database -- public version of the function.
791 */
792 function views_load_view($arg) {
793 return _views_load_view($arg);
794 }
795
796 /**
797 * Load a view from the database.
798 * (deprecated; use views_load_view in favor of this function).
799 */
800 function _views_load_view($arg) {
801 static $cache = array();
802 $which = is_numeric($arg) ? 'vid' : 'name';
803 if (isset($cache[$which][$arg])) {
804 return $cache[$which][$arg];
805 }
806
807 $where = (is_numeric($arg) ? "v.vid = %d" : "v.name = '%s'");
808 $view = db_fetch_object(db_query("SELECT v.* FROM {view_view} v WHERE $where", $arg));
809
810 if (!$view->name) {
811 return NULL;
812 }
813
814 $view->access = ($view->access ? explode(', ', $view->access) : array());
815
816 // load the sorting criteria too.
817 $result = db_query("SELECT * FROM {view_sort} vs WHERE vid = $view->vid ORDER BY position ASC");
818
819 $view->sort = array();
820 while ($sort = db_fetch_array($result)) {
821 if (substr($sort['field'], 0, 2) == 'n.') {
822 $sort['field'] = 'node' . substr($sort['field'], 1);
823 }
824 $sort['id'] = $sort['field'];
825 $view->sort[] = $sort;
826 }
827
828 $result = db_query("SELECT * FROM {view_argument} WHERE vid = $view->vid ORDER BY position ASC");
829
830 $view->argument = array();
831 while ($arg = db_fetch_array($result)) {
832 $arg['id'] = $arg['type'];
833 $view->argument[] = $arg;
834 }
835
836 $result = db_query("SELECT * FROM {view_tablefield} WHERE vid = $view->vid ORDER BY position ASC");
837
838 $view->field = array();
839 while ($arg = db_fetch_array($result)) {
840 if ($arg['tablename'] == 'n') {
841 $arg['tablename'] = 'node';
842 }
843 $arg['id'] = $arg['fullname'] = "$arg[tablename].$arg[field]";
844 $arg['queryname'] = "$arg[tablename]_$arg[field]";
845 $view->field[] = $arg;
846 }
847
848 $result = db_query("SELECT * FROM {view_filter} WHERE vid = $view->vid ORDER BY position ASC");
849
850 views_load_cache();
851 $filters = _views_get_filters();
852 $view->filter = array();
853 while ($filter = db_fetch_array($result)) {
854 if (substr($filter['field'], 0, 2) == 'n.') {
855 $filter['field'] = 'node' . substr($filter['field'], 1);
856 }
857
858 if ($filter['operator'] == 'AND' ||
859 $filter['operator'] == 'OR' ||
860 $filter['operator'] == 'NOR' ||
861 $filters[$filter['field']]['value-type'] == 'array' ) {
862 if ($filter['value'] !== NULL && $filter['value'] !== '') {
863 $filter['value'] = explode(',', $filter['value']);
864 }
865 else {
866 $filter['value'] = array();
867 }
868 }
869 $filter['id'] = $filter['field'];
870 $view->filter[] = $filter;
871 }
872
873 $result = db_query("SELECT * FROM {view_exposed_filter} WHERE vid = $view->vid ORDER BY position ASC");
874
875 $view->exposed_filter = array();
876 while ($arg = db_fetch_array($result)) {
877 $arg['id'] = $arg['field'];
878 $view->exposed_filter[] = $arg;
879 }
880
881 $cache['vid'][$view->vid] = $view;
882 $cache['name'][$view->name] = $view;
883
884 return $view;
885 }
886
887 /**
888 * Save a view to the database.
889 */
890 function _views_save_view($view) {
891 _views_check_arrays($view);
892
893 $view->is_cacheable = _views_is_cacheable($view);
894
895 $view->access = implode(', ', $view->access);
896
897 $view->changed = time();
898 $fields = _views_view_fields();
899 if ($view->vid) {
900 // update
901 // Prepare the query:
902 foreach ($view as $key => $value) {
903 if (in_array($key, $fields)) {
904 $q[] = db_escape_string($key) ." = '%s'";
905 $v[] = $value;
906 }
907 }
908
909 // Update the view in the database:
910 db_query("UPDATE {view_view} SET " . implode(', ', $q) . " WHERE vid = '$view->vid'", $v);
911 db_query("DELETE from {view_sort} WHERE vid='$view->vid'");
912 db_query("DELETE from {view_argument} WHERE vid='$view->vid'");
913 db_query("DELETE from {view_tablefield} WHERE vid='$view->vid'");
914 db_query("DELETE from {view_filter} WHERE vid='$view->vid'");
915 db_query("DELETE from {view_exposed_filter} WHERE vid='$view->vid'");
916
917 cache_clear_all('views_query:' . $view->name, 'cache_views');
918 }
919 else {
920 // insert
921
922 // This method really saves on typos, and makes it a lot easier to add fields
923 // later on.
924 $view->vid = db_next_id('{view_view}_vid');
925
926 // Prepare the query:
927 foreach ($view as $key => $value) {
928 if (in_array((string) $key, $fields)) {
929 $k[] = db_escape_string($key);
930 $v[] = $value;
931 $s[] = is_numeric($value) ? '%d' : "'%s'";
932 }
933 }
934
935 db_query("INSERT INTO {view_view} (" . implode(", ", $k) . ") VALUES (" . implode(", ", $s) . ")", $v);
936 }
937
938 foreach ($view->sort as $i => $sort) {
939 db_query("INSERT INTO {view_sort} (vid, position, field, sortorder, options) VALUES (%d, %d, '%s', '%s', '%s')", $view->vid, $i, $sort['field'], $sort['sortorder'], $sort['options']);
940 }
941
942 foreach ($view->argument as $i => $arg) {
943 db_query("INSERT INTO {view_argument} (vid, type, argdefault, title, options, position, wildcard, wildcard_substitution) VALUES (%d, '%s', %d, '%s', '%s', %d, '%s', '%s')", $view->vid, $arg['type'], $arg['argdefault'], $arg['title'], $arg['options'], $i, $arg['wildcard'], $arg['wildcard_substitution']);
944 }
945
946 foreach ($view->field as $i => $arg) {
947 db_query("INSERT INTO {view_tablefield} (vid, tablename, field, label, handler, sortable, defaultsort, options, position) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s', '%s', %d)", $view->vid, $arg['tablename'], $arg['field'], $arg['label'], $arg['handler'], $arg['sortable'], $arg['defaultsort'], $arg['options'], $i);
948 }
949
950 foreach ($view->filter as $i => $arg) {
951 if (is_array($arg['value'])) {
952 $arg['value'] = implode(',', $arg['value']);
953 }
954 db_query("INSERT INTO {view_filter} (vid, tablename, field, value, operator, options, position) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $view->vid, $arg['tablename'], $arg['field'], $arg['value'], $arg['operator'], $arg['options'], $i);
955 }
956
957 foreach ($view->exposed_filter as $i => $arg) {
958 db_query("INSERT INTO {view_exposed_filter} (vid, field, label, optional, is_default, single, operator, position) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d)", $view->vid, $arg['field'], $arg['label'], $arg['optional'], $arg['is_default'], $arg['single'], $arg['operator'], $i);
959 }
960 }
961
962 // ---------------------------------------------------------------------------
963 // Helper functions to build views and view data
964
965 /**
966 * Helper function to make table creation a little easier. It adds the necessary
967 * data to a $table array and returns it.
968 */
969 function views_new_table($table_name, $provider, $left_table, $left_field, $right_field, $extra = NULL) {
970 $table['name'] = $table_name;
971 $table['provider'] = $provider;
972 $table['join']['left']['table'] = $left_table;
973 $table['join']['left']['field'] = $left_field;
974 $table['join']['right']['field'] = $right_field;
975 if ($extra) {
976 $table['join']['extra'] = $extra;
977 }
978 return $table;
979 }
980
981 /**
982 * Helper function to make table creation a little easier. It adds the necessary
983 * data to the $table array.
984 */
985 function views_table_add_field(&$table, $name, $label, $help, $others = array()) {
986 views_table_add_data($table, 'fields', $name, $label, $help, $others);
987 }
988
989 /**
990 * Helper function to make table creation a little easier. It adds the necessary
991 * data to the $table array.
992 */
993 function views_table_add_filter(&$table, $name, $label, $help, $others = array()) {
994 views_table_add_data($table, 'filters', $name, $label, $help, $others);
995 }
996
997 /**
998 * Helper function to make table creation a little easier. It adds the necessary
999 * data to the $table array.
1000 */
1001 function views_table_add_sort(&$table, $name, $label, $help, $others = array()) {
1002 views_table_add_data($table, 'sorts', $name, $label, $help, $others);
1003 }
1004
1005 /**
1006 * Helper function to make table creation a little easier. It adds the necessary
1007 * data to the $table array.
1008 */
1009 function views_table_add_data(&$table, $type, $name, $label, $help, $others = array()) {
1010 $table[$type][$name]['name'] = $label;
1011 $table[$type][$name]['help'] = $help;
1012 foreach ($others as $key => $value) {
1013 $table[$type][$name][$key] = $value;
1014 }
1015 }
1016
1017 /**
1018 * Create a blank view.
1019 */
1020 function views_create_view($name, $description, $access = array()) {
1021 $view = new stdClass();
1022 _views_check_arrays($view);
1023
1024 $view->name = $name;
1025 $view->description = $description;
1026 $view->access = $access;
1027
1028 // ensure some things are numerically 0.
1029 $view->nodes_per_page = 0;
1030 $view->nodes_per_block = 0;
1031 return $view;
1032 }
1033
1034 /**
1035 * Add page info to a view.
1036 */
1037 function views_view_add_page(&$view, $title, $url, $type, $pager, $nodes_per_page, $header, $header_format, $breadcrumb_no_home = FALSE) {
1038 $view->page = TRUE;
1039 $view->page_title = $title;
1040 $view->url = $url;
1041 $view->page_type = $type;
1042 $view->use_pager = $pager;
1043 $view->nodes_per_page = $nodes_per_page;
1044 $view->page_header = $header;
1045 $view->page_header_format = $header_format;
1046 $view->breadcrumb_no_home = $breadcrumb_no_home;
1047 }
1048
1049 /**
1050 * Add menu info to a view.
1051 */
1052 function views_view_add_menu(&$view, $title, $tab, $tab_weight, $default_tab) {
1053 $view->menu = TRUE;
1054 $view->menu_title = $title;
1055 $view->menu_tab = $tab;
1056 $view->menu_tab_weight = $tab_weight;
1057 $view->menu_tab_default = $default_tab;
1058 }
1059
1060 /**
1061 * Add block info to a view.
1062 */
1063 function views_view_add_block(&$view, $title, $type, $nodes_per_block, $more, $use_page_header, $header = '', $header_format = 0) {
1064 $view->block = TRUE;
1065 $view->block_title = $title;
1066 $view->block_type = $type;
1067 $view->nodes_per_block = $nodes_per_block;
1068 $view->block_more = $more;
1069 $view->block_use_page_header = $use_page_header;
1070 $view->block_header = $header;
1071 $view->block_header_format = $header_format;
1072 }
1073
1074 /**
1075 * Add field info to a view.
1076 */
1077 function views_view_add_field(&$view, $table, $field, $label, $sortable = FALSE, $default_sort = 0, $handler = '') {
1078 $view->field[] = array(
1079 'tablename' => $table,
1080 'field' => $field,
1081 'label' => $label,
1082 'sortable' => $sortable,
1083 'defaultsort' => $default_sort,
1084 'handler' => $handler
1085 );
1086 }
1087
1088 /**
1089 * Add argument info to a view.
1090 */
1091 function views_view_add_argument(&$view, $type, $default, $title, $option = '') {
1092 $view->argument[] = array(
1093 'type' => $type,
1094 'argdefault' => $default,
1095 'title' => $title,
1096 'options' => $option,
1097 );
1098 }
1099
1100 /**
1101 * Add filter info to a view.
1102 */
1103 function views_view_add_filter(&$view, $table, $field, $operator, $value, $option) {
1104 $view->filter[] = array(
1105 'tablename' => $table,
1106 'field' => $field,
1107 'operator' => $operator,
1108 'value' => $value,
1109 'options' => $option,
1110 );
1111 }
1112
1113 /**
1114 * Add exposed_filter info to a view.
1115 */
1116 function views_view_add_exposed_filter(&$view, $table, $field, $optional, $is_default, $lock_operator, $single) {
1117 $view->exposed_filter[] = array(
1118 'tablename' => $table,
1119 'field' => $field,
1120 'optional' => $optional,
1121 'is_default' => $is_default,
1122 'operator' => $lock_operator,
1123 'single' => $single
1124 );
1125 }
1126
1127 /**
1128 * Add sort info to a view.
1129 */
1130 function views_view_add_sort(&$view, $table, $field, $order, $option) {
1131 $view->sort[] = array(
1132 'tablename' => $table,
1133 'field' => $field,
1134 'sortorder' => $order,
1135 'options' => $option
1136 );
1137 }
1138
1139 // ---------------------------------------------------------------------------
1140 // Themeable and support for themeables.
1141
1142 /**
1143 * Themeable function to handle displaying a specific field.
1144 */
1145 function theme_views_handle_field($fields, $field, $data) {
1146 $info = $fields[$field['fullname']];
1147
1148 if ($field['handler'] && function_exists($field['handler'])) {
1149 return $field['handler']($info, $field, $data->$field['queryname'], $data);
1150 }
1151
1152 if ($info['handler'] && is_string($info['handler']) && function_exists($info['handler'])) {
1153 return $info['handler']($info, $field, $data->$field['queryname'], $data);
1154 }
1155
1156 return check_plain($data->$field['queryname']);
1157 }
1158
1159 /**
1160 * Construct a header for a table view.
1161 */
1162 function _views_construct_header($view, $fields) {
1163 foreach ($view->field as $field) {
1164 $header = array();
1165 $info = $fields[$field['fullname']];
1166
1167 if ($field['sortable']) {
1168 $header['data'] = ($field['label'] ? $field['label'] : $info['name']);
1169 if (function_exists($info['sort_handler'])) {
1170 $header['field'] = $info['sort_handler']($field, $info);
1171 }
1172 else {
1173 $header['field'] = $field['queryname'];
1174 }
1175 }
1176 else if ($field['label']) {
1177 $header['data'] = $field['label'];
1178 }
1179
1180 if ($field['defaultsort']) {
1181 $header['sort'] = strtolower($field['defaultsort']);
1182 }
1183
1184 // Add CSS id to table cell header cell.
1185 $header['class'] = "view-cell-header" . views_css_safe(' view-field-'. $field['queryname']);
1186 $headers[] = $header;
1187 }
1188 return $headers;
1189 }
1190
1191 function theme_views_display_filters($view) {
1192 return drupal_get_form("views_filters", $view);
1193 }
1194
1195 function views_filters($view) {
1196 $filters = _views_get_filters();
1197 foreach ($view->exposed_filter as $count => $expose) {
1198 $id = $expose['id'];
1199 $filterinfo = $filters[$id];
1200 foreach ($view->filter as $filter) {
1201 if ($filter['id'] == $id) {
1202 break;
1203 }
1204 }
1205
1206 // set up the operator widget.
1207 if (!$expose['operator']) {
1208 // 'operator' is either an array or a handler
1209 $operator = $filterinfo['operator'];
1210 if (!is_array($operator) && function_exists($filterinfo['operator'])) {
1211 $operator = $filterinfo['operator']('operator', $filterinfo);
1212 }
1213
1214 $form["op$count"] = array(
1215 '#name' => "op$count", // get rid of edit[] array.
1216 '#type' => 'select',
1217 '#default_value' => $filter['operator'],
1218 '#options' => $operator,
1219 );
1220 if (array_key_exists("op$count", $_GET)) {
1221 $form["op$count"]["#default_value"] = $_GET["op$count"];
1222 }
1223 }
1224
1225
1226 // set up the filter widget.
1227 $item = $filterinfo['value'];
1228 $item['#name'] = "filter$count";
1229
1230 if (!is_array($item['#options']) && function_exists($item['#options'])) {
1231 $item['#options'] = $item['#options']('value', $filterinfo);
1232 }
1233 if (!$expose['optional'] || $expose['is_default']) {
1234 $item['#default_value'] = $filter['value'];
1235 }
1236
1237 if ($expose['single']) {
1238 unset($item['#multiple']);
1239 }
1240 if ($expose['optional'] && is_array($item['#options'])) {
1241 $item['#options'] = array('**ALL**' => t('<All>')) + $item['#options'];
1242 }
1243
1244 if ($item['#multiple'] && is_array($item['#options'])) {
1245 $item['#size'] = min(count($item['#options']), 8);
1246 }
1247
1248 if (array_key_exists("filter$count", $_GET)) {
1249 $item["#default_value"] = $_GET["filter$count"];
1250 }
1251 $form["filter$count"] = $item;
1252 }
1253 $form['#method'] = 'get';
1254 $form['#process'] = array('views_filters_process' => array());
1255 $form['#action'] = url($view->real_url ? $view->real_url : $view->url, NULL, NULL, true);
1256 $form['view'] = array('#type' => 'value', '#value' => $view);
1257 $form['submit'] = array('#type' => 'button', '#name' => '', '#value' => t('Submit'));
1258 // clean URL get forms breaks if we don't give it a 'q'.
1259 if (!(bool)variable_get('clean_url', '0')) {
1260 $form['q'] = array(
1261 '#type' => 'hidden',
1262 '#value' => $view->real_url ? $view->real_url : $view->url,
1263 '#name' => 'q',
1264 );
1265 }
1266
1267
1268 return $form;
1269 }
1270
1271 function views_filters_process($form) {
1272 unset($form['form_id']);
1273 unset($form['form_token']);
1274 return $form;
1275 }
1276 function theme_views_filters($form) {
1277 $view = $form['view']['#value'];
1278
1279 foreach ($view->exposed_filter as $count => $expose) {
1280 $row[] = drupal_render($form["op$count"]) . drupal_render($form["filter$count"]);
1281 $label[] = $expose['label'];
1282 }
1283 $row[] = drupal_render($form['submit']);
1284 $label[] = ''; // so the column count is the same.
1285
1286 // make the 'q' come first
1287 return drupal_render($form['q']) . theme('table', $label, array($row)) . drupal_render($form);
1288 }
1289
1290 /**
1291 * Display the nodes of a view as a list.
1292 */
1293 function theme_views_view_list($view, $nodes, $type) {
1294 $fields = _views_get_fields();
1295
1296 foreach ($nodes as $node) {
1297 $item = '';
1298 foreach ($view->field as $field) {
1299 if ($fields[$field['id']]['visible'] !== FALSE) {
1300 if ($field['label']) {
1301 $item .= "<div class='view-label ". views_css_safe('view-label-'. $field['queryname']) ."'>" . $field['label'] . "</div>";
1302 }
1303 $item .= "<div class='view-field ". views_css_safe('view-data-'. $field['queryname']) ."'>" . views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $node, $view) . "</div>";
1304 }
1305 }
1306 $items[] = "<div class='view-item ". views_css_safe('view-item-'. $view->name) ."'>$item</div>\n"; // l($node->title, "node/$node->nid");
1307 }
1308 if ($items) {
1309 return theme('item_list', $items);
1310 }
1311 }
1312
1313 /**
1314 * Display the nodes of a view as a table.
1315 */
1316 function theme_views_view_table($view, $nodes, $type) {
1317 $fields = _views_get_fields();
1318
1319 foreach ($nodes as $node) {
1320 $row = array();
1321 foreach ($view->field as $field) {
1322 if ($fields[$field['id']]['visible'] !== FALSE) {
1323 $cell['data'] = views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $node, $view);
1324 $cell['class'] = "view-field ". views_css_safe('view-field-'. $field['queryname']);
1325 $row[] = $cell;
1326 }
1327 }
1328 $rows[] = $row;
1329 }
1330 return theme('table', $view->table_header, $rows);
1331 }
1332
1333 /**
1334 * Display the nodes of a view as teasers.
1335 */
1336 function theme_views_view_teasers($view, $nodes, $type) {
1337 return views_theme('views_view_nodes', $view, $nodes, $type, true);
1338 }
1339
1340 /**
1341 * Display the nodes of a view as plain nodes.
1342 */
1343 function theme_views_view_nodes($view, $nodes, $type, $teasers = false, $links = true) {
1344 foreach ($nodes as $n) {
1345 $node = node_load($n->nid);
1346 $output .= node_view($node, $teasers, false, $links);
1347 }
1348 return $output;
1349 }
1350
1351 function views_set_breadcrumb($view) {
1352 $breadcrumb = drupal_get_breadcrumb();
1353 if ($view->breadcrumb_no_home) {
1354 array_shift($breadcrumb);
1355 }
1356
1357 if ($view->args) {
1358 // Add a breadcrumb trail for each level of argument we're at.
1359 $url = $view->url;
1360 $args = array();
1361 $where = 1;
1362 foreach ($view->args as $level => $arg) {
1363 if ($view->argument[$level]['argdefault'] != 1) {
1364 $breadcrumb[] = l(filter_xss_admin(views_get_title($view, 'page', $args)), $url, NULL, NULL, NULL, NULL, TRUE);
1365 // For next round.
1366 }
1367 $args[] = $arg;
1368 if ($where && $where = strpos('$arg', $url)) {
1369 $url = substr_replace($url, $arg, $where, 4);
1370 }
1371 else {
1372 $url .= "/$arg";
1373 }
1374 }
1375 }
1376
1377 drupal_set_breadcrumb($breadcrumb);
1378 }
1379
1380 function views_get_textarea($view, $type, $textarea) {
1381 $use_page = "block_use_page_$textarea";
1382 $var = ($type != 'block' || $view->$use_page ? 'page_' : 'block_') . $textarea;
1383 $format = $var . '_format';
1384
1385 if ($view->$var) {
1386 return "<div class='". views_css_safe('view-'. $textarea .' view-'. $textarea .'-'. $view->name) ."'>"
1387 . check_markup($view->$var, $view->$format, false) . "</div>\n";
1388 }
1389 }
1390
1391 /**
1392 * Prepare the specified string for use as a CSS identifier.
1393 */
1394 function views_css_safe($string) {
1395 return str_replace('_', '-', $string);
1396 }
1397
1398 /**
1399 * Display a view.
1400 */
1401 function theme_views_view($view, $type, $nodes, $level = NULL, $args = NULL) {
1402 $num_nodes = count($nodes);
1403
1404 if ($type == 'page') {
1405 drupal_set_title(filter_xss_admin(views_get_title($view, 'page')));
1406 views_set_breadcrumb($view);
1407 }
1408
1409 if ($num_nodes) {
1410 $output .= views_get_textarea($view, $type, 'header');
1411 }
1412
1413 if ($type != 'block' && $view->exposed_filter) {
1414 $output .= views_theme('views_display_filters', $view);
1415 }
1416
1417 $plugins = _views_get_style_plugins();
1418 $view_type = ($type == 'block') ? $view->block_type : $view->page_type;
1419 if ($num_nodes || $plugins[$view_type]['even_empty']) {
1420 if ($level !== NULL) {
1421 $output .= "<div class='view-summary ". views_css_safe('view-summary-'. $view->name) ."'>". views_theme($plugins[$view_type]['summary_theme'], $view, $type, $level, $nodes, $args) . '</div>';
1422 }
1423 else {
1424 $output .= "<div class='view-content ". views_css_safe('view-content-'. $view->name) ."'>". views_theme($plugins[$view_type]['theme'], $view, $nodes, $type) . '</div>';
1425 }
1426 $output .= views_get_textarea($view, $type, 'footer');
1427
1428 if ($type == 'block' && $view->block_more && $num_nodes >= $view->nodes_per_block) {
1429 $output .= theme('views_more', $view->real_url);
1430 }
1431 }
1432 else {
1433 $output .= views_get_textarea($view, $type, 'empty');
1434 }
1435
1436 if ($view->use_pager) {
1437 $output .= theme('pager', '', $view->pager_limit, $view->use_pager - 1);
1438 }
1439
1440 if ($output) {
1441 $output = "<div class='view ". views_css_safe('view-'. $view->name) ."'>$output</div>\n";
1442 }
1443 return $output;
1444 }
1445
1446 /**
1447 * Format the 'more' link for a view. Personally I prefer [More] but I've
1448 * been convinced to go with simply 'more'.
1449 */
1450 function theme_views_more($url) {
1451 return "<div class='more-link'>" . l(t('more'), $url) . "</div>";
1452 }
1453
1454 /**
1455 * Get the summary link for a view.
1456 */
1457 function views_get_summary_link($argtype, $item, $base) {
1458 $arginfo = _views_get_arguments();
1459 return $arginfo[$argtype]['handler']('link', $item, $argtype, $base);
1460 }
1461
1462 /**
1463 * Display a summary version of a view.
1464 */
1465 function theme_views_summary($view, $type, $level, $nodes, $args) {
1466 foreach ($nodes as $node) {
1467 $items[] = views_get_summary_link($view->argument[$level]['type'], $node, $view->real_url) . " (" . $node->num_nodes . ")";
1468 }
1469 if ($items) {
1470 $output .= theme('item_list', $items);
1471 }
1472
1473 return $output;
1474 }
1475
1476 // ---------------------------------------------------------------------------
1477 // Generic handlers. These make sense to be used in a lot of different places.
1478
1479 /**
1480 * Field handlers accept the following arguments:
1481 * @param $fieldinfo
1482 * The array of info for that field from the global tables array.
1483 * @param $fielddata
1484 * All of the info about that field in the database.
1485 * @param $value
1486 * The value of the field fetched from the database.
1487 * @param $data
1488 * The rest of the data about the node fetched from the database, in case
1489 * the handler needs more than just the field.
1490 */
1491
1492 /**
1493 * Format a date.
1494 */
1495 function views_handler_field_date($fieldinfo, $fielddata, $value, $data) {
1496 return $value ? format_date($value) : theme('views_nodate');
1497 }
1498
1499 /**
1500 * Format a date using small representation.
1501 */
1502 function views_handler_field_date_small($fieldinfo, $fielddata, $value, $data) {
1503 return $value ? format_date($value, 'small') : theme('views_nodate');
1504 }
1505
1506 /**
1507 * Format a date using large representation.
1508 */
1509 function views_handler_field_date_large($fieldinfo, $fielddata, $value, $data) {
1510 return $value ? format_date($value, 'large') : theme('views_nodate');
1511 }
1512
1513 /**
1514 * Format a date using custom representation.
1515 */
1516 function views_handler_field_date_custom($fieldinfo, $fielddata, $value, $data) {
1517 return $value ? format_date($value, 'custom', $fielddata['options']) : theme('views_nodate');
1518 }
1519
1520 /**
1521 * Format a date as "X time ago".
1522 */
1523 function views_handler_field_since($fieldinfo, $fielddata, $value, $data) {
1524 return $value ? t('%time ago', array('%time' => format_interval(time() - $value, is_numeric($fielddata['options']) ? $fielddata['options'] : 2))) : theme('views_nodate');
1525 }
1526
1527 function theme_views_nodate() {
1528 return '<span class="views-nodate">' . t('never') . '</span>';
1529 }
1530
1531 /**
1532 * Provide a list of all standard supproted date output handlers.
1533 */
1534 function views_handler_field_dates() {
1535 return array(
1536 'views_handler_field_date_small' => t('As Short Date'),
1537 'views_handler_field_date' => t('As Medium Date'),
1538 'views_handler_field_date_large' => t('As Long Date'),
1539 'views_handler_field_date_custom' => t('As Custom Date'),
1540 'views_handler_field_since' => t('As Time Ago')
1541 );
1542 }
1543
1544 function views_handler_sort_date_options() {
1545 return array(
1546 '#type' => 'select',
1547 '#options' => array(
1548 'normal' => t('Normal'),
1549 'minute' => t('Granularity: minute'),
1550 'hour' => t('Granularity: hour'),
1551 'day' => t('Granularity: day'),
1552 'month' => t('Granularity: month'),
1553 'year' => t('Granularity: year'),
1554 ),
1555 );
1556 }
1557
1558 function views_handler_sort_date($op, &$query, $sortinfo, $sort) {
1559 switch($sort['options']) {
1560 case 'normal':
1561 default:
1562 $table = $sortinfo['table'];
1563 $field = $sortinfo['field'];
1564 break;
1565 case 'minute':
1566 $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%m%%d%H%m')";
1567 break;
1568 case 'hour':
1569 $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%m%%d%H')";
1570 break;
1571 case 'day':
1572 $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%m%%d')";
1573 break;
1574 case 'month':
1575 $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%m%)";
1576 break;
1577 case 'year':
1578 $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%')";
1579 break;
1580 }
1581 $alias = $as = $sortinfo['table'] . '_' . $sortinfo['field'];
1582 if (!$table) {
1583 $as .= '_orderby';
1584 $alias = $field;
1585 }
1586
1587 // $query->add_field($field, $table, $as);
1588 // $query->orderby[] = "$alias $sort[sortorder]";
1589 $query->add_orderby($table, $field, $sort['sortorder'], $as);
1590 }
1591
1592 function views_handler_sort_date_minute($op, &$query, $sortinfo, $sort) {
1593 $field = "DATE_FORMAT(FROM_UNIXTIME($table.$sortinfo[field]), '%Y%m%%d%H%m')";
1594 $query->add_orderby(NULL, $field, $sort['sortorder']);
1595 }
1596
1597 /**
1598 * Format a field as an integer.
1599 */
1600 function views_handler_field_int($fieldinfo, $fielddata, $value, $data) {
1601 return intval($value);
1602 }
1603
1604 /**
1605 * Argument handlers take up to 4 fields, which vary based upon the operation.
1606 * @param $op
1607 * The operation to perform:
1608 * 'summary': A summary view is being constructed. In this case the handler
1609 * is to add the necessary components to the query to display
1610 * the summary. It must return a $fieldinfo array with 'field'
1611 * set to the field the summary is ordered by; if this is aliased
1612 * for some reason (such as being an aggregate field) set 'fieldname'
1613 * to the alias.
1614 * 'sort': Set up the view to sort based upon the setting in $a2.
1615 * 'filter': Filter the view based upon the argument sent; essentially just
1616 * add the where clause here.
1617 * 'link': Provide a link from a summary view based upon the argument sent.
1618 * 'title': Provide the title of a view for substitution.
1619 * @param &$query
1620 * For summary, filter and link, this is the actual query object; for title this is
1621 * simply the value of the argument.
1622 * @param $a2
1623 * For summary, this is the type of the argument. For the others, this is the info
1624 * for the argument from the global table. (Why is this not consistent? I dunno).
1625 * @param $a3
1626 * For summary, this is the 'options' field from the db. For 'filter' this is
1627 * the argument received. For 'link' this is the base URL of the link. Not used
1628 * for 'title'.
1629 *
1630 */
1631
1632 // ---------------------------------------------------------------------------
1633 // Filter handlers
1634
1635 /**
1636 * There are two kinds of filter handlers here; the easy kind simply creates an
1637 * array of options. For example, for taxonomy we provide a list of all taxonomy
1638 * terms which is placed in the select box.
1639 *
1640 * The other type is the 'custom' handler which is used to create a customized
1641 * WHERE clause for specialized filters.
1642 *
1643 * It takes 4 parameters.
1644 * @param $op
1645 * At this time it will always be 'handler'.
1646 * @param $filter
1647 * Information on the filter from the database, including 'options', 'value' and 'operator'.
1648 * @param $filterinfo
1649 * Information on the filter from the global table array.
1650 * @param &$query
1651 * The query object being worked on.
1652 */
1653
1654 /**
1655 * A list of and/or/nor.
1656 */
1657 function views_handler_operator_andor() {
1658 return array('AND' => t('Is All Of'), 'OR' => t('Is One Of'), 'NOR' => t('Is None Of'));
1659 }
1660
1661 /**
1662 * A list of or/nor.
1663 */
1664 function views_handler_operator_or() {
1665 return array('OR' => t('Is One Of'), 'NOR' => t('Is None Of'));
1666 }
1667
1668 /**
1669 * A list of equal or not equal to.
1670 */
1671 function views_handler_operator_eqneq() {
1672 return array('=' => t('Is Equal To'), '!=' => t('Is Not Equal To'));
1673 }
1674
1675 /**
1676 * A list of greater / equal / less than
1677 */
1678 function views_handler_operator_gtlt() {
1679 return array('>' => t("Is Greater Than"), '>=' => t("Is Greater Than Or Equals"), '=' => t("Is Equal To"), '!=' => t("Is Not Equal To"), '<=' => t("Is Less Than Or Equals"), '<' => t("Is Less Than"));
1680 }
1681
1682 /**
1683 * A list of yes/no.
1684 */
1685 function views_handler_operator_yesno() {
1686 return array('1' => t('Yes'), '0' => t('No'));
1687 }
1688
1689 /*
1690 * Break x,y,z and x+y+z into an array. Numeric only.
1691 */
1692 function _views_break_phrase($str) {
1693 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
1694 // The '+' character in a query string may be parsed as ' '.
1695 return array('or', preg_split('/[+ ]/', $str));
1696 }
1697 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
1698 return array('and', explode(',', $str));
1699 }
1700 else {
1701 return NULL;
1702 }
1703 }
1704
1705 /**
1706 * Default Views style plugins. Implementation of hook_views_style_plugins()
1707 */
1708 function views_views_style_plugins() {
1709 return array(
1710 'list' => array(
1711 'name' => t('List View'),
1712 'theme' => 'views_view_list',
1713 'validate' => 'views_ui_plugin_validate_list',
1714 'needs_fields' => true,
1715 'weight' => -10,
1716 ),
1717 'table' => array(
1718 'name' => t('Table View'),
1719 'theme' => 'views_view_table',
1720 'validate' => 'views_ui_plugin_validate_table',
1721 'needs_fields' => true,
1722 'needs_table_header' => true,
1723 'weight' => -9,
1724 ),
1725 'teaser' => array(
1726 'name' => t('Teaser List'),
1727 'theme' => 'views_view_teasers',
1728 'weight' => -8,
1729 ),
1730 'node' => array(
1731 'name' => t('Full Nodes'),
1732 'theme' => 'views_view_nodes',
1733 'weight' => -7,
1734 ),
1735 );
1736 }
1737
1738 /**
1739 * Default Views URL tokens
1740 */
1741 function views_views_url_tokens() {
1742 return array(
1743 '$arg' => 'views_url_arg',
1744 '$node' => 'views_url_node',
1745 '$user' => 'views_url_user',
1746 );
1747 }
1748
1749 /**
1750 * Handle '$arg' in a URL. Any non-empty value is true.
1751 */
1752 function views_url_arg($token, $argument, $arg) {
1753 return $arg !== '' && $arg !== NULL;
1754 }
1755
1756 /**
1757 * Handle '$node' in a URL. Any valid node wil ldo.
1758 */
1759 function views_url_node($token, $argument, $arg) {
1760 if (!is_numeric($arg)) {
1761 return FALSE;
1762 }
1763
1764 $node = node_load($arg);
1765 if (!$node) {
1766 return FALSE;
1767 }
1768
1769 if ($argument && $node->type != $argument) {
1770 return FALSE;
1771 }
1772
1773 // if a node loads, return true.
1774 return TRUE;
1775 }
1776
1777 /**
1778 * Handle '$user' in a URL. Any valid user wil ldo.
1779 */
1780 function views_url_user($token, $argument, $arg) {
1781 if (!is_numeric($arg)) {
1782 return FALSE;
1783 }
1784
1785 // if a user loads, return true.
1786 return (bool) user_load($arg);
1787 }
1788
1789 /**
1790 * A list of options to be used in LIKE queries
1791 */
1792 function views_handler_operator_like() {
1793 return array('=' => t('Is Equal To'), 'contains' => t('Contains'), 'word' => t('Contains Any Word'), 'allwords' => t('Contains All Words'), 'starts' => t('Starts With'), 'ends' => t('Ends With'), 'not' => t('Does Not Contain'));
1794 }
1795
1796 /**
1797 * Custom filter for LIKE operations
1798 */
1799 function views_handler_filter_like($op, $filter, $filterinfo, &$query) {
1800 switch (trim($filter['value'])) {
1801 case (''):
1802 return;
1803 break;
1804 }
1805 switch ($op) {
1806 case 'handler':
1807 $table = $filterinfo['table'];
1808 $column = $filterinfo['field'];
1809 $field = "$table.$column";
1810 $query->ensure_table($table);
1811
1812 switch ($filter['operator']) {
1813 case 'contains':
1814 $query->add_where("UPPER(%s) LIKE UPPER('%%%s%%')",
1815 $field, $filter['value']);
1816 break;
1817 case 'word':
1818 case 'allwords':
1819 preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $filter['value'], $matches, PREG_SET_ORDER);
1820 foreach ($matches as $match) {
1821 $phrase = false;
1822 // Strip off phrase quotes
1823 if ($match[2]{0} == '"') {
1824 $match[2] = substr($match[2], 1, -1);
1825 $phrase = true;
1826 }
1827 $words = trim($match[2], ',?!();:-');
1828 $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
1829 foreach ($words as $word) {
1830 $where[] = "UPPER(%s) LIKE UPPER('%%%s%%')";
1831 $values[] = $field;
1832 $values[] = trim($word, " ,!?");
1833 }
1834 }
1835 if ($filter['operator'] == 'word') {
1836 $where = '('. implode(' OR ', $where) .')';
1837 }
1838 else {
1839 $where = implode(' AND ', $where);
1840 }
1841 // previously this was a call_user_func_array but that's unnecessary
1842 // as views will unpack an array that is a single arg.
1843 $query->add_where($where, $values);
1844 break;
1845 case 'starts':
1846 $query->add_where("UPPER(%s) LIKE UPPER('%s%%')",
1847 $field, $filter['value']);
1848 break;
1849 case 'ends':
1850 $query->add_where("UPPER(%s) LIKE UPPER('%%%s')",
1851 $field, $filter['value']);
1852 break;
1853 case 'not':
1854 $query->add_where("UPPER(%s) NOT LIKE UPPER('%%%s%%')",
1855 $field, $filter['value']);
1856 break;
1857 case '=':
1858 $query->add_where("UPPER(%s) = UPPER('%s')",
1859 $field, $filter['value']);
1860 break;
1861 }
1862 break;
1863 }
1864 }
1865
1866 /**
1867 * Format a field as file size.
1868 */
1869 function views_handler_field_filesize($fieldinfo, $fielddata, $value, $data) {
1870 return format_size($value);
1871 }
1872
1873 /**
1874 * Handle a timestamp filter.
1875 */
1876 function views_handler_filter_timestamp($op, $filter, $filterinfo, &$query) {
1877 $value = 0;
1878 if ($filter['value']) {
1879 $value = $filter['value'] == 'now' ? "***CURRENT_TIME***" : strtotime($filter['value']);
1880 }
1881
1882 $table = $filterinfo['table'];
1883 $column = $filterinfo['field'];
1884 $field = "$table.$column";
1885 if ($filterinfo['from_unixtime']) {
1886 $field = "from_UNIXTIME($field)";
1887 }
1888 $query->ensure_table($table);
1889 $query->add_where("%s %s %s + %d", $field, $filter['operator'], $value, $filter['options']);
1890 }
1891
1892 /**
1893 * Provide validation for filters with array values. The $name
1894 * must be provided by whatever added the validator to the
1895 * form gadget.
1896 */
1897 function views_filter_validate_array($form, $name) {
1898 $op = $_POST['op'];
1899
1900 if ($op != t('Save') && $op != t('Save and edit')) {
1901 return; // only validate on saving!
1902 }
1903 if (empty($form['#value'])) {
1904 form_error($form, t('@filter must have a value!', array('@filter' => $name)));
1905 }
1906 }
1907
1908 /**
1909 * Provide a form gadget for dates.
1910 */
1911 function views_handler_filter_date_value_form() {
1912 return array(
1913 '#type' => 'textfield',
1914 '#attributes' => array('class' => 'jscalendar'),
1915 );
1916 }
1917 /**
1918 * Substitute current time; this works with cached queries.
1919 */
1920 function views_views_query_substitutions($view) {
1921 global $user;
1922 return array('***CURRENT_TIME***' => time());
1923 }
1924
1925 /**
1926 * Returns a themed view.
1927 * @param $view_name
1928 * The name of the view.
1929 * @param $limit
1930 * Maximum number of nodes displayed on one page. if $limit is set and $use_pager is
1931 * not, this will be the maximum number of records returned. This is ignored
1932 * if using a view set to return a random result.
1933 * If NULL, the setting defined for the $view will be used.
1934 * @param $use_pager
1935 * If set, use a pager. Set this to the pager id you want it to use if you
1936 * plan on using multiple pagers on a page. Note that the pager element id
1937 * will be decremented in order to have the IDs start at 0.
1938 * If NULL, the setting defined for the $view will be used.
1939 * @param $type
1940 * 'page' -- Produce output as a page, sent through theme.
1941 * The only real difference between this and block is that
1942 * a page uses drupal_set_title to change the page title.
1943 * 'block' -- Produce output as a block, sent through theme.
1944 * 'embed' -- Use this if you want to embed a view onto another page,
1945 * and don't want any block or page specific things to happen to it.
1946 * @param $view_args
1947 * An array containing the arguments for the view
1948 */
1949 function theme_view($view_name, $limit = NULL, $use_pager = NULL, $type = 'embed', $view_args = array()) {
1950 if ($view = views_get_view($view_name)) {
1951 $use_pager = isset($use_pager) ? $use_pager : $view->use_pager;
1952 $limit_default = ($type == 'block') ? $view->nodes_per_block : $view->nodes_per_page;
1953 $limit = isset($limit) ? $limit : $limit_default;
1954 return views_build_view($type, $view, $view_args, $use_pager, $limit);
1955 }
1956 }
1957
1958
1959 /**
1960 * This function is used as a central place to manage some translatable text strings
1961 * that are used in different places.
1962 * @param $text
1963 * which string to return.
1964 */
1965 function views_t_strings($text) {
1966 switch ($text) {
1967 case 'filter date':
1968 return t('The "Value" can either be a date in the format: CCYY-MM-DD HH:MM:SS or the word "now" to use the current time. You may enter a positive or negative number in the "Option" field that will represent the amount of seconds that will be added or substracted to the time; this is most useful when combined with "now". If you have the jscalendar module from jstools installed, you can use a popup date picker here.');
1969 }
1970 }