Skip to content
DisplayPluginBase.php 86.7 KiB
Newer Older
Earl Miles's avatar
Earl Miles committed
<?php

/**
 * @file
 * Contains \Drupal\views\Plugin\views\display\DisplayPluginBase.
namespace Drupal\views\Plugin\views\display;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\area\AreaPluginBase;
use Drupal\views\Plugin\views\PluginBase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException;
Earl Miles's avatar
Earl Miles committed
/**
 * @defgroup views_display_plugins Views display plugins
 * @{
 * Plugins to handle the overall display of views.
 * Display plugins are responsible for controlling where a view is rendered;
 * that is, how it is exposed to other parts of Drupal. 'Page' and 'block' are
 * the most commonly used display plugins. Each view also has a 'master' (or
 * 'default') display that includes information shared between all its
 * displays (see \Drupal\views\Plugin\views\display\DefaultDisplay).
 *
 * Display plugins extend \Drupal\views\Plugin\views\display\DisplayPluginBase.
 * They must be annotated with \Drupal\views\Annotation\ViewsDisplay
 * annotation, and they must be in namespace directory Plugin\views\display.
 *
 * @ingroup views_plugins
 *
 * @see plugin_api
 * @see views_display_extender_plugins
abstract class DisplayPluginBase extends PluginBase {
Earl Miles's avatar
Earl Miles committed
  /**
   * The top object of a view.
   *
Earl Miles's avatar
Earl Miles committed
   */
  var $view = NULL;

  /**
   * An array of instantiated handlers used in this display.
   *
   * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[]
   */
   public $handlers = [];
  /**
   * An array of instantiated plugins used in this display.
   *
   * @var \Drupal\views\Plugin\views\ViewsPluginInterface[]
Earl Miles's avatar
Earl Miles committed
  /**
   * Stores all available display extenders.
   */
  var $extender = array();

  /**
   * Overrides Drupal\views\Plugin\Plugin::$usesOptions.
   */
  /**
   * Stores the rendered output of the display.
   *
  /**
   * Whether the display allows the use of AJAX or not.
   *
   * @var bool
   */
  protected $usesAJAX = TRUE;

  /**
   * Whether the display allows the use of a pager or not.
   *
   * @var bool
   */
  protected $usesPager = TRUE;

  /**
   * Whether the display allows the use of a 'more' link or not.
   *
   * @var bool
   */
  protected $usesMore = TRUE;

  /**
   * Whether the display allows attachments.
   *
   * @var bool
   *   TRUE if the display can use attachments, or FALSE otherwise.
   */
  protected $usesAttachments = FALSE;

  /**
   * Whether the display allows area plugins.
   *
   * @var bool
   */
  protected $usesAreas = TRUE;

  /**
   * Static cache for unpackOptions, but not if we are in the UI.
   *
   * @var array
   */
  protected static $unpackOptions = array();

  /**
   * Constructs a new DisplayPluginBase object.
   *
   * Because DisplayPluginBase::initDisplay() takes the display configuration by
   * reference and handles it differently than usual plugin configuration, pass
   * an empty array of configuration to the parent. This prevents our
   * configuration from being duplicated.
   *
   * @todo Replace DisplayPluginBase::$display with
   *   DisplayPluginBase::$configuration to standardize with other plugins.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct(array(), $plugin_id, $plugin_definition);
  }

  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
    $this->view = $view;
    $this->setOptionDefaults($this->options, $this->defineOptions());
Earl Miles's avatar
Earl Miles committed
    $this->display = &$display;

    // Load extenders as soon as possible.
    $this->extender = array();
    if ($extenders = Views::getEnabledDisplayExtenders()) {
      $manager = Views::pluginManager('display_extender');
Earl Miles's avatar
Earl Miles committed
      foreach ($extenders as $extender) {
        if ($plugin = $manager->createInstance($extender)) {
Earl Miles's avatar
Earl Miles committed
          $plugin->init($this->view, $this);
          $this->extender[$extender] = $plugin;
        }
      }
    }

    // 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->isDefaultDisplay() && isset($options['defaults'])) {
Earl Miles's avatar
Earl Miles committed
      unset($options['defaults']);
    }

    $skip_cache = \Drupal::config('views.settings')->get('skip_cache');

    if (empty($view->editing) || !$skip_cache) {
      $cid = 'views:unpack_options:' . hash('sha256', serialize(array($this->options, $options))) . ':' . \Drupal::languageManager()->getCurrentLanguage()->getId();
      if (empty(static::$unpackOptions[$cid])) {
        $cache = \Drupal::cache('data')->get($cid);
Earl Miles's avatar
Earl Miles committed
        if (!empty($cache->data)) {
          $this->options = $cache->data;
        }
        else {
          $this->unpackOptions($this->options, $options);
          \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, array('extension', 'extension:views', 'view:' . $id));
        static::$unpackOptions[$cid] = $this->options;
Earl Miles's avatar
Earl Miles committed
      }
      else {
        $this->options = static::$unpackOptions[$cid];
      $this->unpackOptions($this->options, $options);
    // Convert the field_langcode and field_language_add_to_query settings.
    $field_langcode = $this->getOption('field_langcode');
    $field_language_add_to_query = $this->getOption('field_language_add_to_query');
      $this->setOption('field_langcode', $field_langcode);
      $this->setOption('field_langcode_add_to_query', $field_language_add_to_query);
Earl Miles's avatar
Earl Miles committed
    // Mark the view as changed so the user has a chance to save it.
    if ($changed) {
      $this->view->changed = TRUE;
    }
  }

Earl Miles's avatar
Earl Miles committed
    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();
    }
  }

  /**
   * Determine if this display is the 'default' display which contains
   * fallback settings
   */
  public function isDefaultDisplay() { return FALSE; }
