set_definition($definition); // let the handler have something like a constructor. if (isset($definition['arguments'])) { call_user_func_array(array(&$handler, 'construct'), $definition['arguments']); } else { $handler->construct(); } return $handler; } /** * Prepare a handler's data by checking defaults and such. */ function _views_prepare_handler($definition, $data, $field) { foreach (array('group', 'title', 'help') as $key) { // First check the field level if (!isset($definition[$key]) && !empty($data[$field][$key])) { $definition[$key] = $data[$field][$key]; } // Then if that doesn't work, check the table level if (!isset($definition['table'][$key]) && !empty($data['table'][$key])) { $definition[$key] = $data['table'][$key]; } } return _views_create_handler($definition); } /** * Fetch a handler to join one table to a primary table from the data cache */ function views_get_table_join($table, $primary_table) { $data = views_fetch_data($table); if (isset($data['table']['join'][$primary_table])) { $h = $data['table']['join'][$primary_table]; if (!empty($h['handler']) && class_exists($h['handler'])) { $handler = new $h['handler']; } else { $handler = new views_join(); } // Fill in some easy defaults $handler->definition = $h; if (empty($handler->definition['table'])) { $handler->definition['table'] = $table; } // If this is empty, it's a direct link. if (empty($handler->definition['left_table'])) { $handler->definition['left_table'] = $primary_table; } if (isset($h['arguments'])) { call_user_func_array(array(&$handler, 'construct'), $h['arguments']); } else { $handler->construct(); } return $handler; } // DEBUG -- identify missing handlers vpr("Missing join: $table $primary_table"); } /** * @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 construct($table, $left_table, $left_field, $field, $extra = array(), $type = 'LEFT') { * parent::construct($table, $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. * * join definition * - table: table to join (right table) * - field: field to join on (right field) * - left_table: The table we join to * - left_field: The field we join to * - type: either LEFT (default) or INNER * - extra: Either a string that's directly added, or an array of items: * - - table: if not set, current table; if NULL, no table. This field can't * be set in the cached definition because it can't know aliases; this field * can only be used by realtime joins. * - - field: Field or formula * - - operator: defaults to = * - - value: Must be set. If an array, operator will be defaulted to IN. * - - numeric: If true, the value will not be surrounded in quotes. * - extra type: How all the extras will be combined. Either AND or OR. Defaults to AND. */ class views_join { /** * Construct the views_join object. */ function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') { $this->extra_type = 'AND'; if (!empty($table)) { $this->table = $table; $this->left_table = $left_table; $this->left_field = $left_field; $this->field = $field; $this->extra = $extra; $this->type = strtoupper($type); } else if (!empty($this->definition)) { // if no arguments, construct from definition. // These four must exist or it will throw notices. $this->table = $this->definition['table']; $this->left_table = $this->definition['left_table']; $this->left_field = $this->definition['left_field']; $this->field = $this->definition['field']; if (!empty($this->definition['extra'])) { $this->extra = $this->definition['extra']; } if (!empty($this->definition['extra type'])) { $this->extra = strtoupper($this->definition['extra_type']); } $this->type = !empty($this->definition['type']) ? strtoupper($this->definition['type']) : 'LEFT'; } } /** * 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($this->extra)) { if (is_array($this->extra)) { $extras = array(); foreach ($this->extra as $info) { $extra = ''; // Figure out the table name. Remember, only use aliases provided // if at all possible. $join_table = ''; if (!array_key_exists('table', $info)) { $join_table = $table['alias'] . '.'; } elseif (isset($info['table'])) { $join_table = $info['table'] . '.'; } // And now deal with the value and the operator $value = $info['value']; if (is_array($value) && !empty($value)) { $extra = "$join_table$info[field] IN ('". implode("','", $value) ."')"; } else { $operator = "="; if (!empty($info['operator'])) { $operator = $info['operator']; } if (empty($info['numeric'])) { $extra .= "$join_table$info[field] $operator '$value'"; } else { $extra .= "$join_table$info[field] $operator $value"; } } $extras[] = $extra; } if ($extras) { $output .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')'; } } else if ($this->extra && is_string($this->extra)) { $output .= " AND ($this->extra)"; } } 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 extends views_object { /** * A constructor for the handler base object * * This should be overridden to provide for a consistent constructor * mechanism. */ function construct() { } /** * init the handler with necessary data. * @param $view * The $view object this handler is attached to. * @param $options * The item from the database; the actual contents of this will vary * based upon the type of handler. */ function init(&$view, $options) { $this->view = &$view; $this->options = &$options; // This exist on most handlers, but not all. So they are still optional. if (isset($options['table'])) { $this->table = $options['table']; } if (isset($options['field'])) { $this->field = $options['field']; if (!isset($this->real_field)) { $this->real_field = $options['field']; } } if (!empty($view->query)) { $this->query = &$view->query; } } /** * Return a string representing this handler's name in the UI. */ function ui_name() { return t('@group: @title', array('@group' => $this->definition['group'], '@title' => $this->definition['title'])); } /** * Provide defaults for the handler. */ function options(&$option) { } /** * Provide a form for setting options. */ function options_form(&$form, &$form_state) { } /** * 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) { } /** * Set new exposed option defaults when exposed setting is flipped * on. */ function expose_options() { } /** * Render our chunk of the exposed filter form when selecting */ function exposed_form(&$form, &$form_state) { } /** * Validate the exposed filter form */ function exposed_validate(&$form, &$form_state) { } /** * Submit the exposed filter form */ function exposed_submit(&$form, &$form_state) { } /** * Get information about the exposed form for the form renderer. * * @return * An array with the following keys: * - operator: The $form key of the operator. Set to NULL if no operator. * - value: The $form key of the value. Set to NULL if no value. * - label: The label to use for this piece. */ function exposed_info() { } /** * Check whether current user has access to this handler. * * @return boolean */ function access() { return TRUE; } /** * Run before the view is built. * * This gives all the handlers some time to set up before any handler has * been fully run. */ function pre_query() { } /** * Called just prior to query(), this lets a handler set up any relationship * it needs. */ function set_relationship() { // Ensure this gets set to something. $this->relationship = NULL; // Don't process non-existant relationships. if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') { return; } $relationship = $this->options['relationship']; // Ignore missing/broken relationships. if (empty($this->view->relationship[$relationship]) || empty($this->view->relationship[$relationship]['handler'])) { return; } // Check to see if the relationship has already processed. If not, then we // cannot process it. if (empty($this->view->relationship[$relationship]['handler']->alias)) { return; } // Finally! $this->relationship = $this->view->relationship[$relationship]['handler']->alias; } /** * Add this handler into the query. * * If we were using PHP5, this would be abstract. */ function query() { } /** * Ensure the main table for this handler is in the query. This is used * a lot. */ function ensure_my_table() { if (!isset($this->table_alias)) { $this->table_alias = $this->query->ensure_table($this->table, $this->relationship); } return $this->table_alias; } /** * Provide text for the administrative summary */ function admin_summary() { } /** * Determine if the argument needs a style plugin. * * @return TRUE/FALSE */ function needs_style_plugin() { return FALSE; } /** * Determine if this item is 'exposed', meaning it provides form elements * to let users modify the view. * * @return TRUE/FALSE */ function is_exposed() { return !empty($this->options['exposed']); } /** * Take input from exposed filters and assign to this handler, if necessary. */ function accept_exposed_input($input) { return TRUE; } } /** * @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 { /** * Get this field's label. */ function label() { if (!isset($this->options['label'])) { return $this->ui_name(); } return $this->options['label']; } /** * Provide a default label */ function options(&$options) { parent::options($options); $options['label'] = !empty($this->definition['label']) ? $this->definition['label'] : $this->definition['field']; } /** * Default options form that provides the label widget that all fields * should have. */ function options_form(&$form, &$form_state) { $form['label'] = array( '#type' => 'textfield', '#title' => t('Label'), '#default_value' => isset($this->options['label']) ? $this->options['label'] : '', '#description' => t('The label for this relationship that will be displayed only administratively.'), ); } /** * Called to implement a relationship in a query. */ function query() { // Figure out what base table this relationship brings to the party. $table_data = views_fetch_data($this->definition['base']); $base_field = $table_data['table']['base']['field']; $def = $this->definition; $def['table'] = $this->definition['base']; $def['field'] = $base_field; $def['left_table'] = $this->table; $def['left_field'] = $this->real_field; if (!empty($def['join_handler']) && class_exists($def['join_handler'])) { $join = new $def['join_handler']; } else { $join = new views_join(); } $join->definition = $def; $join->construct(); $this->ensure_my_table(); // use a short alias for this: $alias = $this->options['id']; $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $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 { var $field_alias = 'unknown'; /** * Construct a new field handler. */ function construct() { $this->additional_fields = array(); if (!empty($this->definition['additional fields'])) { $this->additional_fields = $this->definition['additional fields']; } } /** * Called to add the field to a query. */ function query() { $this->ensure_my_table(); // Add the field. $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field); $this->add_additional_fields(); } /** * Add 'additional' fields to the query. * * @param $fields * An array of fields. The key is an identifier used to later find the * field alias used. The value is either a string in which case it's * assumed to be a field on this handler's table; or it's an array in the * form of * @code array('table' => $tablename, 'field' => $fieldname) @endcode */ function add_additional_fields($fields = NULL) { if (!isset($fields)) { // notice check if (empty($this->additional_fields)) { return; } $fields = $this->additional_fields; } if (!empty($fields) && is_array($fields)) { foreach ($fields as $identifier => $info) { if (is_array($info)) { if (isset($info['table'])) { $table_alias = $this->query->ensure_table($info['table'], $this->relationship); } else { $table_alias = $this->table_alias; } $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field']); } else { $this->aliases[$info] = $this->query->add_field($this->table_alias, $info); } } } } /** * Called to determine what to tell the clicksorter. */ function click_sort($order) { $this->query->add_orderby($this->table, $this->field, $order, $this->field_alias); } /** * Determine if this field is click sortable. */ function click_sortable() { return !empty($this->definition['click sortable']); } /** * Get this field's label. */ function label() { if (!isset($this->options['label'])) { return ''; } return $this->options['label']; } /** * Provide a default label */ function options(&$options) { parent::options($options); $options['label'] = $this->ui_name(); } /** * Default options form that provides the label widget that all fields * should have. */ function options_form(&$form, &$form_state) { $form['label'] = array( '#type' => 'textfield', '#title' => t('Label'), '#default_value' => isset($this->options['label']) ? $this->options['label'] : '', '#description' => t('The label for this field that will be displayed to end users if the style requires it.'), ); } /** * Provide extra data to the administration form */ function admin_summary() { return $this->label(); } /** * Run before any fields are rendered. * * This gives the handlers some time to set up before any handler has * been rendered. * * @param $values * An array of all objects returned from the query. */ function pre_render($values) { } /** * 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 { /** * Fill in default options. */ function options(&$options) { parent::options($options); $options['date_format'] = 'small'; $options['custom_date_format'] = ''; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $time = time(); $form['date_format'] = array( '#type' => 'select', '#title' => t('Date format'), '#options' => array( 'small' => format_date($time, 'small'), 'medium' => format_date($time, 'medium'), 'large' => format_date($time, '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 the PHP docs 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'] : '', '#process' => array('views_process_dependency'), '#dependency' => array('edit-options-date-format' => array('custom', 'time ago')), ); } function render($values) { $value = $values->{$this->field_alias}; $format = $this->options['date_format']; if ($format == 'custom') { $custom_format = $this->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'); } } } /** * A handler to provide proper displays for dates. * * Allows for display of true/false, yes/no, on/off. */ class views_handler_field_boolean extends views_handler_field { function options(&$options) { parent::options($options); $options['type'] = 'yes-no'; $options['not'] = FALSE; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $form['type'] = array( '#type' => 'select', '#title' => t('Output format'), '#options' => array( 'yes-no' => t('Yes/No'), 'true-false' => t('True/False'), 'on-off' => t('On/Off'), ), '#default_value' => $this->options['type'], ); $form['not'] = array( '#type' => 'checkbox', '#title' => t('Reverse'), '#description' => t('If checked, true will be displayed as false.'), '#default_value' => $this->options['not'], ); } function render($values) { $value = $values->{$this->field_alias}; if (!empty($this->options['not'])) { $value = !$value; } switch ($this->options['type']) { case 'yes-no': default: return $value ? t('Yes') : t('No'); case 'true-false': return $value ? t('True') : t('False'); case 'on-off': return $value ? t('On') : t('Off'); } } } /** * A handler to run a field through check_markup, using a companion * format field. */ class views_handler_field_markup extends views_handler_field { /** * Constructor; calls to base object constructor. */ function construct() { $this->format = $this->definition['format']; $this->additional_fields = array(); if (!is_numeric($this->format)) { $this->additional_fields['format'] = $this->format; } } function render($values) { $value = $values->{$this->field_alias}; $format = is_numeric($this->format) ? $this->format : $values->{$this->aliases['format']}; return check_markup($value, $format, FALSE); } } /** * A handler to run a field through simple XSS filtering */ class views_handler_field_xss extends views_handler_field { function render($values) { $value = $values->{$this->field_alias}; return filter_xss($value); } } /** * Field handler to provide simple renderer that turns a URL into a clickable link. */ class views_handler_field_url extends views_handler_field { /** * Provide link to the page being visited. */ function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $form['display_as_link'] = array( '#title' => t('Display as link'), '#type' => 'checkbox', '#default_value' => !empty($this->options['display_as_link']), ); } function render($values) { $value = $values->{$this->field_alias}; if (!empty($this->options['display_as_link'])) { return l(check_plain($value), $value, array('html' => TRUE)); } else { return $value; } } } /** * @} */ /** * @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() { $this->ensure_my_table(); // Add the field. $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']); } /** * Set defaults for new handler */ function options(&$options) { parent::options($options); $options['order'] = 'ASC'; } /** * Display whether or not the sort order is ascending or descending */ function admin_summary() { switch ($this->options['order']) { case 'ASC': case 'asc': default: $type = t('asc'); break; case 'DESC'; case 'desc'; $type = t('desc'); break; } return '' . $type . ''; } /** * Basic options for all sort criteria */ function options_form(&$form, &$form_state) { $form['order'] = array( '#type' => 'radios', '#title' => t('Sort order'), '#options' => array('ASC' => t('Ascending'), 'DESC' => t('Descending')), '#default_value' => $this->options['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. */ function construct() { $this->formula = $this->definition['formula']; if (is_array($this->formula) && !isset($this->formula['default'])) { $this->error = t('views_handler_sort_formula missing default: @formula', array('@formula' => var_export($this->formula, TRUE))); } parent::construct(); } /** * 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; } $this->ensure_my_table(); // Add the field. $this->query->add_orderby(NULL, $formula, $this->options['order'], $this->table_alias . '_' . $this->field); } } class views_handler_sort_date extends views_handler_sort { function options(&$options) { parent::options($options); $options['granularity'] = 'second'; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $form['granularity'] = array( '#type' => 'radios', '#title' => t('Granularity'), '#options' => array( 'second' => t('Second'), 'minute' => t('Minute'), 'hour' => t('Hour'), 'day' => t('Day'), 'month' => t('Month'), 'year' => t('Year'), ), '#description' => t('The granularity is the smallest unit to use when determining whether two dates are the same; for example, if the granularity is "Year" then all dates in 1999, regardless of when they fall in 1999, will be considered the same date.'), '#default_value' => $this->options['granularity'], ); } /** * Called to add the sort to a query. */ function query() { $this->ensure_my_table(); switch($this->options['granularity']) { case 'second': default: $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']); return; case 'minute': $formula = views_date_sql_format('YmdHi', "$this->table_alias.$this->real_field"); break; case 'hour': $formula = views_date_sql_format('YmdH', "$this->table_alias.$this->real_field"); break; case 'day': $formula = views_date_sql_format('Ymd', "$this->table_alias.$this->real_field"); break; case 'month': $formula = views_date_sql_format('Ym', "$this->table_alias.$this->real_field"); break; case 'year': $formula = views_date_sql_format('Y', "$this->table_alias.$this->real_field"); break; } // Add the field. $this->query->add_orderby(NULL, $formula, $this->options['order'], $this->table_alias . '_' . $this->field . '_' . $this->options['granularity']); } } /** * @} */ /** * @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 some extra help to get the operator/value easier to use. * * This likely has to be overridden by filters which are more complex * than simple operator/value. */ function init(&$view, $options) { parent::init($view, $options); $this->operator = $options['operator']; $this->value = $options['value']; } /** * Provide a simple default initializer -- should be overridden. */ function options(&$options) { parent::options($options); $options['operator'] = '='; $options['value'] = ''; $options['group'] = 0; $options['exposed'] = FALSE; $options['expose'] = array( 'operator' => FALSE, 'label' => '', ); } /** * Display the filter on the administrative summary */ function admin_summary() { return check_plain($this->operator) . ' ' . check_plain($this->value); } /** * Provide the basic form which calls through to subforms. * If overridden, it is best to call through to the parent, * or to at least make sure all of the functions in this form * are called. */ function options_form(&$form, &$form_state) { $this->show_expose_button($form, $form_state); $this->show_operator_form($form, $form_state); $this->show_value_form($form, $form_state); $this->show_expose_form($form, $form_state); } /** * Simple validate handler */ function options_validate(&$form, &$form_state) { $this->operator_validate($form, $form_state); $this->value_validate($form, $form_state); if (!empty($this->options['exposed'])) { $this->expose_validate($form, $form_state); } } /** * Simple submit handler */ function options_submit(&$form, &$form_state) { $this->operator_submit($form, $form_state); $this->value_submit($form, $form_state); if (!empty($this->options['exposed'])) { $this->expose_submit($form, $form_state); } } /** * Shortcut to display the operator form. */ function show_operator_form(&$form, &$form_state) { $this->operator_form($form, $form_state); $form['operator']['#prefix'] = '
'; $form['operator']['#suffix'] = '
'; } /** * Provide a form for setting the operator. * * This may be overridden by child classes, and it must * define $form['operator']; */ function operator_form(&$form, &$form_state) { $options = $this->operator_options(); $form['operator'] = array( '#type' => count($options) < 10 ? 'radios' : 'select', '#title' => t('Operator'), '#default_value' => $this->operator, '#options' => $options, ); } /** * Provide a list of options for the default operator form. * Should be overridden by classes that don't override operator_form */ function operator_options() { return array(); } /** * 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) { } /** * Shortcut to display the value form. */ function show_value_form(&$form, &$form_state) { $this->value_form($form, $form_state); $form['value']['#prefix'] = '
'; $form['value']['#suffix'] = '
'; } /** * Provide a form for setting options. * * This should be overridden by all child classes and it must * define $form['value'] */ function value_form(&$form, &$form_state) { $form['value'] = array(); } /** * 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) { } /** * Shortcut to display the expose/hide button. */ function show_expose_button(&$form, &$form_state) { $form['expose_button'] = array( '#prefix' => '
', '#suffix' => '
', ); if (empty($this->options['exposed'])) { $form['expose_button']['button'] = array( '#type' => 'submit', '#value' => t('Expose'), '#submit' => array('views_ui_config_item_form_expose'), ); $form['expose_button']['markup'] = array( '#prefix' => '
', '#value' => t('This item is currently not exposed. If you expose it, users will be able to change the filter as they view it.'), '#suffix' => '
', ); } else { $form['expose_button']['button'] = array( '#type' => 'submit', '#value' => t('Hide'), '#submit' => array('views_ui_config_item_form_expose'), ); $form['expose_button']['markup'] = array( '#prefix' => '
', '#value' => t('This item is currently exposed. If you hide it, users will not able to change the filter as they view it.'), '#suffix' => '
', ); } } /** * Shortcut to display the exposed options form. */ function show_expose_form(&$form, &$form_state) { if (empty($this->options['exposed'])) { return; } $form['expose'] = array( '#prefix' => '
', '#suffix' => '
', ); $this->expose_form($form, $form_state); } /** * Overridable form for exposed filter options. * * If overridden, it is best to call the parent or re-implement * the stuff here. * * Many filters will need to override this in order to provide options * that are nicely tailored to the given filter. */ function expose_form(&$form, &$form_state) { // @todo we should break this up into two functions to make it easier // for child objects to put options in the left or right side without // having to override this whole thing. $form['expose']['start_left'] = array( '#value' => '
', ); if (!empty($form['operator']['#type'])) { $form['expose']['operator'] = array( '#type' => 'textfield', '#default_value' => $this->options['expose']['operator'], '#title' => t('Operator identifier'), '#size' => 40, '#description' => t('This will appear in the URL after the ? to identify this operator. Leave blank to not expose the operator.'), ); } else { $form['expose']['operator'] = array( '#type' => 'value', '#value' => '', ); } $form['expose']['identifier'] = array( '#type' => 'textfield', '#default_value' => $this->options['expose']['identifier'], '#title' => t('Filter identifier'), '#size' => 40, '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'), ); $form['expose']['label'] = array( '#type' => 'textfield', '#default_value' => $this->options['expose']['label'], '#title' => t('Label'), '#size' => 40, ); $form['expose']['end_left'] = array( '#value' => '
', ); $form['expose']['start_checkboxes'] = array( '#value' => '
', ); $form['expose']['optional'] = array( '#type' => 'checkbox', '#title' => t('Optional'), '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'), '#default_value' => $this->options['expose']['optional'], ); if (empty($this->no_single)) { $form['expose']['single'] = array( '#type' => 'checkbox', '#title' => t('Force single'), '#description' => t('Force this exposed filter to accept only one option.'), '#default_value' => $this->options['expose']['single'], ); } $form['expose']['remember'] = array( '#type' => 'checkbox', '#title' => t('Remember'), '#description' => t('Remember the last setting the user gave this filter.'), '#default_value' => $this->options['expose']['remember'], ); $form['expose']['end_checkboxes'] = array( '#value' => '
', ); } /** * Validate the options form. */ function expose_validate($form, &$form_state) { if (empty($this->options['expose']['identifier'])) { if (empty($form_state['values']['options']['expose']['identifier'])) { form_error($form['expose']['identifier'], t('The identifier is required if the filter is exposed.')); } } } /** * Perform any necessary changes to the form exposes prior to storage. * There is no need for this function to actually store the data. */ function expose_submit($form, &$form_state) { } function expose_options() { $this->options['expose'] = array( 'operator' => $this->options['id'] . '_oper', 'identifier' => $this->options['id'], 'label' => $this->ui_name(), 'remember' => FALSE, 'single' => TRUE, 'optional' => TRUE, ); } /** * Render our chunk of the exposed filter form when selecting * * You can override this if it doesn't do what you expect. */ function exposed_form(&$form, &$form_state) { if (empty($this->options['exposed'])) { return; } if (!empty($this->options['expose']['operator'])) { $operator = $this->options['expose']['operator']; $this->operator_form($form, $form_state); $form[$operator] = $form['operator']; if (isset($form[$operator]['#title'])) { unset($form[$operator]['#title']); } $this->exposed_translate($form[$operator], 'operator'); unset($form['operator']); } if (!empty($this->options['expose']['identifier'])) { $value = $this->options['expose']['identifier']; $this->value_form($form, $form_state); $form[$value] = $form['value']; if (isset($form[$value]['#title']) && !empty($form[$value]['#type']) && $form[$value]['#type'] != 'checkbox') { unset($form[$value]['#title']); } $this->exposed_translate($form[$value], 'value'); if (!empty($form['#type']) && ($form['#type'] == 'checkboxes' || ($form['#type'] == 'select' && !empty($form['#multiple'])))) { unset($form[$value]['#default_value']); } if (!empty($form['#type']) && $form['#type'] == 'select' && empty($form['#multiple'])) { $form[$value]['#default_value'] = 'All'; } unset($form['value']); } } /** * Make some translations to a form item to make it more suitable to * exposing. */ function exposed_translate(&$form, $type) { if (!isset($form['#type'])) { return; } if ($form['#type'] == 'radios') { $form['#type'] = 'select'; } if ($form['#type'] == 'checkboxes' && !empty($this->options['expose']['single'])) { $form['#type'] = 'select'; } if (!empty($this->options['expose']['single']) && isset($form['#multiple'])) { unset($form['#multiple']); } if ($type == 'value' && !empty($this->options['expose']['optional']) && $form['#type'] == 'select' && empty($form['#multiple'])) { $form['#options'] = array('All' => t('')) + $form['#options']; $form['#default_value'] = 'All'; } } /** * Tell the renderer about our exposed form. This only needs to be * overridden for particularly complex forms. And maybe not even then. */ function exposed_info() { if (empty($this->options['exposed'])) { return; } return array( 'operator' => $this->options['expose']['operator'], 'value' => $this->options['expose']['identifier'], 'label' => $this->options['expose']['label'], ); } /** * Check to see if input from the exposed filters should change * the behavior if this filter. */ function accept_exposed_input($input) { if (empty($this->options['exposed'])) { return TRUE; } if (!empty($this->options['expose']['operator']) && isset($input[$this->options['expose']['operator']])) { $this->operator = $input[$this->options['expose']['operator']]; if ($this->options['expose']['remember']) { $_SESSION['views'][$this->view->name][$this->view->current_display][$this->options['expose']['operator']] = $input[$this->options['expose']['operator']]; } } if (!empty($this->options['expose']['identifier'])) { $value = $input[$this->options['expose']['identifier']]; if ($this->options['expose']['remember']) { $_SESSION['views'][$this->view->name][$this->view->current_display][$this->options['expose']['identifier']] = $value; } // Various ways to check for the absence of optional input. if (!empty($this->options['expose']['optional'])) { if ($value == 'All' || $value === array()) { return FALSE; } if (!empty($this->no_single) && $value === '') { return FALSE; } } if (isset($value)) { $this->value = $value; } else { return FALSE; } } return TRUE; } /** * Add this filter to the query. * * Due to the nature of fapi, the value and the operator have an unintended * level of indirection. You will find them in $this->operator * and $this->value respectively. */ function query() { $this->ensure_my_table(); $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . " '%s'", $this->value); } } /** * Simple filter to handle equal to / not equal to filters */ class views_handler_filter_equality extends views_handler_filter { // exposed filter options var $no_single = TRUE; /** * Provide basic defaults for the equality operator */ function options(&$options) { parent::options($options); $options['operator'] = '='; $options['value'] = ''; } /** * Provide simple equality operator */ function operator_options() { return array( '=' => t('Is equal to'), '!=' => t('Is not equal to'), ); } /** * Provide a simple textfield for equality */ function value_form(&$form, &$form_state) { $form['value'] = array( '#type' => 'textfield', '#title' => t('Value'), '#size' => 30, '#default_value' => $this->value, ); } } /** * Basic textfield filter to handle string filtering commands * including equality, like, not like, etc. */ class views_handler_filter_string extends views_handler_filter { // exposed filter options var $no_single = TRUE; /** * Provide basic defaults for the equality operator */ function options(&$options) { parent::options($options); $options['operator'] = '='; $options['value'] = ''; $options['case'] = TRUE; } /** * This kind of construct makes it relatively easy for a child class * to add or remove functionality by overriding this function and * adding/removing items from this array. */ function operators() { $operators = array( '=' => array( 'title' => t('Is equal to'), 'short' => t('='), 'method' => 'op_equal', 'values' => 1, ), '!=' => array( 'title' => t('Is not equal to'), 'short' => t('='), 'method' => 'op_equal', 'values' => 1, ), 'contains' => array( 'title' => t('Contains'), 'short' => t('contains'), 'method' => 'op_contains', 'values' => 1, ), 'word' => array( 'title' => t('Contains any word'), 'short' => t('has word'), 'method' => 'op_word', 'values' => 1, ), 'allwords' => array( 'title' => t('Contains all words'), 'short' => t('has all'), 'method' => 'op_word', 'values' => 1, ), 'starts' => array( 'title' => t('Starts with'), 'short' => t('begins'), 'method' => 'op_starts', 'values' => 1, ), 'ends' => array( 'title' => t('Ends with'), 'short' => t('ends'), 'method' => 'op_ends', 'values' => 1, ), 'not' => array( 'title' => t('Does not contain'), 'short' => t('!has'), 'method' => 'op_not', 'values' => 1, ), ); // if the definition allows for the empty operator, add it. if (!empty($this->definition['allow empty'])) { $operators += array( 'empty' => array( 'title' => t('Is empty (NULL)'), 'method' => 'op_empty', 'short' => t('empty'), 'values' => 0, ), 'not empty' => array( 'title' => t('Is not empty (NULL)'), 'method' => 'op_empty', 'short' => t('not empty'), 'values' => 0, ), ); } return $operators; } /** * Build strings from the operators() for 'select' options */ function operator_options($which = 'title') { $options = array(); foreach ($this->operators() as $id => $info) { $options[$id] = $info[$which]; } return $options; } function admin_summary() { $options = $this->operator_options('short'); return (!empty($this->options['exposed']) ? t('exposed
') : '') . $options[$this->operator]; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $form['case'] = array( '#type' => 'checkbox', '#title' => t('Case sensitive'), '#default_value' => $this->options['case'], '#description' => t('Case sensitive filters may be faster; MySQL might ignore case sensitivity.'), ); } function operator_values($values = 1) { $options = array(); foreach ($this->operators() as $id => $info) { if (isset($info['values']) && $info['values'] == $values) { $options[] = $id; } } return $options; } /** * Provide a simple textfield for equality */ function value_form(&$form, &$form_state) { // We have to make some choices when creating this as an exposed // filter form. For example, if the operator is locked and thus // not rendered, we can't render dependencies; instead we only // render the form items we need. $which = 'all'; if (!empty($form['operator'])) { $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator'; } if (!empty($form_state['exposed'])) { if (empty($this->options['expose']['operator'])) { // exposed and locked. $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none'; } else { $source = 'edit-' . form_clean_id($this->options['expose']['operator']); } } if ($which == 'all' || $which == 'value') { $form['value'] = array( '#type' => 'textfield', '#title' => t('Value'), '#size' => 30, '#default_value' => $this->value, ); if ($which == 'all') { $form['value'] += array( '#process' => array('views_process_dependency'), '#dependency' => array($source => $this->operator_values(1)), ); } } } function case_transform() { return empty($this->options['case']) ? '' : 'UPPER'; } /** * Add this filter to the query. * * Due to the nature of fapi, the value and the operator have an unintended * level of indirection. You will find them in $this->operator * and $this->value respectively. */ function query() { $this->ensure_my_table(); $field = "$this->table_alias.$this->real_field"; $upper = $this->case_transform(); $info = $this->operators(); if (!empty($info[$this->operator]['method'])) { $this->{$info[$this->operator]['method']}($field, $upper); } } function op_equal($field, $upper) { // operator is either = or != $this->query->add_where($this->options['group'], "$upper(%s) $this->operator $upper('%s')", $field, $this->value); } function op_contains($field, $upper) { $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%%%s%%')", $field, $this->value); } function op_word($field, $upper) { preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $this->value, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $phrase = false; // Strip off phrase quotes if ($match[2]{0} == '"') { $match[2] = substr($match[2], 1, -1); $phrase = true; } $words = trim($match[2], ',?!();:-'); $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY); foreach ($words as $word) { $where[] = "$upper(%s) LIKE $upper('%%%s%%')"; $values[] = $field; $values[] = trim($word, " ,!?"); } } if ($this->operator == 'word') { $where = '('. implode(' OR ', $where) .')'; } else { $where = implode(' AND ', $where); } // previously this was a call_user_func_array but that's unnecessary // as views will unpack an array that is a single arg. $this->query->add_where($this->options['group'], $where, $values); } function op_starts($field, $upper) { $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%s%%')", $field, $this->value); } function op_ends($field, $upper) { $this->query->add_where($this->options['group'], "$upper(%s) LIKE $upper('%%%s')", $field, $this->value); } function op_not($field, $upper) { $this->query->add_where($this->options['group'], "$upper(%s) NOT LIKE $upper('%%%s%%')", $field, $this->value); } function op_empty($field) { if ($this->operator == 'empty') { $operator = "IS NULL"; } else { $operator = "IS NOT NULL"; } $this->query->add_where($this->options['group'], "$field $operator"); } } /** * Simple filter to handle matching of boolean values */ class views_handler_filter_boolean_operator extends views_handler_filter { // exposed filter options var $no_single = TRUE; function construct() { $this->value_value = t('True'); if (isset($this->definition['label'])) { $this->value_value = $this->definition['label']; } parent::construct(); } function options(&$options) { parent::options($options); $options['value'] = FALSE; } function operator_form(&$form, &$form_state) { $form['operator'] = array(); } function value_form(&$form, &$form_state) { if (empty($this->options['exposed'])) { $form['value'] = array( '#type' => 'checkbox', '#title' => $this->value_value, '#default_value' => $this->value, ); } else { $form['value'] = array( '#type' => 'select', '#title' => $this->value_value, '#options' => array(1 => t('Yes'), 0 => t('No')), '#default_value' => $this->value, ); } } function admin_summary() { if (!empty($this->options['exposed'])) { return t('exposed'); } return (empty($this->value) ? t("False") : t('True')); } function expose_options() { $this->options['expose'] = array( 'operator' => '', 'identifier' => $this->options['id'], 'label' => $this->value_value, 'remember' => FALSE, 'single' => TRUE, 'optional' => FALSE, ); } function query() { $this->ensure_my_table(); $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . (empty($this->value) ? '=' : '<>') . " 0"); } } /** * Simple filter to handle matching of multiple options selectable via checkboxes */ class views_handler_filter_in_operator extends views_handler_filter { function construct() { parent::construct(); $this->value_title = t('Options'); $this->value_options = array(t('Yes'), t('No')); } function options(&$options) { parent::options($options); $options['operator'] = 'in'; $options['value'] = array(); } /** * Provide inclusive/exclusive matching */ function operator_options() { return array( 'in' => t('Is one of'), 'not in' => t('Is not one of'), ); } function value_form(&$form, &$form_state) { $form['value'] = array( '#type' => 'checkboxes', '#title' => $this->value_title, '#options' => $this->value_options, '#default_value' => (array) $this->value, ); } function value_submit($form, &$form_state) { $form_state['values']['options']['value'] = array_filter($form_state['values']['options']['value']); } function admin_summary() { if (!empty($this->options['exposed'])) { return t('exposed'); } if (count($this->value) == 1) { // If there is only one, show it as an =. $keys = array_keys($this->value); $key = array_shift($keys); if (!empty($this->value_options[$key])) { $value = check_plain($this->value_options[$key]); } else { $value = t('Unknown'); } return ($this->operator == 'in' ? '=' : '<>') . ' ' . $value; } $output = ''; foreach ($this->value as $value) { if ($output) { $output .= ', '; } if (strlen($output) > 8) { $output .= '...'; break; } $output .= check_plain($this->value_options[$value]); } return check_plain($this->operator) . ' ' . $output; } function query() { if (empty($this->value)) { return; } $this->ensure_my_table(); $replace = array_fill(0, sizeof($this->value), "'%s'"); $in = ' (' . implode(", ", $replace) . ')'; $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . $in, $this->value); } } /** * Simple filter to handle greater than/less than filters */ class views_handler_filter_numeric extends views_handler_filter { var $no_single = TRUE; /** * Provide basic defaults for the filter */ function options(&$options) { parent::options($options); $options['operator'] = '='; $options['value']['min'] = ''; $options['value']['max'] = ''; $options['value']['value'] = ''; } function operators() { $operators = array( '<' => array( 'title' => t('Is less than'), 'method' => 'op_simple', 'short' => t('<'), 'values' => 1, ), '<=' => array( 'title' => t('Is less than or equal to'), 'method' => 'op_simple', 'short' => t('<='), 'values' => 1, ), '=' => array( 'title' => t('Is equal to'), 'method' => 'op_simple', 'short' => t('='), 'values' => 1, ), '!=' => array( 'title' => t('Is not equal to'), 'method' => 'op_simple', 'short' => t('!='), 'values' => 1, ), '>=' => array( 'title' => t('Is greater than or equal to'), 'method' => 'op_simple', 'short' => t('>='), 'values' => 1, ), '>' => array( 'title' => t('Is greater than'), 'method' => 'op_simple', 'short' => t('>'), 'values' => 1, ), 'between' => array( 'title' => t('Is between'), 'method' => 'op_between', 'short' => t('between'), 'values' => 2, ), 'not between' => array( 'title' => t('Is not between'), 'method' => 'op_between', 'short' => t('not between'), 'values' => 2, ), ); // if the definition allows for the empty operator, add it. if (!empty($this->definition['allow empty'])) { $operators += array( 'empty' => array( 'title' => t('Is empty (NULL)'), 'method' => 'op_empty', 'short' => t('empty'), 'values' => 0, ), 'not empty' => array( 'title' => t('Is not empty (NULL)'), 'method' => 'op_empty', 'short' => t('not empty'), 'values' => 0, ), ); } return $operators; } /** * Provide a list of all the numeric operators */ function operator_options($which = 'title') { $options = array(); foreach ($this->operators() as $id => $info) { $options[$id] = $info[$which]; } return $options; } function operator_values($values = 1) { $options = array(); foreach ($this->operators() as $id => $info) { if ($info['values'] == $values) { $options[] = $id; } } return $options; } /** * Provide a simple textfield for equality */ function value_form(&$form, &$form_state) { $form['value']['#tree'] = TRUE; // We have to make some choices when creating this as an exposed // filter form. For example, if the operator is locked and thus // not rendered, we can't render dependencies; instead we only // render the form items we need. $which = 'all'; if (!empty($form['operator'])) { $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator'; } if (!empty($form_state['exposed'])) { if (empty($this->options['expose']['operator'])) { // exposed and locked. $which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value'; } else { $source = 'edit-' . form_clean_id($this->options['expose']['operator']); } } if ($which == 'all' || $which == 'value') { $form['value']['value'] = array( '#type' => 'textfield', '#title' => t('Value'), '#size' => 30, '#default_value' => $this->value['value'], ); if ($which == 'all') { $form['value']['value'] += array( '#process' => array('views_process_dependency'), '#dependency' => array($source => $this->operator_values(1)), ); } } if ($which == 'all' || $which == 'minmax') { $form['value']['min'] = array( '#type' => 'textfield', '#title' => t('Min'), '#size' => 30, '#default_value' => $this->value['min'], ); $form['value']['max'] = array( '#type' => 'textfield', '#title' => t('And max'), '#size' => 30, '#default_value' => $this->value['max'], ); if ($which == 'all') { $dependency = array( '#process' => array('views_process_dependency'), '#dependency' => array($source => $this->operator_values(2)), ); $form['value']['min'] += $dependency; $form['value']['max'] += $dependency; } } } function query() { $this->ensure_my_table(); $field = "$this->table_alias.$this->real_field"; $info = $this->operators(); if (!empty($info[$this->operator]['method'])) { $this->{$info[$this->operator]['method']}($field); } } function op_between($field) { if ($this->operator == 'between') { $a = $this->value['min']; $b = $this->value['max']; } else { $a = $this->value['max']; $b = $this->value['min']; } $this->query->add_where($this->options['group'], "$field >= %d", $a); $this->query->add_where($this->options['group'], "$field <= %d", $b); } function op_simple($field) { $this->query->add_where($this->options['group'], "$field $this->operator %d", $this->value['value']); } function op_empty($field) { if ($this->operator == 'empty') { $operator = "IS NULL"; } else { $operator = "IS NOT NULL"; } $this->query->add_where($this->options['group'], "$field $operator"); } function admin_summary() { $output = check_plain($this->operator) . ' '; if (in_array($this->operator, $this->operator_values(2))) { $output .= t('@min and @max', array('@min' => $this->value['min'], '@max' => $this->value['max'])); } else { $output .= check_plain($this->value['value']); } return $output; } } class views_handler_filter_date extends views_handler_filter_numeric { function options(&$options) { parent::options($options); $options['value']['type'] = 'date'; } /** * Add a type selector to the value form */ function value_form(&$form, &$form_state) { $form['value']['type'] = array( '#type' => 'radios', '#title' => t('Value type'), '#options' => array( 'date' => t('A date in any machine readable format. CCYY-MM-DD HH:MM:SS is preferred.'), 'offset' => t('An offset from the current time such as "+1 day" or "-2 hours and 30 minutes"'), ), '#default_value' => $this->value['type'], ); parent::value_form($form, $form_state); } function options_validate(&$form, &$form_state) { parent::options_validate($form, $form_state); $operators = $this->operators(); if ($operators[$form_state['values']['options']['operator']]['values'] == 1) { $convert = strtotime($form_state['values']['options']['value']['value']); if ($convert == -1 || $convert === FALSE) { form_error($form['value']['value'], t('Invalid date format.')); } } else { $min = strtotime($form_state['values']['options']['value']['min']); if ($min == -1 || $min === FALSE) { form_error($form['value']['min'], t('Invalid date format.')); } $max = strtotime($form_state['values']['options']['value']['max']); if ($max == -1 || $max === FALSE) { form_error($form['value']['max'], t('Invalid date format.')); } } } function op_between($field) { if ($this->operator == 'between') { $a = strtotime($this->value['min'], 0); $b = strtotime($this->value['max'], 0); } else { $a = strtotime($this->value['max'], 0); $b = strtotime($this->value['min'], 0); } if ($this->value['type'] == 'offset') { $a = '***CURRENT_TIME***' . sprintf('%+d', $a); // keep sign $b = '***CURRENT_TIME***' . sprintf('%+d', $b); // keep sign } // %s is safe here because strtotime scrubbed the input and we might // have a string if using offset. $this->query->add_where($this->options['group'], "$field >= %s", $a); $this->query->add_where($this->options['group'], "$field <= %s", $b); } function op_simple($field) { $value = strtotime($this->value['value'], 0); if ($this->value['type'] == 'offset') { $value = '***CURRENT_TIME***' . sprintf('%+d', $value); // keep sign } $this->query->add_where($this->options['group'], "$field $this->operator %s", $value); } } /** * Complex filter to handle filtering for many to one relationships, * such as terms (many terms per node) or roles (many roles per user). * * The construct method needs to be overridden to provide a list of options; * alternately, the value_form and admin_summary methods need to be overriden * to provide something that isn't just a select list. */ class views_handler_filter_many_to_one extends views_handler_filter { function construct() { parent::construct(); // @todo Pull depth information from definition. $this->allow_depth = FALSE; $this->value_title = t('Options'); $this->value_options = array(); } function options(&$options) { parent::options($options); $options['operator'] = 'or'; $options['value'] = array(); $options['depth'] = 0; } /** * Provide inclusive/exclusive matching */ function operator_options() { return array( 'or' => t('Is one of'), 'and' => t('Is all of'), 'not' => t('Is none of'), ); } function value_form(&$form, &$form_state) { $form['value'] = array( '#type' => 'select', '#title' => $this->value_title, '#options' => $this->value_options, '#multiple' => TRUE, '#size' => count($this->value_options) > 8 ? 8 : $this->value_options, '#default_value' => (array) $this->value, ); } function admin_summary() { if (!empty($this->options['exposed'])) { return t('exposed'); } if (count($this->value) == 1) { // If there is only one, show it as an =. $keys = array_keys($this->value); $key = array_shift($keys); if (!empty($this->value_options[$key])) { $value = check_plain($this->value_options[$key]); } else { $value = t('Unknown'); } return ($this->operator == 'in' ? '=' : '<>') . ' ' . $value; } $output = ''; foreach ($this->value as $value) { if ($output) { $output .= ', '; } if (strlen($output) > 8) { $output .= '...'; break; } $output .= check_plain($this->value_options[$value]); } return check_plain($this->operator) . ' ' . $output; } /** * Override ensure_my_table so we can control how this joins in. * The operator actually has influence over joining. */ function ensure_my_table() { if (!isset($this->table_alias)) { $base_join = views_get_table_join($this->table, $this->query->primary_table); if ($this->operator != 'not') { // If it's an and or an or, we do one join per selected value. // Clone the join for each table: $this->table_aliases = array(); foreach ($this->value as $value) { // Clone to make sure we aren't overwriting previous joins due // to overzealous references. $join = drupal_clone($base_join); $join->extra = array( array( 'field' => $this->real_field, 'value' => $value, 'numeric' => !empty($this->definition['numeric']), ), ); $alias = $this->table_aliases[$value] = $this->query->add_table($this->table, $this->relationship, $join, $this->table . '_' . $value); // and set table_alias to the first of these. if (empty($this->table_alias)) { $this->table_alias = $alias; } } } else { // For not, we just do one join. We'll add a where clause during // the query phase to ensure that $table.$field IS NULL. $join = drupal_clone($base_join); // be safe! $join->type = 'LEFT'; $join->extra = array(); $join->extra_type = 'OR'; foreach ($this->value as $value) { $join->extra[] = array( 'field' => $this->field, 'value' => $value, 'numeric' => !empty($this->definition['numeric']), ); } $this->table_alias = $this->query->add_table($this->table, $this->relationship, $join); } } return $this->table_alias; } function query() { if (empty($this->value)) { return; } $this->ensure_my_table(); if ($this->operator == 'not') { $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field IS NULL"); } else { $clauses = array(); foreach ($this->table_aliases as $value => $alias) { if (!empty($this->definition['numeric'])) { $clauses[] = "$alias.$this->real_field = %d"; } else { $clauses[] = "$alias.$this->real_field = '%s'"; } } // implode on either AND or OR. $this->query->add_where($this->options['group'], implode(' ' . strtoupper($this->operator) . ' ', $clauses), $this->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 { var $name_field = NULL; /** * Constructor */ function construct() { if (!empty($this->definition['name field'])) { $this->name_field = $this->definition['name field']; } } /** * Give an argument the opportunity to modify the breadcrumb, if it wants. * This only gets called on displays where a breadcrumb is actually used. * * The breadcrumb will be in the form of an array, with the keys being * the path and the value being the already sanitized title of the path. */ function set_breadcrumb(&$breadcrumb) { } /** * Provide defaults for the argument when a new one is created. */ function options(&$options) { parent::options($options); $options['default_action'] = 'ignore'; $options['style_plugin'] = 'default_summary'; $options['style_options'] = array(); $options['wildcard'] = 'all'; $options['wildcard_substitution'] = t('All'); $options['title'] = ''; } /** * Provide a default options form for the argument. */ function options_form(&$form, &$form_state) { $defaults = $this->default_actions(); foreach ($defaults as $id => $info) { $options[$id] = $info['title']; } $form['default_action'] = array( '#prefix' => '
', '#suffix' => '
', '#type' => 'radios', '#title' => t('Action to take if argument is not present'), '#options' => $options, '#default_value' => $this->options['default_action'], ); $form['wildcard'] = array( '#prefix' => '
', // prefix and no suffix means these two items will be grouped together. '#type' => 'textfield', '#title' => t('Wildcard'), '#size' => 20, '#default_value' => $this->options['wildcard'], '#description' => t('If this value is received as an argument, the argument will be ignored; i.e, "all values"'), ); $form['wildcard_substitution'] = array( '#suffix' => '
', '#type' => 'textfield', '#title' => t('Wildcard title'), '#size' => 20, '#default_value' => $this->options['wildcard_substitution'], '#description' => t('The title to use for the wildcard in substitutions elsewhere.'), ); $form['title'] = array( '#prefix' => '
', '#suffix' => '
', '#type' => 'textfield', '#title' => t('Title'), '#default_value' => $this->options['title'], '#description' => t('The title to use when this argument is present; it will override the title of the view and titles from previous arguments.'), ); } /** * 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 default_actions($which = NULL) { $defaults = array( 'ignore' => array( 'title' => t('Display all values'), 'method' => 'default_ignore', 'breadcrumb' => TRUE, // generate a breadcrumb to here ), 'not found' => array( 'title' => t('Display page not found'), 'method' => 'default_not_found', ), 'empty' => array( 'title' => t('Display empty text'), 'method' => 'default_empty', 'breadcrumb' => TRUE, // generate a breadcrumb to here ), 'summary asc' => array( 'title' => t('Summary, sorted ascending'), 'method' => 'default_summary', 'method args' => array('asc'), 'style plugin' => TRUE, 'breadcrumb' => TRUE, // generate a breadcrumb to here ), 'summary desc' => array( 'title' => t('Summary, sorted descending'), 'method' => 'default_summary', 'method args' => array('desc'), 'style plugin' => TRUE, 'breadcrumb' => TRUE, // generate a breadcrumb to here ), ); if ($which) { if (!empty($defaults[$which])) { return $defaults[$which]; } } else { return $defaults; } } /** * Determine if the can generate a breadcrumb * * @return TRUE/FALSE */ function uses_breadcrumb() { $info = $this->default_actions($this->options['default_action']); return !empty($info['breadcrumb']); } /** * Determine if the argument needs a style plugin. * * @return TRUE/FALSE */ function needs_style_plugin() { $info = $this->default_actions($this->options['default_action']); return !empty($info['style plugin']); } /** * 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() { $info = $this->default_actions($this->options['default_action']); if (!$info) { return FALSE; } if (!empty($info['method args'])) { return call_user_func_array(array($this, $info['method']), $info['method args']); } else { return $this->{$info['method']}(); } } /** * Default action: ignore. * * If an argument was expected and was not given, in this case, simply * ignore the argument entirely. */ function default_ignore() { return TRUE; } /** * Default action: not found. * * If an argument was expected and was not given, in this case, report * the view as 'not found' or hide it. */ function default_not_found() { // Set a failure condition and let the display manager handle it. $this->view->build_info['fail'] = TRUE; return FALSE; } /** * Default action: empty * * If an argument was expected and was not given, in this case, display * the view's empty text */ function default_empty() { // We return with no query; this will force the empty text. $this->view->built = TRUE; $this->view->executed = TRUE; $this->view->result = array(); return FALSE; } /** * Default action: summary. * * If an argument was expected and was not given, in this case, display * a summary query. */ function default_summary($order) { $this->view->build_info['summary'] = TRUE; $this->view->build_info['summary_level'] = $this->options['id']; // Change the display style to the summary style for this // argument. $this->view->style_plugin = $this->options['style_plugin']; $this->view->style_options = $this->options['style_options']; // 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(); $this->summary_sort($order); // Summaries have their own sorting and fields, so tell the View not // to build these. $this->view->build_sort = $this->view->build_fields = FALSE; return TRUE; } /** * 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() { $this->ensure_my_table(); // Add the field. $this->base_alias = $this->query->add_field($this->table_alias, $this->real_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($this->table_alias, $this->name_field); } else { $this->name_alias = $this->base_alias; } return $this->summary_basics(); } /** * Some basic summary behavior that doesn't need to be repeated as much as * code that goes into summary_query() */ function summary_basics($count_field = TRUE) { // Add the number of nodes counter $count_alias = $this->query->add_field(NULL, 'COUNT('. $this->query->primary_table .'.'. $this->query->primary_field . ')', 'num_records'); $this->query->add_groupby($this->base_alias); if ($count_field) { $this->query->set_count_field($this->table_alias, $this->real_field); } $this->count_alias = $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) { $this->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) { $value = $data->{$this->base_alias}; return url("$url/$value"); } /** * Provides the name to use for the summary. By default this is just * the name field. * * @param $data * The query results for the row. */ function summary_name($data) { return check_plain($data->{$this->name_alias}); } /** * Set up the query for this argument. * * The argument sent may be found at $this->argument. */ function query() { $this->ensure_my_table(); $this->query->add_where(0, "$this->table_alias.$this->real_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() { return check_plain($this->argument); } /** * Validate that this argument works. By default, all arguments are valid. */ function validate($arg) { return TRUE; } } /** * Abstract argument handler for simple formulae. * * Child classes of this object should implement summary_link, at least. */ class views_handler_argument_formula extends views_handler_argument { var $formula = NULL; /** * Constructor */ function construct() { if (!empty($this->definition['formula'])) { $this->formula = $this->definition['formula']; } } function get_formula() { return str_replace('***table***', $this->table_alias, $this->formula); } /** * Build the summary query based on a formula */ function summary_query() { $this->ensure_my_table(); // Now that our table is secure, get our formula. $formula = $this->get_formula(); // Add the field. $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field); $this->query->set_count_field(NULL, $formula, $this->field); return $this->summary_basics(FALSE); } /** * Build the query based upon the formula */ function query() { $this->ensure_my_table(); // Now that our table is secure, get our formula. $formula = $this->get_formula(); $this->query->add_where(0, "$formula = '%s'", $this->argument); } } /** * Basic argument handler to implement string arguments that may have length * limits. */ class views_handler_argument_string extends views_handler_argument { function options(&$options) { parent::options($options); $options['glossary'] = FALSE; $options['limit'] = 0; $options['case'] = 'none'; $options['path_case'] = 'none'; $options['transform_dash'] = FALSE; } function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); $form['glossary'] = array( '#type' => 'checkbox', '#title' => t('Glossary mode'), '#description' => t('Glossary mode applies a limit to the number of characters used in the argument, which allows the summary view to act as a glossary.'), '#default_value' => $this->options['glossary'], ); $form['limit'] = array( '#type' => 'textfield', '#title' => t('Character limit'), '#description' => t('How many characters of the argument to filter against. If set to 1, all fields starting with the letter in the argument would be matched.'), '#default_value' => $this->options['limit'], '#process' => array('views_process_dependency'), '#dependency' => array('edit-options-glossary' => array(TRUE)), ); $form['case'] = array( '#type' => 'select', '#title' => t('Case'), '#description' => t('When printing the argument result, how to transform the case.'), '#options' => array( 'none' => t('No transform'), 'upper' => t('Upper case'), 'lower' => t('Lower case'), 'ucfirst' => t('Capitalize first letter'), 'ucwords' => t('Capitalize each word'), ), '#default_value' => $this->options['case'], ); $form['path_case'] = array( '#type' => 'select', '#title' => t('Case in path'), '#description' => t('When printing url paths, how to transform the of the argument. Do not use this unless with Postgres as it uses case sensitive comparisons.'), '#options' => array( 'none' => t('No transform'), 'upper' => t('Upper case'), 'lower' => t('Lower case'), 'ucfirst' => t('Capitalize first letter'), 'ucwords' => t('Capitalize each word'), ), '#default_value' => $this->options['path_case'], ); $form['transform_dash'] = array( '#type' => 'checkbox', '#title' => t('Transform spaces to dashes in URL'), '#description' => t('Glossary mode applies a limit to the number of characters used in the argument, which allows the summary view to act as a glossary.'), '#default_value' => $this->options['transform_dash'], ); } /** * Build the summary query based on a formula */ function summary_query() { $this->ensure_my_table(); if (empty($this->options['glossary'])) { // Add the field. $this->base_alias = $this->name_alias = $this->query->add_field($this->table_alias, $this->real_field); $this->query->set_count_field($this->table_alias, $this->real_field); } else { // Add the field. $formula = $this->get_formula(); $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field . '_truncated'); $this->query->set_count_field(NULL, $formula, $this->field, $this->field . '_truncated'); } return $this->summary_basics(FALSE); } /** * Get the formula for this argument. * * $this->ensure_my_table() MUST have been called prior to this. */ function get_formula() { return "LEFT($this->table_alias.$this->real_field, " . intval($this->options['limit']) . ")"; } /** * Build the query based upon the formula */ function query() { $this->ensure_my_table(); $argument = $this->argument; if (!empty($this->options['transform_dash'])) { $argument = strtr($argument, '-', ' '); } if (empty($this->options['glossary'])) { $field = "$this->table_alias.$this->real_field"; } else { $field = $this->get_formula(); } $this->query->add_where(0, "$field = '%s'", $argument); } /** * 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) { $value = $this->case_transform($data->{$this->base_alias}, 'path_case'); if (!empty($this->options['transform_dash'])) { $value = strtr($value, ' ', '-'); } return url("$url/$value"); } function case_transform($string, $option) { switch ($this->options[$option]) { default: return $string; case 'upper': return strtoupper($string); case 'lower': return strtolower($string); case 'upper': return strtoupper($string); case 'ucfirst': return ucfirst($string); case 'ucwords': return ucwords($string); } } function title() { return check_plain($this->case_transform($this->argument, 'case')); } } /** * @} */