Newer
Older
namespace Drupal\views;
catch
committed
use Drupal\Component\Render\FormattableMarkup;
Alex Pott
committed
use Drupal\Component\Utility\Html;
catch
committed
use Drupal\Component\Utility\Tags;
use Drupal\Core\Routing\RouteProviderInterface;
Angie Byron
committed
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
use Symfony\Component\HttpFoundation\Request;
catch
committed
use Symfony\Component\HttpFoundation\Response;
Jess
committed
use Symfony\Component\Routing\Exception\RouteNotFoundException;
Daniel Wehner
committed
Angie Byron
committed
* Represents a view as a whole.
*
* 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
class ViewExecutable {
Tim Plunkett
committed
Daniel Wehner
committed
* The config entity in which the view is stored.
Alex Pott
committed
* @var \Drupal\views\Entity\View
Tim Plunkett
committed
public $storage;
Damian Lee
committed
/**
* Whether or not the view has been built.
*
Tim Plunkett
committed
* @todo Group with other static properties.
*
Damian Lee
committed
* @var bool
*/
public $built = FALSE;
/**
* Whether the view has been executed/query has been run.
*
Tim Plunkett
committed
* @todo Group with other static properties.
*
Damian Lee
committed
* @var bool
*/
public $executed = FALSE;
/**
* Any arguments that have been passed into the view.
*
* @var array
*/
public $args = [];
Damian Lee
committed
/**
* An array of build info.
*
* @var array
*/
public $build_info = [];
Damian Lee
committed
/**
* Whether this view uses AJAX.
*
* @var bool
*/
protected $ajaxEnabled = FALSE;
Peter Philipp
committed
/**
* Where the results of a query will go.
*
* The array must use a numeric index starting at 0.
*
* @var \Drupal\views\ResultRow[]
Peter Philipp
committed
*/
public $result = [];
// May be used to override the current pager info.
Damian Lee
committed
/**
* The current page. If the view uses pagination.
*
* @var int
*/
protected $current_page = NULL;
Damian Lee
committed
/**
* The number of items per page.
*
* @var int
*/
protected $items_per_page = NULL;
Damian Lee
committed
/**
* The pager offset.
*
* @var int
*/
protected $offset = NULL;
Damian Lee
committed
/**
* The total number of rows returned from the query.
*
* @var int
Damian Lee
committed
*/
public $total_rows = NULL;
/**
Dries Buytaert
committed
* Attachments to place before the view.
Damian Lee
committed
*
* @var array
Damian Lee
committed
*/
public $attachment_before = [];
Damian Lee
committed
/**
Dries Buytaert
committed
* Attachments to place after the view.
Damian Lee
committed
*
Dries Buytaert
committed
* @var array
Damian Lee
committed
*/
public $attachment_after = [];
Alex Pott
committed
/**
* Feed icons attached to the view.
*
* @var array
*/
public $feedIcons = [];
Alex Pott
committed
Damian Lee
committed
/**
Alex Pott
committed
* All the form data from $form_state->getValues().
Damian Lee
committed
*
* @var array
*/
public $exposed_data = [];
Damian Lee
committed
/**
* An array of input values from exposed forms.
*
* @var array
*/
protected $exposed_input = [];
Damian Lee
committed
/**
Alex Pott
committed
* Exposed widget input directly from the $form_state->getValues().
Damian Lee
committed
*
* @var array
*/
public $exposed_raw_input = [];
Damian Lee
committed
/**
* Used to store views that were previously running if we recurse.
*
* @var \Drupal\views\ViewExecutable[]
Damian Lee
committed
*/
public $old_view = [];
Damian Lee
committed
/**
* To avoid recursion in views embedded into areas.
*
* @var \Drupal\views\ViewExecutable[]
Damian Lee
committed
*/
public $parent_views = [];
Damian Lee
committed
/**
* Whether this view is an attachment to another view.
*
* @var bool
*/
public $is_attachment = NULL;
/**
* Identifier of the current display.
*
* @var string
*/
Damian Lee
committed
public $current_display;
Damian Lee
committed
* Where the $query object will reside.
* @var \Drupal\views\Plugin\views\query\QueryPluginBase
Damian Lee
committed
public $query = NULL;
Daniel Wehner
committed
/**
* The used pager plugin used by the current executed view.
*
Alex Pott
committed
* @var \Drupal\views\Plugin\views\pager\PagerPluginBase
Daniel Wehner
committed
*/
public $pager = NULL;
/**
Alex Pott
committed
* @var \Drupal\views\Plugin\views\display\DisplayPluginBase
Damian Lee
committed
public $display_handler;
Daniel Wehner
committed
/**
* The list of used displays of the view.
*
* An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
* objects.
*
Alex Pott
committed
* @var \Drupal\views\DisplayPluginCollection
Daniel Wehner
committed
*/
public $displayHandlers;
/**
* The current used style plugin.
*
Alex Pott
committed
* @var \Drupal\views\Plugin\views\style\StylePluginBase
Damian Lee
committed
public $style_plugin;
Angie Byron
committed
/**
* The current used row plugin, if the style plugin supports row plugins.
*
* @var \Drupal\views\Plugin\views\row\RowPluginBase
*/
public $rowPlugin;
/**
* Stores the current active row while rendering.
*
* @var int
*/
Damian Lee
committed
public $row_index;
Dries Buytaert
committed
/**
* Allow to override the url of the current view.
*
* @var \Drupal\Core\Url
public $override_url;
/**
* Allow to override the path used for generated urls.
*
* @var string
*/
Damian Lee
committed
public $override_path = NULL;
/**
* Allow to override the used database which is used for this query.
Damian Lee
committed
*
* @var bool
Damian Lee
committed
public $base_database = NULL;
Damian Lee
committed
// Handlers which are active on this view.
/**
* Stores the field handlers which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\field\FieldPluginBase[]
Damian Lee
committed
public $field;
/**
* Stores the argument handlers which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase[]
Damian Lee
committed
public $argument;
/**
* Stores the sort handlers which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\sort\SortPluginBase[]
Damian Lee
committed
public $sort;
/**
* Stores the filter handlers which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\filter\FilterPluginBase[]
Damian Lee
committed
public $filter;
/**
* Stores the relationship handlers which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\relationship\RelationshipPluginBase[]
Damian Lee
committed
public $relationship;
/**
* Stores the area handlers for the header which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
Damian Lee
committed
public $header;
/**
* Stores the area handlers for the footer which are initialized on this view.
Damian Lee
committed
*
* @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
Damian Lee
committed
public $footer;
/**
* Stores the area handlers for the empty text which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
*
* @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
Damian Lee
committed
public $empty;
Daniel Wehner
committed
/**
* Stores the current response object.
*
* @var \Symfony\Component\HttpFoundation\Response
Daniel Wehner
committed
*/
protected $response = NULL;
/**
* Stores the current request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
Tim Plunkett
committed
/**
* Does this view already have loaded its handlers.
Tim Plunkett
committed
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
*
* @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;
catch
committed
/**
* 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.
catch
committed
*
* @var array
*
Alex Pott
committed
* @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
catch
committed
*/
public $element = [
'#attached' => [
Alex Pott
committed
'library' => ['views/views.module'],
'drupalSettings' => [],
],
'#cache' => [],
catch
committed
Angie Byron
committed
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
Angie Byron
committed
/**
* 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;
Alex Pott
committed
/**
* 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;
Daniel Wehner
committed
/**
* Constructs a new ViewExecutable object.
*
* @param \Drupal\views\ViewEntityInterface $storage
Daniel Wehner
committed
* The view config entity the actual information is stored on.
Angie Byron
committed
* @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.
Daniel Wehner
committed
*/
public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data, RouteProviderInterface $route_provider) {
Daniel Wehner
committed
// Reference the storage and the executable to each other.
$this->storage = $storage;
$this->storage->set('executable', $this);
Angie Byron
committed
$this->user = $user;
$this->viewsData = $views_data;
$this->routeProvider = $route_provider;
Daniel Wehner
committed
}
/**
* 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.
Daniel Wehner
committed
*/
Tim Plunkett
committed
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.
*
* @param int $page
* The current page.
public function setCurrentPage($page) {
Daniel Wehner
committed
// 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;
}
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->setCurrentPage($page);
Daniel Wehner
committed
}
* Gets the current page from the pager.
*
* @return int
* The current page.
Damian Lee
committed
public function getCurrentPage() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
Angie Byron
committed
return $this->pager->getCurrentPage();
Daniel Wehner
committed
if (isset($this->current_page)) {
return $this->current_page;
}
* Gets the items per page from the pager.
*
* @return int
* The items per page.
Damian Lee
committed
public function getItemsPerPage() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->getItemsPerPage();
Daniel Wehner
committed
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;
}
$this->items_per_page = $items_per_page;
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
$this->pager->setItemsPerPage($items_per_page);
* Gets the pager offset from the pager.
*
* @return int
* The pager offset.
Damian Lee
committed
public function getOffset() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
Angie Byron
committed
return $this->pager->getOffset();
Daniel Wehner
committed
if (isset($this->offset)) {
return $this->offset;
}
* Sets the offset on the pager.
*
* @param int $offset
* The pager offset.
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;
}
$this->offset = $offset;
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
Alex Pott
committed
$this->pager->setOffset($offset);
* Determines if the view uses a pager.
*
* @return bool
* TRUE if the view uses a pager, FALSE otherwise.
Damian Lee
committed
public function usePager() {
Daniel Wehner
committed
if (!empty($this->pager)) {
Alex Pott
committed
return $this->pager->usePager();
* 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.
*
* @param bool $ajax_enabled
* 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.
*
* @return bool
* 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.
Damian Lee
committed
public function setExposedInput($filters) {
$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()
Damian Lee
committed
public function getExposedInput() {
Angie Byron
committed
// Fill our input either from \Drupal::request()->query or from something
// previously set on the view.
// Ensure that we can call the method at any point in time.
$this->initDisplay();
$this->exposed_input = \Drupal::request()->query->all();
// unset items that are definitely not our input:
foreach (['page', 'q'] as $key) {
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.
Daniel Wehner
committed
$display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
Angie Byron
committed
if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
$this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
}
}
return $this->exposed_input;
}
/**
* Sets the display for this view and initializes the display handler.
*
* @return true
* Always returns TRUE.
Damian Lee
committed
public function initDisplay() {
if (isset($this->current_display)) {
return TRUE;
}
catch
committed
// Initialize the display cache array.
Alex Pott
committed
$this->displayHandlers = new DisplayPluginCollection($this, Views::pluginManager('display'));
$this->display_handler = $this->displayHandlers->get('default');
* Gets the first display that is accessible to the user.
Daniel Wehner
committed
* @param array|string $displays
* Either a single display id or an array of display ids.
Daniel Wehner
committed
*
* @return string
* The first accessible display id, at least default.
Damian Lee
committed
public function chooseDisplay($displays) {
if (!is_array($displays)) {
return $displays;
}
Damian Lee
committed
$this->initDisplay();
Angie Byron
committed
if ($this->displayHandlers->get($display_id)->access($this->user)) {
return $display_id;
}
}
return 'default';
}
catch
committed
/**
* Gets the current display plugin.
*
* @return \Drupal\views\Plugin\views\display\DisplayPluginBase
* The current display plugin.
catch
committed
*/
public function getDisplay() {
if (!isset($this->display_handler)) {
$this->initDisplay();
}
return $this->display_handler;
}
Dries Buytaert
committed
* Sets the current display.
Dries Buytaert
committed
* @param string $display_id
* The ID of the display to mark as current.
*
* @return bool
* TRUE if the display was correctly set, FALSE otherwise.
Damian Lee
committed
public function setDisplay($display_id = NULL) {
Dries Buytaert
committed
// 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.
Damian Lee
committed
$this->initDisplay();
Dries Buytaert
committed
}
Dries Buytaert
committed
// If no display ID is passed, we either have initialized the default or
// already have a display set.
if (!isset($display_id)) {
return TRUE;
Damian Lee
committed
$display_id = $this->chooseDisplay($display_id);
if (!$this->displayHandlers->has($display_id)) {
catch
committed
trigger_error(new FormattableMarkup('setDisplay() called with invalid display ID "@display".', ['@display' => $display_id]), E_USER_WARNING);
Dries Buytaert
committed
return FALSE;
Angie Byron
committed
// 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;
}
Dries Buytaert
committed
if ($display = $this->displayHandlers->get($display_id)) {
// Set a shortcut.
$this->display_handler = $display;
return TRUE;
}
Dries Buytaert
committed
return FALSE;
/**
* 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);
catch
committed
$this->displayHandlers->addInstanceId($id);
Angie Byron
committed
$display = $this->displayHandlers->get($id);
$display->newDisplay();
return $display;
}
catch
committed
/**
* Gets the current style plugin.
*
* @return \Drupal\views\Plugin\views\style\StylePluginBase
* The current style plugin.
catch
committed
*/
public function getStyle() {
if (!isset($this->style_plugin)) {
$this->initStyle();
}
return $this->style_plugin;
}
* Finds and initializes the style plugin.
*
* 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.
Damian Lee
committed
public function initStyle() {
Alex Pott
committed
return TRUE;
Alex Pott
committed
$this->style_plugin = $this->display_handler->getPlugin('style');
if (empty($this->style_plugin)) {
return FALSE;
}
return TRUE;
}
/**
* Acquires and attaches all of the handlers.
Damian Lee
committed
public function initHandlers() {
Tim Plunkett
committed
$this->initDisplay();
catch
committed
foreach ($this::getHandlerTypes() as $key => $info) {
Damian Lee
committed
$this->_initHandler($key, $info);
catch
committed
/**
* Gets the current pager plugin.
catch
committed
*
* @return \Drupal\views\Plugin\views\pager\PagerPluginBase
* The current pager plugin.
catch
committed
*/
public function getPager() {
if (!isset($this->pager)) {
$this->initPager();
}
return $this->pager;
}
* Initializes the pager.
* Like style initialization, pager initialization is held until late to allow
* for overrides.
Damian Lee
committed
public function initPager() {
Daniel Wehner
committed
if (!isset($this->pager)) {
Daniel Wehner
committed
$this->pager = $this->display_handler->getPlugin('pager');
Alex Pott
committed
if ($this->pager->usePager()) {
$this->pager->setCurrentPage($this->current_page);
}
// These overrides may have been set earlier via $view->set_*
// functions.
if (isset($this->items_per_page)) {
$this->pager->setItemsPerPage($this->items_per_page);
Alex Pott
committed
$this->pager->setOffset($this->offset);
Daniel Wehner
committed
/**
* 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.
Daniel Wehner
committed
*/
Damian Lee
committed
public function renderPager($exposed_input) {
Alex Pott
committed
if (!empty($this->pager) && $this->pager->usePager()) {
Daniel Wehner
committed
return $this->pager->render($exposed_input);
}
return '';
}
* 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.
Damian Lee
committed
public function getBaseTables() {
$base_tables = [
Angie Byron
committed
$this->storage->get('base_table') => TRUE,
];
Daniel Wehner
committed
foreach ($this->display_handler->getHandlers('relationship') as $handler) {
$base_tables[$handler->definition['base']] = TRUE;
}
return $base_tables;
}
Alex Pott
committed
/**
* 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;
}