view = &$view; $this->display = &$display; // Load extenders as soon as possible. $this->extender = array(); $extenders = views_get_enabled_display_extenders(); // If you update to the dev version the registry might not be loaded yet. if (!empty($extenders) && class_exists('views_plugin_display_extender')) { foreach ($extenders as $extender) { $plugin = views_get_plugin('display_extender', $extender); if ($plugin) { $plugin->init($this->view, $this); $this->extender[$extender] = $plugin; } else { vpr('Invalid display extender @extender', array('@extender' => $extender)); } } } // Track changes that the user should know about. $changed = FALSE; // Make some modifications. if (!isset($options) && isset($display->display_options)) { $options = $display->display_options; } if ($this->is_default_display() && isset($options['defaults'])) { unset($options['defaults']); } // Cache for unpack_options, but not if we are in the ui. static $unpack_options = array(); if (empty($view->editing)) { $cid = 'unpack_options:' . md5(serialize(array($this->options, $options))); if (empty($unpack_options[$cid])) { $cache = views_cache_get($cid, TRUE); if (!empty($cache->data)) { $this->options = $cache->data; } else { $this->unpack_options($this->options, $options); views_cache_set($cid, $this->options, TRUE); } $unpack_options[$cid] = $this->options; } else { $this->options = $unpack_options[$cid]; } } else { $this->unpack_options($this->options, $options); } // Translate changed settings. $items_per_page = $this->get_option('items_per_page'); $offset = $this->get_option('offset'); $use_pager = $this->get_option('use_pager'); $pager = $this->get_option('pager'); // Check if the pager options were already converted. // The pager settings of a Views 2.x view specifying 10 items with an // offset of 0 and no pager is the same as of a Views 3.x view with // default settings. In this case, the only way to determine which case we // are dealing with is checking the API version but that's only available // for exported Views as it's not stored in the database. // If you would like to change this code, really take care that you thought // of every possibility. // @todo Provide a way to convert the database views as well. if (((!empty($items_per_page) && $items_per_page != 10) || !empty($offset) || !empty($use_pager)) || (!empty($view->api_version) && $view->api_version == 2)) { // Find out the right pager type. // If the view "use pager" it's a normal/full pager. if ($use_pager) { $type = 'full'; } // If it does not use pager, but 0 items per page it should not page // else it should display just a certain amount of items. else { $type = $items_per_page ? 'some' : 'none'; } // Setup the pager options. $pager = array( 'type' => $type, 'options' => array( 'offset' => intval($offset), ), ); if ($items_per_page) { $pager['options']['items_per_page'] = $items_per_page; } // Setup the pager element. if ($id = $this->get_option('pager_element')) { $pager['options']['id'] = $id; } // Unset the previous options. After edit and save the view they will be // erased. $this->set_option('items_per_page', NULL); $this->set_option('offset', NULL); $this->set_option('use_pager', NULL); $this->set_option('pager', $pager); $changed = TRUE; } // Plugable headers, footer and empty texts are not compatible with // previous version of views. This code converts old values into a // configured handler for each area. foreach (array('header', 'footer', 'empty') as $area) { $converted = FALSE; if (isset($this->options[$area]) && !is_array($this->options[$area])) { if (!empty($this->options[$area])) { $content = $this->get_option($area); if (!empty($content) && !is_array($content)) { $format = $this->get_option($area . '_format'); $options = array( 'id' => 'area', 'table' => 'views', 'field' => 'area', 'label' => '', 'relationship' => 'none', 'group_type' => 'group', 'content' => $content, 'format' => !empty($format) ? $format : filter_default_format(), ); if ($area != 'empty' && $empty = $this->get_option($area . '_empty')) { $options['empty'] = $empty; } $this->set_option($area, array('text' => $options)); $converted = TRUE; $changed = TRUE; } } // Ensure that options are at least an empty array. if (!$converted) { $this->set_option($area, array()); } } } // Convert distinct setting from display to query settings. $distinct = $this->get_option('distinct'); if (!empty($distinct)) { $query_settings = $this->get_option('query'); $query_settings['options']['distinct'] = $distinct; $this->set_option('query', $query_settings); // Clear the values. $this->set_option('distinct', NULL); $changed = TRUE; } // Convert field language settings. $query_options = $this->get_option('query'); if (isset($query_options['options']['field_language'])) { $this->set_option('field_language', $query_options['options']['field_language']); unset($query_options['options']['field_language']); $changed = TRUE; } if (isset($query_options['options']['field_language_add_to_query'])) { $this->set_option('field_language_add_to_query', $query_options['options']['field_language_add_to_query']); unset($query_options['options']['field_language_add_to_query']); $changed = TRUE; } $this->set_option('query', $query_options); // Convert filter groups. $filter_groups = $this->get_option('filter_groups'); // Only convert if it wasn't converted yet, which is the case if there is a // '0' group. if (isset($filter_groups['groups'][0])) { // Update filter groups. $filter_groups['groups'] = views_array_key_plus($filter_groups['groups']); $this->set_option('filter_groups', $filter_groups); // Update the filter group on each filter. $filters = $this->get_option('filters'); foreach ($filters as &$filter) { if (isset($filter['group'])) { $filter['group']++; } else { $filter['group'] = 1; } } $this->set_option('filters', $filters); $changed = TRUE; } // Filter groups were allowed to be rewritten without its filters, so // before this update the view was using the default values. To be sure that // the existing view isn't broken, don't use this overridden values but copy // them from the default display. Only do this if the filters are overridden // but the filter_groups are not marked as so. if (!$this->is_default_display() && !$this->options['defaults']['filters'] && $this->options['defaults']['filter_groups']) { // Set filter_groups to be overridden and save the value in the // display_options as well. $this->options['defaults']['filter_groups'] = FALSE; $this->display->display_options['defaults']['filter_groups'] = $this->options['defaults']['filter_groups']; // Copy the filter_groups from the default, and add them to the // display_options as well. $this->default_display is not initialized at // this point. $this->options['filter_groups'] = $this->view->display['default']->handler->options['filter_groups']; $this->display->display_options['filter_groups'] = $this->options['filter_groups']; $changed = TRUE; } // Mark the view as changed so the user has a chance to save it. if ($changed) { $this->view->changed = TRUE; } } /** * {@inheritdoc} */ public function destroy() { parent::destroy(); foreach ($this->handlers as $type => $handlers) { foreach ($handlers as $id => $handler) { if (is_object($handler)) { $this->handlers[$type][$id]->destroy(); } } } if (isset($this->default_display)) { unset($this->default_display); } foreach ($this->extender as $extender) { $extender->destroy(); } } /** * If this display is the 'default' display which contains fallback settings. * * @return bool * This is the default display and contains fallback settings. */ public function is_default_display() { return FALSE; } /** * Does this display uses exposed filters? * * So the view will know whether or not to build them. */ public function uses_exposed() { if (!isset($this->has_exposed)) { foreach ($this->handlers as $type => $value) { foreach ($this->view->$type as $id => $handler) { if ($handler->can_expose() && $handler->is_exposed()) { // one is all we need; if we find it, return true. $this->has_exposed = TRUE; return TRUE; } } } $pager = $this->get_plugin('pager'); if (isset($pager) && $pager->uses_exposed()) { $this->has_exposed = TRUE; return TRUE; } $this->has_exposed = FALSE; } return $this->has_exposed; } /** * Determine if this display should display the exposed filters widgets. * * If so, the view will know whether or not to render them. * * Regardless of what this function returns, exposed filters will not be used * nor displayed unless uses_exposed() returns TRUE. */ public function displays_exposed() { return TRUE; } /** * Does the display use AJAX? */ public function use_ajax() { if (!empty($this->definition['use ajax'])) { return $this->get_option('use_ajax'); } return FALSE; } /** * Does the display have a pager enabled? */ public function use_pager() { $pager = $this->get_plugin('pager'); if ($pager) { return $pager->use_pager(); } } /** * Does the display have a more link enabled? */ public function use_more() { if (!empty($this->definition['use more'])) { return $this->get_option('use_more'); } return FALSE; } /** * Does the display have groupby enabled? */ public function use_group_by() { return $this->get_option('group_by'); } /** * Should the enabled display more link be shown when no more items? */ public function use_more_always() { if (!empty($this->definition['use more'])) { return $this->get_option('use_more_always'); } return FALSE; } /** * Should the enabled display more link being opened in an new window? */ public function use_more_open_new_window() { if (!empty($this->definition['use more'])) { return $this->get_option('open_new_window'); } return FALSE; } /** * Does the display have custom link text? */ public function use_more_text() { if (!empty($this->definition['use more'])) { return $this->get_option('use_more_text'); } return FALSE; } /** * Can this display accept attachments? */ public function accept_attachments() { if (empty($this->definition['accept attachments'])) { return FALSE; } if (!empty($this->view->argument) && $this->get_option('hide_attachment_summary')) { foreach ($this->view->argument as $argument_id => $argument) { if ($argument->needs_style_plugin() && empty($argument->argument_validated)) { return FALSE; } } } return TRUE; } /** * Allow displays to attach to other views. */ public function attach_to($display_id) { } /** * List which sections are defaultable and what items each section contains. */ public function defaultable_sections($section = NULL) { $sections = array( 'access' => array('access', 'access_options'), 'access_options' => array('access', 'access_options'), 'cache' => array('cache', 'cache_options'), 'cache_options' => array('cache', 'cache_options'), 'title' => array('title'), 'css_class' => array('css_class'), 'use_ajax' => array('use_ajax'), 'hide_attachment_summary' => array('hide_attachment_summary'), 'hide_admin_links' => array('hide_admin_links'), 'group_by' => array('group_by'), 'query' => array('query'), 'use_more' => array('use_more', 'use_more_always', 'open_new_window', 'use_more_text', ), 'use_more_always' => array( 'use_more', 'use_more_always', 'open_new_window', 'use_more_text', ), 'use_more_text' => array( 'use_more', 'use_more_always', 'open_new_window', 'use_more_text', ), 'open_new_window' => array( 'use_more', 'use_more_always', 'open_new_window', 'use_more_text', ), 'link_display' => array('link_display', 'link_url'), // Force these to cascade properly. 'style_plugin' => array( 'style_plugin', 'style_options', 'row_plugin', 'row_options', ), 'style_options' => array( 'style_plugin', 'style_options', 'row_plugin', 'row_options', ), 'row_plugin' => array( 'style_plugin', 'style_options', 'row_plugin', 'row_options', ), 'row_options' => array( 'style_plugin', 'style_options', 'row_plugin', 'row_options', ), 'pager' => array('pager', 'pager_options'), 'pager_options' => array('pager', 'pager_options'), 'exposed_form' => array('exposed_form', 'exposed_form_options'), 'exposed_form_options' => array('exposed_form', 'exposed_form_options'), // These guys are special. 'header' => array('header'), 'footer' => array('footer'), 'empty' => array('empty'), 'relationships' => array('relationships'), 'fields' => array('fields'), 'sorts' => array('sorts'), 'arguments' => array('arguments'), 'filters' => array('filters', 'filter_groups'), 'filter_groups' => array('filters', 'filter_groups'), ); // If the display cannot use a pager, then we cannot default it. if (empty($this->definition['use pager'])) { unset($sections['pager']); unset($sections['items_per_page']); } foreach ($this->extender as $extender) { $extender->defaultable_sections($sections, $section); } if ($section) { if (!empty($sections[$section])) { return $sections[$section]; } } else { return $sections; } } /** * {@inheritdoc} */ public function option_definition() { $options = array( 'defaults' => array( 'default' => array( 'access' => TRUE, 'cache' => TRUE, 'query' => TRUE, 'title' => TRUE, 'css_class' => TRUE, 'display_description' => FALSE, 'use_ajax' => TRUE, 'hide_attachment_summary' => TRUE, 'hide_admin_links' => TRUE, 'pager' => TRUE, 'pager_options' => TRUE, 'use_more' => TRUE, 'use_more_always' => TRUE, 'open_new_window' => FALSE, 'use_more_text' => TRUE, 'exposed_form' => TRUE, 'exposed_form_options' => TRUE, 'link_display' => TRUE, 'link_url' => '', 'group_by' => TRUE, 'style_plugin' => TRUE, 'style_options' => TRUE, 'row_plugin' => TRUE, 'row_options' => TRUE, 'header' => TRUE, 'footer' => TRUE, 'empty' => TRUE, 'relationships' => TRUE, 'fields' => TRUE, 'sorts' => TRUE, 'arguments' => TRUE, 'filters' => TRUE, 'filter_groups' => TRUE, ), 'export' => FALSE, ), 'title' => array( 'default' => '', 'translatable' => TRUE, ), 'enabled' => array( 'default' => TRUE, 'translatable' => FALSE, 'bool' => TRUE, ), 'display_comment' => array( 'default' => '', ), 'css_class' => array( 'default' => '', 'translatable' => FALSE, ), 'display_description' => array( 'default' => '', 'translatable' => TRUE, ), 'use_ajax' => array( 'default' => FALSE, 'bool' => TRUE, ), 'hide_attachment_summary' => array( 'default' => FALSE, 'bool' => TRUE, ), 'hide_admin_links' => array( 'default' => FALSE, 'bool' => TRUE, ), // This is legacy code. // Items_per/offset/use_pager is moved to the pager plugin but the // automatic update path needs this items defined, so don't remove it. // @see views_plugin_display::init() 'items_per_page' => array( 'default' => 10, ), 'offset' => array( 'default' => 0, ), 'use_pager' => array( 'default' => FALSE, 'bool' => TRUE, ), 'use_more' => array( 'default' => FALSE, 'bool' => TRUE, ), 'use_more_always' => array( 'default' => FALSE, 'bool' => TRUE, 'export' => 'export_option_always', ), 'use_more_text' => array( 'default' => 'more', 'translatable' => TRUE, ), 'link_display' => array( 'default' => '', ), 'link_url' => array( 'default' => '', ), 'group_by' => array( 'default' => FALSE, 'bool' => TRUE, ), 'field_language' => array( 'default' => '***CURRENT_LANGUAGE***', ), 'field_language_add_to_query' => array( 'default' => 1, ), // These types are all plugins that can have individual settings and // therefore need special handling. 'access' => array( 'contains' => array( 'type' => array( 'default' => 'none', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin', ), ), ), 'cache' => array( 'contains' => array( 'type' => array( 'default' => 'none', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin', ), ), ), 'query' => array( 'contains' => array( 'type' => array( 'default' => 'views_query', 'export' => 'export_plugin', ), 'options' => array('default' => array(), 'export' => FALSE), ), ), // Note that exposed_form plugin has options in a separate array, while // access and cache do not. access and cache are legacy and that pattern // should not be repeated, but it is left as is to reduce the need to // modify older views. Let's consider the pattern used here to be the // template from which future plugins should be copied. 'exposed_form' => array( 'contains' => array( 'type' => array( 'default' => 'basic', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin', ), 'options' => array('default' => array(), 'export' => FALSE), ), ), 'pager' => array( 'contains' => array( 'type' => array( 'default' => 'full', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin', ), 'options' => array('default' => array(), 'export' => FALSE), ), ), // Note that the styles have their options completely independent. Like // access and cache above, this is a legacy pattern and should not be // repeated. 'style_plugin' => array( 'default' => 'default', 'export' => 'export_style', 'unpack_translatable' => 'unpack_style', ), 'style_options' => array( 'default' => array(), 'export' => FALSE, ), 'row_plugin' => array( 'default' => 'fields', 'export' => 'export_style', 'unpack_translatable' => 'unpack_style', ), 'row_options' => array( 'default' => array(), 'export' => FALSE, ), 'exposed_block' => array( 'default' => FALSE, ), 'header' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'footer' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'empty' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), // We want these to export last. // These are the 5 handler types. 'relationships' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'fields' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'sorts' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'arguments' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), 'filter_groups' => array( 'contains' => array( 'operator' => array('default' => 'AND'), 'groups' => array('default' => array(1 => 'AND')), ), ), 'filters' => array( 'default' => array(), 'export' => 'export_handler', 'unpack_translatable' => 'unpack_handler', ), ); if (empty($this->definition['use pager'])) { $options['defaults']['default']['use_pager'] = FALSE; $options['defaults']['default']['items_per_page'] = FALSE; $options['defaults']['default']['offset'] = FALSE; $options['defaults']['default']['pager'] = FALSE; $options['pager']['contains']['type']['default'] = 'some'; } if ($this->is_default_display()) { unset($options['defaults']); } foreach ($this->extender as $extender) { $extender->options_definition_alter($options); } return $options; } /** * Check to see if the display has a 'path' field. * * This is a pure function and not just a setting on the definition * because some displays (such as a panel pane) may have a path based * upon configuration. * * By default, displays do not have a path. */ public function has_path() { return FALSE; } /** * Check to see if the display has some need to link to another display. * * For the most part, displays without a path will use a link display. * However, sometimes displays that have a path might also need to link to * another display. This is true for feeds. */ public function uses_link_display() { return !$this->has_path(); } /** * Check to see if the display can put the exposed form in a block. * * By default, displays that do not have a path cannot disconnect the exposed * form and put it in a block, because the form has no place to go and Views * really wants the forms to go to a specific page. */ public function uses_exposed_form_in_block() { return $this->has_path(); } /** * Check to see which display to use when creating links. */ public function get_link_display() { $display_id = $this->get_option('link_display'); // If unknown, pick the first one. if (empty($display_id) || empty($this->view->display[$display_id])) { foreach ($this->view->display as $display_id => $display) { if (!empty($display->handler) && $display->handler->has_path()) { return $display_id; } } } else { return $display_id; } // Fall-through returns NULL. } /** * Return the base path to use for this display. * * This can be overridden for displays that do strange things with the path. */ public function get_path() { if ($this->has_path()) { return $this->get_option('path'); } $display_id = $this->get_link_display(); if ($display_id && !empty($this->view->display[$display_id]) && is_object($this->view->display[$display_id]->handler)) { return $this->view->display[$display_id]->handler->get_path(); } if ($this->get_option('link_display') == 'custom_url' && $link_url = $this->get_option('link_url')) { return $link_url; } } /** * */ public function get_url() { return $this->view->get_url(); } /** * Check to see if the display needs a breadcrumb. * * By default, displays do not need breadcrumbs. */ public function uses_breadcrumb() { return FALSE; } /** * Determine if a given option is set to use the default or current display. * * @return * TRUE for the default display. */ public function is_defaulted($option) { return !$this->is_default_display() && !empty($this->default_display) && !empty($this->options['defaults'][$option]); } /** * Intelligently get an option either from this or default display. */ public function get_option($option) { if ($this->is_defaulted($option)) { return $this->default_display->get_option($option); } if (isset($this->options[$option]) || array_key_exists($option, $this->options)) { return $this->options[$option]; } } /** * Determine if the display's style uses fields. */ public function uses_fields() { $plugin = $this->get_plugin(); if ($plugin) { return $plugin->uses_fields(); } } /** * Get the instance of a plugin, for example style or row. * * @param string $type * The type of the plugin. * @param string $name * The name of the plugin defined in hook_views_plugins. * * @return views_plugin|FALSE * FALSE if no plugin, otherwise the requested instance of a plugin. */ public function get_plugin($type = 'style', $name = NULL) { static $cache = array(); if (!isset($cache[$type][$name])) { switch ($type) { case 'style': case 'row': $option_name = $type . '_plugin'; $options = $this->get_option($type . '_options'); if (!$name) { $name = $this->get_option($option_name); } break; case 'query': $views_data = views_fetch_data($this->view->base_table); $name = !empty($views_data['table']['base']['query class']) ? $views_data['table']['base']['query class'] : 'views_query'; default: $option_name = $type; $options = $this->get_option($type); if (!$name) { $name = $options['type']; } // Access & cache store their options as siblings with the type; all // others use an 'options' array. if ($type != 'access' && $type != 'cache') { $options = $options['options']; } } $plugin = views_get_plugin($type, $name); if (!$plugin) { return FALSE; } if ($type != 'query') { $plugin->init($this->view, $this->display, $options); } else { $display_id = $this->is_defaulted($option_name) ? $this->display->id : 'default'; $plugin->localization_keys = array($display_id, $type); if (!isset($this->base_field)) { $views_data = views_fetch_data($this->view->base_table); $this->view->base_field = !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : ''; } $plugin->init($this->view->base_table, $this->view->base_field, $options); } $cache[$type][$name] = $plugin; } return $cache[$type][$name]; } /** * Get the handler object for a single handler. */ public function &get_handler($type, $id) { if (!isset($this->handlers[$type])) { $this->get_handlers($type); } if (isset($this->handlers[$type][$id])) { return $this->handlers[$type][$id]; } // So we can return a reference. $null = NULL; return $null; } /** * Get a full array of handlers for $type. This caches them. */ public function &get_handlers($type) { if (!isset($this->handlers[$type])) { $this->handlers[$type] = array(); $types = views_object_types(); $plural = $types[$type]['plural']; foreach ($this->get_option($plural) as $id => $info) { // If this is during form submission and there are temporary options // which can only appear if the view is in the edit cache, use those // options instead. This is used for AJAX multi-step stuff. if (isset($_POST['form_id']) && isset($this->view->temporary_options[$type][$id])) { $info = $this->view->temporary_options[$type][$id]; } if ($info['id'] != $id) { $info['id'] = $id; } // If aggregation is on, the group type might override the actual // handler that is in use. This piece of code checks that and, // if necessary, sets the override handler. $override = NULL; if ($this->use_group_by() && !empty($info['group_type'])) { if (empty($this->view->query)) { $this->view->init_query(); } $aggregate = $this->view->query->get_aggregation_info(); if (!empty($aggregate[$info['group_type']]['handler'][$type])) { $override = $aggregate[$info['group_type']]['handler'][$type]; } } if (!empty($types[$type]['type'])) { $handler_type = $types[$type]['type']; } else { $handler_type = $type; } $handler = views_get_handler($info['table'], $info['field'], $handler_type, $override); if ($handler) { // Special override for area types so they know where they come from. if ($handler_type == 'area') { $handler->handler_type = $type; } $handler->init($this->view, $info); $this->handlers[$type][$id] = &$handler; } // Prevent reference problems. unset($handler); } } return $this->handlers[$type]; } /** * List of fields for the current display with the associated relationship. * * @param bool $groupable_only * Return only an array of field labels from handler that return TRUE from * use_string_group_by method. */ public function get_field_labels() { // Use func_get_arg so the function signature isn't amended but we can still // pass TRUE into the function to filter by groupable handlers. $args = func_get_args(); $groupable_only = isset($args[0]) ? $args[0] : FALSE; $options = array(); foreach ($this->get_handlers('relationship') as $relationship => $handler) { if ($label = $handler->label()) { $relationships[$relationship] = $label; } else { $relationships[$relationship] = $handler->ui_name(); } } foreach ($this->get_handlers('field') as $id => $handler) { if ($groupable_only && !$handler->use_string_group_by()) { // Continue to next handler if it's not groupable. continue; } if ($label = $handler->label()) { $options[$id] = $label; } else { $options[$id] = $handler->ui_name(); } if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) { $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id]; } } return $options; } /** * Intelligently set an option either from this display or from the * default display, if directed to do so. */ public function set_option($option, $value) { if ($this->is_defaulted($option)) { return $this->default_display->set_option($option, $value); } // Set this in two places: On the handler where we'll notice it but also on // the display object so it gets saved. This should only be a temporary fix. $this->display->display_options[$option] = $value; return $this->options[$option] = $value; } /** * Set an option and force it to be an override. */ public function override_option($option, $value) { $this->set_override($option, FALSE); $this->set_option($option, $value); } /** * Because forms may be split up into sections, this provides * an easy URL to exactly the right section. Don't override this. */ public function option_link($text, $section, $class = '', $title = '') { views_add_js('ajax'); if (!empty($class)) { $text = '' . $text . ''; } if (!trim($text)) { $text = t('Broken field'); } if (empty($title)) { $title = $text; } // Truncate the path as it is displayed as a link. if ($section == 'path') { $text = views_ui_truncate($text, 24); } return l($text, 'admin/structure/views/nojs/display/' . $this->view->name . '/' . $this->display->id . '/' . $section, array( 'attributes' => array( 'class' => 'views-ajax-link ' . $class, 'title' => $title, 'id' => drupal_html_id('views-' . $this->display->id . '-' . $section), ), 'html' => TRUE, ) ); } /** * Returns to tokens for arguments. * * This function is similar to views_handler_field::get_render_tokens() but * without fields tokens. */ public function get_arguments_tokens() { $tokens = array(); if (!empty($this->view->build_info['substitutions'])) { $tokens = $this->view->build_info['substitutions']; } $count = 0; foreach ($this->view->display_handler->get_handlers('argument') as $arg => $handler) { $token = '%' . ++$count; if (!isset($tokens[$token])) { $tokens[$token] = ''; } // Use strip tags as there should never be HTML in the path. // However, we need to preserve special characters like " that // were removed by check_plain(). $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(decode_entities($this->view->args[$count - 1])) : ''; } return $tokens; } /** * Provide the default summary for options in the views UI. * * This output is returned as an array. */ public function options_summary(&$categories, &$options) { $categories = array( 'title' => array( 'title' => t('Title'), 'column' => 'first', ), 'format' => array( 'title' => t('Format'), 'column' => 'first', ), 'filters' => array( 'title' => t('Filters'), 'column' => 'first', ), 'fields' => array( 'title' => t('Fields'), 'column' => 'first', ), 'pager' => array( 'title' => t('Pager'), 'column' => 'second', ), 'exposed' => array( 'title' => t('Exposed form'), 'column' => 'third', 'build' => array( '#weight' => 1, ), ), 'access' => array( 'title' => '', 'column' => 'second', 'build' => array( '#weight' => -5, ), ), 'other' => array( 'title' => t('Other'), 'column' => 'third', 'build' => array( '#weight' => 2, ), ), ); if ($this->display->id != 'default') { $options['display_id'] = array( 'category' => 'other', 'title' => t('Machine Name'), 'value' => !empty($this->display->new_id) ? check_plain($this->display->new_id) : check_plain($this->display->id), 'desc' => t('Change the machine name of this display.'), ); } $display_comment = check_plain(views_ui_truncate($this->get_option('display_comment'), 80)); $options['display_comment'] = array( 'category' => 'other', 'title' => t('Comment'), 'value' => !empty($display_comment) ? $display_comment : t('No comment'), 'desc' => t('Comment or document this display.'), ); $title = strip_tags($this->get_option('title')); if (!$title) { $title = t('None'); } $options['title'] = array( 'category' => 'title', 'title' => t('Title'), 'value' => $title, 'desc' => t('Change the title that this display will use.'), ); $style_plugin = views_fetch_plugin_data('style', $this->get_option('style_plugin')); $style_plugin_instance = $this->get_plugin('style'); $style_summary = empty($style_plugin['title']) ? t('Missing style plugin') : $style_plugin_instance->summary_title(); $style_title = empty($style_plugin['title']) ? t('Missing style plugin') : $style_plugin_instance->plugin_title(); $style = ''; $options['style_plugin'] = array( 'category' => 'format', 'title' => t('Format'), 'value' => $style_title, 'setting' => $style_summary, 'desc' => t('Change the way content is formatted.'), ); // This adds a 'Settings' link to the style_options setting if the style // has options. if (!empty($style_plugin['uses options'])) { $options['style_plugin']['links']['style_options'] = t('Change settings for this format'); } if (!empty($style_plugin['uses row plugin'])) { $row_plugin = views_fetch_plugin_data('row', $this->get_option('row_plugin')); $row_plugin_instance = $this->get_plugin('row'); $row_summary = empty($row_plugin['title']) ? t('Missing style plugin') : $row_plugin_instance->summary_title(); $row_title = empty($row_plugin['title']) ? t('Missing style plugin') : $row_plugin_instance->plugin_title(); $options['row_plugin'] = array( 'category' => 'format', 'title' => t('Show'), 'value' => $row_title, 'setting' => $row_summary, 'desc' => t('Change the way each row in the view is styled.'), ); // This adds a 'Settings' link to the row_options setting if the row // style has options. if (!empty($row_plugin['uses options'])) { $options['row_plugin']['links']['row_options'] = t('Change settings for this style'); } } if (!empty($this->definition['use ajax'])) { $options['use_ajax'] = array( 'category' => 'other', 'title' => t('Use AJAX'), 'value' => $this->get_option('use_ajax') ? t('Yes') : t('No'), 'desc' => t('Change whether or not this display will use AJAX.'), ); } if (!empty($this->definition['accept attachments'])) { $options['hide_attachment_summary'] = array( 'category' => 'other', 'title' => t('Hide attachments in summary'), 'value' => $this->get_option('hide_attachment_summary') ? t('Yes') : t('No'), 'desc' => t('Change whether or not to display attachments when displaying a contextual filter summary.'), ); } if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) { $options['hide_admin_links'] = array( 'category' => 'other', 'title' => t('Hide contextual links'), 'value' => $this->get_option('hide_admin_links') ? t('Yes') : t('No'), 'desc' => t('Change whether or not to display contextual links for this view.'), ); } $pager_plugin = $this->get_plugin('pager'); if (!$pager_plugin) { // Default to the no pager plugin. $pager_plugin = views_get_plugin('pager', 'none'); } $pager_str = $pager_plugin->summary_title(); $options['pager'] = array( 'category' => 'pager', 'title' => t('Use pager'), 'value' => $pager_plugin->plugin_title(), 'setting' => $pager_str, 'desc' => t("Change this display's pager setting."), ); // If pagers aren't allowed, change the text of the item. if (empty($this->definition['use pager'])) { $options['pager']['title'] = t('Items to display'); } if (!empty($pager_plugin->definition['uses options'])) { $options['pager']['links']['pager_options'] = t('Change settings for this pager type.'); } if (!empty($this->definition['use more'])) { $options['use_more'] = array( 'category' => 'pager', 'title' => t('More link'), 'value' => $this->get_option('use_more') ? t('Yes') : t('No'), 'desc' => t('Specify whether this display will provide a "more" link.'), ); } $this->view->init_query(); if ($this->view->query->get_aggregation_info()) { $options['group_by'] = array( 'category' => 'other', 'title' => t('Use aggregation'), 'value' => $this->get_option('group_by') ? t('Yes') : t('No'), 'desc' => t('Allow grouping and aggregation (calculation) of fields.'), ); } $options['query'] = array( 'category' => 'other', 'title' => t('Query settings'), 'value' => t('Settings'), 'desc' => t('Allow to set some advanced settings for the query plugin'), ); $languages = array( '***CURRENT_LANGUAGE***' => t("Current user's language"), '***DEFAULT_LANGUAGE***' => t("Default site language"), LANGUAGE_NONE => t('Language neutral'), ); if (module_exists('locale')) { $languages = array_merge($languages, locale_language_list()); } $field_language = array(); $options['field_language'] = array( 'category' => 'other', 'title' => t('Field Language'), 'value' => $languages[$this->get_option('field_language')], 'desc' => t('All fields which support translations will be displayed in the selected language.'), ); $access_plugin = $this->get_plugin('access'); if (!$access_plugin) { // Default to the no access control plugin. $access_plugin = views_get_plugin('access', 'none'); } $access_str = $access_plugin->summary_title(); $options['access'] = array( 'category' => 'access', 'title' => t('Access'), 'value' => $access_plugin->plugin_title(), 'setting' => $access_str, 'desc' => t('Specify access control type for this display.'), ); if (!empty($access_plugin->definition['uses options'])) { $options['access']['links']['access_options'] = t('Change settings for this access type.'); } $cache_plugin = $this->get_plugin('cache'); if (!$cache_plugin) { // Default to the no cache control plugin. $cache_plugin = views_get_plugin('cache', 'none'); } $cache_str = $cache_plugin->summary_title(); $options['cache'] = array( 'category' => 'other', 'title' => t('Caching'), 'value' => $cache_plugin->plugin_title(), 'setting' => $cache_str, 'desc' => t('Specify caching type for this display.'), ); if (!empty($cache_plugin->definition['uses options'])) { $options['cache']['links']['cache_options'] = t('Change settings for this caching type.'); } if (!empty($access_plugin->definition['uses options'])) { $options['access']['links']['access_options'] = t('Change settings for this access type.'); } if ($this->uses_link_display()) { $display_id = $this->get_link_display(); $link_display = empty($this->view->display[$display_id]) ? t('None') : check_plain($this->view->display[$display_id]->display_title); $link_display = $this->get_option('link_display') == 'custom_url' ? t('Custom URL') : $link_display; $options['link_display'] = array( 'category' => 'other', 'title' => t('Link display'), 'value' => $link_display, 'desc' => t('Specify which display or custom url this display will link to.'), ); } if ($this->uses_exposed_form_in_block()) { $options['exposed_block'] = array( 'category' => 'exposed', 'title' => t('Exposed form in block'), 'value' => $this->get_option('exposed_block') ? t('Yes') : t('No'), 'desc' => t('Allow the exposed form to appear in a block instead of the view.'), ); } $exposed_form_plugin = $this->get_plugin('exposed_form'); if (!$exposed_form_plugin) { // Default to the no cache control plugin. $exposed_form_plugin = views_get_plugin('exposed_form', 'basic'); } $exposed_form_str = $exposed_form_plugin->summary_title(); $options['exposed_form'] = array( 'category' => 'exposed', 'title' => t('Exposed form style'), 'value' => $exposed_form_plugin->plugin_title(), 'setting' => $exposed_form_str, 'desc' => t('Select the kind of exposed filter to use.'), ); if (!empty($exposed_form_plugin->definition['uses options'])) { $options['exposed_form']['links']['exposed_form_options'] = t('Exposed form settings for this exposed form style.'); } $css_class = check_plain(trim($this->get_option('css_class'))); if (!$css_class) { $css_class = t('None'); } $options['css_class'] = array( 'category' => 'other', 'title' => t('CSS class'), 'value' => $css_class, 'desc' => t('Change the CSS class name(s) that will be added to this display.'), ); $options['analyze-theme'] = array( 'category' => 'other', 'title' => t('Theme'), 'value' => t('Information'), 'desc' => t('Get information on how to theme this display'), ); foreach ($this->extender as $extender) { $extender->options_summary($categories, $options); } } /** * Provide the default form for setting options. */ public function options_form(&$form, &$form_state) { parent::options_form($form, $form_state); if ($this->defaultable_sections($form_state['section'])) { views_ui_standard_display_dropdown($form, $form_state, $form_state['section']); } $form['#title'] = check_plain($this->display->display_title) . ': '; // Set the 'section' to highlight on the form. If it's the item we're // looking at is pulling from the default display, reflect that. Don't use // is_defaulted since we want it to show up even on the default display. if (!empty($this->options['defaults'][$form_state['section']])) { $form['#section'] = 'default-' . $form_state['section']; } else { $form['#section'] = $this->display->id . '-' . $form_state['section']; } switch ($form_state['section']) { case 'display_id': $form['#title'] .= t('The machine name of this display'); $form['display_id'] = array( '#type' => 'textfield', '#description' => t('This is machine name of the display.'), '#default_value' => !empty($this->display->new_id) ? $this->display->new_id : $this->display->id, '#required' => TRUE, '#size' => 64, ); break; case 'display_title': $form['#title'] .= t('The name and the description of this display'); $form['display_title'] = array( '#title' => t('Name'), '#type' => 'textfield', '#description' => t('This name will appear only in the administrative interface for the View.'), '#default_value' => $this->display->display_title, ); $form['display_description'] = array( '#title' => t('Description'), '#type' => 'textfield', '#description' => t('This description will appear only in the administrative interface for the View.'), '#default_value' => $this->get_option('display_description'), ); break; case 'display_comment': $form['#title'] .= t("This display's comments"); $form['display_comment'] = array( '#type' => 'textarea', '#description' => t('This value will be seen and used only within the Views UI and can be used to document this display. You can use this to provide notes for other or future maintainers of your site about how or why this display is configured.'), '#default_value' => $this->get_option('display_comment'), ); break; case 'title': $form['#title'] .= t('The title of this view'); $form['title'] = array( '#type' => 'textfield', '#description' => t('This title will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc. Use <none> to not assign a title; this can allow other modules to control the page title.'), '#default_value' => $this->get_option('title'), ); break; case 'css_class': $form['#title'] .= t('CSS class'); $form['css_class'] = array( '#type' => 'textfield', '#description' => t('The CSS class names will be added to the view. This enables you to use specific CSS code for each view. You may define multiples classes separated by spaces.'), '#default_value' => $this->get_option('css_class'), ); break; case 'use_ajax': $form['#title'] .= t('Use AJAX when available to load this view'); $form['description'] = array( '#markup' => '
The following tokens are available for this link.
'); foreach (array_keys($options) as $type) { if (!empty($options[$type])) { $items = array(); foreach ($options[$type] as $key => $value) { $items[] = $key . ' == ' . $value; } $output .= theme('item_list', array( 'items' => $items, 'type' => $type, )); } } } $form['link_url'] = array( '#type' => 'textfield', '#title' => t('Custom URL'), '#default_value' => $this->get_option('link_url'), '#description' => t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.') . $output, '#dependency' => array('radio:link_display' => array('custom_url')), ); break; case 'analyze-theme': $form['#title'] .= t('Theming information'); $form['#help_topic'] = 'analyze-theme'; if (isset($_POST['theme'])) { $this->theme = $_POST['theme']; } elseif (empty($this->theme)) { $this->theme = variable_get('theme_default', 'bartik'); } if (isset($GLOBALS['theme']) && $GLOBALS['theme'] == $this->theme) { $this->theme_registry = theme_get_registry(); $theme_engine = $GLOBALS['theme_engine']; } else { $themes = list_themes(); $theme = $themes[$this->theme]; // Find all our ancestor themes and put them in an array. $base_theme = array(); $ancestor = $this->theme; while ($ancestor && isset($themes[$ancestor]->base_theme)) { $ancestor = $themes[$ancestor]->base_theme; $base_theme[] = $themes[$ancestor]; } // The base themes should be initialized in the right order. $base_theme = array_reverse($base_theme); // This code is copied directly from _drupal_theme_initialize(). $theme_engine = NULL; // Initialize the theme. if (isset($theme->engine)) { // Include the engine. include_once DRUPAL_ROOT . '/' . $theme->owner; $theme_engine = $theme->engine; if (function_exists($theme_engine . '_init')) { foreach ($base_theme as $base) { call_user_func($theme_engine . '_init', $base); } call_user_func($theme_engine . '_init', $theme); } } else { // Include non-engine theme files. foreach ($base_theme as $base) { // Include the theme file or the engine. if (!empty($base->owner)) { include_once DRUPAL_ROOT . '/' . $base->owner; } } // And our theme gets one too. if (!empty($theme->owner)) { include_once DRUPAL_ROOT . '/' . $theme->owner; } } $this->theme_registry = _theme_load_registry($theme, $base_theme, $theme_engine); } // If there's a theme engine involved, we also need to know its // extension so we can give the proper filename. $this->theme_extension = '.tpl.php'; if (isset($theme_engine)) { $extension_function = $theme_engine . '_extension'; if (function_exists($extension_function)) { $this->theme_extension = $extension_function(); } } $funcs = array(); // Get theme functions for the display. Note that some displays may not // have themes. The 'feed' display, for example, completely delegates to // the style. if (!empty($this->definition['theme'])) { $funcs[] = $this->option_link(t('Display output'), 'analyze-theme-display') . ': ' . $this->format_themes($this->theme_functions()); $themes = $this->additional_theme_functions(); if ($themes) { foreach ($themes as $theme) { $funcs[] = $this->option_link(t('Alternative display output'), 'analyze-theme-display') . ': ' . $this->format_themes($theme); } } } $plugin = $this->get_plugin(); if ($plugin) { $funcs[] = $this->option_link(t('Style output'), 'analyze-theme-style') . ': ' . $this->format_themes($plugin->theme_functions(), $plugin->additional_theme_functions()); $themes = $plugin->additional_theme_functions(); if ($themes) { foreach ($themes as $theme) { $funcs[] = $this->option_link(t('Alternative style'), 'analyze-theme-style') . ': ' . $this->format_themes($theme); } } if ($plugin->uses_row_plugin()) { $row_plugin = $this->get_plugin('row'); if ($row_plugin) { $funcs[] = $this->option_link(t('Row style output'), 'analyze-theme-row') . ': ' . $this->format_themes($row_plugin->theme_functions()); $themes = $row_plugin->additional_theme_functions(); if ($themes) { foreach ($themes as $theme) { $funcs[] = $this->option_link(t('Alternative row style'), 'analyze-theme-row') . ': ' . $this->format_themes($theme); } } } } if ($plugin->uses_fields()) { foreach ($this->get_handlers('field') as $id => $handler) { $funcs[] = $this->option_link(t('Field @field (ID: @id)', array('@field' => $handler->ui_name(), '@id' => $id)), 'analyze-theme-field') . ': ' . $this->format_themes($handler->theme_functions()); } } } $form['important'] = array( '#markup' => '' . t('This section lists all possible templates for the display plugin and for the style plugins, ordered roughly from the least specific to the most specific. The active template for each plugin -- which is the most specific template found on the system -- is highlighted in bold.') . '
' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '
'; if (empty($this->definition['theme'])) { $output .= t('This display has no theming information'); } else { $output .= '' . t('This is the default theme template used for this display.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $this->definition['theme path'] . '/' . strtr($this->definition['theme'], '_', '-') . '.tpl.php')) . ''; } if (!empty($this->definition['additional themes'])) { foreach ($this->definition['additional themes'] as $theme => $type) { $output .= '
' . t('This is an alternative template for this display.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $this->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . ''; } } $form['analysis'] = array( '#markup' => '
' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '
'; $plugin = $this->get_plugin(); if (empty($plugin->definition['theme'])) { $output .= t('This display has no style theming information'); } else { $output .= '' . t('This is the default theme template used for this style.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . ''; } if (!empty($plugin->definition['additional themes'])) { foreach ($plugin->definition['additional themes'] as $theme => $type) { $output .= '
' . t('This is an alternative template for this style.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . ''; } } $form['analysis'] = array( '#markup' => '
' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '
'; $plugin = $this->get_plugin('row'); if (empty($plugin->definition['theme'])) { $output .= t('This display has no row style theming information'); } else { $output .= '' . t('This is the default theme template used for this row style.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($plugin->definition['theme'], '_', '-') . '.tpl.php')) . ''; } if (!empty($plugin->definition['additional themes'])) { foreach ($plugin->definition['additional themes'] as $theme => $type) { $output .= '
' . t('This is an alternative template for this row style.') . '
'; $output .= '' . check_plain(file_get_contents('./' . $plugin->definition['theme path'] . '/' . strtr($theme, '_', '-') . '.tpl.php')) . ''; } } $form['analysis'] = array( '#markup' => '
' . t('Back to !info.', array('!info' => $this->option_link(t('theming information'), 'analyze-theme'))) . '
'; $output .= '' . t('This is the default theme template used for this row style.') . '
'; // Field templates aren't registered the normal way... and they're // always this one, anyhow. $output .= '' . check_plain(file_get_contents(drupal_get_path('module', 'views') . '/theme/views-view-field.tpl.php')) . ''; $form['analysis'] = array( '#markup' => '