3 namespace Drupal\views;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Utility\Tags;
8 use Drupal\Core\Routing\RouteProviderInterface;
9 use Drupal\Core\Session\AccountInterface;
10 use Drupal\views\Plugin\views\display\DisplayRouterInterface;
11 use Symfony\Component\HttpFoundation\Request;
12 use Symfony\Component\HttpFoundation\Response;
13 use Symfony\Component\Routing\Exception\RouteNotFoundException;
16 * Represents a view as a whole.
18 * An object to contain all of the data to generate a view, plus the member
19 * functions to build the view query, execute the query and render the output.
21 * This class does not implement the Serializable interface since problems
22 * occurred when using the serialize method.
24 * @see https://www.drupal.org/node/2849674
25 * @see https://bugs.php.net/bug.php?id=66052
27 class ViewExecutable {
30 * The config entity in which the view is stored.
32 * @var \Drupal\views\Entity\View
37 * Whether or not the view has been built.
39 * @todo Group with other static properties.
43 public $built = FALSE;
46 * Whether the view has been executed/query has been run.
48 * @todo Group with other static properties.
52 public $executed = FALSE;
55 * Any arguments that have been passed into the view.
62 * An array of build info.
66 public $build_info = [];
69 * Whether this view uses AJAX.
73 protected $ajaxEnabled = FALSE;
76 * Where the results of a query will go.
78 * The array must use a numeric index starting at 0.
80 * @var \Drupal\views\ResultRow[]
84 // May be used to override the current pager info.
87 * The current page. If the view uses pagination.
91 protected $current_page = NULL;
94 * The number of items per page.
98 protected $items_per_page = NULL;
105 protected $offset = NULL;
108 * The total number of rows returned from the query.
112 public $total_rows = NULL;
115 * Attachments to place before the view.
119 public $attachment_before = [];
122 * Attachments to place after the view.
126 public $attachment_after = [];
129 * Feed icons attached to the view.
133 public $feedIcons = [];
135 // Exposed widget input
138 * All the form data from $form_state->getValues().
142 public $exposed_data = [];
145 * An array of input values from exposed forms.
149 protected $exposed_input = [];
152 * Exposed widget input directly from the $form_state->getValues().
156 public $exposed_raw_input = [];
159 * Used to store views that were previously running if we recurse.
161 * @var \Drupal\views\ViewExecutable[]
163 public $old_view = [];
166 * To avoid recursion in views embedded into areas.
168 * @var \Drupal\views\ViewExecutable[]
170 public $parent_views = [];
173 * Whether this view is an attachment to another view.
177 public $is_attachment = NULL;
180 * Identifier of the current display.
184 public $current_display;
187 * Where the $query object will reside.
189 * @var \Drupal\views\Plugin\views\query\QueryPluginBase
191 public $query = NULL;
194 * The used pager plugin used by the current executed view.
196 * @var \Drupal\views\Plugin\views\pager\PagerPluginBase
198 public $pager = NULL;
201 * The current used display plugin.
203 * @var \Drupal\views\Plugin\views\display\DisplayPluginBase
205 public $display_handler;
208 * The list of used displays of the view.
210 * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
213 * @var \Drupal\views\DisplayPluginCollection
215 public $displayHandlers;
218 * The current used style plugin.
220 * @var \Drupal\views\Plugin\views\style\StylePluginBase
222 public $style_plugin;
225 * The current used row plugin, if the style plugin supports row plugins.
227 * @var \Drupal\views\Plugin\views\row\RowPluginBase
232 * Stores the current active row while rendering.
239 * Allow to override the url of the current view.
241 * @var \Drupal\Core\Url
243 public $override_url;
246 * Allow to override the path used for generated urls.
250 public $override_path = NULL;
253 * Allow to override the used database which is used for this query.
257 public $base_database = NULL;
259 // Handlers which are active on this view.
262 * Stores the field handlers which are initialized on this view.
264 * @var \Drupal\views\Plugin\views\field\FieldPluginBase[]
269 * Stores the argument handlers which are initialized on this view.
271 * @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase[]
276 * Stores the sort handlers which are initialized on this view.
278 * @var \Drupal\views\Plugin\views\sort\SortPluginBase[]
283 * Stores the filter handlers which are initialized on this view.
285 * @var \Drupal\views\Plugin\views\filter\FilterPluginBase[]
290 * Stores the relationship handlers which are initialized on this view.
292 * @var \Drupal\views\Plugin\views\relationship\RelationshipPluginBase[]
294 public $relationship;
297 * Stores the area handlers for the header which are initialized on this view.
299 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
304 * Stores the area handlers for the footer which are initialized on this view.
306 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
311 * Stores the area handlers for the empty text which are initialized on this view.
313 * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
315 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[]
320 * Stores the current response object.
322 * @var \Symfony\Component\HttpFoundation\Response
324 protected $response = NULL;
327 * Stores the current request object.
329 * @var \Symfony\Component\HttpFoundation\Request
334 * Does this view already have loaded its handlers.
336 * @todo Group with other static properties.
343 * The rendered output of the exposed form.
347 public $exposed_widgets;
350 * If this view has been previewed.
357 * Force the query to calculate the total number of results.
359 * @todo Move to the query.
363 public $get_total_rows;
366 * Indicates if the sorts have been built.
368 * @todo Group with other static properties.
375 * Stores the many-to-one tables for performance.
379 public $many_to_one_tables;
382 * A unique identifier which allows to update multiple views output via js.
389 * A render array container to store render related information.
391 * For example you can alter the array and attach some asset library or JS
392 * settings via the #attached key. This is the required way to add custom
397 * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()
401 'library' => ['views/views.module'],
402 'drupalSettings' => [],
410 * @var \Drupal\Core\Session\AccountInterface
415 * Should the admin links be shown on the rendered view.
419 protected $showAdminLinks;
424 * @var \Drupal\views\ViewsData
426 protected $viewsData;
429 * The route provider.
431 * @var \Drupal\Core\Routing\RouteProviderInterface
433 protected $routeProvider;
436 * The entity type of the base table, if available.
438 * @var \Drupal\Core\Entity\EntityTypeInterface|false
440 protected $baseEntityType;
443 * Holds all necessary data for proper unserialization.
447 protected $serializationData;
450 * Constructs a new ViewExecutable object.
452 * @param \Drupal\views\ViewEntityInterface $storage
453 * The view config entity the actual information is stored on.
454 * @param \Drupal\Core\Session\AccountInterface $user
456 * @param \Drupal\views\ViewsData $views_data
458 * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
459 * The route provider.
461 public function __construct(ViewEntityInterface $storage, AccountInterface $user, ViewsData $views_data, RouteProviderInterface $route_provider) {
462 // Reference the storage and the executable to each other.
463 $this->storage = $storage;
464 $this->storage->set('executable', $this);
466 $this->viewsData = $views_data;
467 $this->routeProvider = $route_provider;
471 * Returns the identifier.
473 * @return string|null
474 * The entity identifier, or NULL if the object does not yet have an
477 public function id() {
478 return $this->storage->id();
484 public function save() {
485 $this->storage->save();
489 * Sets the arguments for the view.
492 * The arguments passed to the view.
494 public function setArguments(array $args) {
495 // The array keys of the arguments will be incorrect if set by
496 // views_embed_view() or \Drupal\views\ViewExecutable:preview().
497 $this->args = array_values($args);
501 * Expands the list of used cache contexts for the view.
503 * @param string $cache_context
504 * The additional cache context.
508 public function addCacheContext($cache_context) {
509 $this->element['#cache']['contexts'][] = $cache_context;
515 * Sets the current page for the pager.
520 public function setCurrentPage($page) {
521 $this->current_page = $page;
523 // Calls like ::unserialize() might call this method without a proper $page.
524 // Also check whether the element is pre rendered. At that point, the cache
525 // keys cannot longer be manipulated.
526 if ($page !== NULL && empty($this->element['#pre_rendered'])) {
527 $this->element['#cache']['keys'][] = 'page:' . $page;
530 // If the pager is already initialized, pass it through to the pager.
531 if (!empty($this->pager)) {
532 return $this->pager->setCurrentPage($page);
537 * Gets the current page from the pager.
542 public function getCurrentPage() {
543 // If the pager is already initialized, pass it through to the pager.
544 if (!empty($this->pager)) {
545 return $this->pager->getCurrentPage();
548 if (isset($this->current_page)) {
549 return $this->current_page;
554 * Gets the items per page from the pager.
557 * The items per page.
559 public function getItemsPerPage() {
560 // If the pager is already initialized, pass it through to the pager.
561 if (!empty($this->pager)) {
562 return $this->pager->getItemsPerPage();
565 if (isset($this->items_per_page)) {
566 return $this->items_per_page;
571 * Sets the items per page on the pager.
573 * @param int $items_per_page
574 * The items per page.
576 public function setItemsPerPage($items_per_page) {
577 // Check whether the element is pre rendered. At that point, the cache keys
578 // cannot longer be manipulated.
579 if (empty($this->element['#pre_rendered'])) {
580 $this->element['#cache']['keys'][] = 'items_per_page:' . $items_per_page;
582 $this->items_per_page = $items_per_page;
584 // If the pager is already initialized, pass it through to the pager.
585 if (!empty($this->pager)) {
586 $this->pager->setItemsPerPage($items_per_page);
591 * Gets the pager offset from the pager.
596 public function getOffset() {
597 // If the pager is already initialized, pass it through to the pager.
598 if (!empty($this->pager)) {
599 return $this->pager->getOffset();
602 if (isset($this->offset)) {
603 return $this->offset;
608 * Sets the offset on the pager.
613 public function setOffset($offset) {
614 // Check whether the element is pre rendered. At that point, the cache keys
615 // cannot longer be manipulated.
616 if (empty($this->element['#pre_rendered'])) {
617 $this->element['#cache']['keys'][] = 'offset:' . $offset;
620 $this->offset = $offset;
622 // If the pager is already initialized, pass it through to the pager.
623 if (!empty($this->pager)) {
624 $this->pager->setOffset($offset);
629 * Determines if the view uses a pager.
632 * TRUE if the view uses a pager, FALSE otherwise.
634 public function usePager() {
635 if (!empty($this->pager)) {
636 return $this->pager->usePager();
641 * Sets whether or not AJAX should be used.
643 * If AJAX is used, paging, table sorting, and exposed filters will be fetched
644 * via an AJAX call rather than a page refresh.
646 * @param bool $ajax_enabled
647 * TRUE if AJAX should be used, FALSE otherwise.
649 public function setAjaxEnabled($ajax_enabled) {
650 $this->ajaxEnabled = (bool) $ajax_enabled;
654 * Determines whether or not AJAX should be used.
657 * TRUE if AJAX is enabled, FALSE otherwise.
659 public function ajaxEnabled() {
660 return $this->ajaxEnabled;
664 * Sets the exposed filters input to an array.
666 * @param string[] $filters
667 * The values taken from the view's exposed filters and sorts.
669 public function setExposedInput($filters) {
670 $this->exposed_input = $filters;
674 * Figures out what the exposed input for this view is.
676 * They will be taken from \Drupal::request()->query or from
677 * something previously set on the view.
680 * An array containing the exposed input values keyed by the filter and sort
683 * @see self::setExposedInput()
685 public function getExposedInput() {
686 // Fill our input either from \Drupal::request()->query or from something
687 // previously set on the view.
688 if (empty($this->exposed_input)) {
689 // Ensure that we can call the method at any point in time.
690 $this->initDisplay();
692 $this->exposed_input = \Drupal::request()->query->all();
693 // unset items that are definitely not our input:
694 foreach (['page', 'q'] as $key) {
695 if (isset($this->exposed_input[$key])) {
696 unset($this->exposed_input[$key]);
700 // If we have no input at all, check for remembered input via session.
702 // If filters are not overridden, store the 'remember' settings on the
703 // default display. If they are, store them on this display. This way,
704 // multiple displays in the same view can share the same filters and
705 // remember settings.
706 $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
708 if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
709 $this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
713 return $this->exposed_input;
717 * Sets the display for this view and initializes the display handler.
720 * Always returns TRUE.
722 public function initDisplay() {
723 if (isset($this->current_display)) {
727 // Initialize the display cache array.
728 $this->displayHandlers = new DisplayPluginCollection($this, Views::pluginManager('display'));
730 $this->current_display = 'default';
731 $this->display_handler = $this->displayHandlers->get('default');
737 * Gets the first display that is accessible to the user.
739 * @param array|string $displays
740 * Either a single display id or an array of display ids.
743 * The first accessible display id, at least default.
745 public function chooseDisplay($displays) {
746 if (!is_array($displays)) {
750 $this->initDisplay();
752 foreach ($displays as $display_id) {
753 if ($this->displayHandlers->get($display_id)->access($this->user)) {
762 * Gets the current display plugin.
764 * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
765 * The current display plugin.
767 public function getDisplay() {
768 if (!isset($this->display_handler)) {
769 $this->initDisplay();
772 return $this->display_handler;
776 * Sets the current display.
778 * @param string $display_id
779 * The ID of the display to mark as current.
782 * TRUE if the display was correctly set, FALSE otherwise.
784 public function setDisplay($display_id = NULL) {
785 // If we have not already initialized the display, do so.
786 if (!isset($this->current_display)) {
787 // This will set the default display and instantiate the default display
789 $this->initDisplay();
792 // If no display ID is passed, we either have initialized the default or
793 // already have a display set.
794 if (!isset($display_id)) {
798 $display_id = $this->chooseDisplay($display_id);
800 // Ensure the requested display exists.
801 if (!$this->displayHandlers->has($display_id)) {
802 trigger_error(new FormattableMarkup('setDisplay() called with invalid display ID "@display".', ['@display' => $display_id]), E_USER_WARNING);
806 // Reset if the display has changed. It could be called multiple times for
807 // the same display, especially in the UI.
808 if ($this->current_display != $display_id) {
809 // Set the current display.
810 $this->current_display = $display_id;
812 // Reset the style and row plugins.
813 $this->style_plugin = NULL;
814 $this->plugin_name = NULL;
815 $this->rowPlugin = NULL;
818 if ($display = $this->displayHandlers->get($display_id)) {
820 $this->display_handler = $display;
828 * Creates a new display and a display handler instance for it.
830 * @param string $plugin_id
831 * (optional) The plugin type from the Views plugin annotation. Defaults to
833 * @param string $title
834 * (optional) The title of the display. Defaults to NULL.
836 * (optional) The ID to use, e.g., 'default', 'page_1', 'block_2'. Defaults
839 * @return \Drupal\views\Plugin\views\display\DisplayPluginBase
840 * A new display plugin instance if executable is set, the new display ID
843 public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
844 $this->initDisplay();
846 $id = $this->storage->addDisplay($plugin_id, $title, $id);
847 $this->displayHandlers->addInstanceId($id);
849 $display = $this->displayHandlers->get($id);
850 $display->newDisplay();
855 * Gets the current style plugin.
857 * @return \Drupal\views\Plugin\views\style\StylePluginBase
858 * The current style plugin.
860 public function getStyle() {
861 if (!isset($this->style_plugin)) {
865 return $this->style_plugin;
869 * Finds and initializes the style plugin.
871 * Note that arguments may have changed which style plugin we use, so
872 * check the view object first, then ask the display handler.
875 * TRUE if the style plugin was or could be initialized, FALSE otherwise.
877 public function initStyle() {
878 if (isset($this->style_plugin)) {
882 $this->style_plugin = $this->display_handler->getPlugin('style');
884 if (empty($this->style_plugin)) {
892 * Acquires and attaches all of the handlers.
894 public function initHandlers() {
895 $this->initDisplay();
896 if (empty($this->inited)) {
897 foreach ($this::getHandlerTypes() as $key => $info) {
898 $this->_initHandler($key, $info);
900 $this->inited = TRUE;
905 * Gets the current pager plugin.
907 * @return \Drupal\views\Plugin\views\pager\PagerPluginBase
908 * The current pager plugin.
910 public function getPager() {
911 if (!isset($this->pager)) {
919 * Initializes the pager.
921 * Like style initialization, pager initialization is held until late to allow
924 public function initPager() {
925 if (!isset($this->pager)) {
926 $this->pager = $this->display_handler->getPlugin('pager');
928 if ($this->pager->usePager()) {
929 $this->pager->setCurrentPage($this->current_page);
932 // These overrides may have been set earlier via $view->set_*
934 if (isset($this->items_per_page)) {
935 $this->pager->setItemsPerPage($this->items_per_page);
938 if (isset($this->offset)) {
939 $this->pager->setOffset($this->offset);
945 * Renders the pager, if necessary.
947 * @param string[] $exposed_input
948 * The input values from the exposed forms and sorts of the view.
950 * @return array|string
951 * The render array of the pager if it's set, blank string otherwise.
953 public function renderPager($exposed_input) {
954 if (!empty($this->pager) && $this->pager->usePager()) {
955 return $this->pager->render($exposed_input);
962 * Creates a list of base tables to be used by the view.
964 * This is used primarily for the UI. The display must be already initialized.
967 * An array of base tables to be used by the view.
969 public function getBaseTables() {
971 $this->storage->get('base_table') => TRUE,
975 foreach ($this->display_handler->getHandlers('relationship') as $handler) {
976 $base_tables[$handler->definition['base']] = TRUE;
982 * Returns the entity type of the base table, if available.
984 * @return \Drupal\Core\Entity\EntityType|false
985 * The entity type of the base table, or FALSE if none exists.
987 public function getBaseEntityType() {
988 if (!isset($this->baseEntityType)) {
989 $view_base_table = $this->storage->get('base_table');
990 $views_data = $this->viewsData->get($view_base_table);
991 if (!empty($views_data['table']['entity type'])) {
992 $entity_type_id = $views_data['table']['entity type'];
993 $this->baseEntityType = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
996 $this->baseEntityType = FALSE;
999 return $this->baseEntityType;
1003 * Runs the preQuery() on all active handlers.
1005 protected function _preQuery() {
1006 foreach ($this::getHandlerTypes() as $key => $info) {
1007 $handlers = &$this->$key;
1009 foreach ($handlers as $id => $handler) {
1010 $handlers[$id]->position = $position;
1011 $handlers[$id]->preQuery();
1018 * Runs the postExecute() on all active handlers.
1020 protected function _postExecute() {
1021 foreach ($this::getHandlerTypes() as $key => $info) {
1022 $handlers = &$this->$key;
1023 foreach ($handlers as $id => $handler) {
1024 $handlers[$id]->postExecute($this->result);
1030 * Attaches the views handler for the specific type.
1032 * @param string $key
1033 * One of 'argument', 'field', 'sort', 'filter', 'relationship'.
1034 * @param array $info
1035 * An array of views handler types use in the view with additional
1036 * information about them.
1038 protected function _initHandler($key, $info) {
1039 // Load the requested items from the display onto the object.
1040 $this->$key = &$this->display_handler->getHandlers($key);
1042 // This reference deals with difficult PHP indirection.
1043 $handlers = &$this->$key;
1045 // Run through and test for accessibility.
1046 foreach ($handlers as $id => $handler) {
1047 if (!$handler->access($this->user)) {
1048 unset($handlers[$id]);
1054 * Builds all the arguments.
1057 * TRUE if the arguments were built successfully, FALSE otherwise.
1059 protected function _buildArguments() {
1060 // Initially, we want to build sorts and fields. This can change, though,
1061 // if we get a summary view.
1062 if (empty($this->argument)) {
1068 $substitutions = [];
1072 $title = $this->display_handler->getOption('title');
1074 // Iterate through each argument and process.
1075 foreach ($this->argument as $id => $arg) {
1077 $argument = $this->argument[$id];
1079 if ($argument->broken()) {
1083 $argument->setRelationship();
1085 $arg = isset($this->args[$position]) ? $this->args[$position] : NULL;
1086 $argument->position = $position;
1088 if (isset($arg) || $argument->hasDefaultArgument()) {
1090 $arg = $argument->getDefaultArgument();
1091 // make sure default args get put back.
1093 $this->args[$position] = $arg;
1095 // remember that this argument was computed, not passed on the URL.
1096 $argument->is_default = TRUE;
1099 // Set the argument, which ensures that the argument is valid and
1100 // possibly transforms the value.
1101 if (!$argument->setArgument($arg)) {
1102 $status = $argument->validateFail($arg);
1106 if ($argument->isException()) {
1107 $arg_title = $argument->exceptionTitle();
1110 $arg_title = $argument->getTitle();
1111 $argument->query($this->display_handler->useGroupBy());
1114 // Add this argument's substitution.
1115 $substitutions["{{ arguments.$id }}"] = $arg_title;
1116 // Since argument validator plugins can potentially transform the value,
1117 // use whatever value the argument handler now has, not the raw value.
1118 $substitutions["{{ raw_arguments.$id }}"] = strip_tags(Html::decodeEntities($argument->getValue()));
1120 // Test to see if we should use this argument's title
1121 if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) {
1122 $title = $argument->options['title'];
1126 // determine default condition and handle.
1127 $status = $argument->defaultAction();
1131 // Be safe with references and loops:
1135 // set the title in the build info.
1136 if (!empty($title)) {
1137 $this->build_info['title'] = $title;
1140 // Store the arguments for later use.
1141 $this->build_info['substitutions'] = $substitutions;
1147 * Gets the current query plugin.
1149 * @return \Drupal\views\Plugin\views\query\QueryPluginBase
1150 * The current query plugin.
1152 public function getQuery() {
1153 if (!isset($this->query)) {
1157 return $this->query;
1161 * Initializes the query object for the view.
1164 * Always returns TRUE.
1166 public function initQuery() {
1167 if (!empty($this->query)) {
1168 $class = get_class($this->query);
1169 if ($class && $class != 'stdClass') {
1170 // return if query is already initialized.
1175 // Create and initialize the query object.
1176 $views_data = Views::viewsData()->get($this->storage->get('base_table'));
1177 $this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
1178 if (!empty($views_data['table']['base']['database'])) {
1179 $this->base_database = $views_data['table']['base']['database'];
1182 $this->query = $this->display_handler->getPlugin('query');
1187 * Builds the query for the view.
1189 * @param string $display_id
1190 * The display ID of the view.
1193 * TRUE if the view build process was successful, FALSE if setting the
1194 * display fails or NULL if the view has been built already.
1196 public function build($display_id = NULL) {
1197 if (!empty($this->built)) {
1201 if (empty($this->current_display) || $display_id) {
1202 if (!$this->setDisplay($display_id)) {
1207 // Let modules modify the view just prior to building it.
1208 $module_handler = \Drupal::moduleHandler();
1209 $module_handler->invokeAll('views_pre_build', [$this]);
1211 // Attempt to load from cache.
1212 // @todo Load a build_info from cache.
1214 $start = microtime(TRUE);
1215 // If that fails, let's build!
1216 $this->build_info = [
1218 'count_query' => '',
1224 // Call a module hook and see if it wants to present us with a
1225 // pre-built query or instruct us not to build the query for
1227 // @todo: Implement this. Use the same mechanism Panels uses.
1229 // Run through our handlers and ensure they have necessary information.
1230 $this->initHandlers();
1232 // Let the handlers interact with each other if they really want.
1235 if ($this->display_handler->usesExposed()) {
1236 /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
1237 $exposed_form = $this->display_handler->getPlugin('exposed_form');
1238 $this->exposed_widgets = $exposed_form->renderExposedForm();
1239 if (!empty($this->build_info['abort'])) {
1240 $this->built = TRUE;
1241 // Don't execute the query, $form_state, but rendering will still be executed to display the empty text.
1242 $this->executed = TRUE;
1243 return empty($this->build_info['fail']);
1247 // Build all the relationships first thing.
1248 $this->_build('relationship');
1250 // Set the filtering groups.
1251 if (!empty($this->filter)) {
1252 $filter_groups = $this->display_handler->getOption('filter_groups');
1253 if ($filter_groups) {
1254 $this->query->setGroupOperator($filter_groups['operator']);
1255 foreach ($filter_groups['groups'] as $id => $operator) {
1256 $this->query->setWhereGroup($operator, $id);
1261 // Build all the filters.
1262 $this->_build('filter');
1264 $this->build_sort = TRUE;
1266 // Arguments can, in fact, cause this whole thing to abort.
1267 if (!$this->_buildArguments()) {
1268 $this->build_time = microtime(TRUE) - $start;
1269 $this->attachDisplays();
1270 return $this->built;
1273 // Initialize the style; arguments may have changed which style we use,
1274 // so waiting as long as possible is important. But we need to know
1275 // about the style when we go to build fields.
1276 if (!$this->initStyle()) {
1277 $this->build_info['fail'] = TRUE;
1281 if ($this->style_plugin->usesFields()) {
1282 $this->_build('field');
1285 // Build our sort criteria if we were instructed to do so.
1286 if (!empty($this->build_sort)) {
1287 // Allow the style handler to deal with sorting.
1288 if ($this->style_plugin->buildSort()) {
1289 $this->_build('sort');
1291 // allow the plugin to build second sorts as well.
1292 $this->style_plugin->buildSortPost();
1295 // Allow area handlers to affect the query.
1296 $this->_build('header');
1297 $this->_build('footer');
1298 $this->_build('empty');
1300 // Allow display handler to affect the query:
1301 $this->display_handler->query($this->display_handler->useGroupBy());
1303 // Allow style handler to affect the query:
1304 $this->style_plugin->query($this->display_handler->useGroupBy());
1306 // Allow exposed form to affect the query:
1307 if (isset($exposed_form)) {
1308 $exposed_form->query();
1311 if (\Drupal::config('views.settings')->get('sql_signature')) {
1312 $this->query->addSignature($this);
1315 // Let modules modify the query just prior to finalizing it.
1316 $this->query->alter($this);
1318 // Only build the query if we weren't interrupted.
1319 if (empty($this->built)) {
1320 // Build the necessary info to execute the query.
1321 $this->query->build($this);
1324 $this->built = TRUE;
1325 $this->build_time = microtime(TRUE) - $start;
1328 $this->attachDisplays();
1330 // Let modules modify the view just after building it.
1331 $module_handler->invokeAll('views_post_build', [$this]);
1337 * Builds an individual set of handlers.
1339 * This is an internal method.
1341 * @todo Some filter needs this function, even it is internal.
1343 * @param string $key
1344 * The type of handlers (filter etc.) which should be iterated over to build
1345 * the relationship and query information.
1347 public function _build($key) {
1348 $handlers = &$this->$key;
1349 foreach ($handlers as $id => $data) {
1351 if (!empty($handlers[$id]) && is_object($handlers[$id])) {
1352 $multiple_exposed_input = [0 => NULL];
1353 if ($handlers[$id]->multipleExposedInput()) {
1354 $multiple_exposed_input = $handlers[$id]->groupMultipleExposedInput($this->exposed_data);
1356 foreach ($multiple_exposed_input as $group_id) {
1357 // Give this handler access to the exposed filter input.
1358 if (!empty($this->exposed_data)) {
1359 if ($handlers[$id]->isAGroup()) {
1360 $converted = $handlers[$id]->convertExposedInput($this->exposed_data, $group_id);
1361 $handlers[$id]->storeGroupInput($this->exposed_data, $converted);
1366 $rc = $handlers[$id]->acceptExposedInput($this->exposed_data);
1367 $handlers[$id]->storeExposedInput($this->exposed_data, $rc);
1372 $handlers[$id]->setRelationship();
1373 $handlers[$id]->query($this->display_handler->useGroupBy());
1380 * Executes the view's query.
1382 * @param string $display_id
1383 * The machine name of the display, which should be executed.
1386 * TRUE if the view execution was successful, FALSE otherwise. For example,
1387 * an argument could stop the process.
1389 public function execute($display_id = NULL) {
1390 if (empty($this->built)) {
1391 if (!$this->build($display_id)) {
1396 if (!empty($this->executed)) {
1400 // Don't allow to use deactivated displays, but display them on the live preview.
1401 if (!$this->display_handler->isEnabled() && empty($this->live_preview)) {
1402 $this->build_info['fail'] = TRUE;
1406 // Let modules modify the view just prior to executing it.
1407 $module_handler = \Drupal::moduleHandler();
1408 $module_handler->invokeAll('views_pre_execute', [$this]);
1410 // Check for already-cached results.
1411 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
1412 if (!empty($this->live_preview)) {
1413 $cache = Views::pluginManager('cache')->createInstance('none');
1416 $cache = $this->display_handler->getPlugin('cache');
1419 if ($cache->cacheGet('results')) {
1420 if ($this->pager->usePager()) {
1421 $this->pager->total_items = $this->total_rows;
1422 $this->pager->updatePageInfo();
1426 $this->query->execute($this);
1427 // Enforce the array key rule as documented in
1428 // views_plugin_query::execute().
1429 $this->result = array_values($this->result);
1430 $this->_postExecute();
1431 $cache->cacheSet('results');
1434 // Let modules modify the view just after executing it.
1435 $module_handler->invokeAll('views_post_execute', [$this]);
1437 return $this->executed = TRUE;
1441 * Renders this view for a certain display.
1443 * Note: You should better use just the preview function if you want to
1446 * @param string $display_id
1447 * The machine name of the display, which should be rendered.
1449 * @return array|null
1450 * A renderable array containing the view output or NULL if the build
1453 public function render($display_id = NULL) {
1454 $this->execute($display_id);
1456 // Check to see if the build failed.
1457 if (!empty($this->build_info['fail'])) {
1460 if (!empty($this->build_info['denied'])) {
1464 /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
1465 $exposed_form = $this->display_handler->getPlugin('exposed_form');
1466 $exposed_form->preRender($this->result);
1468 $module_handler = \Drupal::moduleHandler();
1470 // @TODO In the longrun, it would be great to execute a view without
1471 // the theme system at all. See https://www.drupal.org/node/2322623.
1472 $active_theme = \Drupal::theme()->getActiveTheme();
1473 $themes = array_keys($active_theme->getBaseThemes());
1474 $themes[] = $active_theme->getName();
1476 // Check for already-cached output.
1477 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
1478 if (!empty($this->live_preview)) {
1479 $cache = Views::pluginManager('cache')->createInstance('none');
1482 $cache = $this->display_handler->getPlugin('cache');
1485 // Run preRender for the pager as it might change the result.
1486 if (!empty($this->pager)) {
1487 $this->pager->preRender($this->result);
1490 // Initialize the style plugin.
1493 if (!isset($this->response)) {
1494 // Set the response so other parts can alter it.
1495 $this->response = new Response('', 200);
1498 // Give field handlers the opportunity to perform additional queries
1499 // using the entire resultset prior to rendering.
1500 if ($this->style_plugin->usesFields()) {
1501 foreach ($this->field as $id => $handler) {
1502 if (!empty($this->field[$id])) {
1503 $this->field[$id]->preRender($this->result);
1508 $this->style_plugin->preRender($this->result);
1510 // Let each area handler have access to the result set.
1511 $areas = ['header', 'footer'];
1512 // Only call preRender() on the empty handlers if the result is empty.
1513 if (empty($this->result)) {
1516 foreach ($areas as $area) {
1517 foreach ($this->{$area} as $handler) {
1518 $handler->preRender($this->result);
1522 // Let modules modify the view just prior to rendering it.
1523 $module_handler->invokeAll('views_pre_render', [$this]);
1525 // Let the themes play too, because prerender is a very themey thing.
1526 foreach ($themes as $theme_name) {
1527 $function = $theme_name . '_views_pre_render';
1528 if (function_exists($function)) {
1533 $this->display_handler->output = $this->display_handler->render();
1535 $exposed_form->postRender($this->display_handler->output);
1537 $cache->postRender($this->display_handler->output);
1539 // Let modules modify the view output after it is rendered.
1540 $module_handler->invokeAll('views_post_render', [$this, &$this->display_handler->output, $cache]);
1542 // Let the themes play too, because post render is a very themey thing.
1543 foreach ($themes as $theme_name) {
1544 $function = $theme_name . '_views_post_render';
1545 if (function_exists($function)) {
1546 $function($this, $this->display_handler->output, $cache);
1550 return $this->display_handler->output;
1554 * Gets the cache tags associated with the executed view.
1556 * Note: The cache plugin controls the used tags, so you can override it, if
1560 * An array of cache tags.
1562 public function getCacheTags() {
1563 $this->initDisplay();
1564 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
1565 $cache = $this->display_handler->getPlugin('cache');
1566 return $cache->getCacheTags();
1570 * Builds the render array outline for the given display.
1572 * This render array has a #pre_render callback which will call
1573 * ::executeDisplay in order to actually execute the view and then build the
1574 * final render array structure.
1576 * @param string $display_id
1578 * @param array $args
1579 * An array of arguments passed along to the view.
1580 * @param bool $cache
1581 * (optional) Should the result be render cached.
1583 * @return array|null
1584 * A renderable array with #type 'view' or NULL if the display ID was
1587 public function buildRenderable($display_id = NULL, $args = [], $cache = TRUE) {
1588 // @todo Extract that into a generic method.
1589 if (empty($this->current_display) || $this->current_display != $this->chooseDisplay($display_id)) {
1590 if (!$this->setDisplay($display_id)) {
1595 return $this->display_handler->buildRenderable($args, $cache);
1599 * Executes the given display, with the given arguments.
1601 * To be called externally by whatever mechanism invokes the view,
1602 * such as a page callback, hook_block, etc.
1604 * This function should NOT be used by anything external as this
1605 * returns data in the format specified by the display. It can also
1606 * have other side effects that are only intended for the 'proper'
1607 * use of the display, such as setting page titles.
1609 * If you simply want to view the display, use View::preview() instead.
1611 * @param string $display_id
1612 * The display ID of the view to be executed.
1613 * @param string[] $args
1614 * The arguments to be passed to the view.
1616 * @return array|null
1617 * A renderable array containing the view output or NULL if the display ID
1618 * of the view to be executed doesn't exist.
1620 public function executeDisplay($display_id = NULL, $args = []) {
1621 if (empty($this->current_display) || $this->current_display != $this->chooseDisplay($display_id)) {
1622 if (!$this->setDisplay($display_id)) {
1627 $this->preExecute($args);
1630 $output = $this->display_handler->execute();
1632 $this->postExecute();
1637 * Previews the given display, with the given arguments.
1639 * To be called externally, probably by an AJAX handler of some flavor.
1640 * Can also be called when views are embedded, as this guarantees
1641 * normalized output.
1643 * This function does not do any access checks on the view. It is the
1644 * responsibility of the caller to check $view->access() or implement other
1645 * access logic. To render the view normally with access checks, use
1646 * views_embed_view() instead.
1648 * @return array|null
1649 * A renderable array containing the view output or NULL if the display ID
1650 * of the view to be executed doesn't exist.
1652 public function preview($display_id = NULL, $args = []) {
1653 if (empty($this->current_display) || ((!empty($display_id)) && $this->current_display != $display_id)) {
1654 if (!$this->setDisplay($display_id)) {
1659 $this->preview = TRUE;
1660 $this->preExecute($args);
1661 // Preview the view.
1662 $output = $this->display_handler->preview();
1664 $this->postExecute();
1669 * Runs attachments and lets the display do what it needs to before running.
1671 * @param array $args
1672 * An array of arguments from the URL that can be used by the view.
1674 public function preExecute($args = []) {
1675 $this->old_view[] = views_get_current_view();
1676 views_set_current_view($this);
1677 $display_id = $this->current_display;
1679 // Prepare the view with the information we have, but only if we were
1680 // passed arguments, as they may have been set previously.
1682 $this->setArguments($args);
1685 // Let modules modify the view just prior to executing it.
1686 \Drupal::moduleHandler()->invokeAll('views_pre_view', [$this, $display_id, &$this->args]);
1688 // Allow hook_views_pre_view() to set the dom_id, then ensure it is set.
1689 $this->dom_id = !empty($this->dom_id) ? $this->dom_id : hash('sha256', $this->storage->id() . REQUEST_TIME . mt_rand());
1691 // Allow the display handler to set up for execution
1692 $this->display_handler->preExecute();
1696 * Unsets the current view, mostly.
1698 public function postExecute() {
1699 // unset current view so we can be properly destructed later on.
1700 // Return the previous value in case we're an attachment.
1702 if ($this->old_view) {
1703 $old_view = array_pop($this->old_view);
1706 views_set_current_view(isset($old_view) ? $old_view : FALSE);
1710 * Runs attachment displays for the view.
1712 public function attachDisplays() {
1713 if (!empty($this->is_attachment)) {
1717 if (!$this->display_handler->acceptAttachments()) {
1721 $this->is_attachment = TRUE;
1722 // Find out which other displays attach to the current one.
1723 foreach ($this->display_handler->getAttachedDisplays() as $id) {
1724 $display_handler = $this->displayHandlers->get($id);
1725 // Only attach enabled attachments.
1726 if ($display_handler->isEnabled()) {
1727 $cloned_view = Views::executableFactory()->get($this->storage);
1728 $display_handler->attachTo($cloned_view, $this->current_display, $this->element);
1731 $this->is_attachment = FALSE;
1735 * Determines if the given user has access to the view.
1737 * Note that this sets the display handler if it hasn't been set.
1739 * @param string $displays
1740 * The machine name of the display.
1741 * @param \Drupal\Core\Session\AccountInterface $account
1745 * TRUE if the user has access to the view, FALSE otherwise.
1747 public function access($displays = NULL, $account = NULL) {
1748 // No one should have access to disabled views.
1749 if (!$this->storage->status()) {
1753 if (!isset($this->current_display)) {
1754 $this->initDisplay();
1758 $account = $this->user;
1761 // We can't use choose_display() here because that function
1763 $displays = (array) $displays;
1764 foreach ($displays as $display_id) {
1765 if ($this->displayHandlers->has($display_id)) {
1766 if (($display = $this->displayHandlers->get($display_id)) && $display->access($account)) {
1776 * Sets the used response object of the view.
1778 * @param \Symfony\Component\HttpFoundation\Response $response
1779 * The response object which should be set.
1781 public function setResponse(Response $response) {
1782 $this->response = $response;
1786 * Gets the response object used by the view.
1788 * @return \Symfony\Component\HttpFoundation\Response
1789 * The response object of the view.
1791 public function getResponse() {
1792 if (!isset($this->response)) {
1793 $this->response = new Response();
1795 return $this->response;
1799 * Sets the request object.
1801 * @param \Symfony\Component\HttpFoundation\Request $request
1802 * The request object.
1804 public function setRequest(Request $request) {
1805 $this->request = $request;
1809 * Gets the request object.
1811 * @return \Symfony\Component\HttpFoundation\Request
1812 * The request object.
1814 public function getRequest() {
1815 return $this->request;
1819 * Gets the view's current title.
1821 * This can change depending upon how it was built.
1823 * @return string|false
1824 * The view title, FALSE if the display is not set.
1826 public function getTitle() {
1827 if (empty($this->display_handler)) {
1828 if (!$this->setDisplay('default')) {
1833 // During building, we might find a title override. If so, use it.
1834 if (!empty($this->build_info['title'])) {
1835 $title = $this->build_info['title'];
1838 $title = $this->display_handler->getOption('title');
1841 // Allow substitutions from the first row.
1842 if ($this->initStyle()) {
1843 $title = $this->style_plugin->tokenizeValue($title, 0);
1849 * Overrides the view's current title.
1851 * The tokens in the title get's replaced before rendering.
1854 * Always returns TRUE.
1856 public function setTitle($title) {
1857 $this->build_info['title'] = $title;
1862 * Forces the view to build a title.
1864 public function buildTitle() {
1865 $this->initDisplay();
1867 if (empty($this->built)) {
1871 $this->initHandlers();
1873 $this->_buildArguments();
1877 * Determines whether you can link to the view or a particular display.
1879 * Some displays (e.g. block displays) do not have their own route, but may
1880 * optionally provide a link to another display that does have a route.
1882 * @param array $args
1883 * (optional) The arguments.
1884 * @param string $display_id
1885 * (optional) The display ID. The current display will be used by default.
1888 * TRUE if the current display has a valid route available, FALSE otherwise.
1890 public function hasUrl($args = NULL, $display_id = NULL) {
1891 if (!empty($this->override_url)) {
1895 // If the display has a valid route available (either its own or for a
1896 // linked display), then we can provide a URL for it.
1897 $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay();
1898 if (!$display_handler instanceof DisplayRouterInterface) {
1902 // Look up the route name to make sure it exists. The name may exist, but
1903 // not be available yet in some instances when editing a view and doing
1905 $provider = \Drupal::service('router.route_provider');
1907 $provider->getRouteByName($display_handler->getRouteName());
1909 catch (RouteNotFoundException $e) {
1917 * Gets the URL for the current view.
1919 * This URL will be adjusted for arguments.
1921 * @param array $args
1922 * (optional) Passed in arguments.
1923 * @param string $display_id
1924 * (optional) Specify the display ID to link to, fallback to the current ID.
1926 * @return \Drupal\Core\Url
1927 * The URL of the current view.
1929 * @throws \InvalidArgumentException
1930 * Thrown when the current view doesn't have a route available.
1932 public function getUrl($args = NULL, $display_id = NULL) {
1933 if (!empty($this->override_url)) {
1934 return $this->override_url;
1937 $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay();
1938 if (!$display_handler instanceof DisplayRouterInterface) {
1939 throw new \InvalidArgumentException('You cannot create a URL to a display without routes.');
1942 if (!isset($args)) {
1943 $args = $this->args;
1945 // Exclude arguments that were computed, not passed on the URL.
1947 if (!empty($this->argument)) {
1948 foreach ($this->argument as $argument) {
1949 if (!empty($argument->is_default) && !empty($argument->options['default_argument_skip_url'])) {
1950 unset($args[$position]);
1957 $path = $this->getPath();
1959 // Don't bother working if there's nothing to do:
1960 if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) {
1961 return $display_handler->getUrlInfo();
1964 $argument_keys = isset($this->argument) ? array_keys($this->argument) : [];
1965 $id = current($argument_keys);
1967 /** @var \Drupal\Core\Url $url */
1968 $url = $display_handler->getUrlInfo();
1969 $route = $this->routeProvider->getRouteByName($url->getRouteName());
1971 $variables = $route->compile()->getVariables();
1972 $parameters = $url->getRouteParameters();
1974 foreach ($variables as $variable_name) {
1976 // Try to never put % in a URL; use the wildcard instead.
1977 if ($id && !empty($this->argument[$id]->options['exception']['value'])) {
1978 $parameters[$variable_name] = $this->argument[$id]->options['exception']['value'];
1981 // Provide some fallback in case no exception value could be found.
1982 $parameters[$variable_name] = '*';
1986 $parameters[$variable_name] = array_shift($args);
1990 $id = next($argument_keys);
1994 $url->setRouteParameters($parameters);
1999 * Gets the Url object associated with the display handler.
2001 * @param string $display_id
2002 * (optional) The display ID (used only to detail an exception).
2004 * @return \Drupal\Core\Url
2005 * The display handlers URL object.
2007 * @throws \InvalidArgumentException
2008 * Thrown when the display plugin does not have a URL to return.
2010 public function getUrlInfo($display_id = '') {
2011 $this->initDisplay();
2012 if (!$this->display_handler instanceof DisplayRouterInterface) {
2013 throw new \InvalidArgumentException("You cannot generate a URL for the display '$display_id'");
2015 return $this->display_handler->getUrlInfo();
2019 * Gets the base path used for this view.
2021 * @return string|false
2022 * The base path used for the view or FALSE if setting the display fails.
2024 public function getPath() {
2025 if (!empty($this->override_path)) {
2026 return $this->override_path;
2029 if (empty($this->display_handler)) {
2030 if (!$this->setDisplay('default')) {
2034 return $this->display_handler->getPath();
2038 * Gets the current user.
2040 * Views plugins can receive the current user in order to not need dependency
2043 * @return \Drupal\Core\Session\AccountInterface
2046 public function getUser() {
2051 * Creates a duplicate ViewExecutable object.
2053 * Makes a copy of this view that has been sanitized of handlers, any runtime
2054 * data, ID, and UUID.
2056 public function createDuplicate() {
2057 return $this->storage->createDuplicate()->getExecutable();
2061 * Unsets references so that a $view object may be properly garbage collected.
2063 public function destroy() {
2064 foreach ($this::getHandlerTypes() as $type => $info) {
2065 if (isset($this->$type)) {
2066 foreach ($this->{$type} as $handler) {
2067 $handler->destroy();
2072 if (isset($this->style_plugin)) {
2073 $this->style_plugin->destroy();
2076 $reflection = new \ReflectionClass($this);
2077 $defaults = $reflection->getDefaultProperties();
2078 // The external dependencies should not be reset. This is not generated by
2079 // the execution of a view.
2081 $defaults['storage'],
2083 $defaults['request'],
2084 $defaults['routeProvider'],
2085 $defaults['viewsData']
2088 foreach ($defaults as $property => $default) {
2089 $this->{$property} = $default;
2094 * Makes sure the view is completely valid.
2097 * An array of error strings. This will be empty if there are no validation
2100 public function validate() {
2103 $this->initDisplay();
2104 $current_display = $this->current_display;
2106 foreach ($this->displayHandlers as $id => $display) {
2107 if (!empty($display)) {
2108 if (!empty($display->display['deleted'])) {
2112 $result = $this->displayHandlers->get($id)->validate();
2113 if (!empty($result) && is_array($result)) {
2114 $errors[$id] = $result;
2119 $this->setDisplay($current_display);
2125 * Provides a list of views handler types used in a view.
2127 * This also provides some information about the views handler types.
2130 * An array of associative arrays containing:
2131 * - title: The title of the handler type.
2132 * - ltitle: The lowercase title of the handler type.
2133 * - stitle: A singular title of the handler type.
2134 * - lstitle: A singular lowercase title of the handler type.
2135 * - plural: Plural version of the handler type.
2136 * - (optional) type: The actual internal used handler type. This key is
2137 * just used for header,footer,empty to link to the internal type: area.
2139 public static function getHandlerTypes() {
2140 return Views::getHandlerTypes();
2144 * Returns the valid types of plugins that can be used.
2147 * An array of plugin type strings.
2149 public static function getPluginTypes($type = NULL) {
2150 return Views::getPluginTypes($type);
2154 * Adds an instance of a handler to the view.
2156 * Items may be fields, filters, sort criteria, or arguments.
2158 * @param string $display_id
2159 * The machine name of the display.
2160 * @param string $type
2161 * The type of handler being added.
2162 * @param string $table
2163 * The name of the table this handler is from.
2164 * @param string $field
2165 * The name of the field this handler is from.
2166 * @param array $options
2167 * (optional) Extra options for this instance. Defaults to an empty array.
2169 * (optional) A unique ID for this handler instance. Defaults to NULL, in
2170 * which case one will be generated.
2173 * The unique ID for this handler instance.
2175 public function addHandler($display_id, $type, $table, $field, $options = [], $id = NULL) {
2176 $types = $this::getHandlerTypes();
2177 $this->setDisplay($display_id);
2179 $data = $this->viewsData->get($table);
2180 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']);
2183 $id = $this->generateHandlerId($field, $fields);
2186 // If the desired type is not found, use the original value directly.
2187 $handler_type = !empty($types[$type]['type']) ? $types[$type]['type'] : $type;
2195 if (isset($data['table']['entity type'])) {
2196 $fields[$id]['entity_type'] = $data['table']['entity type'];
2198 if (isset($data[$field]['entity field'])) {
2199 $fields[$id]['entity_field'] = $data[$field]['entity field'];
2202 // Load the plugin ID if available.
2203 if (isset($data[$field][$handler_type]['id'])) {
2204 $fields[$id]['plugin_id'] = $data[$field][$handler_type]['id'];
2207 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields);
2213 * Generates a unique ID for an handler instance.
2215 * These handler instances are typically fields, filters, sort criteria, or
2218 * @param string $requested_id
2219 * The requested ID for the handler instance.
2220 * @param array $existing_items
2221 * An array of existing handler instances, keyed by their IDs.
2224 * A unique ID. This will be equal to $requested_id if no handler instance
2225 * with that ID already exists. Otherwise, it will be appended with an
2226 * integer to make it unique, e.g., "{$requested_id}_1",
2227 * "{$requested_id}_2", etc.
2229 public static function generateHandlerId($requested_id, $existing_items) {
2231 $id = $requested_id;
2232 while (!empty($existing_items[$id])) {
2233 $id = $requested_id . '_' . ++$count;
2239 * Gets an array of handler instances for the current display.
2241 * @param string $type
2242 * The type of handlers to retrieve.
2243 * @param string $display_id
2244 * (optional) A specific display machine name to use. If NULL, the current
2245 * display will be used.
2248 * An array of handler instances of a given type for this display.
2250 public function getHandlers($type, $display_id = NULL) {
2251 $old_display_id = !empty($this->current_display) ? $this->current_display : 'default';
2253 $this->setDisplay($display_id);
2255 if (!isset($display_id)) {
2256 $display_id = $this->current_display;
2259 // Get info about the types so we can get the right data.
2260 $types = static::getHandlerTypes();
2262 $handlers = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']);
2264 // Restore initial display id (if any) or set to 'default'.
2265 if ($display_id != $old_display_id) {
2266 $this->setDisplay($old_display_id);
2272 * Gets the configuration of a handler instance on a given display.
2274 * @param string $display_id
2275 * The machine name of the display.
2276 * @param string $type
2277 * The type of handler to retrieve.
2279 * The ID of the handler to retrieve.
2281 * @return array|null
2282 * Either the handler instance's configuration, or NULL if the handler is
2283 * not used on the display.
2285 public function getHandler($display_id, $type, $id) {
2286 // Get info about the types so we can get the right data.
2287 $types = static::getHandlerTypes();
2288 // Initialize the display
2289 $this->setDisplay($display_id);
2291 // Get the existing configuration
2292 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']);
2294 return isset($fields[$id]) ? $fields[$id] : NULL;
2298 * Sets the configuration of a handler instance on a given display.
2300 * @param string $display_id
2301 * The machine name of the display.
2302 * @param string $type
2303 * The type of handler being set.
2305 * The ID of the handler being set.
2306 * @param array|null $item
2307 * An array of configuration for a handler, or NULL to remove this instance.
2309 * @see set_item_option()
2311 public function setHandler($display_id, $type, $id, $item) {
2312 // Get info about the types so we can get the right data.
2313 $types = static::getHandlerTypes();
2314 // Initialize the display.
2315 $this->setDisplay($display_id);
2317 // Get the existing configuration.
2318 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']);
2320 $fields[$id] = $item;
2324 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields);
2328 * Removes configuration for a handler instance on a given display.
2330 * @param string $display_id
2331 * The machine name of the display.
2332 * @param string $type
2333 * The type of handler being removed.
2335 * The ID of the handler being removed.
2337 public function removeHandler($display_id, $type, $id) {
2338 // Get info about the types so we can get the right data.
2339 $types = static::getHandlerTypes();
2340 // Initialize the display.
2341 $this->setDisplay($display_id);
2343 // Get the existing configuration.
2344 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']);
2346 unset($fields[$id]);
2349 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields);
2353 * Sets an option on a handler instance.
2355 * Use this only if you have just 1 or 2 options to set; if you have many,
2356 * consider getting the handler instance, adding the options and using
2357 * set_item() directly.
2359 * @param string $display_id
2360 * The machine name of the display.
2361 * @param string $type
2362 * The type of handler being set.
2364 * The ID of the handler being set.
2365 * @param string $option
2366 * The configuration key for the value being set.
2367 * @param mixed $value
2368 * The value being set.
2372 public function setHandlerOption($display_id, $type, $id, $option, $value) {
2373 $item = $this->getHandler($display_id, $type, $id);
2374 $item[$option] = $value;
2375 $this->setHandler($display_id, $type, $id, $item);
2379 * Enables admin links on the rendered view.
2381 * @param bool $show_admin_links
2382 * TRUE if the admin links should be shown.
2384 public function setShowAdminLinks($show_admin_links) {
2385 $this->showAdminLinks = (bool) $show_admin_links;
2389 * Returns whether admin links should be rendered on the view.
2392 * TRUE if admin links should be rendered, else FALSE.
2394 public function getShowAdminLinks() {
2395 if (!isset($this->showAdminLinks)) {
2396 return $this->getDisplay()->getOption('show_admin_links');
2398 return $this->showAdminLinks;
2402 * Merges all plugin default values for each display.
2404 public function mergeDefaults() {
2405 $this->initDisplay();
2406 // Initialize displays and merge all plugin defaults.
2407 foreach ($this->displayHandlers as $display) {
2408 $display->mergeDefaults();
2413 * Provides a full array of possible theme functions to try for a given hook.
2415 * @param string $hook
2416 * The hook to use. This is the base theme/template name.
2419 * An array of theme hook suggestions.
2421 public function buildThemeFunctions($hook) {
2423 $display = isset($this->display_handler) ? $this->display_handler->display : NULL;
2424 $id = $this->storage->id();
2427 $themes[] = $hook . '__' . $id . '__' . $display['id'];
2428 $themes[] = $hook . '__' . $display['id'];
2429 // Add theme suggestions for each single tag.
2430 foreach (Tags::explode($this->storage->get('tag')) as $tag) {
2431 $themes[] = $hook . '__' . preg_replace('/[^a-z0-9]/', '_', strtolower($tag));
2434 if ($display['id'] != $display['display_plugin']) {
2435 $themes[] = $hook . '__' . $id . '__' . $display['display_plugin'];
2436 $themes[] = $hook . '__' . $display['display_plugin'];
2439 $themes[] = $hook . '__' . $id;
2446 * Determines if this view has form elements.
2449 * TRUE if this view contains handlers with views form implementations,
2452 public function hasFormElements() {
2453 foreach ($this->field as $field) {
2454 if (property_exists($field, 'views_form_callback') || method_exists($field, 'viewsForm')) {
2458 $area_handlers = array_merge(array_values($this->header), array_values($this->footer));
2459 $empty = empty($this->result);
2460 foreach ($area_handlers as $area) {
2461 if (method_exists($area, 'viewsForm') && !$area->viewsFormEmpty($empty)) {
2470 * Gets dependencies for the view.
2472 * @see \Drupal\views\Entity\View::calculateDependencies()
2473 * @see \Drupal\views\Entity\View::getDependencies()
2476 * An array of dependencies grouped by type (module, theme, entity).
2478 public function getDependencies() {
2479 return $this->storage->calculateDependencies()->getDependencies();
2483 * Magic method implementation to serialize the view executable.
2486 * The names of all variables that should be serialized.
2488 public function __sleep() {
2489 // Limit to only the required data which is needed to properly restore the
2490 // state during unserialization.
2491 $this->serializationData = [
2492 'storage' => $this->storage->id(),
2493 'views_data' => $this->viewsData->_serviceId,
2494 'route_provider' => $this->routeProvider->_serviceId,
2495 'current_display' => $this->current_display,
2496 'args' => $this->args,
2497 'current_page' => $this->current_page,
2498 'exposed_input' => $this->exposed_input,
2499 'exposed_raw_input' => $this->exposed_raw_input,
2500 'exposed_data' => $this->exposed_data,
2501 'dom_id' => $this->dom_id,
2502 'executed' => $this->executed,
2504 return ['serializationData'];
2508 * Magic method implementation to unserialize the view executable.
2510 public function __wakeup() {
2511 // There are cases, like in testing where we don't have a container
2513 if (\Drupal::hasContainer() && !empty($this->serializationData)) {
2514 // Load and reference the storage.
2515 $this->storage = \Drupal::entityTypeManager()->getStorage('view')
2516 ->load($this->serializationData['storage']);
2517 $this->storage->set('executable', $this);
2519 // Attach all necessary services.
2520 $this->user = \Drupal::currentUser();
2521 $this->viewsData = \Drupal::service($this->serializationData['views_data']);
2522 $this->routeProvider = \Drupal::service($this->serializationData['route_provider']);
2524 // Restore the state of this executable.
2525 if ($request = \Drupal::request()) {
2526 $this->setRequest($request);
2528 $this->setDisplay($this->serializationData['current_display']);
2529 $this->setArguments($this->serializationData['args']);
2530 $this->setCurrentPage($this->serializationData['current_page']);
2531 $this->setExposedInput($this->serializationData['exposed_input']);
2532 $this->exposed_data = $this->serializationData['exposed_data'];
2533 $this->exposed_raw_input = $this->serializationData['exposed_raw_input'];
2534 $this->dom_id = $this->serializationData['dom_id'];
2536 $this->initHandlers();
2538 // If the display was previously executed, execute it now.
2539 if ($this->serializationData['executed']) {
2540 $this->execute($this->current_display);
2543 // Unset serializationData since it serves no further purpose.
2544 unset($this->serializationData);