Earl Miles's avatar
Earl Miles committed

  /**
   * Determine if this display uses exposed filters, so the view
   * will know whether or not to build them.
   */
Earl Miles's avatar
Earl Miles committed
    if (!isset($this->has_exposed)) {
      foreach ($this->handlers as $type => $value) {
        foreach ($this->view->$type as $handler) {
          if ($handler->canExpose() && $handler->isExposed()) {
Earl Miles's avatar
Earl Miles committed
            // one is all we need; if we find it, return true.
            $this->has_exposed = TRUE;
            return TRUE;
          }
        }
      }
Earl Miles's avatar
Earl Miles committed
        $this->has_exposed = TRUE;
        return TRUE;
      }
      $this->has_exposed = FALSE;
    }

    return $this->has_exposed;
  }

  /**
   * Determine if this display should display the exposed
   * filters widgets, 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 usesExposed() returns TRUE.
Earl Miles's avatar
Earl Miles committed
    return TRUE;
  }

  /**
   * Whether the display allows the use of AJAX or not.
   *
   * @return bool
   */
    return $this->usesAJAX;
  }

  /**
   * Whether the display is actually using AJAX or not.
   *
   * @return bool
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
   * Whether the display is enabled.
   *
   * @return bool
   *   Returns TRUE if the display is marked as enabled, else FALSE.
   */
  public function isEnabled() {
    return (bool) $this->getOption('enabled');
  }

   * Whether the display allows the use of a pager or not.
   *
   * @return bool
   */

    return $this->usesPager;
  }

  /**
   * Whether the display is using a pager or not.
   *
   * @return bool
   * Whether the display allows the use of a 'more' link or not.
   *
   * @return bool
   */
    return $this->usesMore;
  }

  /**
   * Whether the display is using the 'more' link or not.
   *
   * @return bool
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
   * Does the display have groupby enabled?
   */
  public function useGroupBy() {
    return $this->getOption('group_by');
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Should the enabled display more link be shown when no more items?
   */
      return $this->getOption('use_more_always');
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
   * Does the display have custom link text?
   */
      return $this->getOption('use_more_text');
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
   * Determines whether this display can use attachments.
   *
   * @return bool
    // To be able to accept attachments this display have to be able to use
    // attachments but at the same time, you cannot attach a display to itself.
    if (!$this->usesAttachments() || ($this->definition['id'] == $this->view->current_display)) {
Earl Miles's avatar
Earl Miles committed
      return FALSE;
    }
    if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
      foreach ($this->view->argument as $argument) {
        if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
Earl Miles's avatar
Earl Miles committed
          return FALSE;
        }
      }
    }
