diff --git a/includes/handlers.inc b/includes/handlers.inc new file mode 100644 index 0000000000000000000000000000000000000000..22d67a531ff77c92cd96ad4d38f67e1574b1b5bc --- /dev/null +++ b/includes/handlers.inc @@ -0,0 +1,583 @@ +<?php +// $Id$ +/** + * @file handlers.inc + * Defines the various handler objects to help build and display views. + */ + +/** + * @defgroup views_join_handlers Views' join handlers + * @{ + * Handlers to tell Views how to join tables together. + + * Here is how you do complex joins: + * + * @code + * class views_join_complex extends views_join { + * // PHP 4 doesn't call constructors of the base class automatically from a + * // constructor of a derived class. It is your responsibility to propagate + * // the call to constructors upstream where appropriate. + * function views_complex_join($left_table, $left_field, $field, $extra = array(), $type = 'LEFT') { + * parent::views_join($left_table, $left_field, $field, $extra, $type); + * } + * + * function join($table, &$query) { + * $output = parent::join($table, $query); + * } + * $output .= "AND foo.bar = baz.boing"; + * return $output; + * } + * @endcode + */ +/** + * A function class to represent a join and create the SQL necessary + * to implement the join. + * + * This is the Delegation pattern. If we had PHP5 exclusively, we would + * declare this an interface. + * + * Extensions of this class can be used to create more interesting joins. + */ +class views_join { + /** + * Construct the views_join object. + */ + function views_join($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') { + $this->table = $table; + $this->left_table = $left_table; + $this->left_field = $left_field; + $this->field = $field; + $this->extra = $extra; + $this->type = strtoupper($type); + } + + /** + * Build the SQL for the join this object represents. + */ + function join($table, &$query) { + $left = $query->get_table_info($this->left_table); + $output = " $this->type JOIN {" . $this->table . "} $table[alias] ON $left[alias].$this->left_field = $table[alias].$this->field"; + + // Tack on the extra. + if (isset($extra)) { + foreach ($extra as $field => $value) { + $output .= " AND $table[alias].$this->field"; + if (is_array($value) && !empty($value)) { + $output .= " IN ('". implode("','", $value) ."')"; + } + else if ($value !== NULL) { + $output .= " = '$value'"; + } + } + } + return $output; + } +} + +/** + * @} + */ + +/** + * Base handler, from which all the other handlers are derived. + * It creates a common interface to create consistency amongst + * handlers and data. + * + * The default handler has no constructor, so there's no need to jank with + * parent::views_handler() here. + * + * This class would be abstract in PHP5, but PHP4 doesn't understand that. + * + */ +class views_handler { + /** + * Seed the handler with necessary data. + * @param $view + * The $view object this handler is attached to. + * @param $data + * The item from the database; the actual contents of this will vary + * based upon the type of handler. + */ + function seed(&$view, &$data) { + $this->view = &$view; + $this->data = &$data; + + // Mostly this exists to make things easier to reference. $this->options['...'] + // is a little easier than $this->data->options['...']; + if (isset($data->options)) { + $this->options = $data->options; + } + else { + $this->options = array(); + } + + // This exist on most handlers, but not all. So they are still optional. + if (isset($data->table)) { + $this->table = $data->table; + } + + if (isset($data->field)) { + $this->field = $data->field; + } + + if (isset($data->relationship)) { + $this->relationship = $data->relationship; + } + + if (!empty($view->query)) { + $this->query = &$view->query; + } + } + + /** + * Provide a form for setting options. + */ + function options_form(&$form) { } + + /** + * Validate the options form. + */ + function options_validate($form, &$form_state) { } + + /** + * Perform any necessary changes to the form values prior to storage. + * There is no need for this function to actually store the data. + */ + function options_submit($form, &$form_state) { } + + /** + * Add this handler into the query. + * + * If we were using PHP5, this would be abstract. + */ + function query() { } +} + +/** + * @defgroup views_relationship_handlers Views' relationship handlers + * @{ + * Handlers to tell Views how to create alternate relationships. + */ + +/** + * Simple relationship handler that allows a new version of the primary table + * to be linked in. + */ +class views_handler_relationship extends views_handler { + /** + * Called to implement a relationship in a query. + */ + function query() { + $alias = $this->table . '_' . $this->field . '_' . $this->relationship; + return $this->query->add_relationship($alias, new views_join($this->view->primary_table, $this->table, $this->field, $this->primary_field), $this->relationship); + } +} + +/** + * @} + */ + +/** + * @defgroup views_field_handlers Views' field handlers + * @{ + * Handlers to tell Views how to build and display fields. + */ + +/** + * Base field handler that has no options and renders an unformatted field. + */ +class views_handler_field extends views_handler { + + /** + * Construct a new field handler. + */ + function views_handler_field($click_sortable = FALSE, $additional_fields = array()) { + $this->click_sortable = $click_sortable; + $this->additional_fields = $additional_fields; + } + + /** + * Called to add the field to a query. + */ + function query() { + // Ensure the requested table is part of the query, and get the proper alias fori t. + $alias = $this->query->ensure_table($this->table, $this->relationship); + // Add the field. + $this->field_alias = $this->query->add_field($alias, $this->field); + // Add any additional fields we are given. + if (!empty($this->additional_fields) && is_array($this->additional_fields)) { + foreach ($this->additional_fields as $this->field) { + $this->query->add_field($alias, $this->field); + } + } + } + + /** + * Called to determine what to tell the clicksorter. + */ + function click_sort() { + // Ensure the requested table is part of the query, and get the proper alias for it. + $alias = $this->query->ensure_table($this->table, $this->relationship); + return "$alias.$this->field"; + } + + /** + * Render the field. + * + * @param $values + * The values retrieved from the database. + */ + function render($values) { + $value = $values->{$this->field_alias}; + return check_plain($value); + } +} + +/** + * A handler to provide proper displays for dates. + */ +class views_handler_field_date extends views_handler_field { + /** + * Constructor; calls to base object constructor. + */ + function views_handler_field_date($click_sortable = FALSE, $additional_fields = array()) { + parent::views_handler_field($click_sortable, $additional_fields); + } + + function options_form(&$form) { + $form['date_format'] = array( + '#type' => 'select', + '#title' => t('Date format'), + '#options' => array( + 'small' => t('Small'), + 'medium' => t('Medium'), + 'large' => t('Large'), + 'custom' => t('Custom'), + 'time ago' => t('Time ago'), + ), + '#default_value' => isset($this->options['date_format']) ? $this->options['date_format'] : 'small', + ); + $form['custom_date_format'] = array( + '#type' => 'textfield', + '#title' => t('Custom date format'), + '#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.'), + '#default_value' => isset($this->options['custom_date_format']) ? $this->options['custom_date_format'] : '', + ); + } + + function render($values) { + $value = $values->{$this->field_alias}; + $format = $this->options['date_format']; + $custom_format = $this->options['custom_date_format']; + + switch ($format) { + case 'time ago': + return $value ? t('%time ago', array('%time' => format_interval(time() - $value, is_numeric($custom_format) ? $custom_format : 2))) : theme('views_nodate'); + case 'custom': + return $value ? format_date($value, $format, $custom_format) : theme('views_nodate'); + default: + return $value ? format_date($value, $format) : theme('views_nodate'); + } + } +} + +/** + * @} + */ + +/** + * @defgroup views_sort_handlers Views' sort handlers + * @{ + * Handlers to tell Views how to sort queries + */ +/** + * Base sort handler that has no options and performs a simple sort + */ +class views_handler_sort extends views_handler { + /** + * Called to add the sort to a query. + */ + function query() { + // Ensure the requested table is part of the query, and get the proper alias for it. + $alias = $this->query->ensure_table($this->table, $this->relationship); + // Add the field. + $this->query->add_orderby($alias, $this->field, $this->data->order); + } +} + +/** + * Base sort handler that has no options and performs a simple sort + */ +class views_handler_sort_formula extends views_handler_sort { + /** + * Constructor to take the formula this sorts on. + * + * @param $formula + * The formula used to sort. If an array, may be keyed by database type. If + * used, 'default' MUST be defined. + */ + function views_handler_sort_formula($formula) { + $this->formula = $formula; + if (is_array($formula) && !isset($formula['default'])) { + $this->error = t('views_handler_sort_formula missing default: @formula', array('@formula' => var_export($formula, TRUE))); + } + } + /** + * Called to add the sort to a query. + */ + function query() { + if (is_array($this->formula)) { + global $db_type; + if (isset($this->formula[$db_type])) { + $formula = $this->formula[$db_type]; + } + else { + $formula = $this->formula['default']; + } + } + else { + $formula = $this->formula; + } + // Ensure the requested table is part of the query, and get the proper alias for it. + $alias = $this->query->ensure_table($this->table, $this->relationship); + // Add the field. + $this->add_orderby(NULL, $this->formula, $this->data->order, $alias . '_' . $this->field); + } +} + +/** + * @} + */ + +/** + * @defgroup views_filter_handlers Views' filter handlers + * @{ + * Handlers to tell Views how to filter queries. + */ + +/** + * Base class for filters. + */ +class views_handler_filter extends views_handler { + /** + * Provide a form for setting the operator. + */ + function operator_form(&$form) { } + + /** + * Validate the operator form. + */ + function operator_validate($form, &$form_state) { } + + /** + * Perform any necessary changes to the form values prior to storage. + * There is no need for this function to actually store the data. + */ + function operator_submit($form, &$form_state) { } + + /** + * Provide a form for setting options. + */ + function value_form(&$form) { } + + /** + * Validate the options form. + */ + function value_validate($form, &$form_state) { } + + /** + * Perform any necessary changes to the form values prior to storage. + * There is no need for this function to actually store the data. + */ + function value_submit($form, &$form_state) { } + + /** + * Add this filter to the query. + */ + function query() { + $alias = $this->query->ensure_table($this->table, $this->relationship); + $this->query->add_where($this->data->group, "$alias.$this->field " . $this->data->operator . " '%s'", $this->data->value); + } +} + +/** + * @} + */ + +/** + * @defgroup views_argument_handlers Handlers for arguments + * @{ + */ +/** + * Base class for arguments. + * + * The basic argument works for very simple arguments such as nid and uid + */ +class views_handler_argument extends views_handler { + /** + * Constructor + */ + function views_handler_argument($name_field = NULL) { + $this->name_field = $name_field; + } + + /** + * Build the info for the summary query. + * + * This must: + * - add_groupby: group on this field in order to create summaries. + * - add_field: add a 'num_nodes' field for the count. Usually it will + * be a count on $view->primary_field + * - set_count_field: Reset the count field so we get the right paging. + * + * @return + * The alias used to get the number of records (count) for this entry. + */ + function summary_query() { + $alias = $this->query->ensure_table($this->table, $this->relationship); + // Add the field. + $this->base_alias = $this->query->add_field($alias, $this->field); + + // Add the 'name' field. For example, if this is a uid argument, the + // name field would be 'name' (i.e, the username). + if (isset($this->name_field)) { + $this->name_alias = $this->query->add_field($alias, $this->name_field); + } + else { + $this->name_alias = $this->base_alias; + } + + // Add the number of nodes counter + $count_alias = $this->query->add_field(NULL, 'COUNT(' . $this->query->primary_field . ')', 'num_records'); + $this->query->add_groupby($this->base_alias); + + $this->query->set_count_field($alias, $this->field); + + return $count_alias; + } + + /** + * Sorts the summary based upon the user's selection. The base variant of + * this is usually adequte. + * + * @param $order + * The order selected in the UI. + */ + function summary_sort($order) { + $query->add_orderby(NULL, NULL, $order, $this->base_alias); + } + + /** + * Provides a link from the summary to the next level; this will be called + * once per row of a summary. + * + * @param $data + * The query results for the row. + * @param $url + * The base URL to use. + */ + function summary_link($data, $url) { + return l($data->{$this->name_alias}, "$url/$this->base_field"); + } + + /** + * Provide a list of default behaviors for this argument if the argument + * is not present. + * + * Override this method to provide additional (or fewer) default behaviors. + */ + function defaults() { + return array( + 'ignore' => t('Display all values'), + 'not found' => t('Display page not found'), + 'empty' => t('Display empty text'), + 'summary asc' => t('Summary, sorted ascending'), + 'summary desc' => t('Summary, sorted descending'), + ); + } + + /** + * Handle the default action, which means our argument wasn't present. + * + * Override this method only with extreme care. + * + * @return + * A boolean value; if TRUE, continue building this view. If FALSE, + * building the view will be aborted here. + */ + function default_action() { + $action = $this->data->default_action; + switch ($action) { + default: + case 'ignore': + // Do nothing at all. + return TRUE; + case 'not found': + // Set a failure condition and let the display manager handle it. + $this->view->build_info['fail'] = TRUE; + return FALSE; + case 'empty': + // We return with no query; this will force the empty text. + $this->view->built = TRUE; + return FALSE; + case 'summary': + case 'summary asc': + case 'summary desc': + $this->view->build_info['summary'] = TRUE; + $this->view->build_info['summary_level'] = $this->data->position; + + // Clear out the normal primary field and whatever else may have + // been added and let the summary do the work. + $this->query->clear_fields(); + $this->summary_query(); + + // Cut 'summary' out of our action to see how, if at all, we should + // sort. + $order = trim(str_replace($action, 'summary', '')); + if ($order) { + $argument->handler->summary_sort($order); + } + + // DISTINCT can cause the summaries to fail. + // TODO: This may not be true anymore. +// $this->query->no_distinct = TRUE; + + // Summaries have their own sorting and fields, so tell the View not + // to build these. + $this->view->build_sort = $this->view->build_fields = FALSE; + } + } + + /** + * Set up the query for this argument. + * + * The argument sent may be found at $this->argument. + */ + function query() { + // Ensure the requested table is part of the query, and get the proper alias fori t. + $alias = $this->query->ensure_table($this->table, $this->relationship); + // Add the field. + $field = $this->query->add_field($alias, $this->field); + $this->query->add_where(0, "$field = '%s'", $this->argument); + } + + /** + * Get the title this argument will assign the view, given the argument. + * + * This usually needs to be overridden to provide a proper title. + */ + function title($argument) { + return check_plain($argument); + } +} + +/** + * Argument handler for simple formulae. + */ +class views_handler_argument_date extends views_handler_argument { + +} + +/** + * @} + */ diff --git a/includes/view.inc b/includes/view.inc index ec987c6e8a795d024ed2ae06e0fadee3d474d2ab..a0041fee9f5c1ad18b43a1f638e086a886d799ab 100644 --- a/includes/view.inc +++ b/includes/view.inc @@ -13,8 +13,8 @@ function views_objects() { } /** - * Returns the complete list of objects in a view, including the display which is - * often special. + * Returns the complete list of objects in a view, including the display which + * is often special. */ function views_objects_all() { return array('display', 'argument', 'field', 'sort', 'filter', 'relationship');