Add basic support for summary styles; they don't quite work yet because view URL...
[project/views.git] / includes / handlers.inc
CommitLineData
013538bb
EM
1<?php
2// $Id$
3/**
4 * @file handlers.inc
5 * Defines the various handler objects to help build and display views.
6 */
7
8/**
9 * @defgroup views_join_handlers Views' join handlers
10 * @{
11 * Handlers to tell Views how to join tables together.
12
13 * Here is how you do complex joins:
14 *
15 * @code
16 * class views_join_complex extends views_join {
86ed07d8
EM
17 * // PHP 4 doesn't call constructors of the base class automatically from a
18 * // constructor of a derived class. It is your responsibility to propagate
013538bb 19 * // the call to constructors upstream where appropriate.
cffc1056
EM
20 * function construct($left_table, $left_field, $field, $extra = array(), $type = 'LEFT') {
21 * parent::construct($left_table, $left_field, $field, $extra, $type);
013538bb
EM
22 * }
23 *
24 * function join($table, &$query) {
25 * $output = parent::join($table, $query);
26 * }
27 * $output .= "AND foo.bar = baz.boing";
28 * return $output;
29 * }
30 * @endcode
31 */
32/**
33 * A function class to represent a join and create the SQL necessary
34 * to implement the join.
86ed07d8 35 *
013538bb
EM
36 * This is the Delegation pattern. If we had PHP5 exclusively, we would
37 * declare this an interface.
38 *
39 * Extensions of this class can be used to create more interesting joins.
40 */
41class views_join {
42 /**
43 * Construct the views_join object.
44 */
cffc1056 45 function construct($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') {
013538bb
EM
46 $this->table = $table;
47 $this->left_table = $left_table;
48 $this->left_field = $left_field;
49 $this->field = $field;
50 $this->extra = $extra;
51 $this->type = strtoupper($type);
52 }
53
54 /**
55 * Build the SQL for the join this object represents.
56 */
57 function join($table, &$query) {
58 $left = $query->get_table_info($this->left_table);
59 $output = " $this->type JOIN {" . $this->table . "} $table[alias] ON $left[alias].$this->left_field = $table[alias].$this->field";
60
61 // Tack on the extra.
62 if (isset($extra)) {
63 foreach ($extra as $field => $value) {
64 $output .= " AND $table[alias].$this->field";
65 if (is_array($value) && !empty($value)) {
66 $output .= " IN ('". implode("','", $value) ."')";
67 }
68 else if ($value !== NULL) {
69 $output .= " = '$value'";
70 }
71 }
72 }
86ed07d8 73 return $output;
013538bb
EM
74 }
75}
76
77/**
78 * @}
79 */
80
81/**
86ed07d8 82 * Base handler, from which all the other handlers are derived.
013538bb
EM
83 * It creates a common interface to create consistency amongst
84 * handlers and data.
85 *
86 * The default handler has no constructor, so there's no need to jank with
87 * parent::views_handler() here.
88 *
89 * This class would be abstract in PHP5, but PHP4 doesn't understand that.
86ed07d8 90 *
013538bb 91 */
cffc1056 92class views_handler extends views_object {
013538bb 93 /**
86ed07d8 94 * init the handler with necessary data.
013538bb
EM
95 * @param $view
96 * The $view object this handler is attached to.
97 * @param $data
98 * The item from the database; the actual contents of this will vary
99 * based upon the type of handler.
100 */
86ed07d8 101 function init(&$view, &$data) {
013538bb
EM
102 $this->view = &$view;
103 $this->data = &$data;
104
105 // Mostly this exists to make things easier to reference. $this->options['...']
106 // is a little easier than $this->data->options['...'];
107 if (isset($data->options)) {
108 $this->options = $data->options;
109 }
110 else {
111 $this->options = array();
112 }
113
114 // This exist on most handlers, but not all. So they are still optional.
cffc1056
EM
115 if (isset($data->tablename)) {
116 $this->table = $data->tablename;
013538bb
EM
117 }
118
119 if (isset($data->field)) {
120 $this->field = $data->field;
cffc1056
EM
121 if (!isset($this->real_field)) {
122 $this->real_field = $data->field;
123 }
013538bb
EM
124 }
125
126 if (isset($data->relationship)) {
127 $this->relationship = $data->relationship;
128 }
129
130 if (!empty($view->query)) {
131 $this->query = &$view->query;
132 }
133 }
134
135 /**
136 * Provide a form for setting options.
137 */
138 function options_form(&$form) { }
86ed07d8 139
013538bb
EM
140 /**
141 * Validate the options form.
142 */
143 function options_validate($form, &$form_state) { }
144
145 /**
146 * Perform any necessary changes to the form values prior to storage.
147 * There is no need for this function to actually store the data.
148 */
149 function options_submit($form, &$form_state) { }
150
151 /**
152 * Add this handler into the query.
153 *
154 * If we were using PHP5, this would be abstract.
155 */
156 function query() { }
cffc1056
EM
157
158 /**
159 * Ensure the main table for this handler is in the query. This is used
160 * a lot.
161 */
162 function ensure_my_table() {
163 if (!isset($this->alias)) {
164 $this->table_alias = $this->query->ensure_table($this->table, $this->relationship);
165 }
166 return $this->table_alias;
167 }
013538bb
EM
168}
169
170/**
171 * @defgroup views_relationship_handlers Views' relationship handlers
172 * @{
173 * Handlers to tell Views how to create alternate relationships.
174 */
175
176/**
177 * Simple relationship handler that allows a new version of the primary table
178 * to be linked in.
179 */
180class views_handler_relationship extends views_handler {
181 /**
182 * Called to implement a relationship in a query.
183 */
184 function query() {
185 $alias = $this->table . '_' . $this->field . '_' . $this->relationship;
cffc1056 186 return $this->query->add_relationship($alias, new views_join($this->view->primary_table, $this->table, $this->real_field, $this->primary_field), $this->relationship);
013538bb
EM
187 }
188}
189
190/**
191 * @}
192 */
193
194/**
195 * @defgroup views_field_handlers Views' field handlers
196 * @{
197 * Handlers to tell Views how to build and display fields.
cffc1056 198 *
013538bb
EM
199 */
200
201/**
202 * Base field handler that has no options and renders an unformatted field.
203 */
204class views_handler_field extends views_handler {
b06399e3 205 var $field_alias = 'unknown';
013538bb
EM
206
207 /**
208 * Construct a new field handler.
209 */
cffc1056 210 function construct($click_sortable = FALSE, $additional_fields = array()) {
013538bb
EM
211 $this->click_sortable = $click_sortable;
212 $this->additional_fields = $additional_fields;
213 }
214
215 /**
216 * Called to add the field to a query.
217 */
218 function query() {
cffc1056 219 $this->ensure_my_table();
013538bb 220 // Add the field.
cffc1056 221 $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field);
b06399e3 222
013538bb
EM
223 // Add any additional fields we are given.
224 if (!empty($this->additional_fields) && is_array($this->additional_fields)) {
cffc1056
EM
225 foreach ($this->additional_fields as $field) {
226 $this->aliases[$field] = $this->query->add_field($this->table_alias, $field);
013538bb
EM
227 }
228 }
229 }
230
231 /**
232 * Called to determine what to tell the clicksorter.
233 */
234 function click_sort() {
cffc1056 235 return "$this->field_alias";
013538bb
EM
236 }
237
238 /**
239 * Render the field.
240 *
241 * @param $values
242 * The values retrieved from the database.
243 */
244 function render($values) {
245 $value = $values->{$this->field_alias};
246 return check_plain($value);
247 }
248}
249
250/**
251 * A handler to provide proper displays for dates.
252 */
253class views_handler_field_date extends views_handler_field {
254 /**
255 * Constructor; calls to base object constructor.
256 */
cffc1056
EM
257 function construct($click_sortable = FALSE, $additional_fields = array()) {
258 parent::construct($click_sortable, $additional_fields);
013538bb
EM
259 }
260
261 function options_form(&$form) {
262 $form['date_format'] = array(
263 '#type' => 'select',
264 '#title' => t('Date format'),
265 '#options' => array(
266 'small' => t('Small'),
267 'medium' => t('Medium'),
268 'large' => t('Large'),
269 'custom' => t('Custom'),
270 'time ago' => t('Time ago'),
271 ),
272 '#default_value' => isset($this->options['date_format']) ? $this->options['date_format'] : 'small',
273 );
274 $form['custom_date_format'] = array(
275 '#type' => 'textfield',
276 '#title' => t('Custom date format'),
277 '#description' => t('If "Custom", see <a href="http://us.php.net/manual/en/function.date.php">the PHP docs</a> for date formats. If "Time ago" this is the the number of different units to display, which defaults to two.'),
278 '#default_value' => isset($this->options['custom_date_format']) ? $this->options['custom_date_format'] : '',
279 );
280 }
281
282 function render($values) {
283 $value = $values->{$this->field_alias};
cffc1056
EM
284 $format = !empty($this->options['date_format']) ? $this->options['date_format'] : 'medium';
285 if ($format == 'custom') {
286 $custom_format = $this->options['custom_date_format'];
287 }
013538bb
EM
288
289 switch ($format) {
290 case 'time ago':
291 return $value ? t('%time ago', array('%time' => format_interval(time() - $value, is_numeric($custom_format) ? $custom_format : 2))) : theme('views_nodate');
292 case 'custom':
293 return $value ? format_date($value, $format, $custom_format) : theme('views_nodate');
294 default:
295 return $value ? format_date($value, $format) : theme('views_nodate');
296 }
297 }
298}
299
300/**
301 * @}
302 */
303
304/**
305 * @defgroup views_sort_handlers Views' sort handlers
306 * @{
307 * Handlers to tell Views how to sort queries
308 */
309/**
310 * Base sort handler that has no options and performs a simple sort
311 */
312class views_handler_sort extends views_handler {
313 /**
314 * Called to add the sort to a query.
315 */
316 function query() {
cffc1056 317 $this->ensure_my_table();
013538bb 318 // Add the field.
cffc1056 319 $this->query->add_orderby($this->table_alias, $this->real_field, $this->data->order);
013538bb
EM
320 }
321}
322
323/**
324 * Base sort handler that has no options and performs a simple sort
325 */
326class views_handler_sort_formula extends views_handler_sort {
327 /**
328 * Constructor to take the formula this sorts on.
329 *
330 * @param $formula
331 * The formula used to sort. If an array, may be keyed by database type. If
332 * used, 'default' MUST be defined.
333 */
cffc1056 334 function construct($formula) {
013538bb
EM
335 $this->formula = $formula;
336 if (is_array($formula) && !isset($formula['default'])) {
337 $this->error = t('views_handler_sort_formula missing default: @formula', array('@formula' => var_export($formula, TRUE)));
338 }
339 }
340 /**
341 * Called to add the sort to a query.
342 */
343 function query() {
344 if (is_array($this->formula)) {
345 global $db_type;
346 if (isset($this->formula[$db_type])) {
347 $formula = $this->formula[$db_type];
348 }
349 else {
350 $formula = $this->formula['default'];
351 }
352 }
353 else {
354 $formula = $this->formula;
355 }
cffc1056 356 $this->ensure_my_table();
013538bb 357 // Add the field.
cffc1056 358 $this->add_orderby(NULL, $this->formula, $this->data->order, $this->table_alias . '_' . $this->field);
013538bb
EM
359 }
360}
361
362/**
363 * @}
364 */
365
366/**
367 * @defgroup views_filter_handlers Views' filter handlers
368 * @{
369 * Handlers to tell Views how to filter queries.
370 */
371
372/**
373 * Base class for filters.
374 */
375class views_handler_filter extends views_handler {
376 /**
377 * Provide a form for setting the operator.
378 */
379 function operator_form(&$form) { }
86ed07d8 380
013538bb
EM
381 /**
382 * Validate the operator form.
383 */
384 function operator_validate($form, &$form_state) { }
385
386 /**
387 * Perform any necessary changes to the form values prior to storage.
388 * There is no need for this function to actually store the data.
389 */
390 function operator_submit($form, &$form_state) { }
391
392 /**
393 * Provide a form for setting options.
394 */
395 function value_form(&$form) { }
86ed07d8 396
013538bb
EM
397 /**
398 * Validate the options form.
399 */
400 function value_validate($form, &$form_state) { }
401
402 /**
403 * Perform any necessary changes to the form values prior to storage.
404 * There is no need for this function to actually store the data.
405 */
406 function value_submit($form, &$form_state) { }
407
408 /**
409 * Add this filter to the query.
410 */
411 function query() {
cffc1056
EM
412 $this->ensure_my_table();
413 $this->query->add_where($this->data->group, "$this->table_alias.$this->real_field " . $this->data->operator . " '%s'", $this->data->value);
013538bb
EM
414 }
415}
416
417/**
418 * @}
419 */
420
421/**
422 * @defgroup views_argument_handlers Handlers for arguments
423 * @{
424 */
425/**
86ed07d8 426 * Base class for arguments.
013538bb
EM
427 *
428 * The basic argument works for very simple arguments such as nid and uid
429 */
430class views_handler_argument extends views_handler {
431 /**
432 * Constructor
433 */
cffc1056 434 function construct($name_field = NULL) {
013538bb
EM
435 $this->name_field = $name_field;
436 }
437
438 /**
439 * Build the info for the summary query.
440 *
441 * This must:
442 * - add_groupby: group on this field in order to create summaries.
443 * - add_field: add a 'num_nodes' field for the count. Usually it will
444 * be a count on $view->primary_field
445 * - set_count_field: Reset the count field so we get the right paging.
446 *
86ed07d8 447 * @return
013538bb
EM
448 * The alias used to get the number of records (count) for this entry.
449 */
450 function summary_query() {
cffc1056 451 $this->ensure_my_table();
013538bb 452 // Add the field.
86ed07d8
EM
453 $this->base_alias = $this->query->add_field($this->table_alias, $this->real_field);
454
013538bb
EM
455 // Add the 'name' field. For example, if this is a uid argument, the
456 // name field would be 'name' (i.e, the username).
457 if (isset($this->name_field)) {
cffc1056 458 $this->name_alias = $this->query->add_field($this->table_alias, $this->name_field);
013538bb
EM
459 }
460 else {
461 $this->name_alias = $this->base_alias;
462 }
86ed07d8 463
cffc1056
EM
464 return $this->summary_basics();
465 }
013538bb 466
cffc1056
EM
467 /**
468 * Some basic summary behavior that doesn't need to be repeated as much as
469 * code that goes into summary_query()
470 */
471 function summary_basics($count_field = TRUE) {
013538bb
EM
472 // Add the number of nodes counter
473 $count_alias = $this->query->add_field(NULL, 'COUNT(' . $this->query->primary_field . ')', 'num_records');
474 $this->query->add_groupby($this->base_alias);
475
cffc1056
EM
476 if ($count_field) {
477 $this->query->set_count_field($this->table_alias, $this->real_field);
478 }
013538bb 479
86ed07d8 480 $this->count_alias = $count_alias;
013538bb
EM
481 }
482
483 /**
484 * Sorts the summary based upon the user's selection. The base variant of
485 * this is usually adequte.
86ed07d8 486 *
013538bb
EM
487 * @param $order
488 * The order selected in the UI.
489 */
490 function summary_sort($order) {
491 $query->add_orderby(NULL, NULL, $order, $this->base_alias);
492 }
493
494 /**
495 * Provides a link from the summary to the next level; this will be called
496 * once per row of a summary.
497 *
498 * @param $data
499 * The query results for the row.
500 * @param $url
501 * The base URL to use.
502 */
503 function summary_link($data, $url) {
cffc1056 504 $value = $data->{$this->base_alias};
86ed07d8 505 return url("$url/$value");
013538bb
EM
506 }
507
508 /**
509 * Provide a list of default behaviors for this argument if the argument
510 * is not present.
511 *
512 * Override this method to provide additional (or fewer) default behaviors.
513 */
514 function defaults() {
515 return array(
516 'ignore' => t('Display all values'),
517 'not found' => t('Display page not found'),
518 'empty' => t('Display empty text'),
519 'summary asc' => t('Summary, sorted ascending'),
520 'summary desc' => t('Summary, sorted descending'),
521 );
522 }
523
524 /**
525 * Handle the default action, which means our argument wasn't present.
526 *
527 * Override this method only with extreme care.
528 *
529 * @return
530 * A boolean value; if TRUE, continue building this view. If FALSE,
531 * building the view will be aborted here.
532 */
533 function default_action() {
534 $action = $this->data->default_action;
535 switch ($action) {
536 default:
537 case 'ignore':
538 // Do nothing at all.
539 return TRUE;
540 case 'not found':
541 // Set a failure condition and let the display manager handle it.
542 $this->view->build_info['fail'] = TRUE;
543 return FALSE;
544 case 'empty':
545 // We return with no query; this will force the empty text.
546 $this->view->built = TRUE;
547 return FALSE;
548 case 'summary':
549 case 'summary asc':
550 case 'summary desc':
551 $this->view->build_info['summary'] = TRUE;
552 $this->view->build_info['summary_level'] = $this->data->position;
553
86ed07d8
EM
554 // Change the display style to the summary style for this
555 // argument.
556 $this->view->style_plugin = isset($this->options['style_plugin']) ? $this->options['style_plugin'] : 'default_summary';
557
558 // Give it the style's options, too.
559 // @todo
560
561
013538bb
EM
562 // Clear out the normal primary field and whatever else may have
563 // been added and let the summary do the work.
564 $this->query->clear_fields();
565 $this->summary_query();
566
86ed07d8
EM
567 // Allow the new style to add additional fields if it wants.
568 // @todo
569
013538bb
EM
570 // Cut 'summary' out of our action to see how, if at all, we should
571 // sort.
572 $order = trim(str_replace($action, 'summary', ''));
573 if ($order) {
574 $argument->handler->summary_sort($order);
575 }
576
577 // DISTINCT can cause the summaries to fail.
86ed07d8 578 // @todo: This may not be true anymore.
013538bb
EM
579// $this->query->no_distinct = TRUE;
580
86ed07d8 581
013538bb
EM
582 // Summaries have their own sorting and fields, so tell the View not
583 // to build these.
584 $this->view->build_sort = $this->view->build_fields = FALSE;
cffc1056 585 return TRUE;
013538bb
EM
586 }
587 }
588
589 /**
590 * Set up the query for this argument.
591 *
592 * The argument sent may be found at $this->argument.
593 */
594 function query() {
cffc1056
EM
595 $this->ensure_my_table();
596 $this->query->add_where(0, "$this->table_alias.$this->real_field = '%s'", $this->argument);
013538bb
EM
597 }
598
599 /**
600 * Get the title this argument will assign the view, given the argument.
601 *
602 * This usually needs to be overridden to provide a proper title.
603 */
cffc1056
EM
604 function title() {
605 return check_plain($this->argument);
013538bb 606 }
b06399e3
EM
607
608 /**
609 * Validate that this argument works. By default, all arguments are valid.
610 */
611 function validate($arg) {
612 return TRUE;
613 }
013538bb
EM
614}
615
616/**
cffc1056
EM
617 * Abstract argument handler for simple formulae.
618 *
619 * Child classes of this object should implement summary_link, at least.
013538bb 620 */
cffc1056
EM
621class views_handler_argument_formula extends views_handler_argument {
622 /**
623 * Constructor
624 */
625 function construct($formula) {
626 $this->formula = $formula;
627 }
628
629 /**
630 * Build the summary query based on a formula
631 */
632 function summary_query() {
633 $this->ensure_my_table();
634 $field_alias = $alias . '_' . $this->field;
635 // Add the field.
86ed07d8 636 $this->base_alias = $this->query->add_field(NULL, $this->formula, $field_alias);
cffc1056 637 $this->query->set_count_field(NULL, $this->formula, $field_alias);
86ed07d8 638
cffc1056
EM
639 return $this->summary_basics(FALSE);
640 }
641
642 /**
643 * Build the query based upon the formula
644 */
645 function query() {
646 $this->ensure_my_table();
647 $field_alias = $alias . '_' . $this->field;
648 // Add the field.
86ed07d8 649 $this->name_alias = $this->query->add_field(NULL, $this->formula, $field_alias);
cffc1056
EM
650 $this->query->add_where(0, "$field = '%s'", $this->argument);
651 }
652}
653
654/**
655 * Argument handler for a year (CCYY)
656 */
657class views_handler_argument_date_year extends views_handler_argument_formula {
658 /**
659 * Constructor implementation
660 */
661 function construct() {
662 $timezone = views_get_timezone();
663 $this->formula = "YEAR(FROM_UNIXTIME(node.created+$timezone))";
664 }
665
666 /**
667 * Provide a link to the next level of the view
668 */
669 function summary_link($data, $url) {
670 $value = $data->{$this->base_alias};
671 return l($value, "$url/$value");
672 }
673}
674
675/**
676 * Argument handler for a year plus month (CCYYMM)
677 */
678class views_handler_argument_date_year_month extends views_handler_argument_formula {
679 /**
680 * Constructor implementation
681 */
682 function construct() {
683 $timezone = views_get_timezone();
684 $this->formula = "DATE_FORMAT(FROM_UNIXTIME(node.created+$timezone), '%Y%m')";
685 $this->format = 'F, Y';
686 }
687
688 /**
689 * Provide a link to the next level of the view
690 */
691 function summary_link($data, $url) {
692 $value = $data->{$this->base_alias};
693 $created = $data->{$this->name_alias};
694 return l(format_date($created, 'custom', $this->format), "$url/$value");
695 }
696
697 /**
698 * Provide a link to the next level of the view
699 */
700 function title($data, $url) {
701 return format_date(strtotime($this->argument . "15"), 'custom', $this->format, 0);
702 }
703}
704
705/**
706 * Argument handler for a month (MM)
707 */
708class views_handler_argument_date_month extends views_handler_argument_formula {
709 /**
710 * Constructor implementation
711 */
712 function construct() {
713 $timezone = views_get_timezone();
714 $this->formula = "MONTH(FROM_UNIXTIME(node.created+$timezone))";
715 $this->format = 'F';
716 }
717
718 /**
719 * Provide a link to the next level of the view
720 */
721 function summary_link($data, $url) {
722 $value = $data->{$this->base_alias};
723 $created = $data->{$this->name_alias};
724 return l(format_date($created, 'custom', $this->format), "$url/$value");
725 }
726
727 /**
728 * Provide a link to the next level of the view
729 */
730 function title($data, $url) {
731 return format_date(strtotime("2005" . $this->argument . "15"), 'custom', $this->format, 0);
732 }
013538bb
EM
733}
734
735/**
736 * @}
737 */