Earl Miles's avatar
Earl Miles committed
    return TRUE;
  }

  /**
   * Returns whether the display can use attachments.
   *
   * @return bool
   */
  /**
   * Returns whether the display can use areas.
   *
   * @return bool
   *   TRUE if the display can use areas, or FALSE otherwise.
   */
  public function usesAreas() {
    return $this->usesAreas;
  }

Earl Miles's avatar
Earl Miles committed
  /**
   * Allow displays to attach to other views.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   The views executable.
   * @param string $display_id
   *   The display to attach to.
   * @param array $build
   *   The parent view render array.
  public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
Earl Miles's avatar
Earl Miles committed

  /**
   * Static member function to list which sections are defaultable
   * and what items each section contains.
   */
  public function defaultableSections($section = NULL) {
Earl Miles's avatar
Earl Miles committed
    $sections = array(
      'access' => array('access'),
      'cache' => array('cache'),
Earl Miles's avatar
Earl Miles committed
      'title' => array('title'),
      'css_class' => array('css_class'),
      'use_ajax' => array('use_ajax'),
      'hide_attachment_summary' => array('hide_attachment_summary'),
      'show_admin_links' => array('show_admin_links'),
Earl Miles's avatar
Earl Miles committed
      'group_by' => array('group_by'),
      'query' => array('query'),
      'use_more' => array('use_more', 'use_more_always', 'use_more_text'),
      'use_more_always' => array('use_more', 'use_more_always', 'use_more_text'),
      'use_more_text' => array('use_more', 'use_more_always', 'use_more_text'),
Earl Miles's avatar
Earl Miles committed
      'link_display' => array('link_display', 'link_url'),

      // Force these to cascade properly.
      'style' => array('style', 'row'),
      'row' => array('style', 'row'),
      'exposed_form' => array('exposed_form'),
Earl Miles's avatar
Earl Miles committed

      // 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.
Earl Miles's avatar
Earl Miles committed
      unset($sections['pager']);
      unset($sections['items_per_page']);
    }

    foreach ($this->extender as $extender) {
      $extender->defaultableSections($sections, $section);
Earl Miles's avatar
Earl Miles committed
    }

    if ($section) {
      if (!empty($sections[$section])) {
        return $sections[$section];
      }
    }
    else {
      return $sections;
    }
  }

  protected function defineOptions() {
Earl Miles's avatar
Earl Miles committed
    $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,
Earl Miles's avatar
Earl Miles committed
          'pager' => TRUE,
          'use_more' => TRUE,
          'use_more_always' => TRUE,
          'use_more_text' => TRUE,
          'exposed_form' => TRUE,

          'link_display' => TRUE,
Earl Miles's avatar
Earl Miles committed
          'group_by' => TRUE,

Earl Miles's avatar
Earl Miles committed

          'header' => TRUE,
          'footer' => TRUE,
          'empty' => TRUE,

          'relationships' => TRUE,
          'fields' => TRUE,
          'sorts' => TRUE,
          'arguments' => TRUE,
          'filters' => TRUE,
          'filter_groups' => TRUE,
        ),
      ),

      'title' => array(
        'default' => '',
      ),
      'enabled' => array(
        'default' => TRUE,
      ),
      'display_comment' => array(
        'default' => '',
      ),
      'css_class' => array(
        'default' => '',
      ),
      'display_description' => array(
        'default' => '',
      ),
      'use_ajax' => array(
        'default' => FALSE,
      ),
      'hide_attachment_summary' => array(
        'default' => FALSE,
      ),
      'show_admin_links' => array(
        'default' => TRUE,
Earl Miles's avatar
Earl Miles committed
      'use_more' => array(
        'default' => FALSE,
      ),
      'use_more_always' => array(
Earl Miles's avatar
Earl Miles committed
      ),
      'use_more_text' => array(
        'default' => 'more',
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
      ),
        'default' => '***LANGUAGE_language_content***',
      'field_langcode_add_to_query' => array(
        'default' => TRUE,
Earl Miles's avatar
Earl Miles committed
      ),

      // These types are all plugins that can have individual settings
      // and therefore need special handling.
      'access' => array(
        'contains' => array(
          'options' => array('default' => array()),
        ),
Earl Miles's avatar
Earl Miles committed
      ),
      'cache' => array(
        'contains' => array(
          'options' => array('default' => array()),
        ),
Earl Miles's avatar
Earl Miles committed
      ),
      'query' => array(
        'contains' => array(
          'type' => array('default' => 'views_query'),
          'options' => array('default' => array()),
Earl Miles's avatar
Earl Miles committed
      ),
      'exposed_form' => array(
        'contains' => array(
          'type' => array('default' => 'basic'),
          'options' => array('default' => array()),
Earl Miles's avatar
Earl Miles committed
      ),
      'pager' => array(
        'contains' => array(
          'type' => array('default' => 'mini'),
          'options' => array('default' => array()),
      'style' => array(
        'contains' => array(
          'type' => array('default' => 'default'),
          'options' => array('default' => array()),
        ),
      'row' => array(
        'contains' => array(
          'type' => array('default' => 'fields'),
          'options' => array('default' => array()),
        ),
Earl Miles's avatar
Earl Miles committed
      ),

      'exposed_block' => array(
        'default' => FALSE,
      ),

      'header' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'footer' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'empty' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),

      // We want these to export last.
      // These are the 5 handler types.
      'relationships' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'fields' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'sorts' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'arguments' => array(
        'default' => array(),
        'merge_defaults' => array($this, 'mergeHandler'),
Earl Miles's avatar
Earl Miles committed
      ),
      'filter_groups' => array(
        'contains' => array(
          'operator' => array('default' => 'AND'),
          'groups' => array('default' => array(1 => 'AND')),
        ),
      ),
      'filters' => array(
        'default' => array(),
      ),
    );

Earl Miles's avatar
Earl Miles committed
      $options['defaults']['default']['pager'] = FALSE;
      $options['pager']['contains']['type']['default'] = 'some';
    }

Earl Miles's avatar
Earl Miles committed
      unset($options['defaults']);
    }

    foreach ($this->extender as $extender) {
Earl Miles's avatar
Earl Miles committed
    }

    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 hasPath() { return FALSE; }
Earl Miles's avatar
Earl Miles committed

  /**
   * 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 usesLinkDisplay() { return !$this->hasPath(); }
Earl Miles's avatar
Earl Miles committed

  /**
   * Check to see if the display can put the exposed formin 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 usesExposedFormInBlock() { return $this->hasPath(); }
  /**
   * Find out all displays which are attached to this display.
   *
   * The method is just using the pure storage object to avoid loading of the
   * sub displays which would kill lazy loading.
   */
  public function getAttachedDisplays() {
    $current_display_id = $this->display['id'];
    $attached_displays = array();

    // Go through all displays and search displays which link to this one.
    foreach ($this->view->storage->get('display') as $display_id => $display) {
      if (isset($display['display_options']['displays'])) {
        $displays = $display['display_options']['displays'];
        if (isset($displays[$current_display_id])) {
          $attached_displays[] = $display_id;
        }
      }
    }

    return $attached_displays;
  }

Earl Miles's avatar
Earl Miles committed
  /**
   * Check to see which display to use when creating links within
   * a view using this display.
   */
  public function getLinkDisplay() {
    $display_id = $this->getOption('link_display');
Earl Miles's avatar
Earl Miles committed
    // If unknown, pick the first one.
    if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
      foreach ($this->view->displayHandlers as $display_id => $display) {
        if (!empty($display) && $display->hasPath()) {
Earl Miles's avatar
Earl Miles committed
          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 getPath() {
    if ($this->hasPath()) {
      return $this->getOption('path');
    if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
      return $this->view->displayHandlers->get($display_id)->getPath();
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Determine if a given option is set to use the default display or the
   * current display
   *
   * @return
   *   TRUE for the default display
   */
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Intelligently get an option either from this display or from the
   * default display, if directed to do so.
   */
  public function getOption($option) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->getOption($option);
Earl Miles's avatar
Earl Miles committed
    }

    if (array_key_exists($option, $this->options)) {
      return $this->options[$option];
    }
  }

  /**
   * Determine if the display's style uses fields.
    return $this->getPlugin('style')->usesFields();
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Get the instance of a plugin, for example style or row.
   *
   * @param string $type
   *   The type of the plugin.
   *
   * @return \Drupal\views\Plugin\views\ViewsPluginInterface
    // Look up the plugin name to use for this instance.

    // Return now if no options have been loaded.
    if (empty($options) || !isset($options['type'])) {
      return;
    }
    // Query plugins allow specifying a specific query class per base table.
    if ($type == 'query') {
      $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
      $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
    // Plugin instances are stored on the display for re-use.
    if (!isset($this->plugins[$type][$name])) {
      $plugin = Views::pluginManager($type)->createInstance($name);
      // Initialize the plugin.
      $plugin->init($this->view, $this, $options['options']);
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Get the handler object for a single handler.
   */
  public function &getHandler($type, $id) {
Earl Miles's avatar
Earl Miles committed
    if (!isset($this->handlers[$type])) {
Earl Miles's avatar
Earl Miles committed
    }

    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.
   * @return \Drupal\views\Plugin\views\ViewsHandlerInterface[]
Earl Miles's avatar
Earl Miles committed
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
      $types = ViewExecutable::getHandlerTypes();
Earl Miles's avatar
Earl Miles committed
      $plural = $types[$type]['plural'];

      // Cast to an array so that if the display does not have any handlers of
      // this type there is no PHP error.
      foreach ((array) $this->getOption($plural) as $id => $info) {
Earl Miles's avatar
Earl Miles committed
        // 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 ($this->view->getRequest()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
          $info = $this->view->temporary_options[$type][$id];
Earl Miles's avatar
Earl Miles committed
        }

        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->useGroupBy() && !empty($info['group_type'])) {
Earl Miles's avatar
Earl Miles committed
          if (empty($this->view->query)) {
          $aggregate = $this->view->query->getAggregationInfo();
Earl Miles's avatar
Earl Miles committed
          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;
        }

        if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) {
Earl Miles's avatar
Earl Miles committed
          // Special override for area types so they know where they come from.
          if ($handler instanceof AreaPluginBase) {
            $handler->areaType = $type;
          $handler->init($this->view, $this, $info);
Earl Miles's avatar
Earl Miles committed
          $this->handlers[$type][$id] = &$handler;
        }

        // Prevent reference problems.
        unset($handler);
      }
    }

    return $this->handlers[$type];
  }

  /**
   * Retrieves a list of fields for the current display.
   * This also takes into account any associated relationships, if they exist.
   *
   * @param bool $groupable_only
   *   (optional) TRUE to only return an array of field labels from handlers
   *   that support the useStringGroupBy method, defaults to FALSE.
   *
   * @return array
   *   An array of applicable field options, keyed by ID.
  public function getFieldLabels($groupable_only = FALSE) {
Earl Miles's avatar
Earl Miles committed
    $options = array();
    foreach ($this->getHandlers('relationship') as $relationship => $handler) {
      $relationships[$relationship] = $handler->adminLabel();
    foreach ($this->getHandlers('field') as $id => $handler) {
      if ($groupable_only && !$handler->useStringGroupBy()) {
        // Continue to next handler if it's not groupable.
        continue;
      }
Earl Miles's avatar
Earl Miles committed
      if ($label = $handler->label()) {
        $options[$id] = $label;
      }
      else {
        $options[$id] = $handler->adminLabel();
Earl Miles's avatar
Earl Miles committed
      }
      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 setOption($option, $value) {
    if ($this->isDefaulted($option)) {