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]; $handler = new $h['handler']; if (isset($h['arguments'])) { call_user_func_array(array(&$handler, 'construct'), $h['arguments']); } 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($left_table, $left_field, $field, $extra = array(), $type = 'LEFT') { * parent::construct($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 construct($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 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 (isset($options['relationship'])) { $this->relationship = $options['relationship']; } else { $this->relationship = NULL; } if (!empty($view->query)) { $this->query = &$view->query; } } /** * 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() { } /** * 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() { } /** * 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) { } } /** * @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->real_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 { 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); // Add any additional fields we are given. if (!empty($this->additional_fields) && is_array($this->additional_fields)) { foreach ($this->additional_fields as $field) { $this->aliases[$field] = $this->query->add_field($this->table_alias, $field); } } } /** * 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'] = t('!group: !title', array('!group' => $this->definition['group'], '!title' => $this->definition['title'])); } /** * 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.'), ); } /** * 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'] : '', ); } 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[] = $this->format; } } function render($values) { $value = $values->{$this->field_alias}; $format = is_numeric($this->format) ? $this->format : $values->{$this->aliases[$this->format]}; return check_markup($value, $format, FALSE); } } /** * 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->add_orderby(NULL, $this->formula, $this->options['order'], $this->table_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 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); // @todo -- remove this if once it's safe, and just set it directly. if (is_array($options['operator']) && !empty($options['operator']['operator'])) { $this->operator = $options['operator']['operator']; } else { $this->operator = $options['operator']; } // @todo -- remove this if once it's safe, and just set it directly. if (is_array($options['value']) && !empty($options['value']['value'])) { $this->value = $options['value']['value']; } else { $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 should be overridden by all child classes, and it must * define $form['operator']; */ function operator_form(&$form, &$form_state) { $form['operator'] = 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) { $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' => t('@group: @title', array('@group' => $this->definition['group'], '@title' => $this->definition['title'])), '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; } 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_form(&$form, &$form_state) { $form['operator'] = array( '#type' => 'radios', '#title' => t('Operator'), '#default_value' => $this->operator, '#options' => 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, ); } } /** * 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() { // @todo this should actually reverse the operator so it can compare against 0. $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'] = 0; } /** * Provide inclusive/exclusive matching */ function operator_form(&$form, &$form_state) { $form['operator'] = array( '#type' => 'radios', '#title' => t('Operator'), '#default_value' => ($this->operator) ? $this->operator : 'in', '#options' => 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) { // This is so deeply deeply deeply nested due to the way the form is layered. $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 { /** * Provide basic defaults for the filter */ function options(&$options) { parent::options($options); $options['operator'] = '='; $options['value']['min'] = ''; $options['value']['max'] = ''; } /** * Provide a list of all the numeric operators */ function operator_form(&$form, &$form_state) { $form['operator'] = array( '#type' => 'select', '#title' => t('Operator'), '#default_value' => $this->operator, '#options' => array( '<' => t('Is less than'), '<=' => t('Is less than or equal to'), '=' => t('Is equal to'), '!=' => t('Is not equal to'), '>=' => t('Is greater than or equal to'), '>' => t('Is greater than'), 'between' => t('Is between'), 'not between' => t('Is not between'), ), ); } /** * Provide a simple textfield for equality */ function value_form(&$form, &$form_state) { $form['value']['#tree'] = TRUE; $form['value']['min'] = array( '#type' => 'textfield', '#title' => t('Min value'), '#size' => 30, '#default_value' => $this->value['min'], '#description' => t('For all non-range operators, this value is used.'), ); $form['value']['max'] = array( '#type' => 'textfield', '#title' => t('Max value'), '#size' => 30, '#default_value' => $this->value['max'], '#description' => t('This value is ignored for all non-range operators.'), ); } /** * We need to override this to avoid doing a string comparison against numeric fields. */ function query() { $this->ensure_my_table(); switch($this->operator) { case 'between': $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field >= %d", $this->value['min']); $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field <= %d", $this->value['max']); break; case 'not between': $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field <= %d", $this->value['min']); $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field >= %d", $this->value['max']); break; default: $this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field " . $this->operator . " %d", $this->value['min']); break; } } function admin_summary() { $output = check_plain($this->operator) . ' ' . check_plain($this->value['min']); if (!empty($this->value['max'])) { $output .= ' '. t('and') .' '. check_plain($this->value['max']); } return $output; } } /** * @} */ /** * @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']; } } /** * Build the summary query based on a formula */ function summary_query() { $this->ensure_my_table(); // Add the field. $this->base_alias = $this->name_alias = $this->query->add_field(NULL, $this->formula, $this->field); $this->query->set_count_field(NULL, $this->formula, $this->field); return $this->summary_basics(FALSE); } /** * Build the query based upon the formula */ function query() { $this->ensure_my_table(); $this->query->add_where(0, "$this->formula = '%s'", $this->argument); } } /** * @} */