Skip to content
DisplayPluginBase.php 83.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\Plugin\DependentPluginInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\PluginDependencyTrait;
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
/**
abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInterface, DependentPluginInterface {
  use PluginDependencyTrait;
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 \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase[]
  /**
   * 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();

  /**
   * The display information coming directly from the view entity.
   *
   * @see \Drupal\views\Entity\View::getDisplay()
   *
   * @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it.
   *
   * @var array
   */
  public $display;

  /**
   * 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;
Earl Miles's avatar
Earl Miles committed

    // Load extenders as soon as possible.
    $display['display_options'] += ['display_extenders' => []];
    $this->extenders = array();
    if ($extenders = Views::getEnabledDisplayExtenders()) {
      $manager = Views::pluginManager('display_extender');
      $display_extender_options = $display['display_options']['display_extenders'];
Earl Miles's avatar
Earl Miles committed
      foreach ($extenders as $extender) {
        /** @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase $plugin */
        if ($plugin = $manager->createInstance($extender)) {
          $extender_options = isset($display_extender_options[$plugin->getPluginId()]) ? $display_extender_options[$plugin->getPluginId()] : [];
          $plugin->init($this->view, $this, $extender_options);
          $this->extenders[$extender] = $plugin;

    $this->setOptionDefaults($this->options, $this->defineOptions());
    $this->display = &$display;

Earl Miles's avatar
Earl Miles committed
    // Track changes that the user should know about.
    $changed = FALSE;

    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, $this->view->storage->getCacheTags());
        static::$unpackOptions[$cid] = $this->options;
Earl Miles's avatar
Earl Miles committed
      }
      else {
        $this->options = static::$unpackOptions[$cid];
      $this->unpackOptions($this->options, $options);
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->extenders as $extender) {
Earl Miles's avatar
Earl Miles committed
      $extender->destroy();
    }
  }

  /**
  public function isDefaultDisplay() { return FALSE; }
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()) {
            // One is all we need; if we find it, return TRUE.
Earl Miles's avatar
Earl Miles committed
            $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;
  }

  /**
Earl Miles's avatar
Earl Miles committed
    return TRUE;
  }

  /**
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

   */
  public function isEnabled() {
    return (bool) $this->getOption('enabled');
  }

Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
  public function useGroupBy() {
    return $this->getOption('group_by');
      return $this->getOption('use_more_always');
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
      return $this->getOption('use_more_text');
Earl Miles's avatar
Earl Miles committed
    }
    return FALSE;
  }

  /**
    // 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;
  }

  public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
  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
      '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->extenders 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_entity_translation***',
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']);
    }

    $options['display_extenders'] = ['default' => []];
    // First allow display extenders to provide new options.
    foreach ($this->extenders as $extender_id => $extender) {
      $options['display_extenders']['contains'][$extender_id]['contains'] = $extender->defineOptions();
    }

    // Then allow display extenders to alter existing default values.
    foreach ($this->extenders as $extender) {
  public function hasPath() { return FALSE; }
  public function usesLinkDisplay() { return !$this->hasPath(); }
  public function usesExposedFormInBlock() { return $this->hasPath(); }
   */
  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;
  }

  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;
    }
  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();
  /**
   * {@inheritdoc}
   */
  public function getRoutedDisplay() {
    // If this display has a route, return this display.
    if ($this instanceof DisplayRouterInterface) {
      return $this;
    }

    // If the display does not have a route (e.g. a block display), get the
    // route for the linked display.
    $display_id = $this->getLinkDisplay();
    if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
      return $this->view->displayHandlers->get($display_id)->getRoutedDisplay();
    }

    // No routed display exists, so return NULL
    return NULL;
  }

    return $this->view->getUrl(NULL, $this->display['id']);
  public function isDefaulted($option) {
    return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
  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];
    }
  }

  /**
    return $this->getPlugin('style')->usesFields();
    // 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']);
  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;
  }

  /**
  public function &getHandlers($type) {
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];
  }

  /**
   * Gets all the handlers used by the display.
   *
   * @param bool $only_overrides
   *   Whether to include only overridden handlers.
   *
   * @return \Drupal\views\Plugin\views\ViewsHandlerInterface[]
   */
  protected function getAllHandlers($only_overrides = FALSE) {
    $handler_types = Views::getHandlerTypes();
    $handlers = [];
    // Collect all dependencies of all handlers.
    foreach ($handler_types as $handler_type => $handler_type_info) {
      if ($only_overrides && $this->isDefaulted($handler_type_info['plural'])) {
        continue;
      }
      $handlers = array_merge($handlers, array_values($this->getHandlers($handler_type)));
    }
    return $handlers;
  }

  /**
   * Gets all the plugins used by the display.
   *
   * @param bool $only_overrides
   *   Whether to include only overridden plugins.
   *
   * @return \Drupal\views\Plugin\views\ViewsPluginInterface[]
   */
  protected function getAllPlugins($only_overrides = FALSE) {
    $plugins = [];
    // Collect all dependencies of plugins.
    foreach (Views::getPluginTypes('plugin') as $plugin_type) {
      $plugin = $this->getPlugin($plugin_type);
      if (!$plugin) {
        continue;
      }
      if ($only_overrides && $this->isDefaulted($plugin_type)) {
        continue;
      }
      $plugins[] = $plugin;
    }
    return $plugins;
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    $this->addDependencies(parent::calculateDependencies());
    // Collect all the dependencies of handlers and plugins. Only calculate
    // their dependencies if they are configured by this display.
    $plugins = array_merge($this->getAllHandlers(TRUE), $this->getAllPlugins(TRUE));
    array_walk($plugins, array($this, 'calculatePluginDependencies'));

    return $this->dependencies;
  }


  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;
  }

  /**
  public function setOption($option, $value) {
    if ($this->isDefaulted($option)) {
      return $this->default_display->setOption($option, $value);