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

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
Earl Miles's avatar
Earl Miles committed
/**
Earl Miles's avatar
Earl Miles committed
 * An object to contain all of the data to generate a view, plus the member
 * functions to build the view query, execute the query and render the output.
 *
 * This class does not implement the Serializable interface since problems
 * occurred when using the serialize method.
 *
 * @see https://www.drupal.org/node/2849674
 * @see https://bugs.php.net/bug.php?id=66052
Earl Miles's avatar
Earl Miles committed
  /**
   * The config entity in which the view is stored.
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $built = FALSE;

  /**
   * Whether the view has been executed/query has been run.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $executed = FALSE;

  /**
   * Any arguments that have been passed into the view.
   *
   * @var array
   */
  /**
   * Where the results of a query will go.
   *
   * The array must use a numeric index starting at 0.
   *
Earl Miles's avatar
Earl Miles committed

  // May be used to override the current pager info.

  /**
   * The current page. If the view uses pagination.
   *
   * @var int
   */
Earl Miles's avatar
Earl Miles committed
  // Exposed widget input

   * All the form data from $form_state->getValues().
  /**
   * An array of input values from exposed forms.
   *
   * @var array
   */
   * Exposed widget input directly from the $form_state->getValues().

  /**
   * Used to store views that were previously running if we recurse.
   *
   * @var \Drupal\views\ViewExecutable[]
   * @var \Drupal\views\ViewExecutable[]

  /**
   * Whether this view is an attachment to another view.
   *
   * @var bool
   */
  public $is_attachment = NULL;
Earl Miles's avatar
Earl Miles committed

  /**
   * Identifier of the current display.
   *
   * @var string
   */
   * @var \Drupal\views\Plugin\views\query\QueryPluginBase
  /**
   * The used pager plugin used by the current executed view.
   *
   * @var \Drupal\views\Plugin\views\pager\PagerPluginBase
Earl Miles's avatar
Earl Miles committed
   * The current used display plugin.
   *
   * @var \Drupal\views\Plugin\views\display\DisplayPluginBase
  /**
   * The list of used displays of the view.
   *
   * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
   * objects.
   *
   * @var \Drupal\views\DisplayPluginCollection
Earl Miles's avatar
Earl Miles committed
  /**
   * The current used style plugin.
   *
   * @var \Drupal\views\Plugin\views\style\StylePluginBase
  /**
   * The current used row plugin, if the style plugin supports row plugins.
   *
   * @var \Drupal\views\Plugin\views\row\RowPluginBase
   */
  public $rowPlugin;

Earl Miles's avatar
Earl Miles committed
  /**
   * Stores the current active row while rendering.
   *
   * @var int
   */
Earl Miles's avatar
Earl Miles committed
   * Allow to override the url of the current view.
   *
Earl Miles's avatar
Earl Miles committed

  /**
   * Allow to override the path used for generated urls.
   *
   * @var string
   */
