Skip to content
DisplayPluginBase.php 94.6 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\Core\Language\Language;
use Drupal\views\Plugin\views\area\AreaPluginBase;
use \Drupal\views\Plugin\views\PluginBase;
Earl Miles's avatar
Earl Miles committed
/**
 * @defgroup views_display_plugins Views display plugins
 * @{
 * Display plugins control how Views interact with the rest of Drupal.
 *
 * They can handle creating Views from a Drupal page hook; they can
 * handle creating Views from a Drupal block hook. They can also
 * handle creating Views from an external module source.
Earl Miles's avatar
Earl Miles committed
 */

/**
 * The default display plugin handler. Display plugins handle options and
 * basic mechanisms for different output methods.
 */
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;

  var $handlers = array();

  /**
   * An array of instantiated plugins used in this display.
   *
   * @var array
   */
  protected $plugins = array();

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;

  /**
   * 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.
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
    parent::__construct(array(), $plugin_id, $plugin_definition);
  }

  public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
    $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();
    $extenders = views_get_enabled_display_extenders();
    if (!empty($extenders)) {
      $manager = Views::pluginManager('display_extender');
Earl Miles's avatar
Earl Miles committed
      foreach ($extenders as $extender) {
        $plugin = $manager->createInstance($extender);
Earl Miles's avatar
Earl Miles committed
        if ($plugin) {
          $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']);
    }

    // Cache for unpackOptions, but not if we are in the ui.
Earl Miles's avatar
Earl Miles committed
    static $unpack_options = array();
    if (empty($view->editing)) {
      $cid = 'unpackOptions:' . hash('sha256', serialize(array($this->options, $options)));
Earl Miles's avatar
Earl Miles committed
      if (empty($unpack_options[$cid])) {
        $cache = views_cache_get($cid, TRUE);
        if (!empty($cache->data)) {
          $this->options = $cache->data;
        }
        else {
          $this->unpackOptions($this->options, $options);
Earl Miles's avatar
Earl Miles committed
          views_cache_set($cid, $this->options, TRUE);
        }
        $unpack_options[$cid] = $this->options;
      }
      else {
        $this->options = $unpack_options[$cid];
      }
    }
    else {
      $this->unpackOptions($this->options, $options);
    // Convert the field_language and field_language_add_to_query settings.
    $field_language = $this->getOption('field_language');
    $field_language_add_to_query = $this->getOption('field_language_add_to_query');
      $this->setOption('field_langcode', $field_language);
      $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 $id => $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')) {
Earl Miles's avatar
Earl Miles committed
      foreach ($this->view->argument as $argument_id => $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.
   */
  public function attachTo(ViewExecutable $view, $display_id) { }
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'),
Earl Miles's avatar
Earl Miles committed

      '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.
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,
          'link_url' => '',
          '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' => '',
        '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,
      ),
      'show_admin_links' => array(
        'default' => TRUE,
Earl Miles's avatar
Earl Miles committed
      'use_more' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
      'use_more_always' => array(
        'default' => FALSE,
Earl Miles's avatar
Earl Miles committed
      ),
      'use_more_text' => array(
        'default' => 'more',
        'translatable' => TRUE,
      ),
      'link_display' => array(
        'default' => '',
      ),
      'link_url' => array(
        'default' => '',
      ),
      'group_by' => array(
        'default' => FALSE,
        'bool' => TRUE,
      ),
Earl Miles's avatar
Earl Miles committed
        'default' => '***CURRENT_LANGUAGE***',
      ),
      'field_langcode_add_to_query' => array(
        'default' => TRUE,
        'bool' => 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']['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';
    }

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
  }

  /**
   * Check to see if the display needs a breadcrumb
   *
   * By default, displays do not need breadcrumbs
   */
  public function usesBreadcrumb() { return FALSE; }
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\PluginBase
    // 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.
   */
Earl Miles's avatar
Earl Miles committed
    if (!isset($this->handlers[$type])) {
      $this->handlers[$type] = array();
      $types = ViewExecutable::viewsHandlerTypes();
Earl Miles's avatar
Earl Miles committed
      $plural = $types[$type]['plural'];

      foreach ($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 (\Drupal::request()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
Earl Miles's avatar
Earl Miles committed
          $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->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)) {
      return $this->default_display->setOption($option, $value);
Earl Miles's avatar
Earl Miles committed
    }

    // 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;
Earl Miles's avatar
Earl Miles committed
    return $this->options[$option] = $value;
  }

  /**
   * Set an option and force it to be an override.
   */
  public function overrideOption($option, $value) {
    $this->setOverride($option, FALSE);
    $this->setOption($option, $value);
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Because forms may be split up into sections, this provides
   * an easy URL to exactly the right section. Don't override this.
   */
  public function optionLink($text, $section, $class = '', $title = '') {
Earl Miles's avatar
Earl Miles committed
    if (!empty($class)) {
      $text = '<span>' . $text . '</span>';