Mercurial > hg > isophonics-drupal-site
comparison core/modules/views/src/ViewExecutable.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 7a779792577d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\views; | |
4 | |
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; | |
14 | |
15 /** | |
16 * Represents a view as a whole. | |
17 * | |
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. | |
20 * | |
21 * This class does not implement the Serializable interface since problems | |
22 * occurred when using the serialize method. | |
23 * | |
24 * @see https://www.drupal.org/node/2849674 | |
25 * @see https://bugs.php.net/bug.php?id=66052 | |
26 */ | |
27 class ViewExecutable { | |
28 | |
29 /** | |
30 * The config entity in which the view is stored. | |
31 * | |
32 * @var \Drupal\views\Entity\View | |
33 */ | |
34 public $storage; | |
35 | |
36 /** | |
37 * Whether or not the view has been built. | |
38 * | |
39 * @todo Group with other static properties. | |
40 * | |
41 * @var bool | |
42 */ | |
43 public $built = FALSE; | |
44 | |
45 /** | |
46 * Whether the view has been executed/query has been run. | |
47 * | |
48 * @todo Group with other static properties. | |
49 * | |
50 * @var bool | |
51 */ | |
52 public $executed = FALSE; | |
53 | |
54 /** | |
55 * Any arguments that have been passed into the view. | |
56 * | |
57 * @var array | |
58 */ | |
59 public $args = []; | |
60 | |
61 /** | |
62 * An array of build info. | |
63 * | |
64 * @var array | |
65 */ | |
66 public $build_info = []; | |
67 | |
68 /** | |
69 * Whether this view uses AJAX. | |
70 * | |
71 * @var bool | |
72 */ | |
73 protected $ajaxEnabled = FALSE; | |
74 | |
75 /** | |
76 * Where the results of a query will go. | |
77 * | |
78 * The array must use a numeric index starting at 0. | |
79 * | |
80 * @var \Drupal\views\ResultRow[] | |
81 */ | |
82 public $result = []; | |
83 | |
84 // May be used to override the current pager info. | |
85 | |
86 /** | |
87 * The current page. If the view uses pagination. | |
88 * | |
89 * @var int | |
90 */ | |
91 protected $current_page = NULL; | |
92 | |
93 /** | |
94 * The number of items per page. | |
95 * | |
96 * @var int | |
97 */ | |
98 protected $items_per_page = NULL; | |
99 | |
100 /** | |
101 * The pager offset. | |
102 * | |
103 * @var int | |
104 */ | |
105 protected $offset = NULL; | |
106 | |
107 /** | |
108 * The total number of rows returned from the query. | |
109 * | |
110 * @var int | |
111 */ | |
112 public $total_rows = NULL; | |
113 | |
114 /** | |
115 * Attachments to place before the view. | |
116 * | |
117 * @var array() | |
118 */ | |
119 public $attachment_before = []; | |
120 | |
121 /** | |
122 * Attachments to place after the view. | |
123 * | |
124 * @var array | |
125 */ | |
126 public $attachment_after = []; | |
127 | |
128 /** | |
129 * Feed icons attached to the view. | |
130 * | |
131 * @var array | |
132 */ | |
133 public $feedIcons = []; | |
134 | |
135 // Exposed widget input | |
136 | |
137 /** | |
138 * All the form data from $form_state->getValues(). | |
139 * | |
140 * @var array | |
141 */ | |
142 public $exposed_data = []; | |
143 | |
144 /** | |
145 * An array of input values from exposed forms. | |
146 * | |
147 * @var array | |
148 */ | |
149 protected $exposed_input = []; | |
150 | |
151 /** | |
152 * Exposed widget input directly from the $form_state->getValues(). | |
153 * | |
154 * @var array | |
155 */ | |
156 public $exposed_raw_input = []; | |
157 | |
158 /** | |
159 * Used to store views that were previously running if we recurse. | |
160 * | |
161 * @var \Drupal\views\ViewExecutable[] | |
162 */ | |
163 public $old_view = []; | |
164 | |
165 /** | |
166 * To avoid recursion in views embedded into areas. | |
167 * | |
168 * @var \Drupal\views\ViewExecutable[] | |
169 */ | |
170 public $parent_views = []; | |
171 | |
172 /** | |
173 * Whether this view is an attachment to another view. | |
174 * | |
175 * @var bool | |
176 */ | |
177 public $is_attachment = NULL; | |
178 | |
179 /** | |
180 * Identifier of the current display. | |
181 * | |
182 * @var string | |
183 */ | |
184 public $current_display; | |
185 | |
186 /** | |
187 * Where the $query object will reside. | |
188 * | |
189 * @var \Drupal\views\Plugin\views\query\QueryPluginBase | |
190 */ | |
191 public $query = NULL; | |
192 | |
193 /** | |
194 * The used pager plugin used by the current executed view. | |
195 * | |
196 * @var \Drupal\views\Plugin\views\pager\PagerPluginBase | |
197 */ | |
198 public $pager = NULL; | |
199 | |
200 /** | |
201 * The current used display plugin. | |
202 * | |
203 * @var \Drupal\views\Plugin\views\display\DisplayPluginBase | |
204 */ | |
205 public $display_handler; | |
206 | |
207 /** | |
208 * The list of used displays of the view. | |
209 * | |
210 * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase | |
211 * objects. | |
212 * | |
213 * @var \Drupal\views\DisplayPluginCollection | |
214 */ | |
215 public $displayHandlers; | |
216 | |
217 /** | |
218 * The current used style plugin. | |
219 * | |
220 * @var \Drupal\views\Plugin\views\style\StylePluginBase | |
221 */ | |
222 public $style_plugin; | |
223 | |
224 /** | |
225 * The current used row plugin, if the style plugin supports row plugins. | |
226 * | |
227 * @var \Drupal\views\Plugin\views\row\RowPluginBase | |
228 */ | |
229 public $rowPlugin; | |
230 | |
231 /** | |
232 * Stores the current active row while rendering. | |
233 * | |
234 * @var int | |
235 */ | |
236 public $row_index; | |
237 | |
238 /** | |
239 * Allow to override the url of the current view. | |
240 * | |
241 * @var \Drupal\Core\Url | |
242 */ | |
243 public $override_url; | |
244 | |
245 /** | |
246 * Allow to override the path used for generated urls. | |
247 * | |
248 * @var string | |
249 */ | |
250 public $override_path = NULL; | |
251 | |
252 /** | |
253 * Allow to override the used database which is used for this query. | |
254 * | |
255 * @var bool | |
256 */ | |
257 public $base_database = NULL; | |
258 | |
259 // Handlers which are active on this view. | |
260 | |
261 /** | |
262 * Stores the field handlers which are initialized on this view. | |
263 * | |
264 * @var \Drupal\views\Plugin\views\field\FieldPluginBase[] | |
265 */ | |
266 public $field; | |
267 | |
268 /** | |
269 * Stores the argument handlers which are initialized on this view. | |
270 * | |
271 * @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase[] | |
272 */ | |
273 public $argument; | |
274 | |
275 /** | |
276 * Stores the sort handlers which are initialized on this view. | |
277 * | |
278 * @var \Drupal\views\Plugin\views\sort\SortPluginBase[] | |
279 */ | |
280 public $sort; | |
281 | |
282 /** | |
283 * Stores the filter handlers which are initialized on this view. | |
284 * | |
285 * @var \Drupal\views\Plugin\views\filter\FilterPluginBase[] | |
286 */ | |
287 public $filter; | |
288 | |
289 /** | |
290 * Stores the relationship handlers which are initialized on this view. | |
291 * | |
292 * @var \Drupal\views\Plugin\views\relationship\RelationshipPluginBase[] | |
293 */ | |
294 public $relationship; | |
295 | |
296 /** | |
297 * Stores the area handlers for the header which are initialized on this view. | |
298 * | |
299 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[] | |
300 */ | |
301 public $header; | |
302 | |
303 /** | |
304 * Stores the area handlers for the footer which are initialized on this view. | |
305 * | |
306 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[] | |
307 */ | |
308 public $footer; | |
309 | |
310 /** | |
311 * Stores the area handlers for the empty text which are initialized on this view. | |
312 * | |
313 * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects. | |
314 * | |
315 * @var \Drupal\views\Plugin\views\area\AreaPluginBase[] | |
316 */ | |
317 public $empty; | |
318 | |
319 /** | |
320 * Stores the current response object. | |
321 * | |
322 * @var \Symfony\Component\HttpFoundation\Response | |
323 */ | |
324 protected $response = NULL; | |
325 | |
326 /** | |
327 * Stores the current request object. | |
328 * | |
329 * @var \Symfony\Component\HttpFoundation\Request | |
330 */ | |
331 protected $request; | |
332 | |
333 /** | |
334 * Does this view already have loaded it's handlers. | |
335 * | |
336 * @todo Group with other static properties. | |
337 * | |
338 * @var bool | |
339 */ | |
340 public $inited; | |
341 | |
342 /** | |
343 * The rendered output of the exposed form. | |
344 * | |
345 * @var string | |
346 */ | |
347 public $exposed_widgets; | |
348 | |
349 /** | |
350 * If this view has been previewed. | |
351 * | |
352 * @var bool | |
353 */ | |
354 public $preview; | |
355 | |
356 /** | |
357 * Force the query to calculate the total number of results. | |
358 * | |
359 * @todo Move to the query. | |
360 * | |
361 * @var bool | |
362 */ | |
363 public $get_total_rows; | |
364 | |
365 /** | |
366 * Indicates if the sorts have been built. | |
367 * | |
368 * @todo Group with other static properties. | |
369 * | |
370 * @var bool | |
371 */ | |
372 public $build_sort; | |
373 | |
374 /** | |
375 * Stores the many-to-one tables for performance. | |
376 * | |
377 * @var array | |
378 */ | |
379 public $many_to_one_tables; | |
380 | |
381 /** | |
382 * A unique identifier which allows to update multiple views output via js. | |
383 * | |
384 * @var string | |
385 */ | |
386 public $dom_id; | |
387 | |
388 /** | |
389 * A render array container to store render related information. | |
390 * | |
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 | |
393 * CSS or JS. | |
394 * | |
395 * @var array | |
396 * | |
397 * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments() | |
398 */ | |
399 public $element = [ | |
400 '#attached' => [ | |
401 'library' => ['views/views.module'], | |
402 'drupalSettings' => [], | |
403 ], | |
404 '#cache' => [], | |
405 ]; | |
406 | |
407 /** | |
408 * The current user. | |
409 * | |
410 * @var \Drupal\Core\Session\AccountInterface | |
411 */ | |
412 protected $user; | |
413 | |
414 /** | |
415 * Should the admin links be shown on the rendered view. | |
416 * | |
417 * @var bool | |
418 */ | |
419 protected $showAdminLinks; | |
420 | |
421 /** | |
422 * The views data. | |
423 * | |
424 * @var \Drupal\views\ViewsData | |
425 */ | |
426 protected $viewsData; | |
427 | |
428 /** | |
429 * The route provider. | |
430 * | |
431 * @var \Drupal\Core\Routing\RouteProviderInterface | |
432 */ | |
433 protected $routeProvider; | |
434 | |
435 /** | |
436 * The entity type of the base table, if available. | |
437 * | |
438 * @var \Drupal\Core\Entity\EntityTypeInterface|false | |
439 */ | |
440 protected $baseEntityType; | |
441 | |
442 /** | |
443 * Holds all necessary data for proper unserialization. | |
444 * | |
445 * @var array | |
446 */ | |
447 protected $serializationData; | |
448 | |
449 /** | |
450 * Constructs a new ViewExecutable object. | |
451 * | |
452 * @param \Drupal\views\ViewEntityInterface $storage | |
453 * The view config entity the actual information is stored on. | |
454 * @param \Drupal\Core\Session\AccountInterface $user | |
455 * The current user. | |
456 * @param \Drupal\views\ViewsData $views_data | |
457 * The views data. | |
458 * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider | |
459 * The route provider. | |
460 */ | |
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); | |
465 $this->user = $user; | |
466 $this->viewsData = $views_data; | |
467 $this->routeProvider = $route_provider; | |
468 } | |
469 | |
470 /** | |
471 * Returns the identifier. | |
472 * | |
473 * @return string|null | |
474 * The entity identifier, or NULL if the object does not yet have an | |
475 * identifier. | |
476 */ | |
477 public function id() { | |
478 return $this->storage->id(); | |
479 } | |
480 | |
481 /** | |
482 * Saves the view. | |
483 */ | |
484 public function save() { | |
485 $this->storage->save(); | |
486 } | |
487 | |
488 /** | |
489 * Sets the arguments for the view. | |
490 * | |
491 * @param array $args | |
492 * The arguments passed to the view. | |
493 */ | |
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); | |
498 } | |
499 | |
500 /** | |
501 * Expands the list of used cache contexts for the view. | |
502 * | |
503 * @param string $cache_context | |
504 * The additional cache context. | |
505 * | |
506 * @return $this | |
507 */ | |
508 public function addCacheContext($cache_context) { | |
509 $this->element['#cache']['contexts'][] = $cache_context; | |
510 | |
511 return $this; | |
512 } | |
513 | |
514 /** | |
515 * Sets the current page for the pager. | |
516 * | |
517 * @param int $page | |
518 * The current page. | |
519 */ | |
520 public function setCurrentPage($page) { | |
521 $this->current_page = $page; | |
522 | |
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; | |
528 } | |
529 | |
530 // If the pager is already initialized, pass it through to the pager. | |
531 if (!empty($this->pager)) { | |
532 return $this->pager->setCurrentPage($page); | |
533 } | |
534 } | |
535 | |
536 /** | |
537 * Gets the current page from the pager. | |
538 * | |
539 * @return int | |
540 * The current page. | |
541 */ | |
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(); | |
546 } | |
547 | |
548 if (isset($this->current_page)) { | |
549 return $this->current_page; | |
550 } | |
551 } | |
552 | |
553 /** | |
554 * Gets the items per page from the pager. | |
555 * | |
556 * @return int | |
557 * The items per page. | |
558 */ | |
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(); | |
563 } | |
564 | |
565 if (isset($this->items_per_page)) { | |
566 return $this->items_per_page; | |
567 } | |
568 } | |
569 | |
570 /** | |
571 * Sets the items per page on the pager. | |
572 * | |
573 * @param int $items_per_page | |
574 * The items per page. | |
575 */ | |
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; | |
581 } | |
582 $this->items_per_page = $items_per_page; | |
583 | |
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); | |
587 } | |
588 } | |
589 | |
590 /** | |
591 * Gets the pager offset from the pager. | |
592 * | |
593 * @return int | |
594 * The pager offset. | |
595 */ | |
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(); | |
600 } | |
601 | |
602 if (isset($this->offset)) { | |
603 return $this->offset; | |
604 } | |
605 } | |
606 | |
607 /** | |
608 * Sets the offset on the pager. | |
609 * | |
610 * @param int $offset | |
611 * The pager offset. | |
612 */ | |
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; | |
618 } | |
619 | |
620 $this->offset = $offset; | |
621 | |
622 | |
623 // If the pager is already initialized, pass it through to the pager. | |
624 if (!empty($this->pager)) { | |
625 $this->pager->setOffset($offset); | |
626 } | |
627 } | |
628 | |
629 /** | |
630 * Determines if the view uses a pager. | |
631 * | |
632 * @return bool | |
633 * TRUE if the view uses a pager, FALSE otherwise. | |
634 */ | |
635 public function usePager() { | |
636 if (!empty($this->pager)) { | |
637 return $this->pager->usePager(); | |
638 } | |
639 } | |
640 | |
641 /** | |
642 * Sets whether or not AJAX should be used. | |
643 * | |
644 * If AJAX is used, paging, table sorting, and exposed filters will be fetched | |
645 * via an AJAX call rather than a page refresh. | |
646 * | |
647 * @param bool $ajax_enabled | |
648 * TRUE if AJAX should be used, FALSE otherwise. | |
649 */ | |
650 public function setAjaxEnabled($ajax_enabled) { | |
651 $this->ajaxEnabled = (bool) $ajax_enabled; | |
652 } | |
653 | |
654 /** | |
655 * Determines whether or not AJAX should be used. | |
656 * | |
657 * @return bool | |
658 * TRUE if AJAX is enabled, FALSE otherwise. | |
659 */ | |
660 public function ajaxEnabled() { | |
661 return $this->ajaxEnabled; | |
662 } | |
663 | |
664 /** | |
665 * Sets the exposed filters input to an array. | |
666 * | |
667 * @param string[] $filters | |
668 * The values taken from the view's exposed filters and sorts. | |
669 */ | |
670 public function setExposedInput($filters) { | |
671 $this->exposed_input = $filters; | |
672 } | |
673 | |
674 /** | |
675 * Figures out what the exposed input for this view is. | |
676 * | |
677 * They will be taken from \Drupal::request()->query or from | |
678 * something previously set on the view. | |
679 * | |
680 * @return string[] | |
681 * An array containing the exposed input values keyed by the filter and sort | |
682 * name. | |
683 * | |
684 * @see self::setExposedInput() | |
685 */ | |
686 public function getExposedInput() { | |
687 // Fill our input either from \Drupal::request()->query or from something | |
688 // previously set on the view. | |
689 if (empty($this->exposed_input)) { | |
690 // Ensure that we can call the method at any point in time. | |
691 $this->initDisplay(); | |
692 | |
693 $this->exposed_input = \Drupal::request()->query->all(); | |
694 // unset items that are definitely not our input: | |
695 foreach (['page', 'q'] as $key) { | |
696 if (isset($this->exposed_input[$key])) { | |
697 unset($this->exposed_input[$key]); | |
698 } | |
699 } | |
700 | |
701 // If we have no input at all, check for remembered input via session. | |
702 | |
703 // If filters are not overridden, store the 'remember' settings on the | |
704 // default display. If they are, store them on this display. This way, | |
705 // multiple displays in the same view can share the same filters and | |
706 // remember settings. | |
707 $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display; | |
708 | |
709 if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) { | |
710 $this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id]; | |
711 } | |
712 } | |
713 | |
714 return $this->exposed_input; | |
715 } | |
716 | |
717 /** | |
718 * Sets the display for this view and initializes the display handler. | |
719 * | |
720 * @return true | |
721 * Always returns TRUE. | |
722 */ | |
723 public function initDisplay() { | |
724 if (isset($this->current_display)) { | |
725 return TRUE; | |
726 } | |
727 | |
728 // Initialize the display cache array. | |
729 $this->displayHandlers = new DisplayPluginCollection($this, Views::pluginManager('display')); | |
730 | |
731 $this->current_display = 'default'; | |
732 $this->display_handler = $this->displayHandlers->get('default'); | |
733 | |
734 return TRUE; | |
735 } | |
736 | |
737 /** | |
738 * Gets the first display that is accessible to the user. | |
739 * | |
740 * @param array|string $displays | |
741 * Either a single display id or an array of display ids. | |
742 * | |
743 * @return string | |
744 * The first accessible display id, at least default. | |
745 */ | |
746 public function chooseDisplay($displays) { | |
747 if (!is_array($displays)) { | |
748 return $displays; | |
749 } | |
750 | |
751 $this->initDisplay(); | |
752 | |
753 foreach ($displays as $display_id) { | |
754 if ($this->displayHandlers->get($display_id)->access($this->user)) { | |
755 return $display_id; | |
756 } | |
757 } | |
758 | |
759 return 'default'; | |
760 } | |
761 | |
762 /** | |
763 * Gets the current display plugin. | |
764 * | |
765 * @return \Drupal\views\Plugin\views\display\DisplayPluginBase | |
766 * The current display plugin. | |
767 */ | |
768 public function getDisplay() { | |
769 if (!isset($this->display_handler)) { | |
770 $this->initDisplay(); | |
771 } | |
772 | |
773 return $this->display_handler; | |
774 } | |
775 | |
776 /** | |
777 * Sets the current display. | |
778 * | |
779 * @param string $display_id | |
780 * The ID of the display to mark as current. | |
781 * | |
782 * @return bool | |
783 * TRUE if the display was correctly set, FALSE otherwise. | |
784 */ | |
785 public function setDisplay($display_id = NULL) { | |
786 // If we have not already initialized the display, do so. | |
787 if (!isset($this->current_display)) { | |
788 // This will set the default display and instantiate the default display | |
789 // plugin. | |
790 $this->initDisplay(); | |
791 } | |
792 | |
793 // If no display ID is passed, we either have initialized the default or | |
794 // already have a display set. | |
795 if (!isset($display_id)) { | |
796 return TRUE; | |
797 } | |
798 | |
799 $display_id = $this->chooseDisplay($display_id); | |
800 | |
801 // Ensure the requested display exists. | |
802 if (!$this->displayHandlers->has($display_id)) { | |
803 trigger_error(new FormattableMarkup('setDisplay() called with invalid display ID "@display".', ['@display' => $display_id]), E_USER_WARNING); | |
804 return FALSE; | |
805 } | |
806 | |
807 // Reset if the display has changed. It could be called multiple times for | |
808 // the same display, especially in the UI. | |
809 if ($this->current_display != $display_id) { | |
810 // Set the current display. | |
811 $this->current_display = $display_id; | |
812 | |
813 // Reset the style and row plugins. | |
814 $this->style_plugin = NULL; | |
815 $this->plugin_name = NULL; | |
816 $this->rowPlugin = NULL; | |
817 } | |
818 | |
819 if ($display = $this->displayHandlers->get($display_id)) { | |
820 // Set a shortcut. | |
821 $this->display_handler = $display; | |
822 return TRUE; | |
823 } | |
824 | |
825 return FALSE; | |
826 } | |
827 | |
828 /** | |
829 * Creates a new display and a display handler instance for it. | |
830 * | |
831 * @param string $plugin_id | |
832 * (optional) The plugin type from the Views plugin annotation. Defaults to | |
833 * 'page'. | |
834 * @param string $title | |
835 * (optional) The title of the display. Defaults to NULL. | |
836 * @param string $id | |
837 * (optional) The ID to use, e.g., 'default', 'page_1', 'block_2'. Defaults | |
838 * to NULL. | |
839 * | |
840 * @return \Drupal\views\Plugin\views\display\DisplayPluginBase | |
841 * A new display plugin instance if executable is set, the new display ID | |
842 * otherwise. | |
843 */ | |
844 public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) { | |
845 $this->initDisplay(); | |
846 | |
847 $id = $this->storage->addDisplay($plugin_id, $title, $id); | |
848 $this->displayHandlers->addInstanceId($id); | |
849 | |
850 $display = $this->displayHandlers->get($id); | |
851 $display->newDisplay(); | |
852 return $display; | |
853 } | |
854 | |
855 /** | |
856 * Gets the current style plugin. | |
857 * | |
858 * @return \Drupal\views\Plugin\views\style\StylePluginBase | |
859 * The current style plugin. | |
860 */ | |
861 public function getStyle() { | |
862 if (!isset($this->style_plugin)) { | |
863 $this->initStyle(); | |
864 } | |
865 | |
866 return $this->style_plugin; | |
867 } | |
868 | |
869 /** | |
870 * Finds and initializes the style plugin. | |
871 * | |
872 * Note that arguments may have changed which style plugin we use, so | |
873 * check the view object first, then ask the display handler. | |
874 * | |
875 * @return bool | |
876 * TRUE if the style plugin was or could be initialized, FALSE otherwise. | |
877 */ | |
878 public function initStyle() { | |
879 if (isset($this->style_plugin)) { | |
880 return TRUE; | |
881 } | |
882 | |
883 $this->style_plugin = $this->display_handler->getPlugin('style'); | |
884 | |
885 if (empty($this->style_plugin)) { | |
886 return FALSE; | |
887 } | |
888 | |
889 return TRUE; | |
890 } | |
891 | |
892 /** | |
893 * Acquires and attaches all of the handlers. | |
894 */ | |
895 public function initHandlers() { | |
896 $this->initDisplay(); | |
897 if (empty($this->inited)) { | |
898 foreach ($this::getHandlerTypes() as $key => $info) { | |
899 $this->_initHandler($key, $info); | |
900 } | |
901 $this->inited = TRUE; | |
902 } | |
903 } | |
904 | |
905 /** | |
906 * Gets the current pager plugin. | |
907 * | |
908 * @return \Drupal\views\Plugin\views\pager\PagerPluginBase | |
909 * The current pager plugin. | |
910 */ | |
911 public function getPager() { | |
912 if (!isset($this->pager)) { | |
913 $this->initPager(); | |
914 } | |
915 | |
916 return $this->pager; | |
917 } | |
918 | |
919 /** | |
920 * Initializes the pager. | |
921 * | |
922 * Like style initialization, pager initialization is held until late to allow | |
923 * for overrides. | |
924 */ | |
925 public function initPager() { | |
926 if (!isset($this->pager)) { | |
927 $this->pager = $this->display_handler->getPlugin('pager'); | |
928 | |
929 if ($this->pager->usePager()) { | |
930 $this->pager->setCurrentPage($this->current_page); | |
931 } | |
932 | |
933 // These overrides may have been set earlier via $view->set_* | |
934 // functions. | |
935 if (isset($this->items_per_page)) { | |
936 $this->pager->setItemsPerPage($this->items_per_page); | |
937 } | |
938 | |
939 if (isset($this->offset)) { | |
940 $this->pager->setOffset($this->offset); | |
941 } | |
942 } | |
943 } | |
944 | |
945 /** | |
946 * Renders the pager, if necessary. | |
947 * | |
948 * @param string[] $exposed_input | |
949 * The input values from the exposed forms and sorts of the view. | |
950 * | |
951 * @return array|string | |
952 * The render array of the pager if it's set, blank string otherwise. | |
953 */ | |
954 public function renderPager($exposed_input) { | |
955 if (!empty($this->pager) && $this->pager->usePager()) { | |
956 return $this->pager->render($exposed_input); | |
957 } | |
958 | |
959 return ''; | |
960 } | |
961 | |
962 /** | |
963 * Creates a list of base tables to be used by the view. | |
964 * | |
965 * This is used primarily for the UI. The display must be already initialized. | |
966 * | |
967 * @return array | |
968 * An array of base tables to be used by the view. | |
969 */ | |
970 public function getBaseTables() { | |
971 $base_tables = [ | |
972 $this->storage->get('base_table') => TRUE, | |
973 '#global' => TRUE, | |
974 ]; | |
975 | |
976 foreach ($this->display_handler->getHandlers('relationship') as $handler) { | |
977 $base_tables[$handler->definition['base']] = TRUE; | |
978 } | |
979 return $base_tables; | |
980 } | |
981 | |
982 /** | |
983 * Returns the entity type of the base table, if available. | |
984 * | |
985 * @return \Drupal\Core\Entity\EntityType|false | |
986 * The entity type of the base table, or FALSE if none exists. | |
987 */ | |
988 public function getBaseEntityType() { | |
989 if (!isset($this->baseEntityType)) { | |
990 $view_base_table = $this->storage->get('base_table'); | |
991 $views_data = $this->viewsData->get($view_base_table); | |
992 if (!empty($views_data['table']['entity type'])) { | |
993 $entity_type_id = $views_data['table']['entity type']; | |
994 $this->baseEntityType = \Drupal::entityTypeManager()->getDefinition($entity_type_id); | |
995 } | |
996 else { | |
997 $this->baseEntityType = FALSE; | |
998 } | |
999 } | |
1000 return $this->baseEntityType; | |
1001 } | |
1002 | |
1003 /** | |
1004 * Runs the preQuery() on all active handlers. | |
1005 */ | |
1006 protected function _preQuery() { | |
1007 foreach ($this::getHandlerTypes() as $key => $info) { | |
1008 $handlers = &$this->$key; | |
1009 $position = 0; | |
1010 foreach ($handlers as $id => $handler) { | |
1011 $handlers[$id]->position = $position; | |
1012 $handlers[$id]->preQuery(); | |
1013 $position++; | |
1014 } | |
1015 } | |
1016 } | |
1017 | |
1018 /** | |
1019 * Runs the postExecute() on all active handlers. | |
1020 */ | |
1021 protected function _postExecute() { | |
1022 foreach ($this::getHandlerTypes() as $key => $info) { | |
1023 $handlers = &$this->$key; | |
1024 foreach ($handlers as $id => $handler) { | |
1025 $handlers[$id]->postExecute($this->result); | |
1026 } | |
1027 } | |
1028 } | |
1029 | |
1030 /** | |
1031 * Attaches the views handler for the specific type. | |
1032 * | |
1033 * @param string $key | |
1034 * One of 'argument', 'field', 'sort', 'filter', 'relationship'. | |
1035 * @param array $info | |
1036 * An array of views handler types use in the view with additional | |
1037 * information about them. | |
1038 */ | |
1039 protected function _initHandler($key, $info) { | |
1040 // Load the requested items from the display onto the object. | |
1041 $this->$key = &$this->display_handler->getHandlers($key); | |
1042 | |
1043 // This reference deals with difficult PHP indirection. | |
1044 $handlers = &$this->$key; | |
1045 | |
1046 // Run through and test for accessibility. | |
1047 foreach ($handlers as $id => $handler) { | |
1048 if (!$handler->access($this->user)) { | |
1049 unset($handlers[$id]); | |
1050 } | |
1051 } | |
1052 } | |
1053 | |
1054 /** | |
1055 * Builds all the arguments. | |
1056 * | |
1057 * @return bool | |
1058 * TRUE if the arguments were built successfully, FALSE otherwise. | |
1059 */ | |
1060 protected function _buildArguments() { | |
1061 // Initially, we want to build sorts and fields. This can change, though, | |
1062 // if we get a summary view. | |
1063 if (empty($this->argument)) { | |
1064 return TRUE; | |
1065 } | |
1066 | |
1067 // build arguments. | |
1068 $position = -1; | |
1069 $substitutions = []; | |
1070 $status = TRUE; | |
1071 | |
1072 // Get the title. | |
1073 $title = $this->display_handler->getOption('title'); | |
1074 | |
1075 // Iterate through each argument and process. | |
1076 foreach ($this->argument as $id => $arg) { | |
1077 $position++; | |
1078 $argument = $this->argument[$id]; | |
1079 | |
1080 if ($argument->broken()) { | |
1081 continue; | |
1082 } | |
1083 | |
1084 $argument->setRelationship(); | |
1085 | |
1086 $arg = isset($this->args[$position]) ? $this->args[$position] : NULL; | |
1087 $argument->position = $position; | |
1088 | |
1089 if (isset($arg) || $argument->hasDefaultArgument()) { | |
1090 if (!isset($arg)) { | |
1091 $arg = $argument->getDefaultArgument(); | |
1092 // make sure default args get put back. | |
1093 if (isset($arg)) { | |
1094 $this->args[$position] = $arg; | |
1095 } | |
1096 // remember that this argument was computed, not passed on the URL. | |
1097 $argument->is_default = TRUE; | |
1098 } | |
1099 | |
1100 // Set the argument, which will also validate that the argument can be set. | |
1101 if (!$argument->setArgument($arg)) { | |
1102 $status = $argument->validateFail($arg); | |
1103 break; | |
1104 } | |
1105 | |
1106 if ($argument->isException()) { | |
1107 $arg_title = $argument->exceptionTitle(); | |
1108 } | |
1109 else { | |
1110 $arg_title = $argument->getTitle(); | |
1111 $argument->query($this->display_handler->useGroupBy()); | |
1112 } | |
1113 | |
1114 // Add this argument's substitution | |
1115 $substitutions["{{ arguments.$id }}"] = $arg_title; | |
1116 $substitutions["{{ raw_arguments.$id }}"] = strip_tags(Html::decodeEntities($arg)); | |
1117 | |
1118 // Test to see if we should use this argument's title | |
1119 if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) { | |
1120 $title = $argument->options['title']; | |
1121 } | |
1122 } | |
1123 else { | |
1124 // determine default condition and handle. | |
1125 $status = $argument->defaultAction(); | |
1126 break; | |
1127 } | |
1128 | |
1129 // Be safe with references and loops: | |
1130 unset($argument); | |
1131 } | |
1132 | |
1133 // set the title in the build info. | |
1134 if (!empty($title)) { | |
1135 $this->build_info['title'] = $title; | |
1136 } | |
1137 | |
1138 // Store the arguments for later use. | |
1139 $this->build_info['substitutions'] = $substitutions; | |
1140 | |
1141 return $status; | |
1142 } | |
1143 | |
1144 /** | |
1145 * Gets the current query plugin. | |
1146 * | |
1147 * @return \Drupal\views\Plugin\views\query\QueryPluginBase | |
1148 * The current query plugin. | |
1149 */ | |
1150 public function getQuery() { | |
1151 if (!isset($this->query)) { | |
1152 $this->initQuery(); | |
1153 } | |
1154 | |
1155 return $this->query; | |
1156 } | |
1157 | |
1158 /** | |
1159 * Initializes the query object for the view. | |
1160 * | |
1161 * @return true | |
1162 * Always returns TRUE. | |
1163 */ | |
1164 public function initQuery() { | |
1165 if (!empty($this->query)) { | |
1166 $class = get_class($this->query); | |
1167 if ($class && $class != 'stdClass') { | |
1168 // return if query is already initialized. | |
1169 return TRUE; | |
1170 } | |
1171 } | |
1172 | |
1173 // Create and initialize the query object. | |
1174 $views_data = Views::viewsData()->get($this->storage->get('base_table')); | |
1175 $this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : ''); | |
1176 if (!empty($views_data['table']['base']['database'])) { | |
1177 $this->base_database = $views_data['table']['base']['database']; | |
1178 } | |
1179 | |
1180 $this->query = $this->display_handler->getPlugin('query'); | |
1181 return TRUE; | |
1182 } | |
1183 | |
1184 /** | |
1185 * Builds the query for the view. | |
1186 * | |
1187 * @param string $display_id | |
1188 * The display ID of the view. | |
1189 * | |
1190 * @return bool|null | |
1191 * TRUE if the view build process was successful, FALSE if setting the | |
1192 * display fails or NULL if the view has been built already. | |
1193 */ | |
1194 public function build($display_id = NULL) { | |
1195 if (!empty($this->built)) { | |
1196 return; | |
1197 } | |
1198 | |
1199 if (empty($this->current_display) || $display_id) { | |
1200 if (!$this->setDisplay($display_id)) { | |
1201 return FALSE; | |
1202 } | |
1203 } | |
1204 | |
1205 // Let modules modify the view just prior to building it. | |
1206 $module_handler = \Drupal::moduleHandler(); | |
1207 $module_handler->invokeAll('views_pre_build', [$this]); | |
1208 | |
1209 // Attempt to load from cache. | |
1210 // @todo Load a build_info from cache. | |
1211 | |
1212 $start = microtime(TRUE); | |
1213 // If that fails, let's build! | |
1214 $this->build_info = [ | |
1215 'query' => '', | |
1216 'count_query' => '', | |
1217 'query_args' => [], | |
1218 ]; | |
1219 | |
1220 $this->initQuery(); | |
1221 | |
1222 // Call a module hook and see if it wants to present us with a | |
1223 // pre-built query or instruct us not to build the query for | |
1224 // some reason. | |
1225 // @todo: Implement this. Use the same mechanism Panels uses. | |
1226 | |
1227 // Run through our handlers and ensure they have necessary information. | |
1228 $this->initHandlers(); | |
1229 | |
1230 // Let the handlers interact with each other if they really want. | |
1231 $this->_preQuery(); | |
1232 | |
1233 if ($this->display_handler->usesExposed()) { | |
1234 /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */ | |
1235 $exposed_form = $this->display_handler->getPlugin('exposed_form'); | |
1236 $this->exposed_widgets = $exposed_form->renderExposedForm(); | |
1237 if (!empty($this->build_info['abort'])) { | |
1238 $this->built = TRUE; | |
1239 // Don't execute the query, $form_state, but rendering will still be executed to display the empty text. | |
1240 $this->executed = TRUE; | |
1241 return empty($this->build_info['fail']); | |
1242 } | |
1243 } | |
1244 | |
1245 // Build all the relationships first thing. | |
1246 $this->_build('relationship'); | |
1247 | |
1248 // Set the filtering groups. | |
1249 if (!empty($this->filter)) { | |
1250 $filter_groups = $this->display_handler->getOption('filter_groups'); | |
1251 if ($filter_groups) { | |
1252 $this->query->setGroupOperator($filter_groups['operator']); | |
1253 foreach ($filter_groups['groups'] as $id => $operator) { | |
1254 $this->query->setWhereGroup($operator, $id); | |
1255 } | |
1256 } | |
1257 } | |
1258 | |
1259 // Build all the filters. | |
1260 $this->_build('filter'); | |
1261 | |
1262 $this->build_sort = TRUE; | |
1263 | |
1264 // Arguments can, in fact, cause this whole thing to abort. | |
1265 if (!$this->_buildArguments()) { | |
1266 $this->build_time = microtime(TRUE) - $start; | |
1267 $this->attachDisplays(); | |
1268 return $this->built; | |
1269 } | |
1270 | |
1271 // Initialize the style; arguments may have changed which style we use, | |
1272 // so waiting as long as possible is important. But we need to know | |
1273 // about the style when we go to build fields. | |
1274 if (!$this->initStyle()) { | |
1275 $this->build_info['fail'] = TRUE; | |
1276 return FALSE; | |
1277 } | |
1278 | |
1279 if ($this->style_plugin->usesFields()) { | |
1280 $this->_build('field'); | |
1281 } | |
1282 | |
1283 // Build our sort criteria if we were instructed to do so. | |
1284 if (!empty($this->build_sort)) { | |
1285 // Allow the style handler to deal with sorting. | |
1286 if ($this->style_plugin->buildSort()) { | |
1287 $this->_build('sort'); | |
1288 } | |
1289 // allow the plugin to build second sorts as well. | |
1290 $this->style_plugin->buildSortPost(); | |
1291 } | |
1292 | |
1293 // Allow area handlers to affect the query. | |
1294 $this->_build('header'); | |
1295 $this->_build('footer'); | |
1296 $this->_build('empty'); | |
1297 | |
1298 // Allow display handler to affect the query: | |
1299 $this->display_handler->query($this->display_handler->useGroupBy()); | |
1300 | |
1301 // Allow style handler to affect the query: | |
1302 $this->style_plugin->query($this->display_handler->useGroupBy()); | |
1303 | |
1304 // Allow exposed form to affect the query: | |
1305 if (isset($exposed_form)) { | |
1306 $exposed_form->query(); | |
1307 } | |
1308 | |
1309 if (\Drupal::config('views.settings')->get('sql_signature')) { | |
1310 $this->query->addSignature($this); | |
1311 } | |
1312 | |
1313 // Let modules modify the query just prior to finalizing it. | |
1314 $this->query->alter($this); | |
1315 | |
1316 // Only build the query if we weren't interrupted. | |
1317 if (empty($this->built)) { | |
1318 // Build the necessary info to execute the query. | |
1319 $this->query->build($this); | |
1320 } | |
1321 | |
1322 $this->built = TRUE; | |
1323 $this->build_time = microtime(TRUE) - $start; | |
1324 | |
1325 // Attach displays | |
1326 $this->attachDisplays(); | |
1327 | |
1328 // Let modules modify the view just after building it. | |
1329 $module_handler->invokeAll('views_post_build', [$this]); | |
1330 | |
1331 return TRUE; | |
1332 } | |
1333 | |
1334 /** | |
1335 * Builds an individual set of handlers. | |
1336 * | |
1337 * This is an internal method. | |
1338 * | |
1339 * @todo Some filter needs this function, even it is internal. | |
1340 * | |
1341 * @param string $key | |
1342 * The type of handlers (filter etc.) which should be iterated over to build | |
1343 * the relationship and query information. | |
1344 */ | |
1345 public function _build($key) { | |
1346 $handlers = &$this->$key; | |
1347 foreach ($handlers as $id => $data) { | |
1348 | |
1349 if (!empty($handlers[$id]) && is_object($handlers[$id])) { | |
1350 $multiple_exposed_input = [0 => NULL]; | |
1351 if ($handlers[$id]->multipleExposedInput()) { | |
1352 $multiple_exposed_input = $handlers[$id]->groupMultipleExposedInput($this->exposed_data); | |
1353 } | |
1354 foreach ($multiple_exposed_input as $group_id) { | |
1355 // Give this handler access to the exposed filter input. | |
1356 if (!empty($this->exposed_data)) { | |
1357 if ($handlers[$id]->isAGroup()) { | |
1358 $converted = $handlers[$id]->convertExposedInput($this->exposed_data, $group_id); | |
1359 $handlers[$id]->storeGroupInput($this->exposed_data, $converted); | |
1360 if (!$converted) { | |
1361 continue; | |
1362 } | |
1363 } | |
1364 $rc = $handlers[$id]->acceptExposedInput($this->exposed_data); | |
1365 $handlers[$id]->storeExposedInput($this->exposed_data, $rc); | |
1366 if (!$rc) { | |
1367 continue; | |
1368 } | |
1369 } | |
1370 $handlers[$id]->setRelationship(); | |
1371 $handlers[$id]->query($this->display_handler->useGroupBy()); | |
1372 } | |
1373 } | |
1374 } | |
1375 } | |
1376 | |
1377 /** | |
1378 * Executes the view's query. | |
1379 * | |
1380 * @param string $display_id | |
1381 * The machine name of the display, which should be executed. | |
1382 * | |
1383 * @return bool | |
1384 * TRUE if the view execution was successful, FALSE otherwise. For example, | |
1385 * an argument could stop the process. | |
1386 */ | |
1387 public function execute($display_id = NULL) { | |
1388 if (empty($this->built)) { | |
1389 if (!$this->build($display_id)) { | |
1390 return FALSE; | |
1391 } | |
1392 } | |
1393 | |
1394 if (!empty($this->executed)) { | |
1395 return TRUE; | |
1396 } | |
1397 | |
1398 // Don't allow to use deactivated displays, but display them on the live preview. | |
1399 if (!$this->display_handler->isEnabled() && empty($this->live_preview)) { | |
1400 $this->build_info['fail'] = TRUE; | |
1401 return FALSE; | |
1402 } | |
1403 | |
1404 // Let modules modify the view just prior to executing it. | |
1405 $module_handler = \Drupal::moduleHandler(); | |
1406 $module_handler->invokeAll('views_pre_execute', [$this]); | |
1407 | |
1408 // Check for already-cached results. | |
1409 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */ | |
1410 if (!empty($this->live_preview)) { | |
1411 $cache = Views::pluginManager('cache')->createInstance('none'); | |
1412 } | |
1413 else { | |
1414 $cache = $this->display_handler->getPlugin('cache'); | |
1415 } | |
1416 | |
1417 if ($cache->cacheGet('results')) { | |
1418 if ($this->pager->usePager()) { | |
1419 $this->pager->total_items = $this->total_rows; | |
1420 $this->pager->updatePageInfo(); | |
1421 } | |
1422 } | |
1423 else { | |
1424 $this->query->execute($this); | |
1425 // Enforce the array key rule as documented in | |
1426 // views_plugin_query::execute(). | |
1427 $this->result = array_values($this->result); | |
1428 $this->_postExecute(); | |
1429 $cache->cacheSet('results'); | |
1430 } | |
1431 | |
1432 // Let modules modify the view just after executing it. | |
1433 $module_handler->invokeAll('views_post_execute', [$this]); | |
1434 | |
1435 return $this->executed = TRUE; | |
1436 } | |
1437 | |
1438 /** | |
1439 * Renders this view for a certain display. | |
1440 * | |
1441 * Note: You should better use just the preview function if you want to | |
1442 * render a view. | |
1443 * | |
1444 * @param string $display_id | |
1445 * The machine name of the display, which should be rendered. | |
1446 * | |
1447 * @return array|null | |
1448 * A renderable array containing the view output or NULL if the build | |
1449 * process failed. | |
1450 */ | |
1451 public function render($display_id = NULL) { | |
1452 $this->execute($display_id); | |
1453 | |
1454 // Check to see if the build failed. | |
1455 if (!empty($this->build_info['fail'])) { | |
1456 return; | |
1457 } | |
1458 if (!empty($this->build_info['denied'])) { | |
1459 return; | |
1460 } | |
1461 | |
1462 /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */ | |
1463 $exposed_form = $this->display_handler->getPlugin('exposed_form'); | |
1464 $exposed_form->preRender($this->result); | |
1465 | |
1466 $module_handler = \Drupal::moduleHandler(); | |
1467 | |
1468 // @TODO In the longrun, it would be great to execute a view without | |
1469 // the theme system at all. See https://www.drupal.org/node/2322623. | |
1470 $active_theme = \Drupal::theme()->getActiveTheme(); | |
1471 $themes = array_keys($active_theme->getBaseThemes()); | |
1472 $themes[] = $active_theme->getName(); | |
1473 | |
1474 // Check for already-cached output. | |
1475 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */ | |
1476 if (!empty($this->live_preview)) { | |
1477 $cache = Views::pluginManager('cache')->createInstance('none'); | |
1478 } | |
1479 else { | |
1480 $cache = $this->display_handler->getPlugin('cache'); | |
1481 } | |
1482 | |
1483 // Run preRender for the pager as it might change the result. | |
1484 if (!empty($this->pager)) { | |
1485 $this->pager->preRender($this->result); | |
1486 } | |
1487 | |
1488 // Initialize the style plugin. | |
1489 $this->initStyle(); | |
1490 | |
1491 if (!isset($this->response)) { | |
1492 // Set the response so other parts can alter it. | |
1493 $this->response = new Response('', 200); | |
1494 } | |
1495 | |
1496 // Give field handlers the opportunity to perform additional queries | |
1497 // using the entire resultset prior to rendering. | |
1498 if ($this->style_plugin->usesFields()) { | |
1499 foreach ($this->field as $id => $handler) { | |
1500 if (!empty($this->field[$id])) { | |
1501 $this->field[$id]->preRender($this->result); | |
1502 } | |
1503 } | |
1504 } | |
1505 | |
1506 $this->style_plugin->preRender($this->result); | |
1507 | |
1508 // Let each area handler have access to the result set. | |
1509 $areas = ['header', 'footer']; | |
1510 // Only call preRender() on the empty handlers if the result is empty. | |
1511 if (empty($this->result)) { | |
1512 $areas[] = 'empty'; | |
1513 } | |
1514 foreach ($areas as $area) { | |
1515 foreach ($this->{$area} as $handler) { | |
1516 $handler->preRender($this->result); | |
1517 } | |
1518 } | |
1519 | |
1520 // Let modules modify the view just prior to rendering it. | |
1521 $module_handler->invokeAll('views_pre_render', [$this]); | |
1522 | |
1523 // Let the themes play too, because pre render is a very themey thing. | |
1524 foreach ($themes as $theme_name) { | |
1525 $function = $theme_name . '_views_pre_render'; | |
1526 if (function_exists($function)) { | |
1527 $function($this); | |
1528 } | |
1529 } | |
1530 | |
1531 $this->display_handler->output = $this->display_handler->render(); | |
1532 | |
1533 $exposed_form->postRender($this->display_handler->output); | |
1534 | |
1535 $cache->postRender($this->display_handler->output); | |
1536 | |
1537 // Let modules modify the view output after it is rendered. | |
1538 $module_handler->invokeAll('views_post_render', [$this, &$this->display_handler->output, $cache]); | |
1539 | |
1540 // Let the themes play too, because post render is a very themey thing. | |
1541 foreach ($themes as $theme_name) { | |
1542 $function = $theme_name . '_views_post_render'; | |
1543 if (function_exists($function)) { | |
1544 $function($this, $this->display_handler->output, $cache); | |
1545 } | |
1546 } | |
1547 | |
1548 return $this->display_handler->output; | |
1549 } | |
1550 | |
1551 /** | |
1552 * Gets the cache tags associated with the executed view. | |
1553 * | |
1554 * Note: The cache plugin controls the used tags, so you can override it, if | |
1555 * needed. | |
1556 * | |
1557 * @return string[] | |
1558 * An array of cache tags. | |
1559 */ | |
1560 public function getCacheTags() { | |
1561 $this->initDisplay(); | |
1562 /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */ | |
1563 $cache = $this->display_handler->getPlugin('cache'); | |
1564 return $cache->getCacheTags(); | |
1565 } | |
1566 | |
1567 /** | |
1568 * Builds the render array outline for the given display. | |
1569 * | |
1570 * This render array has a #pre_render callback which will call | |
1571 * ::executeDisplay in order to actually execute the view and then build the | |
1572 * final render array structure. | |
1573 * | |
1574 * @param string $display_id | |
1575 * The display ID. | |
1576 * @param array $args | |
1577 * An array of arguments passed along to the view. | |
1578 * @param bool $cache | |
1579 * (optional) Should the result be render cached. | |
1580 * | |
1581 * @return array|null | |
1582 * A renderable array with #type 'view' or NULL if the display ID was | |
1583 * invalid. | |
1584 */ | |
1585 public function buildRenderable($display_id = NULL, $args = [], $cache = TRUE) { | |
1586 // @todo Extract that into a generic method. | |
1587 if (empty($this->current_display) || $this->current_display != $this->chooseDisplay($display_id)) { | |
1588 if (!$this->setDisplay($display_id)) { | |
1589 return NULL; | |
1590 } | |
1591 } | |
1592 | |
1593 return $this->display_handler->buildRenderable($args, $cache); | |
1594 } | |
1595 | |
1596 /** | |
1597 * Executes the given display, with the given arguments. | |
1598 * | |
1599 * To be called externally by whatever mechanism invokes the view, | |
1600 * such as a page callback, hook_block, etc. | |
1601 * | |
1602 * This function should NOT be used by anything external as this | |
1603 * returns data in the format specified by the display. It can also | |
1604 * have other side effects that are only intended for the 'proper' | |
1605 * use of the display, such as setting page titles. | |
1606 * | |
1607 * If you simply want to view the display, use View::preview() instead. | |
1608 * | |
1609 * @param string $display_id | |
1610 * The display ID of the view to be executed. | |
1611 * @param string[] $args | |
1612 * The arguments to be passed to the view. | |
1613 * | |
1614 * @return array|null | |
1615 * A renderable array containing the view output or NULL if the display ID | |
1616 * of the view to be executed doesn't exist. | |
1617 */ | |
1618 public function executeDisplay($display_id = NULL, $args = []) { | |
1619 if (empty($this->current_display) || $this->current_display != $this->chooseDisplay($display_id)) { | |
1620 if (!$this->setDisplay($display_id)) { | |
1621 return NULL; | |
1622 } | |
1623 } | |
1624 | |
1625 $this->preExecute($args); | |
1626 | |
1627 // Execute the view | |
1628 $output = $this->display_handler->execute(); | |
1629 | |
1630 $this->postExecute(); | |
1631 return $output; | |
1632 } | |
1633 | |
1634 /** | |
1635 * Previews the given display, with the given arguments. | |
1636 * | |
1637 * To be called externally, probably by an AJAX handler of some flavor. | |
1638 * Can also be called when views are embedded, as this guarantees | |
1639 * normalized output. | |
1640 * | |
1641 * This function does not do any access checks on the view. It is the | |
1642 * responsibility of the caller to check $view->access() or implement other | |
1643 * access logic. To render the view normally with access checks, use | |
1644 * views_embed_view() instead. | |
1645 * | |
1646 * @return array|null | |
1647 * A renderable array containing the view output or NULL if the display ID | |
1648 * of the view to be executed doesn't exist. | |
1649 */ | |
1650 public function preview($display_id = NULL, $args = []) { | |
1651 if (empty($this->current_display) || ((!empty($display_id)) && $this->current_display != $display_id)) { | |
1652 if (!$this->setDisplay($display_id)) { | |
1653 return FALSE; | |
1654 } | |
1655 } | |
1656 | |
1657 $this->preview = TRUE; | |
1658 $this->preExecute($args); | |
1659 // Preview the view. | |
1660 $output = $this->display_handler->preview(); | |
1661 | |
1662 $this->postExecute(); | |
1663 return $output; | |
1664 } | |
1665 | |
1666 /** | |
1667 * Runs attachments and lets the display do what it needs to before running. | |
1668 * | |
1669 * @param array $args | |
1670 * An array of arguments from the URL that can be used by the view. | |
1671 */ | |
1672 public function preExecute($args = []) { | |
1673 $this->old_view[] = views_get_current_view(); | |
1674 views_set_current_view($this); | |
1675 $display_id = $this->current_display; | |
1676 | |
1677 // Prepare the view with the information we have, but only if we were | |
1678 // passed arguments, as they may have been set previously. | |
1679 if ($args) { | |
1680 $this->setArguments($args); | |
1681 } | |
1682 | |
1683 // Let modules modify the view just prior to executing it. | |
1684 \Drupal::moduleHandler()->invokeAll('views_pre_view', [$this, $display_id, &$this->args]); | |
1685 | |
1686 // Allow hook_views_pre_view() to set the dom_id, then ensure it is set. | |
1687 $this->dom_id = !empty($this->dom_id) ? $this->dom_id : hash('sha256', $this->storage->id() . REQUEST_TIME . mt_rand()); | |
1688 | |
1689 // Allow the display handler to set up for execution | |
1690 $this->display_handler->preExecute(); | |
1691 } | |
1692 | |
1693 /** | |
1694 * Unsets the current view, mostly. | |
1695 */ | |
1696 public function postExecute() { | |
1697 // unset current view so we can be properly destructed later on. | |
1698 // Return the previous value in case we're an attachment. | |
1699 | |
1700 if ($this->old_view) { | |
1701 $old_view = array_pop($this->old_view); | |
1702 } | |
1703 | |
1704 views_set_current_view(isset($old_view) ? $old_view : FALSE); | |
1705 } | |
1706 | |
1707 /** | |
1708 * Runs attachment displays for the view. | |
1709 */ | |
1710 public function attachDisplays() { | |
1711 if (!empty($this->is_attachment)) { | |
1712 return; | |
1713 } | |
1714 | |
1715 if (!$this->display_handler->acceptAttachments()) { | |
1716 return; | |
1717 } | |
1718 | |
1719 $this->is_attachment = TRUE; | |
1720 // Find out which other displays attach to the current one. | |
1721 foreach ($this->display_handler->getAttachedDisplays() as $id) { | |
1722 $display_handler = $this->displayHandlers->get($id); | |
1723 // Only attach enabled attachments. | |
1724 if ($display_handler->isEnabled()) { | |
1725 $cloned_view = Views::executableFactory()->get($this->storage); | |
1726 $display_handler->attachTo($cloned_view, $this->current_display, $this->element); | |
1727 } | |
1728 } | |
1729 $this->is_attachment = FALSE; | |
1730 } | |
1731 | |
1732 /** | |
1733 * Determines if the given user has access to the view. | |
1734 * | |
1735 * Note that this sets the display handler if it hasn't been set. | |
1736 * | |
1737 * @param string $displays | |
1738 * The machine name of the display. | |
1739 * @param \Drupal\Core\Session\AccountInterface $account | |
1740 * The user object. | |
1741 * | |
1742 * @return bool | |
1743 * TRUE if the user has access to the view, FALSE otherwise. | |
1744 */ | |
1745 public function access($displays = NULL, $account = NULL) { | |
1746 // No one should have access to disabled views. | |
1747 if (!$this->storage->status()) { | |
1748 return FALSE; | |
1749 } | |
1750 | |
1751 if (!isset($this->current_display)) { | |
1752 $this->initDisplay(); | |
1753 } | |
1754 | |
1755 if (!$account) { | |
1756 $account = $this->user; | |
1757 } | |
1758 | |
1759 // We can't use choose_display() here because that function | |
1760 // calls this one. | |
1761 $displays = (array) $displays; | |
1762 foreach ($displays as $display_id) { | |
1763 if ($this->displayHandlers->has($display_id)) { | |
1764 if (($display = $this->displayHandlers->get($display_id)) && $display->access($account)) { | |
1765 return TRUE; | |
1766 } | |
1767 } | |
1768 } | |
1769 | |
1770 return FALSE; | |
1771 } | |
1772 | |
1773 /** | |
1774 * Sets the used response object of the view. | |
1775 * | |
1776 * @param \Symfony\Component\HttpFoundation\Response $response | |
1777 * The response object which should be set. | |
1778 */ | |
1779 public function setResponse(Response $response) { | |
1780 $this->response = $response; | |
1781 } | |
1782 | |
1783 /** | |
1784 * Gets the response object used by the view. | |
1785 * | |
1786 * @return \Symfony\Component\HttpFoundation\Response | |
1787 * The response object of the view. | |
1788 */ | |
1789 public function getResponse() { | |
1790 if (!isset($this->response)) { | |
1791 $this->response = new Response(); | |
1792 } | |
1793 return $this->response; | |
1794 } | |
1795 | |
1796 /** | |
1797 * Sets the request object. | |
1798 * | |
1799 * @param \Symfony\Component\HttpFoundation\Request $request | |
1800 * The request object. | |
1801 */ | |
1802 public function setRequest(Request $request) { | |
1803 $this->request = $request; | |
1804 } | |
1805 | |
1806 /** | |
1807 * Gets the request object. | |
1808 * | |
1809 * @return \Symfony\Component\HttpFoundation\Request | |
1810 * The request object. | |
1811 */ | |
1812 public function getRequest() { | |
1813 return $this->request; | |
1814 } | |
1815 | |
1816 /** | |
1817 * Gets the view's current title. | |
1818 * | |
1819 * This can change depending upon how it was built. | |
1820 * | |
1821 * @return string|false | |
1822 * The view title, FALSE if the display is not set. | |
1823 */ | |
1824 public function getTitle() { | |
1825 if (empty($this->display_handler)) { | |
1826 if (!$this->setDisplay('default')) { | |
1827 return FALSE; | |
1828 } | |
1829 } | |
1830 | |
1831 // During building, we might find a title override. If so, use it. | |
1832 if (!empty($this->build_info['title'])) { | |
1833 $title = $this->build_info['title']; | |
1834 } | |
1835 else { | |
1836 $title = $this->display_handler->getOption('title'); | |
1837 } | |
1838 | |
1839 // Allow substitutions from the first row. | |
1840 if ($this->initStyle()) { | |
1841 $title = $this->style_plugin->tokenizeValue($title, 0); | |
1842 } | |
1843 return $title; | |
1844 } | |
1845 | |
1846 /** | |
1847 * Overrides the view's current title. | |
1848 * | |
1849 * The tokens in the title get's replaced before rendering. | |
1850 * | |
1851 * @return true | |
1852 * Always returns TRUE. | |
1853 */ | |
1854 public function setTitle($title) { | |
1855 $this->build_info['title'] = $title; | |
1856 return TRUE; | |
1857 } | |
1858 | |
1859 /** | |
1860 * Forces the view to build a title. | |
1861 */ | |
1862 public function buildTitle() { | |
1863 $this->initDisplay(); | |
1864 | |
1865 if (empty($this->built)) { | |
1866 $this->initQuery(); | |
1867 } | |
1868 | |
1869 $this->initHandlers(); | |
1870 | |
1871 $this->_buildArguments(); | |
1872 } | |
1873 | |
1874 /** | |
1875 * Determines whether you can link to the view or a particular display. | |
1876 * | |
1877 * Some displays (e.g. block displays) do not have their own route, but may | |
1878 * optionally provide a link to another display that does have a route. | |
1879 * | |
1880 * @param array $args | |
1881 * (optional) The arguments. | |
1882 * @param string $display_id | |
1883 * (optional) The display ID. The current display will be used by default. | |
1884 * | |
1885 * @return bool | |
1886 * TRUE if the current display has a valid route available, FALSE otherwise. | |
1887 */ | |
1888 public function hasUrl($args = NULL, $display_id = NULL) { | |
1889 if (!empty($this->override_url)) { | |
1890 return TRUE; | |
1891 } | |
1892 | |
1893 // If the display has a valid route available (either its own or for a | |
1894 // linked display), then we can provide a URL for it. | |
1895 $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay(); | |
1896 if (!$display_handler instanceof DisplayRouterInterface) { | |
1897 return FALSE; | |
1898 } | |
1899 | |
1900 // Look up the route name to make sure it exists. The name may exist, but | |
1901 // not be available yet in some instances when editing a view and doing | |
1902 // a live preview. | |
1903 $provider = \Drupal::service('router.route_provider'); | |
1904 try { | |
1905 $provider->getRouteByName($display_handler->getRouteName()); | |
1906 } | |
1907 catch (RouteNotFoundException $e) { | |
1908 return FALSE; | |
1909 } | |
1910 | |
1911 return TRUE; | |
1912 } | |
1913 | |
1914 /** | |
1915 * Gets the URL for the current view. | |
1916 * | |
1917 * This URL will be adjusted for arguments. | |
1918 * | |
1919 * @param array $args | |
1920 * (optional) Passed in arguments. | |
1921 * @param string $display_id | |
1922 * (optional) Specify the display ID to link to, fallback to the current ID. | |
1923 * | |
1924 * @return \Drupal\Core\Url | |
1925 * The URL of the current view. | |
1926 * | |
1927 * @throws \InvalidArgumentException | |
1928 * Thrown when the current view doesn't have a route available. | |
1929 */ | |
1930 public function getUrl($args = NULL, $display_id = NULL) { | |
1931 if (!empty($this->override_url)) { | |
1932 return $this->override_url; | |
1933 } | |
1934 | |
1935 $display_handler = $this->displayHandlers->get($display_id ?: $this->current_display)->getRoutedDisplay(); | |
1936 if (!$display_handler instanceof DisplayRouterInterface) { | |
1937 throw new \InvalidArgumentException('You cannot create a URL to a display without routes.'); | |
1938 } | |
1939 | |
1940 if (!isset($args)) { | |
1941 $args = $this->args; | |
1942 | |
1943 // Exclude arguments that were computed, not passed on the URL. | |
1944 $position = 0; | |
1945 if (!empty($this->argument)) { | |
1946 foreach ($this->argument as $argument) { | |
1947 if (!empty($argument->is_default) && !empty($argument->options['default_argument_skip_url'])) { | |
1948 unset($args[$position]); | |
1949 } | |
1950 $position++; | |
1951 } | |
1952 } | |
1953 } | |
1954 | |
1955 $path = $this->getPath(); | |
1956 | |
1957 // Don't bother working if there's nothing to do: | |
1958 if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) { | |
1959 return $display_handler->getUrlInfo(); | |
1960 } | |
1961 | |
1962 $argument_keys = isset($this->argument) ? array_keys($this->argument) : []; | |
1963 $id = current($argument_keys); | |
1964 | |
1965 /** @var \Drupal\Core\Url $url */ | |
1966 $url = $display_handler->getUrlInfo(); | |
1967 $route = $this->routeProvider->getRouteByName($url->getRouteName()); | |
1968 | |
1969 $variables = $route->compile()->getVariables(); | |
1970 $parameters = $url->getRouteParameters(); | |
1971 | |
1972 foreach ($variables as $variable_name) { | |
1973 if (empty($args)) { | |
1974 // Try to never put % in a URL; use the wildcard instead. | |
1975 if ($id && !empty($this->argument[$id]->options['exception']['value'])) { | |
1976 $parameters[$variable_name] = $this->argument[$id]->options['exception']['value']; | |
1977 } | |
1978 else { | |
1979 // Provide some fallback in case no exception value could be found. | |
1980 $parameters[$variable_name] = '*'; | |
1981 } | |
1982 } | |
1983 else { | |
1984 $parameters[$variable_name] = array_shift($args); | |
1985 } | |
1986 | |
1987 if ($id) { | |
1988 $id = next($argument_keys); | |
1989 } | |
1990 } | |
1991 | |
1992 $url->setRouteParameters($parameters); | |
1993 return $url; | |
1994 } | |
1995 | |
1996 /** | |
1997 * Gets the Url object associated with the display handler. | |
1998 * | |
1999 * @param string $display_id | |
2000 * (optional) The display ID (used only to detail an exception). | |
2001 * | |
2002 * @return \Drupal\Core\Url | |
2003 * The display handlers URL object. | |
2004 * | |
2005 * @throws \InvalidArgumentException | |
2006 * Thrown when the display plugin does not have a URL to return. | |
2007 */ | |
2008 public function getUrlInfo($display_id = '') { | |
2009 $this->initDisplay(); | |
2010 if (!$this->display_handler instanceof DisplayRouterInterface) { | |
2011 throw new \InvalidArgumentException("You cannot generate a URL for the display '$display_id'"); | |
2012 } | |
2013 return $this->display_handler->getUrlInfo(); | |
2014 } | |
2015 | |
2016 /** | |
2017 * Gets the base path used for this view. | |
2018 * | |
2019 * @return string|false | |
2020 * The base path used for the view or FALSE if setting the display fails. | |
2021 */ | |
2022 public function getPath() { | |
2023 if (!empty($this->override_path)) { | |
2024 return $this->override_path; | |
2025 } | |
2026 | |
2027 if (empty($this->display_handler)) { | |
2028 if (!$this->setDisplay('default')) { | |
2029 return FALSE; | |
2030 } | |
2031 } | |
2032 return $this->display_handler->getPath(); | |
2033 } | |
2034 | |
2035 /** | |
2036 * Gets the current user. | |
2037 * | |
2038 * Views plugins can receive the current user in order to not need dependency | |
2039 * injection. | |
2040 * | |
2041 * @return \Drupal\Core\Session\AccountInterface | |
2042 * The current user. | |
2043 */ | |
2044 public function getUser() { | |
2045 return $this->user; | |
2046 } | |
2047 | |
2048 /** | |
2049 * Creates a duplicate ViewExecutable object. | |
2050 * | |
2051 * Makes a copy of this view that has been sanitized of handlers, any runtime | |
2052 * data, ID, and UUID. | |
2053 */ | |
2054 public function createDuplicate() { | |
2055 return $this->storage->createDuplicate()->getExecutable(); | |
2056 } | |
2057 | |
2058 /** | |
2059 * Unsets references so that a $view object may be properly garbage collected. | |
2060 */ | |
2061 public function destroy() { | |
2062 foreach ($this::getHandlerTypes() as $type => $info) { | |
2063 if (isset($this->$type)) { | |
2064 foreach ($this->{$type} as $handler) { | |
2065 $handler->destroy(); | |
2066 } | |
2067 } | |
2068 } | |
2069 | |
2070 if (isset($this->style_plugin)) { | |
2071 $this->style_plugin->destroy(); | |
2072 } | |
2073 | |
2074 $reflection = new \ReflectionClass($this); | |
2075 $defaults = $reflection->getDefaultProperties(); | |
2076 // The external dependencies should not be reset. This is not generated by | |
2077 // the execution of a view. | |
2078 unset( | |
2079 $defaults['storage'], | |
2080 $defaults['user'], | |
2081 $defaults['request'], | |
2082 $defaults['routeProvider'], | |
2083 $defaults['viewsData'] | |
2084 ); | |
2085 | |
2086 foreach ($defaults as $property => $default) { | |
2087 $this->{$property} = $default; | |
2088 } | |
2089 } | |
2090 | |
2091 /** | |
2092 * Makes sure the view is completely valid. | |
2093 * | |
2094 * @return array | |
2095 * An array of error strings. This will be empty if there are no validation | |
2096 * errors. | |
2097 */ | |
2098 public function validate() { | |
2099 $errors = []; | |
2100 | |
2101 $this->initDisplay(); | |
2102 $current_display = $this->current_display; | |
2103 | |
2104 foreach ($this->displayHandlers as $id => $display) { | |
2105 if (!empty($display)) { | |
2106 if (!empty($display->display['deleted'])) { | |
2107 continue; | |
2108 } | |
2109 | |
2110 $result = $this->displayHandlers->get($id)->validate(); | |
2111 if (!empty($result) && is_array($result)) { | |
2112 $errors[$id] = $result; | |
2113 } | |
2114 } | |
2115 } | |
2116 | |
2117 $this->setDisplay($current_display); | |
2118 | |
2119 return $errors; | |
2120 } | |
2121 | |
2122 /** | |
2123 * Provides a list of views handler types used in a view. | |
2124 * | |
2125 * This also provides some information about the views handler types. | |
2126 * | |
2127 * @return array | |
2128 * An array of associative arrays containing: | |
2129 * - title: The title of the handler type. | |
2130 * - ltitle: The lowercase title of the handler type. | |
2131 * - stitle: A singular title of the handler type. | |
2132 * - lstitle: A singular lowercase title of the handler type. | |
2133 * - plural: Plural version of the handler type. | |
2134 * - (optional) type: The actual internal used handler type. This key is | |
2135 * just used for header,footer,empty to link to the internal type: area. | |
2136 */ | |
2137 public static function getHandlerTypes() { | |
2138 return Views::getHandlerTypes(); | |
2139 } | |
2140 | |
2141 /** | |
2142 * Returns the valid types of plugins that can be used. | |
2143 * | |
2144 * @return array | |
2145 * An array of plugin type strings. | |
2146 */ | |
2147 public static function getPluginTypes($type = NULL) { | |
2148 return Views::getPluginTypes($type); | |
2149 } | |
2150 | |
2151 /** | |
2152 * Adds an instance of a handler to the view. | |
2153 * | |
2154 * Items may be fields, filters, sort criteria, or arguments. | |
2155 * | |
2156 * @param string $display_id | |
2157 * The machine name of the display. | |
2158 * @param string $type | |
2159 * The type of handler being added. | |
2160 * @param string $table | |
2161 * The name of the table this handler is from. | |
2162 * @param string $field | |
2163 * The name of the field this handler is from. | |
2164 * @param array $options | |
2165 * (optional) Extra options for this instance. Defaults to an empty array. | |
2166 * @param string $id | |
2167 * (optional) A unique ID for this handler instance. Defaults to NULL, in | |
2168 * which case one will be generated. | |
2169 * | |
2170 * @return string | |
2171 * The unique ID for this handler instance. | |
2172 */ | |
2173 public function addHandler($display_id, $type, $table, $field, $options = [], $id = NULL) { | |
2174 $types = $this::getHandlerTypes(); | |
2175 $this->setDisplay($display_id); | |
2176 | |
2177 $data = $this->viewsData->get($table); | |
2178 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']); | |
2179 | |
2180 if (empty($id)) { | |
2181 $id = $this->generateHandlerId($field, $fields); | |
2182 } | |
2183 | |
2184 // If the desired type is not found, use the original value directly. | |
2185 $handler_type = !empty($types[$type]['type']) ? $types[$type]['type'] : $type; | |
2186 | |
2187 $fields[$id] = [ | |
2188 'id' => $id, | |
2189 'table' => $table, | |
2190 'field' => $field, | |
2191 ] + $options; | |
2192 | |
2193 if (isset($data['table']['entity type'])) { | |
2194 $fields[$id]['entity_type'] = $data['table']['entity type']; | |
2195 } | |
2196 if (isset($data[$field]['entity field'])) { | |
2197 $fields[$id]['entity_field'] = $data[$field]['entity field']; | |
2198 } | |
2199 | |
2200 // Load the plugin ID if available. | |
2201 if (isset($data[$field][$handler_type]['id'])) { | |
2202 $fields[$id]['plugin_id'] = $data[$field][$handler_type]['id']; | |
2203 } | |
2204 | |
2205 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields); | |
2206 | |
2207 return $id; | |
2208 } | |
2209 | |
2210 /** | |
2211 * Generates a unique ID for an handler instance. | |
2212 * | |
2213 * These handler instances are typically fields, filters, sort criteria, or | |
2214 * arguments. | |
2215 * | |
2216 * @param string $requested_id | |
2217 * The requested ID for the handler instance. | |
2218 * @param array $existing_items | |
2219 * An array of existing handler instances, keyed by their IDs. | |
2220 * | |
2221 * @return string | |
2222 * A unique ID. This will be equal to $requested_id if no handler instance | |
2223 * with that ID already exists. Otherwise, it will be appended with an | |
2224 * integer to make it unique, e.g., "{$requested_id}_1", | |
2225 * "{$requested_id}_2", etc. | |
2226 */ | |
2227 public static function generateHandlerId($requested_id, $existing_items) { | |
2228 $count = 0; | |
2229 $id = $requested_id; | |
2230 while (!empty($existing_items[$id])) { | |
2231 $id = $requested_id . '_' . ++$count; | |
2232 } | |
2233 return $id; | |
2234 } | |
2235 | |
2236 /** | |
2237 * Gets an array of handler instances for the current display. | |
2238 * | |
2239 * @param string $type | |
2240 * The type of handlers to retrieve. | |
2241 * @param string $display_id | |
2242 * (optional) A specific display machine name to use. If NULL, the current | |
2243 * display will be used. | |
2244 * | |
2245 * @return array | |
2246 * An array of handler instances of a given type for this display. | |
2247 */ | |
2248 public function getHandlers($type, $display_id = NULL) { | |
2249 $old_display_id = !empty($this->current_display) ? $this->current_display : 'default'; | |
2250 | |
2251 $this->setDisplay($display_id); | |
2252 | |
2253 if (!isset($display_id)) { | |
2254 $display_id = $this->current_display; | |
2255 } | |
2256 | |
2257 // Get info about the types so we can get the right data. | |
2258 $types = static::getHandlerTypes(); | |
2259 | |
2260 $handlers = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']); | |
2261 | |
2262 // Restore initial display id (if any) or set to 'default'. | |
2263 if ($display_id != $old_display_id) { | |
2264 $this->setDisplay($old_display_id); | |
2265 } | |
2266 return $handlers; | |
2267 } | |
2268 | |
2269 /** | |
2270 * Gets the configuration of a handler instance on a given display. | |
2271 * | |
2272 * @param string $display_id | |
2273 * The machine name of the display. | |
2274 * @param string $type | |
2275 * The type of handler to retrieve. | |
2276 * @param string $id | |
2277 * The ID of the handler to retrieve. | |
2278 * | |
2279 * @return array|null | |
2280 * Either the handler instance's configuration, or NULL if the handler is | |
2281 * not used on the display. | |
2282 */ | |
2283 public function getHandler($display_id, $type, $id) { | |
2284 // Get info about the types so we can get the right data. | |
2285 $types = static::getHandlerTypes(); | |
2286 // Initialize the display | |
2287 $this->setDisplay($display_id); | |
2288 | |
2289 // Get the existing configuration | |
2290 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']); | |
2291 | |
2292 return isset($fields[$id]) ? $fields[$id] : NULL; | |
2293 } | |
2294 | |
2295 /** | |
2296 * Sets the configuration of a handler instance on a given display. | |
2297 * | |
2298 * @param string $display_id | |
2299 * The machine name of the display. | |
2300 * @param string $type | |
2301 * The type of handler being set. | |
2302 * @param string $id | |
2303 * The ID of the handler being set. | |
2304 * @param array|null $item | |
2305 * An array of configuration for a handler, or NULL to remove this instance. | |
2306 * | |
2307 * @see set_item_option() | |
2308 */ | |
2309 public function setHandler($display_id, $type, $id, $item) { | |
2310 // Get info about the types so we can get the right data. | |
2311 $types = static::getHandlerTypes(); | |
2312 // Initialize the display. | |
2313 $this->setDisplay($display_id); | |
2314 | |
2315 // Get the existing configuration. | |
2316 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']); | |
2317 if (isset($item)) { | |
2318 $fields[$id] = $item; | |
2319 } | |
2320 | |
2321 // Store. | |
2322 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields); | |
2323 } | |
2324 | |
2325 /** | |
2326 * Removes configuration for a handler instance on a given display. | |
2327 * | |
2328 * @param string $display_id | |
2329 * The machine name of the display. | |
2330 * @param string $type | |
2331 * The type of handler being removed. | |
2332 * @param string $id | |
2333 * The ID of the handler being removed. | |
2334 */ | |
2335 public function removeHandler($display_id, $type, $id) { | |
2336 // Get info about the types so we can get the right data. | |
2337 $types = static::getHandlerTypes(); | |
2338 // Initialize the display. | |
2339 $this->setDisplay($display_id); | |
2340 | |
2341 // Get the existing configuration. | |
2342 $fields = $this->displayHandlers->get($display_id)->getOption($types[$type]['plural']); | |
2343 // Unset the item. | |
2344 unset($fields[$id]); | |
2345 | |
2346 // Store. | |
2347 $this->displayHandlers->get($display_id)->setOption($types[$type]['plural'], $fields); | |
2348 } | |
2349 | |
2350 /** | |
2351 * Sets an option on a handler instance. | |
2352 * | |
2353 * Use this only if you have just 1 or 2 options to set; if you have many, | |
2354 * consider getting the handler instance, adding the options and using | |
2355 * set_item() directly. | |
2356 * | |
2357 * @param string $display_id | |
2358 * The machine name of the display. | |
2359 * @param string $type | |
2360 * The type of handler being set. | |
2361 * @param string $id | |
2362 * The ID of the handler being set. | |
2363 * @param string $option | |
2364 * The configuration key for the value being set. | |
2365 * @param mixed $value | |
2366 * The value being set. | |
2367 * | |
2368 * @see set_item() | |
2369 */ | |
2370 public function setHandlerOption($display_id, $type, $id, $option, $value) { | |
2371 $item = $this->getHandler($display_id, $type, $id); | |
2372 $item[$option] = $value; | |
2373 $this->setHandler($display_id, $type, $id, $item); | |
2374 } | |
2375 | |
2376 /** | |
2377 * Enables admin links on the rendered view. | |
2378 * | |
2379 * @param bool $show_admin_links | |
2380 * TRUE if the admin links should be shown. | |
2381 */ | |
2382 public function setShowAdminLinks($show_admin_links) { | |
2383 $this->showAdminLinks = (bool) $show_admin_links; | |
2384 } | |
2385 | |
2386 /** | |
2387 * Returns whether admin links should be rendered on the view. | |
2388 * | |
2389 * @return bool | |
2390 * TRUE if admin links should be rendered, else FALSE. | |
2391 */ | |
2392 public function getShowAdminLinks() { | |
2393 if (!isset($this->showAdminLinks)) { | |
2394 return $this->getDisplay()->getOption('show_admin_links'); | |
2395 } | |
2396 return $this->showAdminLinks; | |
2397 } | |
2398 | |
2399 /** | |
2400 * Merges all plugin default values for each display. | |
2401 */ | |
2402 public function mergeDefaults() { | |
2403 $this->initDisplay(); | |
2404 // Initialize displays and merge all plugin defaults. | |
2405 foreach ($this->displayHandlers as $display) { | |
2406 $display->mergeDefaults(); | |
2407 } | |
2408 } | |
2409 | |
2410 /** | |
2411 * Provides a full array of possible theme functions to try for a given hook. | |
2412 * | |
2413 * @param string $hook | |
2414 * The hook to use. This is the base theme/template name. | |
2415 * | |
2416 * @return array | |
2417 * An array of theme hook suggestions. | |
2418 */ | |
2419 public function buildThemeFunctions($hook) { | |
2420 $themes = []; | |
2421 $display = isset($this->display_handler) ? $this->display_handler->display : NULL; | |
2422 $id = $this->storage->id(); | |
2423 | |
2424 if ($display) { | |
2425 $themes[] = $hook . '__' . $id . '__' . $display['id']; | |
2426 $themes[] = $hook . '__' . $display['id']; | |
2427 // Add theme suggestions for each single tag. | |
2428 foreach (Tags::explode($this->storage->get('tag')) as $tag) { | |
2429 $themes[] = $hook . '__' . preg_replace('/[^a-z0-9]/', '_', strtolower($tag)); | |
2430 } | |
2431 | |
2432 if ($display['id'] != $display['display_plugin']) { | |
2433 $themes[] = $hook . '__' . $id . '__' . $display['display_plugin']; | |
2434 $themes[] = $hook . '__' . $display['display_plugin']; | |
2435 } | |
2436 } | |
2437 $themes[] = $hook . '__' . $id; | |
2438 $themes[] = $hook; | |
2439 | |
2440 return $themes; | |
2441 } | |
2442 | |
2443 /** | |
2444 * Determines if this view has form elements. | |
2445 * | |
2446 * @return bool | |
2447 * TRUE if this view contains handlers with views form implementations, | |
2448 * FALSE otherwise. | |
2449 */ | |
2450 public function hasFormElements() { | |
2451 foreach ($this->field as $field) { | |
2452 if (property_exists($field, 'views_form_callback') || method_exists($field, 'viewsForm')) { | |
2453 return TRUE; | |
2454 } | |
2455 } | |
2456 $area_handlers = array_merge(array_values($this->header), array_values($this->footer)); | |
2457 $empty = empty($this->result); | |
2458 foreach ($area_handlers as $area) { | |
2459 if (method_exists($area, 'viewsForm') && !$area->viewsFormEmpty($empty)) { | |
2460 return TRUE; | |
2461 } | |
2462 } | |
2463 | |
2464 return FALSE; | |
2465 } | |
2466 | |
2467 /** | |
2468 * Gets dependencies for the view. | |
2469 * | |
2470 * @see \Drupal\views\Entity\View::calculateDependencies() | |
2471 * @see \Drupal\views\Entity\View::getDependencies() | |
2472 * | |
2473 * @return array | |
2474 * An array of dependencies grouped by type (module, theme, entity). | |
2475 */ | |
2476 public function getDependencies() { | |
2477 return $this->storage->calculateDependencies()->getDependencies(); | |
2478 } | |
2479 | |
2480 /** | |
2481 * Magic method implementation to serialize the view executable. | |
2482 * | |
2483 * @return array | |
2484 * The names of all variables that should be serialized. | |
2485 */ | |
2486 public function __sleep() { | |
2487 // Limit to only the required data which is needed to properly restore the | |
2488 // state during unserialization. | |
2489 $this->serializationData = [ | |
2490 'storage' => $this->storage->id(), | |
2491 'views_data' => $this->viewsData->_serviceId, | |
2492 'route_provider' => $this->routeProvider->_serviceId, | |
2493 'current_display' => $this->current_display, | |
2494 'args' => $this->args, | |
2495 'current_page' => $this->current_page, | |
2496 'exposed_input' => $this->exposed_input, | |
2497 'exposed_raw_input' => $this->exposed_raw_input, | |
2498 'exposed_data' => $this->exposed_data, | |
2499 'dom_id' => $this->dom_id, | |
2500 'executed' => $this->executed, | |
2501 ]; | |
2502 return ['serializationData']; | |
2503 } | |
2504 | |
2505 /** | |
2506 * Magic method implementation to unserialize the view executable. | |
2507 */ | |
2508 public function __wakeup() { | |
2509 // There are cases, like in testing where we don't have a container | |
2510 // available. | |
2511 if (\Drupal::hasContainer() && !empty($this->serializationData)) { | |
2512 // Load and reference the storage. | |
2513 $this->storage = \Drupal::entityTypeManager()->getStorage('view') | |
2514 ->load($this->serializationData['storage']); | |
2515 $this->storage->set('executable', $this); | |
2516 | |
2517 // Attach all necessary services. | |
2518 $this->user = \Drupal::currentUser(); | |
2519 $this->viewsData = \Drupal::service($this->serializationData['views_data']); | |
2520 $this->routeProvider = \Drupal::service($this->serializationData['route_provider']); | |
2521 | |
2522 // Restore the state of this executable. | |
2523 if ($request = \Drupal::request()) { | |
2524 $this->setRequest($request); | |
2525 } | |
2526 $this->setDisplay($this->serializationData['current_display']); | |
2527 $this->setArguments($this->serializationData['args']); | |
2528 $this->setCurrentPage($this->serializationData['current_page']); | |
2529 $this->setExposedInput($this->serializationData['exposed_input']); | |
2530 $this->exposed_data = $this->serializationData['exposed_data']; | |
2531 $this->exposed_raw_input = $this->serializationData['exposed_raw_input']; | |
2532 $this->dom_id = $this->serializationData['dom_id']; | |
2533 | |
2534 $this->initHandlers(); | |
2535 | |
2536 // If the display was previously executed, execute it now. | |
2537 if ($this->serializationData['executed']) { | |
2538 $this->execute($this->current_display); | |
2539 } | |
2540 } | |
2541 // Unset serializationData since it serves no further purpose. | |
2542 unset($this->serializationData); | |
2543 } | |
2544 | |
2545 } |