Earl Miles's avatar
Earl Miles committed

  /**
   * Allow to override the used database which is used for this query.
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the field handlers which are initialized on this view.
   * @var \Drupal\views\Plugin\views\field\FieldPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the argument handlers which are initialized on this view.
   * @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the sort handlers which are initialized on this view.
   * @var \Drupal\views\Plugin\views\sort\SortPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the filter handlers which are initialized on this view.
   * @var \Drupal\views\Plugin\views\filter\FilterPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the relationship handlers which are initialized on this view.
   * @var \Drupal\views\Plugin\views\relationship\RelationshipPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the header which are initialized on this view.
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the footer which are initialized on this view.
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the empty text which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
   * @var \Symfony\Component\HttpFoundation\Response
  /**
   * Stores the current request object.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $request;

   * Does this view already have loaded its handlers.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $inited;

  /**
   * The rendered output of the exposed form.
   *
   * @var string
   */
  public $exposed_widgets;

  /**
   * If this view has been previewed.
   *
   * @var bool
   */
  public $preview;

  /**
   * Force the query to calculate the total number of results.
   *
   * @todo Move to the query.
   *
   * @var bool
   */
  public $get_total_rows;

  /**
   * Indicates if the sorts have been built.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $build_sort;

  /**
   * Stores the many-to-one tables for performance.
   *
   * @var array
   */
  public $many_to_one_tables;

  /**
   * A unique identifier which allows to update multiple views output via js.
   *
   * @var string
   */
  public $dom_id;

  /**
   * A render array container to store render related information.
   *
   * For example you can alter the array and attach some asset library or JS
   * settings via the #attached key. This is the required way to add custom
   * CSS or JS.
   * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
  public $element = [
    '#attached' => [
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $user;

  /**
   * Should the admin links be shown on the rendered view.
   *
   * @var bool
   */
  protected $showAdminLinks;

  /**
   * The views data.
   *
   * @var \Drupal\views\ViewsData
   */
  protected $viewsData;

  /**
   * The route provider.
   *
   * @var \Drupal\Core\Routing\RouteProviderInterface
   */
  protected $routeProvider;

  /**
   * The entity type of the base table, if available.
   *
   * @var \Drupal\Core\Entity\EntityTypeInterface|false
   */
  protected $baseEntityType;

  /**
   * Holds all necessary data for proper unserialization.
   *
   * @var array
   */
  protected $serializationData;

   * @param \Drupal\views\ViewEntityInterface $storage
   *   The view config entity the actual information is stored on.
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
   * @param \Drupal\views\ViewsData $views_data
   *   The views data.
   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
   *   The route provider.
  public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data, RouteProviderInterface $route_provider) {
    // Reference the storage and the executable to each other.
    $this->storage = $storage;
    $this->storage->set('executable', $this);
    $this->routeProvider = $route_provider;
   * Returns the identifier.
   *
   * @return string|null
   *   The entity identifier, or NULL if the object does not yet have an
   *   identifier.
   */
  public function id() {
    return $this->storage->id();
  }

  /**
   * Saves the view.
  public function save() {
    $this->storage->save();
   * Sets the arguments for the view.
   *
   * @param array $args
   *   The arguments passed to the view.
  public function setArguments(array $args) {
    // The array keys of the arguments will be incorrect if set by
    // views_embed_view() or \Drupal\views\ViewExecutable:preview().
    $this->args = array_values($args);
  /**
   * Expands the list of used cache contexts for the view.
   *
   * @param string $cache_context
   *   The additional cache context.
   *
   * @return $this
   */
  public function addCacheContext($cache_context) {
    $this->element['#cache']['contexts'][] = $cache_context;

    return $this;
  }

   * Sets the current page for the pager.
  public function setCurrentPage($page) {
Earl Miles's avatar
Earl Miles committed
    $this->current_page = $page;
    // Calls like ::unserialize() might call this method without a proper $page.
    // Also check whether the element is pre rendered. At that point, the cache
    // keys cannot longer be manipulated.
    if ($page !== NULL && empty($this->element['#pre_rendered'])) {
      $this->element['#cache']['keys'][] = 'page:' . $page;
    // If the pager is already initialized, pass it through to the pager.
      return $this->pager->setCurrentPage($page);
   * Gets the current page from the pager.
   *
   * @return int
   *   The current page.
    // If the pager is already initialized, pass it through to the pager.
      return $this->pager->getCurrentPage();

    if (isset($this->current_page)) {
      return $this->current_page;
    }
   * Gets the items per page from the pager.
   *
   * @return int
   *   The items per page.
    // If the pager is already initialized, pass it through to the pager.
      return $this->pager->getItemsPerPage();

    if (isset($this->items_per_page)) {
      return $this->items_per_page;
    }
   * Sets the items per page on the pager.
   *
   * @param int $items_per_page
   *   The items per page.
  public function setItemsPerPage($items_per_page) {
    // Check whether the element is pre rendered. At that point, the cache keys
    // cannot longer be manipulated.
    if (empty($this->element['#pre_rendered'])) {
      $this->element['#cache']['keys'][] = 'items_per_page:' . $items_per_page;
    }
Earl Miles's avatar
Earl Miles committed
    $this->items_per_page = $items_per_page;

    // If the pager is already initialized, pass it through to the pager.
      $this->pager->setItemsPerPage($items_per_page);
   * Gets the pager offset from the pager.
   *
   * @return int
   *   The pager offset.
    // If the pager is already initialized, pass it through to the pager.
  public function setOffset($offset) {
    // Check whether the element is pre rendered. At that point, the cache keys
    // cannot longer be manipulated.
    if (empty($this->element['#pre_rendered'])) {
      $this->element['#cache']['keys'][] = 'offset:' . $offset;
    }

Earl Miles's avatar
Earl Miles committed
    $this->offset = $offset;

    // If the pager is already initialized, pass it through to the pager.
   * Determines if the view uses a pager.
   *
   * @return bool
   *   TRUE if the view uses a pager, FALSE otherwise.
   * Sets whether or not AJAX should be used.
   *
   * If AJAX is used, paging, table sorting, and exposed filters will be fetched
   * via an AJAX call rather than a page refresh.
   *
   *   TRUE if AJAX should be used, FALSE otherwise.
   */
  public function setAjaxEnabled($ajax_enabled) {
    $this->ajaxEnabled = (bool) $ajax_enabled;
  }

  /**
   * Determines whether or not AJAX should be used.
   *   TRUE if AJAX is enabled, FALSE otherwise.
  public function ajaxEnabled() {
    return $this->ajaxEnabled;
   * Sets the exposed filters input to an array.
   *
   * @param string[] $filters
   *   The values taken from the view's exposed filters and sorts.
Earl Miles's avatar
Earl Miles committed
    $this->exposed_input = $filters;
  }

  /**
   * Figures out what the exposed input for this view is.
   *
   * They will be taken from \Drupal::request()->query or from
   * something previously set on the view.
   *
   * @return string[]
   *   An array containing the exposed input values keyed by the filter and sort
   *   name.
   *
   * @see self::setExposedInput()
    // Fill our input either from \Drupal::request()->query or from something
    // previously set on the view.
Earl Miles's avatar
Earl Miles committed
    if (empty($this->exposed_input)) {
      // Ensure that we can call the method at any point in time.
      $this->initDisplay();

      $this->exposed_input = \Drupal::request()->query->all();
Earl Miles's avatar
Earl Miles committed
      // unset items that are definitely not our input:
Earl Miles's avatar
Earl Miles committed
        if (isset($this->exposed_input[$key])) {
          unset($this->exposed_input[$key]);
        }
      }

      // If we have no input at all, check for remembered input via session.

      // If filters are not overridden, store the 'remember' settings on the
      // default display. If they are, store them on this display. This way,
      // multiple displays in the same view can share the same filters and
      // remember settings.
      $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
      if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
        $this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
Earl Miles's avatar
Earl Miles committed
      }
    }

    return $this->exposed_input;
  }

  /**
   * Sets the display for this view and initializes the display handler.
   *
   * @return true
   *   Always returns TRUE.
Earl Miles's avatar
Earl Miles committed
    if (isset($this->current_display)) {
      return TRUE;
    }

    $this->displayHandlers = new DisplayPluginCollection($this, Views::pluginManager('display'));
Earl Miles's avatar
Earl Miles committed

    $this->current_display = 'default';
    $this->display_handler = $this->displayHandlers->get('default');
   * Gets the first display that is accessible to the user.
Earl Miles's avatar
Earl Miles committed
   *   Either a single display id or an array of display ids.
   *
   * @return string
   *   The first accessible display id, at least default.
Earl Miles's avatar
Earl Miles committed
    if (!is_array($displays)) {
      return $displays;
    }

Earl Miles's avatar
Earl Miles committed

    foreach ($displays as $display_id) {
      if ($this->displayHandlers->get($display_id)->access($this->user)) {
Earl Miles's avatar
Earl Miles committed
        return $display_id;
      }
    }

    return 'default';
  }

  /**
   * Gets the current display plugin.
   *
   * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
   */
  public function getDisplay() {
    if (!isset($this->display_handler)) {
      $this->initDisplay();
    }

    return $this->display_handler;
  }

   * @param string $display_id
   *   The ID of the display to mark as current.
   *
   * @return bool
   *   TRUE if the display was correctly set, FALSE otherwise.
  public function setDisplay($display_id = NULL) {
    // If we have not already initialized the display, do so.
    if (!isset($this->current_display)) {
      // This will set the default display and instantiate the default display
      // plugin.
    // If no display ID is passed, we either have initialized the default or
    // already have a display set.
    if (!isset($display_id)) {
      return TRUE;
    $display_id = $this->chooseDisplay($display_id);
Earl Miles's avatar
Earl Miles committed

    // Ensure the requested display exists.
    if (!$this->displayHandlers->has($display_id)) {
      trigger_error(new FormattableMarkup('setDisplay() called with invalid display ID "@display".', ['@display' => $display_id]), E_USER_WARNING);
    // Reset if the display has changed. It could be called multiple times for
    // the same display, especially in the UI.
    if ($this->current_display != $display_id) {
      // Set the current display.
      $this->current_display = $display_id;

      // Reset the style and row plugins.
      $this->style_plugin = NULL;
      $this->plugin_name = NULL;
      $this->rowPlugin = NULL;
    }

    if ($display = $this->displayHandlers->get($display_id)) {
      // Set a shortcut.
      $this->display_handler = $display;
      return TRUE;
    }
  /**
   * Creates a new display and a display handler instance for it.
   *
   * @param string $plugin_id
   *   (optional) The plugin type from the Views plugin annotation. Defaults to
   *   'page'.
   * @param string $title
   *   (optional) The title of the display. Defaults to NULL.
   * @param string $id
   *   (optional) The ID to use, e.g., 'default', 'page_1', 'block_2'. Defaults
   *   to NULL.
   *
   * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
   *   A new display plugin instance if executable is set, the new display ID
   *   otherwise.
   */
  public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
    $this->initDisplay();

    $id = $this->storage->addDisplay($plugin_id, $title, $id);
    $display = $this->displayHandlers->get($id);
    $display->newDisplay();
    return $display;
  /**
   * Gets the current style plugin.
   *
   * @return \Drupal\views\Plugin\views\style\StylePluginBase
   */
  public function getStyle() {
    if (!isset($this->style_plugin)) {
      $this->initStyle();
    }

    return $this->style_plugin;
  }

   * Finds and initializes the style plugin.
Earl Miles's avatar
Earl Miles committed
   *
   * Note that arguments may have changed which style plugin we use, so
   * check the view object first, then ask the display handler.
   *
   * @return bool
   *   TRUE if the style plugin was or could be initialized, FALSE otherwise.
Earl Miles's avatar
Earl Miles committed
    if (isset($this->style_plugin)) {
    $this->style_plugin = $this->display_handler->getPlugin('style');
Earl Miles's avatar
Earl Miles committed

    if (empty($this->style_plugin)) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Acquires and attaches all of the handlers.
Earl Miles's avatar
Earl Miles committed
    if (empty($this->inited)) {
      foreach ($this::getHandlerTypes() as $key => $info) {
Earl Miles's avatar
Earl Miles committed
      }
      $this->inited = TRUE;
    }
  }

   *
   * @return \Drupal\views\Plugin\views\pager\PagerPluginBase
   */
  public function getPager() {
    if (!isset($this->pager)) {
      $this->initPager();
    }

    return $this->pager;
  }

   * Like style initialization, pager initialization is held until late to allow
   * for overrides.
      $this->pager = $this->display_handler->getPlugin('pager');
        $this->pager->setCurrentPage($this->current_page);
Earl Miles's avatar
Earl Miles committed
      }

      // These overrides may have been set earlier via $view->set_*
      // functions.
      if (isset($this->items_per_page)) {
        $this->pager->setItemsPerPage($this->items_per_page);
Earl Miles's avatar
Earl Miles committed
      }

      if (isset($this->offset)) {
        $this->pager->setOffset($this->offset);
   * Renders the pager, if necessary.
   *
   * @param string[] $exposed_input
   *   The input values from the exposed forms and sorts of the view.
   *
   * @return array|string
   *   The render array of the pager if it's set, blank string otherwise.
  public function renderPager($exposed_input) {
    if (!empty($this->pager) && $this->pager->usePager()) {
   * Creates a list of base tables to be used by the view.
   *
   * This is used primarily for the UI. The display must be already initialized.
   *
   * @return array
   *   An array of base tables to be used by the view.
      $this->storage->get('base_table') => TRUE,
Earl Miles's avatar
Earl Miles committed
      '#global' => TRUE,
    foreach ($this->display_handler->getHandlers('relationship') as $handler) {
Earl Miles's avatar
Earl Miles committed
      $base_tables[$handler->definition['base']] = TRUE;
    }
    return $base_tables;
  }

  /**
   * Returns the entity type of the base table, if available.
   *
   * @return \Drupal\Core\Entity\EntityType|false
   *   The entity type of the base table, or FALSE if none exists.
   */
  public function getBaseEntityType() {
    if (!isset($this->baseEntityType)) {
      $view_base_table = $this->storage->get('base_table');
      $views_data = $this->viewsData->get($view_base_table);
      if (!empty($views_data['table']['entity type'])) {
        $entity_type_id = $views_data['table']['entity type'];
        $this->baseEntityType = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
      }
      else {
        $this->baseEntityType = FALSE;
      }
    }
    return $this->baseEntityType;
  }