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