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