Version 1
[yaffs-website] / web / core / modules / views / src / Plugin / views / display / DisplayPluginBase.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\display;
4
5 use Drupal\Component\Plugin\DependentPluginInterface;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Utility\SafeMarkup;
8 use Drupal\Core\Cache\Cache;
9 use Drupal\Core\Cache\CacheableMetadata;
10 use Drupal\Core\Cache\CacheableDependencyInterface;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Language\LanguageInterface;
13 use Drupal\Core\Plugin\PluginDependencyTrait;
14 use Drupal\Core\Session\AccountInterface;
15 use Drupal\Core\Url;
16 use Drupal\views\Form\ViewsForm;
17 use Drupal\views\Plugin\views\area\AreaPluginBase;
18 use Drupal\views\ViewExecutable;
19 use Drupal\views\Plugin\views\PluginBase;
20 use Drupal\views\Views;
21
22 /**
23  * Base class for views display plugins.
24  */
25 abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInterface, DependentPluginInterface {
26   use PluginDependencyTrait;
27
28   /**
29    * The top object of a view.
30    *
31    * @var \Drupal\views\ViewExecutable
32    */
33   public $view = NULL;
34
35   /**
36    * An array of instantiated handlers used in this display.
37    *
38    * @var \Drupal\views\Plugin\views\ViewsHandlerInterface[]
39    */
40   public $handlers = [];
41
42   /**
43    * An array of instantiated plugins used in this display.
44    *
45    * @var \Drupal\views\Plugin\views\ViewsPluginInterface[]
46    */
47   protected $plugins = [];
48
49   /**
50    * Stores all available display extenders.
51    *
52    * @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase[]
53    */
54   protected $extenders = [];
55
56   /**
57    * {@inheritdoc}
58    */
59   protected $usesOptions = TRUE;
60
61   /**
62    * Stores the rendered output of the display.
63    *
64    * @see View::render
65    * @var string
66    */
67   public $output = NULL;
68
69   /**
70    * Whether the display allows the use of AJAX or not.
71    *
72    * @var bool
73    */
74   protected $usesAJAX = TRUE;
75
76   /**
77    * Whether the display allows the use of a pager or not.
78    *
79    * @var bool
80    */
81   protected $usesPager = TRUE;
82
83   /**
84    * Whether the display allows the use of a 'more' link or not.
85    *
86    * @var bool
87    */
88   protected $usesMore = TRUE;
89
90   /**
91    * Whether the display allows attachments.
92    *
93    * @var bool
94    *   TRUE if the display can use attachments, or FALSE otherwise.
95    */
96   protected $usesAttachments = FALSE;
97
98   /**
99    * Whether the display allows area plugins.
100    *
101    * @var bool
102    */
103   protected $usesAreas = TRUE;
104
105   /**
106    * Static cache for unpackOptions, but not if we are in the UI.
107    *
108    * @var array
109    */
110   protected static $unpackOptions = [];
111
112   /**
113    * The display information coming directly from the view entity.
114    *
115    * @see \Drupal\views\Entity\View::getDisplay()
116    *
117    * @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it.
118    *
119    * @var array
120    */
121   public $display;
122
123   /**
124    * Constructs a new DisplayPluginBase object.
125    *
126    * Because DisplayPluginBase::initDisplay() takes the display configuration by
127    * reference and handles it differently than usual plugin configuration, pass
128    * an empty array of configuration to the parent. This prevents our
129    * configuration from being duplicated.
130    *
131    * @todo Replace DisplayPluginBase::$display with
132    *   DisplayPluginBase::$configuration to standardize with other plugins.
133    *
134    * @param array $configuration
135    *   A configuration array containing information about the plugin instance.
136    * @param string $plugin_id
137    *   The plugin_id for the plugin instance.
138    * @param mixed $plugin_definition
139    *   The plugin implementation definition.
140    */
141   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
142     parent::__construct([], $plugin_id, $plugin_definition);
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
149     $this->view = $view;
150
151     // Load extenders as soon as possible.
152     $display['display_options'] += ['display_extenders' => []];
153     $this->extenders = [];
154     if ($extenders = Views::getEnabledDisplayExtenders()) {
155       $manager = Views::pluginManager('display_extender');
156       $display_extender_options = $display['display_options']['display_extenders'];
157       foreach ($extenders as $extender) {
158         /** @var \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase $plugin */
159         if ($plugin = $manager->createInstance($extender)) {
160           $extender_options = isset($display_extender_options[$plugin->getPluginId()]) ? $display_extender_options[$plugin->getPluginId()] : [];
161           $plugin->init($this->view, $this, $extender_options);
162           $this->extenders[$extender] = $plugin;
163         }
164       }
165     }
166
167
168     $this->setOptionDefaults($this->options, $this->defineOptions());
169     $this->display = &$display;
170
171     // Track changes that the user should know about.
172     $changed = FALSE;
173
174     if (!isset($options) && isset($display['display_options'])) {
175       $options = $display['display_options'];
176     }
177
178     if ($this->isDefaultDisplay() && isset($options['defaults'])) {
179       unset($options['defaults']);
180     }
181
182     $skip_cache = \Drupal::config('views.settings')->get('skip_cache');
183
184     if (empty($view->editing) || !$skip_cache) {
185       $cid = 'views:unpack_options:' . hash('sha256', serialize([$this->options, $options])) . ':' . \Drupal::languageManager()->getCurrentLanguage()->getId();
186       if (empty(static::$unpackOptions[$cid])) {
187         $cache = \Drupal::cache('data')->get($cid);
188         if (!empty($cache->data)) {
189           $this->options = $cache->data;
190         }
191         else {
192           $this->unpackOptions($this->options, $options);
193           \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, $this->view->storage->getCacheTags());
194         }
195         static::$unpackOptions[$cid] = $this->options;
196       }
197       else {
198         $this->options = static::$unpackOptions[$cid];
199       }
200     }
201     else {
202       $this->unpackOptions($this->options, $options);
203     }
204
205     // Mark the view as changed so the user has a chance to save it.
206     if ($changed) {
207       $this->view->changed = TRUE;
208     }
209   }
210
211   /**
212    * {@inheritdoc}
213    */
214   public function destroy() {
215     parent::destroy();
216
217     foreach ($this->handlers as $type => $handlers) {
218       foreach ($handlers as $id => $handler) {
219         if (is_object($handler)) {
220           $this->handlers[$type][$id]->destroy();
221         }
222       }
223     }
224
225     if (isset($this->default_display)) {
226       unset($this->default_display);
227     }
228
229     foreach ($this->extenders as $extender) {
230       $extender->destroy();
231     }
232   }
233
234   /**
235    * {@inheritdoc}
236    */
237   public function isDefaultDisplay() { return FALSE; }
238
239   /**
240    * {@inheritdoc}
241    */
242   public function usesExposed() {
243     if (!isset($this->has_exposed)) {
244       foreach ($this->handlers as $type => $value) {
245         foreach ($this->view->$type as $handler) {
246           if ($handler->canExpose() && $handler->isExposed()) {
247             // One is all we need; if we find it, return TRUE.
248             $this->has_exposed = TRUE;
249             return TRUE;
250           }
251         }
252       }
253       $pager = $this->getPlugin('pager');
254       if (isset($pager) && $pager->usesExposed()) {
255         $this->has_exposed = TRUE;
256         return TRUE;
257       }
258       $this->has_exposed = FALSE;
259     }
260
261     return $this->has_exposed;
262   }
263
264   /**
265    * {@inheritdoc}
266    */
267   public function displaysExposed() {
268     return TRUE;
269   }
270
271   /**
272    * {@inheritdoc}
273    */
274   public function usesAJAX() {
275     return $this->usesAJAX;
276   }
277
278   /**
279    * {@inheritdoc}
280    */
281   public function ajaxEnabled() {
282     if ($this->usesAJAX()) {
283       return $this->getOption('use_ajax');
284     }
285     return FALSE;
286   }
287
288   /**
289    * {@inheritdoc}
290    */
291   public function isEnabled() {
292     return (bool) $this->getOption('enabled');
293   }
294
295   /**
296    * {@inheritdoc}
297    */
298   public function usesPager() {
299     return $this->usesPager;
300   }
301
302   /**
303    * {@inheritdoc}
304    */
305   public function isPagerEnabled() {
306     if ($this->usesPager()) {
307       $pager = $this->getPlugin('pager');
308       if ($pager) {
309         return $pager->usePager();
310       }
311     }
312     return FALSE;
313   }
314
315   /**
316    * {@inheritdoc}
317    */
318   public function usesMore() {
319     return $this->usesMore;
320   }
321
322   /**
323    * {@inheritdoc}
324    */
325   public function isMoreEnabled() {
326     if ($this->usesMore()) {
327       return $this->getOption('use_more');
328     }
329     return FALSE;
330   }
331
332   /**
333    * {@inheritdoc}
334    */
335   public function useGroupBy() {
336     return $this->getOption('group_by');
337   }
338
339   /**
340    * {@inheritdoc}
341    */
342   public function useMoreAlways() {
343     if ($this->usesMore()) {
344       return $this->getOption('use_more_always');
345     }
346     return FALSE;
347   }
348
349   /**
350    * {@inheritdoc}
351    */
352   public function useMoreText() {
353     if ($this->usesMore()) {
354       return $this->getOption('use_more_text');
355     }
356     return FALSE;
357   }
358
359   /**
360    * {@inheritdoc}
361    */
362   public function acceptAttachments() {
363     // To be able to accept attachments this display have to be able to use
364     // attachments but at the same time, you cannot attach a display to itself.
365     if (!$this->usesAttachments() || ($this->definition['id'] == $this->view->current_display)) {
366       return FALSE;
367     }
368
369     if (!empty($this->view->argument) && $this->getOption('hide_attachment_summary')) {
370       foreach ($this->view->argument as $argument) {
371         if ($argument->needsStylePlugin() && empty($argument->argument_validated)) {
372           return FALSE;
373         }
374       }
375     }
376
377     return TRUE;
378   }
379
380   /**
381    * {@inheritdoc}
382    */
383   public function usesAttachments() {
384     return $this->usesAttachments;
385   }
386
387   /**
388    * {@inheritdoc}
389    */
390   public function usesAreas() {
391     return $this->usesAreas;
392   }
393
394   /**
395    * {@inheritdoc}
396    */
397   public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
398
399   /**
400    * {@inheritdoc}
401    */
402   public function defaultableSections($section = NULL) {
403     $sections = [
404       'access' => ['access'],
405       'cache' => ['cache'],
406       'title' => ['title'],
407       'css_class' => ['css_class'],
408       'use_ajax' => ['use_ajax'],
409       'hide_attachment_summary' => ['hide_attachment_summary'],
410       'show_admin_links' => ['show_admin_links'],
411       'group_by' => ['group_by'],
412       'query' => ['query'],
413       'use_more' => ['use_more', 'use_more_always', 'use_more_text'],
414       'use_more_always' => ['use_more', 'use_more_always', 'use_more_text'],
415       'use_more_text' => ['use_more', 'use_more_always', 'use_more_text'],
416       'link_display' => ['link_display', 'link_url'],
417
418       // Force these to cascade properly.
419       'style' => ['style', 'row'],
420       'row' => ['style', 'row'],
421
422       'pager' => ['pager'],
423
424       'exposed_form' => ['exposed_form'],
425
426       // These sections are special.
427       'header' => ['header'],
428       'footer' => ['footer'],
429       'empty' => ['empty'],
430       'relationships' => ['relationships'],
431       'fields' => ['fields'],
432       'sorts' => ['sorts'],
433       'arguments' => ['arguments'],
434       'filters' => ['filters', 'filter_groups'],
435       'filter_groups' => ['filters', 'filter_groups'],
436     ];
437
438     // If the display cannot use a pager, then we cannot default it.
439     if (!$this->usesPager()) {
440       unset($sections['pager']);
441       unset($sections['items_per_page']);
442     }
443
444     foreach ($this->extenders as $extender) {
445       $extender->defaultableSections($sections, $section);
446     }
447
448     if ($section) {
449       if (!empty($sections[$section])) {
450         return $sections[$section];
451       }
452     }
453     else {
454       return $sections;
455     }
456   }
457
458   protected function defineOptions() {
459     $options = [
460       'defaults' => [
461         'default' => [
462           'access' => TRUE,
463           'cache' => TRUE,
464           'query' => TRUE,
465           'title' => TRUE,
466           'css_class' => TRUE,
467
468           'display_description' => FALSE,
469           'use_ajax' => TRUE,
470           'hide_attachment_summary' => TRUE,
471           'show_admin_links' => TRUE,
472           'pager' => TRUE,
473           'use_more' => TRUE,
474           'use_more_always' => TRUE,
475           'use_more_text' => TRUE,
476           'exposed_form' => TRUE,
477
478           'link_display' => TRUE,
479           'link_url' => TRUE,
480           'group_by' => TRUE,
481
482           'style' => TRUE,
483           'row' => TRUE,
484
485           'header' => TRUE,
486           'footer' => TRUE,
487           'empty' => TRUE,
488
489           'relationships' => TRUE,
490           'fields' => TRUE,
491           'sorts' => TRUE,
492           'arguments' => TRUE,
493           'filters' => TRUE,
494           'filter_groups' => TRUE,
495         ],
496       ],
497
498       'title' => [
499         'default' => '',
500       ],
501       'enabled' => [
502         'default' => TRUE,
503       ],
504       'display_comment' => [
505         'default' => '',
506       ],
507       'css_class' => [
508         'default' => '',
509       ],
510       'display_description' => [
511         'default' => '',
512       ],
513       'use_ajax' => [
514         'default' => FALSE,
515       ],
516       'hide_attachment_summary' => [
517         'default' => FALSE,
518       ],
519       'show_admin_links' => [
520         'default' => TRUE,
521       ],
522       'use_more' => [
523         'default' => FALSE,
524       ],
525       'use_more_always' => [
526         'default' => TRUE,
527       ],
528       'use_more_text' => [
529         'default' => 'more',
530       ],
531       'link_display' => [
532         'default' => '',
533       ],
534       'link_url' => [
535         'default' => '',
536       ],
537       'group_by' => [
538         'default' => FALSE,
539       ],
540       'rendering_language' => [
541         'default' => '***LANGUAGE_entity_translation***',
542       ],
543
544       // These types are all plugins that can have individual settings
545       // and therefore need special handling.
546       'access' => [
547         'contains' => [
548           'type' => ['default' => 'none'],
549           'options' => ['default' => []],
550         ],
551         'merge_defaults' => [$this, 'mergePlugin'],
552       ],
553       'cache' => [
554         'contains' => [
555           'type' => ['default' => 'tag'],
556           'options' => ['default' => []],
557         ],
558         'merge_defaults' => [$this, 'mergePlugin'],
559       ],
560       'query' => [
561         'contains' => [
562           'type' => ['default' => 'views_query'],
563           'options' => ['default' => []],
564          ],
565         'merge_defaults' => [$this, 'mergePlugin'],
566       ],
567       'exposed_form' => [
568         'contains' => [
569           'type' => ['default' => 'basic'],
570           'options' => ['default' => []],
571          ],
572         'merge_defaults' => [$this, 'mergePlugin'],
573       ],
574       'pager' => [
575         'contains' => [
576           'type' => ['default' => 'mini'],
577           'options' => ['default' => []],
578          ],
579         'merge_defaults' => [$this, 'mergePlugin'],
580       ],
581       'style' => [
582         'contains' => [
583           'type' => ['default' => 'default'],
584           'options' => ['default' => []],
585         ],
586         'merge_defaults' => [$this, 'mergePlugin'],
587       ],
588       'row' => [
589         'contains' => [
590           'type' => ['default' => 'fields'],
591           'options' => ['default' => []],
592         ],
593         'merge_defaults' => [$this, 'mergePlugin'],
594       ],
595
596       'exposed_block' => [
597         'default' => FALSE,
598       ],
599
600       'header' => [
601         'default' => [],
602         'merge_defaults' => [$this, 'mergeHandler'],
603       ],
604       'footer' => [
605         'default' => [],
606         'merge_defaults' => [$this, 'mergeHandler'],
607       ],
608       'empty' => [
609         'default' => [],
610         'merge_defaults' => [$this, 'mergeHandler'],
611       ],
612
613       // We want these to export last.
614       // These are the 5 handler types.
615       'relationships' => [
616         'default' => [],
617         'merge_defaults' => [$this, 'mergeHandler'],
618       ],
619       'fields' => [
620         'default' => [],
621         'merge_defaults' => [$this, 'mergeHandler'],
622       ],
623       'sorts' => [
624         'default' => [],
625         'merge_defaults' => [$this, 'mergeHandler'],
626       ],
627       'arguments' => [
628         'default' => [],
629         'merge_defaults' => [$this, 'mergeHandler'],
630       ],
631       'filter_groups' => [
632         'contains' => [
633           'operator' => ['default' => 'AND'],
634           'groups' => ['default' => [1 => 'AND']],
635         ],
636       ],
637       'filters' => [
638         'default' => [],
639       ],
640     ];
641
642     if (!$this->usesPager()) {
643       $options['defaults']['default']['pager'] = FALSE;
644       $options['pager']['contains']['type']['default'] = 'some';
645     }
646
647     if ($this->isDefaultDisplay()) {
648       unset($options['defaults']);
649     }
650
651     $options['display_extenders'] = ['default' => []];
652     // First allow display extenders to provide new options.
653     foreach ($this->extenders as $extender_id => $extender) {
654       $options['display_extenders']['contains'][$extender_id]['contains'] = $extender->defineOptions();
655     }
656
657     // Then allow display extenders to alter existing default values.
658     foreach ($this->extenders as $extender) {
659       $extender->defineOptionsAlter($options);
660     }
661
662     return $options;
663   }
664
665   /**
666    * {@inheritdoc}
667    */
668   public function hasPath() { return FALSE; }
669
670   /**
671    * {@inheritdoc}
672    */
673   public function usesLinkDisplay() { return !$this->hasPath(); }
674
675   /**
676    * {@inheritdoc}
677    */
678   public function usesExposedFormInBlock() { return $this->hasPath(); }
679
680   /**
681    * {@inheritdoc}
682    */
683   public function getAttachedDisplays() {
684     $current_display_id = $this->display['id'];
685     $attached_displays = [];
686
687     // Go through all displays and search displays which link to this one.
688     foreach ($this->view->storage->get('display') as $display_id => $display) {
689       if (isset($display['display_options']['displays'])) {
690         $displays = $display['display_options']['displays'];
691         if (isset($displays[$current_display_id])) {
692           $attached_displays[] = $display_id;
693         }
694       }
695     }
696
697     return $attached_displays;
698   }
699
700   /**
701    * {@inheritdoc}
702    */
703   public function getLinkDisplay() {
704     $display_id = $this->getOption('link_display');
705     // If unknown, pick the first one.
706     if (empty($display_id) || !$this->view->displayHandlers->has($display_id)) {
707       foreach ($this->view->displayHandlers as $display_id => $display) {
708         if (!empty($display) && $display->hasPath()) {
709           return $display_id;
710         }
711       }
712     }
713     else {
714       return $display_id;
715     }
716     // Fall-through returns NULL.
717   }
718
719   /**
720    * {@inheritdoc}
721    */
722   public function getPath() {
723     if ($this->hasPath()) {
724       return $this->getOption('path');
725     }
726
727     $display_id = $this->getLinkDisplay();
728     if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
729       return $this->view->displayHandlers->get($display_id)->getPath();
730     }
731   }
732
733   /**
734    * {@inheritdoc}
735    */
736   public function getRoutedDisplay() {
737     // If this display has a route, return this display.
738     if ($this instanceof DisplayRouterInterface) {
739       return $this;
740     }
741
742     // If the display does not have a route (e.g. a block display), get the
743     // route for the linked display.
744     $display_id = $this->getLinkDisplay();
745     if ($display_id && $this->view->displayHandlers->has($display_id) && is_object($this->view->displayHandlers->get($display_id))) {
746       return $this->view->displayHandlers->get($display_id)->getRoutedDisplay();
747     }
748
749     // No routed display exists, so return NULL
750     return NULL;
751   }
752
753   /**
754    * {@inheritdoc}
755    */
756   public function getUrl() {
757     return $this->view->getUrl(NULL, $this->display['id']);
758   }
759
760   /**
761    * {@inheritdoc}
762    */
763   public function isDefaulted($option) {
764     return !$this->isDefaultDisplay() && !empty($this->default_display) && !empty($this->options['defaults'][$option]);
765   }
766
767   /**
768    * {@inheritdoc}
769    */
770   public function getOption($option) {
771     if ($this->isDefaulted($option)) {
772       return $this->default_display->getOption($option);
773     }
774
775     if (array_key_exists($option, $this->options)) {
776       return $this->options[$option];
777     }
778   }
779
780   /**
781    * {@inheritdoc}
782    */
783   public function usesFields() {
784     return $this->getPlugin('style')->usesFields();
785   }
786
787   /**
788    * {@inheritdoc}
789    */
790   public function getPlugin($type) {
791     // Look up the plugin name to use for this instance.
792     $options = $this->getOption($type);
793
794     // Return now if no options have been loaded.
795     if (empty($options) || !isset($options['type'])) {
796       return;
797     }
798
799     // Query plugins allow specifying a specific query class per base table.
800     if ($type == 'query') {
801       $views_data = Views::viewsData()->get($this->view->storage->get('base_table'));
802       $name = isset($views_data['table']['base']['query_id']) ? $views_data['table']['base']['query_id'] : 'views_query';
803     }
804     else {
805       $name = $options['type'];
806     }
807
808     // Plugin instances are stored on the display for re-use.
809     if (!isset($this->plugins[$type][$name])) {
810       $plugin = Views::pluginManager($type)->createInstance($name);
811
812       // Initialize the plugin.
813       $plugin->init($this->view, $this, $options['options']);
814
815       $this->plugins[$type][$name] = $plugin;
816     }
817
818     return $this->plugins[$type][$name];
819   }
820
821   /**
822    * {@inheritdoc}
823    */
824   public function &getHandler($type, $id) {
825     if (!isset($this->handlers[$type])) {
826       $this->getHandlers($type);
827     }
828
829     if (isset($this->handlers[$type][$id])) {
830       return $this->handlers[$type][$id];
831     }
832
833     // So we can return a reference.
834     $null = NULL;
835     return $null;
836   }
837
838   /**
839    * {@inheritdoc}
840    */
841   public function &getHandlers($type) {
842     if (!isset($this->handlers[$type])) {
843       $this->handlers[$type] = [];
844       $types = ViewExecutable::getHandlerTypes();
845       $plural = $types[$type]['plural'];
846
847       // Cast to an array so that if the display does not have any handlers of
848       // this type there is no PHP error.
849       foreach ((array) $this->getOption($plural) as $id => $info) {
850         // If this is during form submission and there are temporary options
851         // which can only appear if the view is in the edit cache, use those
852         // options instead. This is used for AJAX multi-step stuff.
853         if ($this->view->getRequest()->request->get('form_id') && isset($this->view->temporary_options[$type][$id])) {
854           $info = $this->view->temporary_options[$type][$id];
855         }
856
857         if ($info['id'] != $id) {
858           $info['id'] = $id;
859         }
860
861         // If aggregation is on, the group type might override the actual
862         // handler that is in use. This piece of code checks that and,
863         // if necessary, sets the override handler.
864         $override = NULL;
865         if ($this->useGroupBy() && !empty($info['group_type'])) {
866           if (empty($this->view->query)) {
867             $this->view->initQuery();
868           }
869           $aggregate = $this->view->query->getAggregationInfo();
870           if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
871             $override = $aggregate[$info['group_type']]['handler'][$type];
872           }
873         }
874
875         if (!empty($types[$type]['type'])) {
876           $handler_type = $types[$type]['type'];
877         }
878         else {
879           $handler_type = $type;
880         }
881
882         if ($handler = Views::handlerManager($handler_type)->getHandler($info, $override)) {
883           // Special override for area types so they know where they come from.
884           if ($handler instanceof AreaPluginBase) {
885             $handler->areaType = $type;
886           }
887
888           $handler->init($this->view, $this, $info);
889           $this->handlers[$type][$id] = &$handler;
890         }
891
892         // Prevent reference problems.
893         unset($handler);
894       }
895     }
896
897     return $this->handlers[$type];
898   }
899
900   /**
901    * Gets all the handlers used by the display.
902    *
903    * @param bool $only_overrides
904    *   Whether to include only overridden handlers.
905    *
906    * @return \Drupal\views\Plugin\views\ViewsHandlerInterface[]
907    */
908   protected function getAllHandlers($only_overrides = FALSE) {
909     $handler_types = Views::getHandlerTypes();
910     $handlers = [];
911     // Collect all dependencies of all handlers.
912     foreach ($handler_types as $handler_type => $handler_type_info) {
913       if ($only_overrides && $this->isDefaulted($handler_type_info['plural'])) {
914         continue;
915       }
916       $handlers = array_merge($handlers, array_values($this->getHandlers($handler_type)));
917     }
918     return $handlers;
919   }
920
921   /**
922    * Gets all the plugins used by the display.
923    *
924    * @param bool $only_overrides
925    *   Whether to include only overridden plugins.
926    *
927    * @return \Drupal\views\Plugin\views\ViewsPluginInterface[]
928    */
929   protected function getAllPlugins($only_overrides = FALSE) {
930     $plugins = [];
931     // Collect all dependencies of plugins.
932     foreach (Views::getPluginTypes('plugin') as $plugin_type) {
933       $plugin = $this->getPlugin($plugin_type);
934       if (!$plugin) {
935         continue;
936       }
937       if ($only_overrides && $this->isDefaulted($plugin_type)) {
938         continue;
939       }
940       $plugins[] = $plugin;
941     }
942     return $plugins;
943   }
944
945   /**
946    * {@inheritdoc}
947    */
948   public function calculateDependencies() {
949     $this->dependencies = parent::calculateDependencies();
950     // Collect all the dependencies of handlers and plugins. Only calculate
951     // their dependencies if they are configured by this display.
952     $plugins = array_merge($this->getAllHandlers(TRUE), $this->getAllPlugins(TRUE));
953     array_walk($plugins, [$this, 'calculatePluginDependencies']);
954
955     return $this->dependencies;
956   }
957
958
959   /**
960    * {@inheritdoc}
961    */
962   public function getFieldLabels($groupable_only = FALSE) {
963     $options = [];
964     foreach ($this->getHandlers('relationship') as $relationship => $handler) {
965       $relationships[$relationship] = $handler->adminLabel();
966     }
967
968     foreach ($this->getHandlers('field') as $id => $handler) {
969       if ($groupable_only && !$handler->useStringGroupBy()) {
970         // Continue to next handler if it's not groupable.
971         continue;
972       }
973       if ($label = $handler->label()) {
974         $options[$id] = $label;
975       }
976       else {
977         $options[$id] = $handler->adminLabel();
978       }
979       if (!empty($handler->options['relationship']) && !empty($relationships[$handler->options['relationship']])) {
980         $options[$id] = '(' . $relationships[$handler->options['relationship']] . ') ' . $options[$id];
981       }
982     }
983     return $options;
984   }
985
986   /**
987    * {@inheritdoc}
988    */
989   public function setOption($option, $value) {
990     if ($this->isDefaulted($option)) {
991       return $this->default_display->setOption($option, $value);
992     }
993
994     // Set this in two places: On the handler where we'll notice it
995     // but also on the display object so it gets saved. This should
996     // only be a temporary fix.
997     $this->display['display_options'][$option] = $value;
998     return $this->options[$option] = $value;
999   }
1000
1001   /**
1002    * {@inheritdoc}
1003    */
1004   public function overrideOption($option, $value) {
1005     $this->setOverride($option, FALSE);
1006     $this->setOption($option, $value);
1007   }
1008
1009   /**
1010    * {@inheritdoc}
1011    */
1012   public function optionLink($text, $section, $class = '', $title = '') {
1013     if (!trim($text)) {
1014       $text = $this->t('Broken field');
1015     }
1016
1017     if (!empty($class)) {
1018       $text = SafeMarkup::format('<span>@text</span>', ['@text' => $text]);
1019     }
1020
1021     if (empty($title)) {
1022       $title = $text;
1023     }
1024
1025     return \Drupal::l($text, new Url('views_ui.form_display', [
1026         'js' => 'nojs',
1027         'view' => $this->view->storage->id(),
1028         'display_id' => $this->display['id'],
1029         'type' => $section
1030       ], [
1031         'attributes' => [
1032           'class' => ['views-ajax-link', $class],
1033           'title' => $title,
1034           'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section)
1035         ]
1036     ]));
1037   }
1038
1039   /**
1040    * {@inheritdoc}
1041    */
1042   public function getArgumentsTokens() {
1043     $tokens = [];
1044     if (!empty($this->view->build_info['substitutions'])) {
1045       $tokens = $this->view->build_info['substitutions'];
1046     }
1047
1048     return $tokens;
1049   }
1050
1051   /**
1052    * {@inheritdoc}
1053    */
1054   public function optionsSummary(&$categories, &$options) {
1055     $categories = [
1056       'title' => [
1057         'title' => $this->t('Title'),
1058         'column' => 'first',
1059       ],
1060       'format' => [
1061         'title' => $this->t('Format'),
1062         'column' => 'first',
1063       ],
1064       'filters' => [
1065         'title' => $this->t('Filters'),
1066         'column' => 'first',
1067       ],
1068       'fields' => [
1069         'title' => $this->t('Fields'),
1070         'column' => 'first',
1071       ],
1072       'pager' => [
1073         'title' => $this->t('Pager'),
1074         'column' => 'second',
1075       ],
1076       'language' => [
1077         'title' => $this->t('Language'),
1078         'column' => 'second',
1079       ],
1080       'exposed' => [
1081         'title' => $this->t('Exposed form'),
1082         'column' => 'third',
1083         'build' => [
1084           '#weight' => 1,
1085         ],
1086       ],
1087       'access' => [
1088         'title' => '',
1089         'column' => 'second',
1090         'build' => [
1091           '#weight' => -5,
1092         ],
1093       ],
1094       'other' => [
1095         'title' => $this->t('Other'),
1096         'column' => 'third',
1097         'build' => [
1098           '#weight' => 2,
1099         ],
1100       ],
1101     ];
1102
1103     if ($this->display['id'] != 'default') {
1104       $options['display_id'] = [
1105         'category' => 'other',
1106         'title' => $this->t('Machine Name'),
1107         'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1108         'desc' => $this->t('Change the machine name of this display.'),
1109       ];
1110     }
1111
1112     $display_comment = views_ui_truncate($this->getOption('display_comment'), 80);
1113     $options['display_comment'] = [
1114       'category' => 'other',
1115       'title' => $this->t('Administrative comment'),
1116       'value' => !empty($display_comment) ? $display_comment : $this->t('None'),
1117       'desc' => $this->t('Comment or document this display.'),
1118     ];
1119
1120     $title = strip_tags($this->getOption('title'));
1121     if (!$title) {
1122       $title = $this->t('None');
1123     }
1124
1125     $options['title'] = [
1126       'category' => 'title',
1127       'title' => $this->t('Title'),
1128       'value' => views_ui_truncate($title, 32),
1129       'desc' => $this->t('Change the title that this display will use.'),
1130     ];
1131
1132     $style_plugin_instance = $this->getPlugin('style');
1133     $style_summary = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->summaryTitle();
1134     $style_title = empty($style_plugin_instance->definition['title']) ? $this->t('Missing style plugin') : $style_plugin_instance->pluginTitle();
1135
1136     $options['style'] = [
1137       'category' => 'format',
1138       'title' => $this->t('Format'),
1139       'value' => $style_title,
1140       'setting' => $style_summary,
1141       'desc' => $this->t('Change the way content is formatted.'),
1142     ];
1143
1144     // This adds a 'Settings' link to the style_options setting if the style has
1145     // options.
1146     if ($style_plugin_instance->usesOptions()) {
1147       $options['style']['links']['style_options'] = $this->t('Change settings for this format');
1148     }
1149
1150     if ($style_plugin_instance->usesRowPlugin()) {
1151       $row_plugin_instance = $this->getPlugin('row');
1152       $row_summary = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->summaryTitle();
1153       $row_title = empty($row_plugin_instance->definition['title']) ? $this->t('Missing row plugin') : $row_plugin_instance->pluginTitle();
1154
1155       $options['row'] = [
1156         'category' => 'format',
1157         'title' => $this->t('Show'),
1158         'value' => $row_title,
1159         'setting' => $row_summary,
1160         'desc' => $this->t('Change the way each row in the view is styled.'),
1161       ];
1162       // This adds a 'Settings' link to the row_options setting if the row style
1163       // has options.
1164       if ($row_plugin_instance->usesOptions()) {
1165         $options['row']['links']['row_options'] = $this->t('Change settings for this style');
1166       }
1167     }
1168     if ($this->usesAJAX()) {
1169       $options['use_ajax'] = [
1170         'category' => 'other',
1171         'title' => $this->t('Use AJAX'),
1172         'value' => $this->getOption('use_ajax') ? $this->t('Yes') : $this->t('No'),
1173         'desc' => $this->t('Change whether or not this display will use AJAX.'),
1174       ];
1175     }
1176     if ($this->usesAttachments()) {
1177       $options['hide_attachment_summary'] = [
1178         'category' => 'other',
1179         'title' => $this->t('Hide attachments in summary'),
1180         'value' => $this->getOption('hide_attachment_summary') ? $this->t('Yes') : $this->t('No'),
1181         'desc' => $this->t('Change whether or not to display attachments when displaying a contextual filter summary.'),
1182       ];
1183     }
1184     if (!isset($this->definition['contextual links locations']) || !empty($this->definition['contextual links locations'])) {
1185       $options['show_admin_links'] = [
1186         'category' => 'other',
1187         'title' => $this->t('Contextual links'),
1188         'value' => $this->getOption('show_admin_links') ? $this->t('Shown') : $this->t('Hidden'),
1189         'desc' => $this->t('Change whether or not to display contextual links for this view.'),
1190       ];
1191     }
1192
1193     $pager_plugin = $this->getPlugin('pager');
1194     if (!$pager_plugin) {
1195       // Default to the no pager plugin.
1196       $pager_plugin = Views::pluginManager('pager')->createInstance('none');
1197     }
1198
1199     $pager_str = $pager_plugin->summaryTitle();
1200
1201     $options['pager'] = [
1202       'category' => 'pager',
1203       'title' => $this->t('Use pager'),
1204       'value' => $pager_plugin->pluginTitle(),
1205       'setting' => $pager_str,
1206       'desc' => $this->t("Change this display's pager setting."),
1207     ];
1208
1209     // If pagers aren't allowed, change the text of the item.
1210     if (!$this->usesPager()) {
1211       $options['pager']['title'] = $this->t('Items to display');
1212     }
1213
1214     if ($pager_plugin->usesOptions()) {
1215       $options['pager']['links']['pager_options'] = $this->t('Change settings for this pager type.');
1216     }
1217
1218     if ($this->usesMore()) {
1219       $options['use_more'] = [
1220         'category' => 'pager',
1221         'title' => $this->t('More link'),
1222         'value' => $this->getOption('use_more') ? $this->t('Yes') : $this->t('No'),
1223         'desc' => $this->t('Specify whether this display will provide a "more" link.'),
1224       ];
1225     }
1226
1227     $this->view->initQuery();
1228     if ($this->view->query->getAggregationInfo()) {
1229       $options['group_by'] = [
1230         'category' => 'other',
1231         'title' => $this->t('Use aggregation'),
1232         'value' => $this->getOption('group_by') ? $this->t('Yes') : $this->t('No'),
1233         'desc' => $this->t('Allow grouping and aggregation (calculation) of fields.'),
1234       ];
1235     }
1236
1237     $options['query'] = [
1238       'category' => 'other',
1239       'title' => $this->t('Query settings'),
1240       'value' => $this->t('Settings'),
1241       'desc' => $this->t('Allow to set some advanced settings for the query plugin'),
1242     ];
1243
1244     if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1245       $rendering_language_options = $this->buildRenderingLanguageOptions();
1246       $options['rendering_language'] = [
1247         'category' => 'language',
1248         'title' => $this->t('Rendering Language'),
1249         'value' => $rendering_language_options[$this->getOption('rendering_language')],
1250         'desc' => $this->t('All content that supports translations will be displayed in the selected language.'),
1251       ];
1252     }
1253
1254     $access_plugin = $this->getPlugin('access');
1255     if (!$access_plugin) {
1256       // Default to the no access control plugin.
1257       $access_plugin = Views::pluginManager('access')->createInstance('none');
1258     }
1259
1260     $access_str = $access_plugin->summaryTitle();
1261
1262     $options['access'] = [
1263       'category' => 'access',
1264       'title' => $this->t('Access'),
1265       'value' => $access_plugin->pluginTitle(),
1266       'setting' => $access_str,
1267       'desc' => $this->t('Specify access control type for this display.'),
1268     ];
1269
1270     if ($access_plugin->usesOptions()) {
1271       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1272     }
1273
1274     $cache_plugin = $this->getPlugin('cache');
1275     if (!$cache_plugin) {
1276       // Default to the no cache control plugin.
1277       $cache_plugin = Views::pluginManager('cache')->createInstance('none');
1278     }
1279
1280     $cache_str = $cache_plugin->summaryTitle();
1281
1282     $options['cache'] = [
1283       'category' => 'other',
1284       'title' => $this->t('Caching'),
1285       'value' => $cache_plugin->pluginTitle(),
1286       'setting' => $cache_str,
1287       'desc' => $this->t('Specify caching type for this display.'),
1288     ];
1289
1290     if ($cache_plugin->usesOptions()) {
1291       $options['cache']['links']['cache_options'] = $this->t('Change settings for this caching type.');
1292     }
1293
1294     if ($access_plugin->usesOptions()) {
1295       $options['access']['links']['access_options'] = $this->t('Change settings for this access type.');
1296     }
1297
1298     if ($this->usesLinkDisplay()) {
1299       $link_display_option = $this->getOption('link_display');
1300       $link_display = $this->t('None');
1301
1302       if ($link_display_option == 'custom_url') {
1303         $link_display = $this->t('Custom URL');
1304       }
1305       elseif (!empty($link_display_option)) {
1306         $display_id = $this->getLinkDisplay();
1307         $displays = $this->view->storage->get('display');
1308         if (!empty($displays[$display_id])) {
1309           $link_display = $displays[$display_id]['display_title'];
1310         }
1311       }
1312
1313       $options['link_display'] = [
1314         'category' => 'pager',
1315         'title' => $this->t('Link display'),
1316         'value' => $link_display,
1317         'desc' => $this->t('Specify which display or custom URL this display will link to.'),
1318       ];
1319     }
1320
1321     if ($this->usesExposedFormInBlock()) {
1322       $options['exposed_block'] = [
1323         'category' => 'exposed',
1324         'title' => $this->t('Exposed form in block'),
1325         'value' => $this->getOption('exposed_block') ? $this->t('Yes') : $this->t('No'),
1326         'desc' => $this->t('Allow the exposed form to appear in a block instead of the view.'),
1327       ];
1328     }
1329
1330     /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */
1331     $exposed_form_plugin = $this->getPlugin('exposed_form');
1332     if (!$exposed_form_plugin) {
1333       // Default to the no cache control plugin.
1334       $exposed_form_plugin = Views::pluginManager('exposed_form')->createInstance('basic');
1335     }
1336
1337     $exposed_form_str = $exposed_form_plugin->summaryTitle();
1338
1339     $options['exposed_form'] = [
1340       'category' => 'exposed',
1341       'title' => $this->t('Exposed form style'),
1342       'value' => $exposed_form_plugin->pluginTitle(),
1343       'setting' => $exposed_form_str,
1344       'desc' => $this->t('Select the kind of exposed filter to use.'),
1345     ];
1346
1347     if ($exposed_form_plugin->usesOptions()) {
1348       $options['exposed_form']['links']['exposed_form_options'] = $this->t('Exposed form settings for this exposed form style.');
1349     }
1350
1351     $css_class = trim($this->getOption('css_class'));
1352     if (!$css_class) {
1353       $css_class = $this->t('None');
1354     }
1355
1356     $options['css_class'] = [
1357       'category' => 'other',
1358       'title' => $this->t('CSS class'),
1359       'value' => $css_class,
1360       'desc' => $this->t('Change the CSS class name(s) that will be added to this display.'),
1361     ];
1362
1363     foreach ($this->extenders as $extender) {
1364       $extender->optionsSummary($categories, $options);
1365     }
1366   }
1367
1368   /**
1369    * {@inheritdoc}
1370    */
1371   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
1372     parent::buildOptionsForm($form, $form_state);
1373     $section = $form_state->get('section');
1374     if ($this->defaultableSections($section)) {
1375       views_ui_standard_display_dropdown($form, $form_state, $section);
1376     }
1377     $form['#title'] = $this->display['display_title'] . ': ';
1378
1379     // Set the 'section' to highlight on the form.
1380     // If it's the item we're looking at is pulling from the default display,
1381     // reflect that. Don't use is_defaulted since we want it to show up even
1382     // on the default display.
1383     if (!empty($this->options['defaults'][$section])) {
1384       $form['#section'] = 'default-' . $section;
1385     }
1386     else {
1387       $form['#section'] = $this->display['id'] . '-' . $section;
1388     }
1389
1390     switch ($section) {
1391       case 'display_id':
1392         $form['#title'] .= $this->t('The machine name of this display');
1393         $form['display_id'] = [
1394           '#type' => 'textfield',
1395           '#title' => $this->t('Machine name of the display'),
1396           '#default_value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
1397           '#required' => TRUE,
1398           '#size' => 64,
1399         ];
1400         break;
1401       case 'display_title':
1402         $form['#title'] .= $this->t('The name and the description of this display');
1403         $form['display_title'] = [
1404           '#title' => $this->t('Administrative name'),
1405           '#type' => 'textfield',
1406           '#default_value' => $this->display['display_title'],
1407         ];
1408         $form['display_description'] = [
1409           '#title' => $this->t('Administrative description'),
1410           '#type' => 'textfield',
1411           '#default_value' => $this->getOption('display_description'),
1412         ];
1413         break;
1414       case 'display_comment':
1415         $form['#title'] .= $this->t('Administrative comment');
1416         $form['display_comment'] = [
1417           '#type' => 'textarea',
1418           '#title' => $this->t('Administrative comment'),
1419           '#description' => $this->t('This description will only be seen within the administrative interface and can be used to document this display.'),
1420           '#default_value' => $this->getOption('display_comment'),
1421         ];
1422         break;
1423       case 'title':
1424         $form['#title'] .= $this->t('The title of this view');
1425         $form['title'] = [
1426           '#title' => $this->t('Title'),
1427           '#type' => 'textfield',
1428           '#description' => $this->t('This title will be displayed with the view, wherever titles are normally displayed; i.e, as the page title, block title, etc.'),
1429           '#default_value' => $this->getOption('title'),
1430           '#maxlength' => 255,
1431         ];
1432         break;
1433       case 'css_class':
1434         $form['#title'] .= $this->t('CSS class');
1435         $form['css_class'] = [
1436           '#type' => 'textfield',
1437           '#title' => $this->t('CSS class name(s)'),
1438           '#description' => $this->t('Separate multiple classes by spaces.'),
1439           '#default_value' => $this->getOption('css_class'),
1440         ];
1441         break;
1442       case 'use_ajax':
1443         $form['#title'] .= $this->t('AJAX');
1444         $form['use_ajax'] = [
1445           '#description' => $this->t('Options such as paging, table sorting, and exposed filters will not initiate a page refresh.'),
1446           '#type' => 'checkbox',
1447           '#title' => $this->t('Use AJAX'),
1448           '#default_value' => $this->getOption('use_ajax') ? 1 : 0,
1449         ];
1450         break;
1451       case 'hide_attachment_summary':
1452         $form['#title'] .= $this->t('Hide attachments when displaying a contextual filter summary');
1453         $form['hide_attachment_summary'] = [
1454           '#type' => 'checkbox',
1455           '#title' => $this->t('Hide attachments in summary'),
1456           '#default_value' => $this->getOption('hide_attachment_summary') ? 1 : 0,
1457         ];
1458         break;
1459       case 'show_admin_links':
1460         $form['#title'] .= $this->t('Show contextual links on this view.');
1461         $form['show_admin_links'] = [
1462           '#type' => 'checkbox',
1463           '#title' => $this->t('Show contextual links'),
1464           '#default_value' => $this->getOption('show_admin_links'),
1465         ];
1466       break;
1467       case 'use_more':
1468         $form['#title'] .= $this->t('Add a more link to the bottom of the display.');
1469         $form['use_more'] = [
1470           '#type' => 'checkbox',
1471           '#title' => $this->t('Create more link'),
1472           '#description' => $this->t("This will add a more link to the bottom of this view, which will link to the page view. If you have more than one page view, the link will point to the display specified in 'Link display' section under pager. You can override the URL at the link display setting."),
1473           '#default_value' => $this->getOption('use_more'),
1474         ];
1475         $form['use_more_always'] = [
1476           '#type' => 'checkbox',
1477           '#title' => $this->t('Always display the more link'),
1478           '#description' => $this->t('Check this to display the more link even if there are no more items to display.'),
1479           '#default_value' => $this->getOption('use_more_always'),
1480           '#states' => [
1481             'visible' => [
1482               ':input[name="use_more"]' => ['checked' => TRUE],
1483             ],
1484           ],
1485         ];
1486         $form['use_more_text'] = [
1487           '#type' => 'textfield',
1488           '#title' => $this->t('More link text'),
1489           '#description' => $this->t('The text to display for the more link.'),
1490           '#default_value' => $this->getOption('use_more_text'),
1491           '#states' => [
1492             'visible' => [
1493               ':input[name="use_more"]' => ['checked' => TRUE],
1494             ],
1495           ],
1496         ];
1497         break;
1498       case 'group_by':
1499         $form['#title'] .= $this->t('Allow grouping and aggregation (calculation) of fields.');
1500         $form['group_by'] = [
1501           '#type' => 'checkbox',
1502           '#title' => $this->t('Aggregate'),
1503           '#description' => $this->t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'),
1504           '#default_value' => $this->getOption('group_by'),
1505         ];
1506         break;
1507       case 'access':
1508         $form['#title'] .= $this->t('Access restrictions');
1509         $form['access'] = [
1510           '#prefix' => '<div class="clearfix">',
1511           '#suffix' => '</div>',
1512           '#tree' => TRUE,
1513         ];
1514
1515         $access = $this->getOption('access');
1516         $form['access']['type'] = [
1517           '#title' => $this->t('Access'),
1518           '#title_display' => 'invisible',
1519           '#type' => 'radios',
1520           '#options' => Views::fetchPluginNames('access', $this->getType(), [$this->view->storage->get('base_table')]),
1521           '#default_value' => $access['type'],
1522         ];
1523
1524         $access_plugin = $this->getPlugin('access');
1525         if ($access_plugin->usesOptions()) {
1526           $form['markup'] = [
1527             '#prefix' => '<div class="js-form-item form-item description">',
1528             '#markup' => $this->t('You may also adjust the @settings for the currently selected access restriction.', ['@settings' => $this->optionLink($this->t('settings'), 'access_options')]),
1529             '#suffix' => '</div>',
1530           ];
1531         }
1532
1533         break;
1534       case 'access_options':
1535         $plugin = $this->getPlugin('access');
1536         $form['#title'] .= $this->t('Access options');
1537         if ($plugin) {
1538           $form['access_options'] = [
1539             '#tree' => TRUE,
1540           ];
1541           $plugin->buildOptionsForm($form['access_options'], $form_state);
1542         }
1543         break;
1544       case 'cache':
1545         $form['#title'] .= $this->t('Caching');
1546         $form['cache'] = [
1547           '#prefix' => '<div class="clearfix">',
1548           '#suffix' => '</div>',
1549           '#tree' => TRUE,
1550         ];
1551
1552         $cache = $this->getOption('cache');
1553         $form['cache']['type'] = [
1554           '#title' => $this->t('Caching'),
1555           '#title_display' => 'invisible',
1556           '#type' => 'radios',
1557           '#options' => Views::fetchPluginNames('cache', $this->getType(), [$this->view->storage->get('base_table')]),
1558           '#default_value' => $cache['type'],
1559         ];
1560
1561         $cache_plugin = $this->getPlugin('cache');
1562         if ($cache_plugin->usesOptions()) {
1563           $form['markup'] = [
1564             '#prefix' => '<div class="js-form-item form-item description">',
1565             '#suffix' => '</div>',
1566             '#markup' => $this->t('You may also adjust the @settings for the currently selected cache mechanism.', ['@settings' => $this->optionLink($this->t('settings'), 'cache_options')]),
1567           ];
1568         }
1569         break;
1570       case 'cache_options':
1571         $plugin = $this->getPlugin('cache');
1572         $form['#title'] .= $this->t('Caching options');
1573         if ($plugin) {
1574           $form['cache_options'] = [
1575             '#tree' => TRUE,
1576           ];
1577           $plugin->buildOptionsForm($form['cache_options'], $form_state);
1578         }
1579         break;
1580       case 'query':
1581         $query_options = $this->getOption('query');
1582         $plugin_name = $query_options['type'];
1583
1584         $form['#title'] .= $this->t('Query options');
1585         $this->view->initQuery();
1586         if ($this->view->query) {
1587           $form['query'] = [
1588             '#tree' => TRUE,
1589             'type' => [
1590               '#type' => 'value',
1591               '#value' => $plugin_name,
1592             ],
1593             'options' => [
1594               '#tree' => TRUE,
1595             ],
1596           ];
1597
1598           $this->view->query->buildOptionsForm($form['query']['options'], $form_state);
1599         }
1600         break;
1601       case 'rendering_language':
1602         $form['#title'] .= $this->t('Rendering language');
1603         if (\Drupal::languageManager()->isMultilingual() && $this->isBaseTableTranslatable()) {
1604           $options = $this->buildRenderingLanguageOptions();
1605           $form['rendering_language'] = [
1606             '#type' => 'select',
1607             '#options' => $options,
1608             '#title' => $this->t('Rendering language'),
1609             '#description' => $this->t('All content that supports translations will be displayed in the selected language.'),
1610             '#default_value' => $this->getOption('rendering_language'),
1611           ];
1612         }
1613         else {
1614           $form['rendering_language']['#markup'] = $this->t('The view is not based on a translatable entity type or the site is not multilingual.');
1615         }
1616         break;
1617       case 'style':
1618         $form['#title'] .= $this->t('How should this view be styled');
1619         $style_plugin = $this->getPlugin('style');
1620         $form['style'] = [
1621           '#prefix' => '<div class="clearfix">',
1622           '#suffix' => '</div>',
1623           '#tree' => TRUE,
1624         ];
1625         $form['style']['type'] = [
1626           '#title' => $this->t('Style'),
1627           '#title_display' => 'invisible',
1628           '#type' => 'radios',
1629           '#options' => Views::fetchPluginNames('style', $this->getType(), [$this->view->storage->get('base_table')]),
1630           '#default_value' => $style_plugin->definition['id'],
1631           '#description' => $this->t('If the style you choose has settings, be sure to click the settings button that will appear next to it in the View summary.'),
1632         ];
1633
1634         if ($style_plugin->usesOptions()) {
1635           $form['markup'] = [
1636             '#prefix' => '<div class="js-form-item form-item description">',
1637             '#suffix' => '</div>',
1638             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'style_options')]),
1639           ];
1640         }
1641
1642         break;
1643       case 'style_options':
1644         $form['#title'] .= $this->t('Style options');
1645         $style = TRUE;
1646         $style_plugin = $this->getOption('style');
1647         $name = $style_plugin['type'];
1648
1649       case 'row_options':
1650         if (!isset($name)) {
1651           $row_plugin = $this->getOption('row');
1652           $name = $row_plugin['type'];
1653         }
1654         // If row, $style will be empty.
1655         if (empty($style)) {
1656           $form['#title'] .= $this->t('Row style options');
1657         }
1658         $plugin = $this->getPlugin(empty($style) ? 'row' : 'style', $name);
1659         if ($plugin) {
1660           $form[$section] = [
1661             '#tree' => TRUE,
1662           ];
1663           $plugin->buildOptionsForm($form[$section], $form_state);
1664         }
1665         break;
1666       case 'row':
1667         $form['#title'] .= $this->t('How should each row in this view be styled');
1668         $row_plugin_instance = $this->getPlugin('row');
1669         $form['row'] = [
1670           '#prefix' => '<div class="clearfix">',
1671           '#suffix' => '</div>',
1672           '#tree' => TRUE,
1673         ];
1674         $form['row']['type'] = [
1675           '#title' => $this->t('Row'),
1676           '#title_display' => 'invisible',
1677           '#type' => 'radios',
1678           '#options' => Views::fetchPluginNames('row', $this->getType(), [$this->view->storage->get('base_table')]),
1679           '#default_value' => $row_plugin_instance->definition['id'],
1680         ];
1681
1682         if ($row_plugin_instance->usesOptions()) {
1683           $form['markup'] = [
1684             '#prefix' => '<div class="js-form-item form-item description">',
1685             '#suffix' => '</div>',
1686             '#markup' => $this->t('You may also adjust the @settings for the currently selected row style.', ['@settings' => $this->optionLink($this->t('settings'), 'row_options')]),
1687           ];
1688         }
1689
1690         break;
1691       case 'link_display':
1692         $form['#title'] .= $this->t('Which display to use for path');
1693         $options = [FALSE => $this->t('None'), 'custom_url' => $this->t('Custom URL')];
1694
1695         foreach ($this->view->storage->get('display') as $display_id => $display) {
1696           if ($this->view->displayHandlers->get($display_id)->hasPath()) {
1697             $options[$display_id] = $display['display_title'];
1698           }
1699         }
1700
1701         $form['link_display'] = [
1702           '#type' => 'radios',
1703           '#options' => $options,
1704           '#description' => $this->t("Which display to use to get this display's path for things like summary links, rss feed links, more links, etc."),
1705           '#default_value' => $this->getOption('link_display'),
1706         ];
1707
1708         $options = [];
1709         $optgroup_arguments = (string) t('Arguments');
1710         foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
1711           $options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', ['@argument' => $handler->adminLabel()]);
1712           $options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', ['@argument' => $handler->adminLabel()]);
1713         }
1714
1715         // Default text.
1716         // We have some options, so make a list.
1717         $description = [];
1718         $description[] = [
1719           '#markup' => $this->t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.'),
1720         ];
1721         if (!empty($options)) {
1722           $description[] = [
1723             '#prefix' => '<p>',
1724             '#markup' => $this->t('The following tokens are available for this link. You may use Twig syntax in this field.'),
1725             '#suffix' => '</p>',
1726           ];
1727           foreach (array_keys($options) as $type) {
1728             if (!empty($options[$type])) {
1729               $items = [];
1730               foreach ($options[$type] as $key => $value) {
1731                 $items[] = $key . ' == ' . $value;
1732               }
1733               $item_list = [
1734                 '#theme' => 'item_list',
1735                 '#items' => $items,
1736               ];
1737               $description[] = $item_list;
1738             }
1739           }
1740         }
1741
1742         $form['link_url'] = [
1743           '#type' => 'textfield',
1744           '#title' => $this->t('Custom URL'),
1745           '#default_value' => $this->getOption('link_url'),
1746           '#description' => $description,
1747           '#states' => [
1748             'visible' => [
1749               ':input[name="link_display"]' => ['value' => 'custom_url'],
1750             ],
1751           ],
1752         ];
1753         break;
1754       case 'exposed_block':
1755         $form['#title'] .= $this->t('Put the exposed form in a block');
1756         $form['description'] = [
1757           '#markup' => '<div class="js-form-item form-item description">' . $this->t('If set, any exposed widgets will not appear with this view. Instead, a block will be made available to the Drupal block administration system, and the exposed form will appear there. Note that this block must be enabled manually, Views will not enable it for you.') . '</div>',
1758         ];
1759         $form['exposed_block'] = [
1760           '#type' => 'radios',
1761           '#options' => [1 => $this->t('Yes'), 0 => $this->t('No')],
1762           '#default_value' => $this->getOption('exposed_block') ? 1 : 0,
1763         ];
1764         break;
1765       case 'exposed_form':
1766         $form['#title'] .= $this->t('Exposed Form');
1767         $form['exposed_form'] = [
1768           '#prefix' => '<div class="clearfix">',
1769           '#suffix' => '</div>',
1770           '#tree' => TRUE,
1771         ];
1772
1773         $exposed_form = $this->getOption('exposed_form');
1774         $form['exposed_form']['type'] = [
1775           '#title' => $this->t('Exposed form'),
1776           '#title_display' => 'invisible',
1777           '#type' => 'radios',
1778           '#options' => Views::fetchPluginNames('exposed_form', $this->getType(), [$this->view->storage->get('base_table')]),
1779           '#default_value' => $exposed_form['type'],
1780         ];
1781
1782         $exposed_form_plugin = $this->getPlugin('exposed_form');
1783         if ($exposed_form_plugin->usesOptions()) {
1784           $form['markup'] = [
1785             '#prefix' => '<div class="js-form-item form-item description">',
1786             '#suffix' => '</div>',
1787             '#markup' => $this->t('You may also adjust the @settings for the currently selected style.', ['@settings' => $this->optionLink($this->t('settings'), 'exposed_form_options')]),
1788           ];
1789         }
1790         break;
1791       case 'exposed_form_options':
1792         $plugin = $this->getPlugin('exposed_form');
1793         $form['#title'] .= $this->t('Exposed form options');
1794         if ($plugin) {
1795           $form['exposed_form_options'] = [
1796             '#tree' => TRUE,
1797           ];
1798           $plugin->buildOptionsForm($form['exposed_form_options'], $form_state);
1799         }
1800         break;
1801       case 'pager':
1802         $form['#title'] .= $this->t('Select pager');
1803         $form['pager'] = [
1804           '#prefix' => '<div class="clearfix">',
1805           '#suffix' => '</div>',
1806           '#tree' => TRUE,
1807         ];
1808
1809         $pager = $this->getOption('pager');
1810         $form['pager']['type'] = [
1811           '#title' => $this->t('Pager'),
1812           '#title_display' => 'invisible',
1813           '#type' => 'radios',
1814           '#options' => Views::fetchPluginNames('pager', !$this->usesPager() ? 'basic' : NULL, [$this->view->storage->get('base_table')]),
1815           '#default_value' => $pager['type'],
1816         ];
1817
1818         $pager_plugin = $this->getPlugin('pager');
1819         if ($pager_plugin->usesOptions()) {
1820           $form['markup'] = [
1821             '#prefix' => '<div class="js-form-item form-item description">',
1822             '#suffix' => '</div>',
1823             '#markup' => $this->t('You may also adjust the @settings for the currently selected pager.', ['@settings' => $this->optionLink($this->t('settings'), 'pager_options')]),
1824           ];
1825         }
1826
1827         break;
1828       case 'pager_options':
1829         $plugin = $this->getPlugin('pager');
1830         $form['#title'] .= $this->t('Pager options');
1831         if ($plugin) {
1832           $form['pager_options'] = [
1833             '#tree' => TRUE,
1834           ];
1835           $plugin->buildOptionsForm($form['pager_options'], $form_state);
1836         }
1837         break;
1838     }
1839
1840     foreach ($this->extenders as $extender) {
1841       $extender->buildOptionsForm($form, $form_state);
1842     }
1843   }
1844
1845   /**
1846    * {@inheritdoc}
1847    */
1848   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
1849     $section = $form_state->get('section');
1850     switch ($section) {
1851       case 'display_title':
1852         if ($form_state->isValueEmpty('display_title')) {
1853           $form_state->setError($form['display_title'], $this->t('Display title may not be empty.'));
1854         }
1855         break;
1856       case 'css_class':
1857         $css_class = $form_state->getValue('css_class');
1858         if (preg_match('/[^a-zA-Z0-9-_ ]/', $css_class)) {
1859           $form_state->setError($form['css_class'], $this->t('CSS classes must be alphanumeric or dashes only.'));
1860         }
1861       break;
1862       case 'display_id':
1863         if ($form_state->getValue('display_id')) {
1864           if (preg_match('/[^a-z0-9_]/', $form_state->getValue('display_id'))) {
1865             $form_state->setError($form['display_id'], $this->t('Display name must be letters, numbers, or underscores only.'));
1866           }
1867
1868           foreach ($this->view->displayHandlers as $id => $display) {
1869             if ($id != $this->view->current_display && ($form_state->getValue('display_id') == $id || (isset($display->new_id) && $form_state->getValue('display_id') == $display->new_id))) {
1870               $form_state->setError($form['display_id'], $this->t('Display id should be unique.'));
1871             }
1872           }
1873         }
1874         break;
1875       case 'query':
1876         if ($this->view->query) {
1877           $this->view->query->validateOptionsForm($form['query'], $form_state);
1878         }
1879         break;
1880     }
1881
1882     // Validate plugin options. Every section with "_options" in it, belongs to
1883     // a plugin type, like "style_options".
1884     if (strpos($section, '_options') !== FALSE) {
1885       $plugin_type = str_replace('_options', '', $section);
1886       // Load the plugin and let it handle the validation.
1887       if ($plugin = $this->getPlugin($plugin_type)) {
1888         $plugin->validateOptionsForm($form[$section], $form_state);
1889       }
1890     }
1891
1892     foreach ($this->extenders as $extender) {
1893       $extender->validateOptionsForm($form, $form_state);
1894     }
1895   }
1896
1897   /**
1898    * {@inheritdoc}
1899    */
1900   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
1901     // Not sure I like this being here, but it seems (?) like a logical place.
1902     $cache_plugin = $this->getPlugin('cache');
1903     if ($cache_plugin) {
1904       $cache_plugin->cacheFlush();
1905     }
1906
1907     $section = $form_state->get('section');
1908     switch ($section) {
1909       case 'display_id':
1910         if ($form_state->hasValue('display_id')) {
1911           $this->display['new_id'] = $form_state->getValue('display_id');
1912         }
1913         break;
1914       case 'display_title':
1915         $this->display['display_title'] = $form_state->getValue('display_title');
1916         $this->setOption('display_description', $form_state->getValue('display_description'));
1917         break;
1918       case 'query':
1919         $plugin = $this->getPlugin('query');
1920         if ($plugin) {
1921           $plugin->submitOptionsForm($form['query']['options'], $form_state);
1922           $this->setOption('query', $form_state->getValue($section));
1923         }
1924         break;
1925
1926       case 'link_display':
1927         $this->setOption('link_url', $form_state->getValue('link_url'));
1928       case 'title':
1929       case 'css_class':
1930       case 'display_comment':
1931       case 'distinct':
1932       case 'group_by':
1933         $this->setOption($section, $form_state->getValue($section));
1934         break;
1935       case 'rendering_language':
1936         $this->setOption('rendering_language', $form_state->getValue('rendering_language'));
1937         break;
1938       case 'use_ajax':
1939       case 'hide_attachment_summary':
1940       case 'show_admin_links':
1941       case 'exposed_block':
1942         $this->setOption($section, (bool) $form_state->getValue($section));
1943         break;
1944       case 'use_more':
1945         $this->setOption($section, intval($form_state->getValue($section)));
1946         $this->setOption('use_more_always', intval($form_state->getValue('use_more_always')));
1947         $this->setOption('use_more_text', $form_state->getValue('use_more_text'));
1948         break;
1949
1950       case 'access':
1951       case 'cache':
1952       case 'exposed_form':
1953       case 'pager':
1954       case 'row':
1955       case 'style':
1956         $plugin_type = $section;
1957         $plugin_options = $this->getOption($plugin_type);
1958         $type = $form_state->getValue([$plugin_type, 'type']);
1959         if ($plugin_options['type'] != $type) {
1960           /** @var \Drupal\views\Plugin\views\ViewsPluginInterface $plugin */
1961           $plugin = Views::pluginManager($plugin_type)->createInstance($type);
1962           if ($plugin) {
1963             $plugin->init($this->view, $this, $plugin_options['options']);
1964             $plugin_options = [
1965               'type' => $type,
1966               'options' => $plugin->options,
1967             ];
1968             $plugin->filterByDefinedOptions($plugin_options['options']);
1969             $this->setOption($plugin_type, $plugin_options);
1970             if ($plugin->usesOptions()) {
1971               $form_state->get('view')->addFormToStack('display', $this->display['id'], $plugin_type . '_options');
1972             }
1973           }
1974         }
1975         break;
1976
1977       case 'access_options':
1978       case 'cache_options':
1979       case 'exposed_form_options':
1980       case 'pager_options':
1981       case 'row_options':
1982       case 'style_options':
1983         // Submit plugin options. Every section with "_options" in it, belongs to
1984         // a plugin type, like "style_options".
1985         $plugin_type = str_replace('_options', '', $section);
1986         if ($plugin = $this->getPlugin($plugin_type)) {
1987           $plugin_options = $this->getOption($plugin_type);
1988           $plugin->submitOptionsForm($form[$plugin_type . '_options'], $form_state);
1989           $plugin_options['options'] = $form_state->getValue($section);
1990           $this->setOption($plugin_type, $plugin_options);
1991         }
1992         break;
1993     }
1994
1995     $extender_options = $this->getOption('display_extenders');
1996     foreach ($this->extenders as $extender) {
1997       $extender->submitOptionsForm($form, $form_state);
1998
1999       $plugin_id = $extender->getPluginId();
2000       $extender_options[$plugin_id] = $extender->options;
2001     }
2002     $this->setOption('display_extenders', $extender_options);
2003   }
2004
2005   /**
2006    * {@inheritdoc}
2007    */
2008   public function optionsOverride($form, FormStateInterface $form_state) {
2009     $this->setOverride($form_state->get('section'));
2010   }
2011
2012   /**
2013    * {@inheritdoc}
2014    */
2015   public function setOverride($section, $new_state = NULL) {
2016     $options = $this->defaultableSections($section);
2017     if (!$options) {
2018       return;
2019     }
2020
2021     if (!isset($new_state)) {
2022       $new_state = empty($this->options['defaults'][$section]);
2023     }
2024
2025     // For each option that is part of this group, fix our settings.
2026     foreach ($options as $option) {
2027       if ($new_state) {
2028         // Revert to defaults.
2029         unset($this->options[$option]);
2030         unset($this->display['display_options'][$option]);
2031       }
2032       else {
2033         // Copy existing values into our display.
2034         $this->options[$option] = $this->getOption($option);
2035         $this->display['display_options'][$option] = $this->options[$option];
2036       }
2037       $this->options['defaults'][$option] = $new_state;
2038       $this->display['display_options']['defaults'][$option] = $new_state;
2039     }
2040   }
2041
2042   /**
2043    * {@inheritdoc}
2044    */
2045   public function query() {
2046     foreach ($this->extenders as $extender) {
2047       $extender->query();
2048     }
2049   }
2050
2051   /**
2052    * {@inheritdoc}
2053    */
2054   public function renderFilters() { }
2055
2056   /**
2057    * {@inheritdoc}
2058    */
2059   public function renderPager() {
2060     return TRUE;
2061   }
2062
2063   /**
2064    * {@inheritdoc}
2065    */
2066   public function renderMoreLink() {
2067     if ($this->isMoreEnabled() && ($this->useMoreAlways() || (!empty($this->view->pager) && $this->view->pager->hasMoreRecords()))) {
2068       // If the user has supplied a custom "More" link path, replace any
2069       // argument tokens and use that for the URL.
2070       if ($this->getOption('link_display') == 'custom_url' && $override_path = $this->getOption('link_url')) {
2071         $tokens = $this->getArgumentsTokens();
2072         $path = $this->viewsTokenReplace($override_path, $tokens);
2073         // @todo Views should expect and store a leading /. See:
2074         //   https://www.drupal.org/node/2423913
2075         $url = Url::fromUserInput('/' . $path);
2076       }
2077       // Otherwise, use the URL for the display.
2078       else {
2079         $url = $this->view->getUrl(NULL, $this->display['id']);
2080       }
2081
2082       // If a URL is available (either from the display or a custom path),
2083       // render the "More" link.
2084       if ($url) {
2085         $url_options = [];
2086         if (!empty($this->view->exposed_raw_input)) {
2087           $url_options['query'] = $this->view->exposed_raw_input;
2088         }
2089         $url->setOptions($url_options);
2090
2091         return [
2092           '#type' => 'more_link',
2093           '#url' => $url,
2094           '#title' => $this->useMoreText(),
2095           '#view' => $this->view,
2096         ];
2097       }
2098     }
2099   }
2100
2101   /**
2102    * {@inheritdoc}
2103    */
2104   public function render() {
2105     $rows = (!empty($this->view->result) || $this->view->style_plugin->evenEmpty()) ? $this->view->style_plugin->render($this->view->result) : [];
2106
2107     $element = [
2108       '#theme' => $this->themeFunctions(),
2109       '#view' => $this->view,
2110       '#pre_render' => [[$this, 'elementPreRender']],
2111       '#rows' => $rows,
2112       // Assigned by reference so anything added in $element['#attached'] will
2113       // be available on the view.
2114       '#attached' => &$this->view->element['#attached'],
2115       '#cache' => &$this->view->element['#cache'],
2116     ];
2117
2118     $this->applyDisplayCachablityMetadata($this->view->element);
2119
2120     return $element;
2121   }
2122
2123   /**
2124    * Applies the cacheability of the current display to the given render array.
2125    *
2126    * @param array $element
2127    *   The render array with updated cacheability metadata.
2128    */
2129   protected function applyDisplayCachablityMetadata(array &$element) {
2130     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
2131     $cache = $this->getPlugin('cache');
2132
2133     (new CacheableMetadata())
2134       ->setCacheTags(Cache::mergeTags($this->view->getCacheTags(), isset($this->display['cache_metadata']['tags']) ? $this->display['cache_metadata']['tags'] : []))
2135       ->setCacheContexts(isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [])
2136       ->setCacheMaxAge(Cache::mergeMaxAges($cache->getCacheMaxAge(), isset($this->display['cache_metadata']['max-age']) ? $this->display['cache_metadata']['max-age'] : Cache::PERMANENT))
2137       ->merge(CacheableMetadata::createFromRenderArray($element))
2138       ->applyTo($element);
2139   }
2140
2141   /**
2142    * {@inheritdoc}
2143    */
2144   public function elementPreRender(array $element) {
2145     $view = $element['#view'];
2146     $empty = empty($view->result);
2147
2148     // Force a render array so CSS/JS can be attached.
2149     if (!is_array($element['#rows'])) {
2150       $element['#rows'] = ['#markup' => $element['#rows']];
2151     }
2152
2153     $element['#header'] = $view->display_handler->renderArea('header', $empty);
2154     $element['#footer'] = $view->display_handler->renderArea('footer', $empty);
2155     $element['#empty'] = $empty ? $view->display_handler->renderArea('empty', $empty) : [];
2156     $element['#exposed'] = !empty($view->exposed_widgets) ? $view->exposed_widgets : [];
2157     $element['#more'] = $view->display_handler->renderMoreLink();
2158     $element['#feed_icons'] = !empty($view->feedIcons) ? $view->feedIcons : [];
2159
2160     if ($view->display_handler->renderPager()) {
2161       $exposed_input = isset($view->exposed_raw_input) ? $view->exposed_raw_input : NULL;
2162       $element['#pager'] = $view->renderPager($exposed_input);
2163     }
2164
2165     if (!empty($view->attachment_before)) {
2166       $element['#attachment_before'] = $view->attachment_before;
2167     }
2168     if (!empty($view->attachment_after)) {
2169       $element['#attachment_after'] = $view->attachment_after;
2170     }
2171
2172     // If form fields were found in the view, reformat the view output as a form.
2173     if ($view->hasFormElements()) {
2174       // Only render row output if there are rows. Otherwise, render the empty
2175       // region.
2176       if (!empty($element['#rows'])) {
2177         $output = $element['#rows'];
2178       }
2179       else {
2180         $output = $element['#empty'];
2181       }
2182
2183       $form_object = ViewsForm::create(\Drupal::getContainer(), $view->storage->id(), $view->current_display, $view->args);
2184       $form = \Drupal::formBuilder()->getForm($form_object, $view, $output);
2185       // The form is requesting that all non-essential views elements be hidden,
2186       // usually because the rendered step is not a view result.
2187       if ($form['show_view_elements']['#value'] == FALSE) {
2188         $element['#header'] = [];
2189         $element['#exposed'] = [];
2190         $element['#pager'] = [];
2191         $element['#footer'] = [];
2192         $element['#more'] = [];
2193         $element['#feed_icons'] = [];
2194       }
2195
2196       $element['#rows'] = $form;
2197     }
2198
2199     return $element;
2200   }
2201
2202   /**
2203    * {@inheritdoc}
2204    */
2205   public function renderArea($area, $empty = FALSE) {
2206     $return = [];
2207     foreach ($this->getHandlers($area) as $key => $area_handler) {
2208       if ($area_render = $area_handler->render($empty)) {
2209         if (isset($area_handler->position)) {
2210           // Fix weight of area.
2211           $area_render['#weight'] = $area_handler->position;
2212         }
2213         $return[$key] = $area_render;
2214       }
2215     }
2216     return $return;
2217   }
2218
2219   /**
2220    * {@inheritdoc}
2221    */
2222   public function access(AccountInterface $account = NULL) {
2223     if (!isset($account)) {
2224       $account = \Drupal::currentUser();
2225     }
2226
2227     $plugin = $this->getPlugin('access');
2228     /** @var \Drupal\views\Plugin\views\access\AccessPluginBase $plugin */
2229     if ($plugin) {
2230       return $plugin->access($account);
2231     }
2232
2233     // Fallback to all access if no plugin.
2234     return TRUE;
2235   }
2236
2237   /**
2238    * {@inheritdoc}
2239    */
2240   public function preExecute() {
2241     $this->view->setAjaxEnabled($this->ajaxEnabled());
2242     if ($this->isMoreEnabled() && !$this->useMoreAlways()) {
2243       $this->view->get_total_rows = TRUE;
2244     }
2245     $this->view->initHandlers();
2246     if ($this->usesExposed()) {
2247       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2248       $exposed_form = $this->getPlugin('exposed_form');
2249       $exposed_form->preExecute();
2250     }
2251
2252     foreach ($this->extenders as $extender) {
2253       $extender->preExecute();
2254     }
2255   }
2256
2257   /**
2258    * {@inheritdoc}
2259    */
2260   public function calculateCacheMetadata() {
2261     $cache_metadata = new CacheableMetadata();
2262
2263     // Iterate over ordinary views plugins.
2264     foreach (Views::getPluginTypes('plugin') as $plugin_type) {
2265       $plugin = $this->getPlugin($plugin_type);
2266       if ($plugin instanceof CacheableDependencyInterface) {
2267         $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($plugin));
2268       }
2269     }
2270
2271     // Iterate over all handlers. Note that at least the argument handler will
2272     // need to ask all its subplugins.
2273     foreach (array_keys(Views::getHandlerTypes()) as $handler_type) {
2274       $handlers = $this->getHandlers($handler_type);
2275       foreach ($handlers as $handler) {
2276         if ($handler instanceof CacheableDependencyInterface) {
2277           $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($handler));
2278         }
2279       }
2280     }
2281
2282     /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */
2283     if ($cache_plugin = $this->getPlugin('cache')) {
2284       $cache_plugin->alterCacheMetadata($cache_metadata);
2285     }
2286
2287     return $cache_metadata;
2288   }
2289
2290   /**
2291    * {@inheritdoc}
2292    */
2293   public function getCacheMetadata() {
2294     if (!isset($this->display['cache_metadata'])) {
2295       $cache_metadata = $this->calculateCacheMetadata();
2296       $this->display['cache_metadata']['max-age'] = $cache_metadata->getCacheMaxAge();
2297       $this->display['cache_metadata']['contexts'] = $cache_metadata->getCacheContexts();
2298       $this->display['cache_metadata']['tags'] = $cache_metadata->getCacheTags();
2299     }
2300     else {
2301       $cache_metadata = (new CacheableMetadata())
2302         ->setCacheMaxAge($this->display['cache_metadata']['max-age'])
2303         ->setCacheContexts($this->display['cache_metadata']['contexts'])
2304         ->setCacheTags($this->display['cache_metadata']['tags']);
2305     }
2306     return $cache_metadata;
2307   }
2308
2309   /**
2310    * {@inheritdoc}
2311    */
2312   public function execute() { }
2313
2314   /**
2315    * {@inheritdoc}
2316    */
2317   public function buildRenderable(array $args = [], $cache = TRUE) {
2318     $this->view->element += [
2319       '#type' => 'view',
2320       '#name' => $this->view->storage->id(),
2321       '#display_id' => $this->display['id'],
2322       '#arguments' => $args,
2323       '#embed' => FALSE,
2324       '#view' => $this->view,
2325       '#cache_properties' => ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'],
2326     ];
2327
2328     // When something passes $cache = FALSE, they're asking us not to create our
2329     // own render cache for it. However, we still need to include certain pieces
2330     // of cacheability metadata (e.g.: cache contexts), so they can bubble up.
2331     // Thus, we add the cacheability metadata first, then modify / remove the
2332     // cache keys depending on the $cache argument.
2333     $this->applyDisplayCachablityMetadata($this->view->element);
2334     if ($cache) {
2335       $this->view->element['#cache'] += ['keys' => []];
2336       // Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
2337       // additional cache context.
2338       $this->view->element['#cache']['keys'] = array_merge(['views', 'display', $this->view->element['#name'], $this->view->element['#display_id']], $this->view->element['#cache']['keys']);
2339     }
2340     else {
2341       // Remove the cache keys, to ensure render caching is not triggered. We
2342       // don't unset the other #cache values, to allow cacheability metadata to
2343       // still be bubbled.
2344       unset($this->view->element['#cache']['keys']);
2345     }
2346
2347     return $this->view->element;
2348   }
2349
2350   /**
2351    * {@inheritdoc}
2352    */
2353   public static function buildBasicRenderable($view_id, $display_id, array $args = []) {
2354     $build = [
2355       '#type' => 'view',
2356       '#name' => $view_id,
2357       '#display_id' => $display_id,
2358       '#arguments' => $args,
2359       '#embed' => FALSE,
2360       '#cache' => [
2361         'keys' => ['view', $view_id, 'display', $display_id],
2362       ],
2363     ];
2364
2365     if ($args) {
2366       $build['#cache']['keys'][] = 'args';
2367       $build['#cache']['keys'][] = implode(',', $args);
2368     }
2369
2370     $build['#cache_properties'] = ['#view_id', '#view_display_show_admin_links', '#view_display_plugin_id'];
2371
2372     return $build;
2373
2374   }
2375
2376   /**
2377    * {@inheritdoc}
2378    */
2379   public function preview() {
2380     return $this->view->render();
2381   }
2382
2383   /**
2384    * {@inheritdoc}
2385    */
2386   public function getType() {
2387     return 'normal';
2388   }
2389
2390   /**
2391    * {@inheritdoc}
2392    */
2393   public function validate() {
2394     $errors = [];
2395     // Make sure displays that use fields HAVE fields.
2396     if ($this->usesFields()) {
2397       $fields = FALSE;
2398       foreach ($this->getHandlers('field') as $field) {
2399         if (empty($field->options['exclude'])) {
2400           $fields = TRUE;
2401         }
2402       }
2403
2404       if (!$fields) {
2405         $errors[] = $this->t('Display "@display" uses fields but there are none defined for it or all are excluded.', ['@display' => $this->display['display_title']]);
2406       }
2407     }
2408
2409     // Validate the more link.
2410     if ($this->isMoreEnabled() && $this->getOption('link_display') !== 'custom_url') {
2411       $routed_display = $this->getRoutedDisplay();
2412       if (!$routed_display || !$routed_display->isEnabled()) {
2413         $errors[] = $this->t('Display "@display" uses a "more" link but there are no displays it can link to. You need to specify a custom URL.', ['@display' => $this->display['display_title']]);
2414       }
2415     }
2416
2417     if ($this->hasPath() && !$this->getOption('path')) {
2418       $errors[] = $this->t('Display "@display" uses a path but the path is undefined.', ['@display' => $this->display['display_title']]);
2419     }
2420
2421     // Validate style plugin.
2422     $style = $this->getPlugin('style');
2423     if (empty($style)) {
2424       $errors[] = $this->t('Display "@display" has an invalid style plugin.', ['@display' => $this->display['display_title']]);
2425     }
2426     else {
2427       $result = $style->validate();
2428       if (!empty($result) && is_array($result)) {
2429         $errors = array_merge($errors, $result);
2430       }
2431     }
2432
2433     // Validate query plugin.
2434     $query = $this->getPlugin('query');
2435     $result = $query->validate();
2436     if (!empty($result) && is_array($result)) {
2437       $errors = array_merge($errors, $result);
2438     }
2439
2440     // Check for missing relationships.
2441     $relationships = array_keys($this->getHandlers('relationship'));
2442     foreach (ViewExecutable::getHandlerTypes() as $type => $handler_type_info) {
2443       foreach ($this->getHandlers($type) as $handler_id => $handler) {
2444         if (!empty($handler->options['relationship']) && $handler->options['relationship'] != 'none' && !in_array($handler->options['relationship'], $relationships)) {
2445           $errors[] = $this->t('The %handler_type %handler uses a relationship that has been removed.', ['%handler_type' => $handler_type_info['lstitle'], '%handler' => $handler->adminLabel()]);
2446         }
2447       }
2448     }
2449
2450     // Validate handlers.
2451     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2452       foreach ($this->getHandlers($type) as $handler) {
2453         $result = $handler->validate();
2454         if (!empty($result) && is_array($result)) {
2455           $errors = array_merge($errors, $result);
2456         }
2457       }
2458     }
2459
2460     return $errors;
2461   }
2462
2463   /**
2464    * {@inheritdoc}
2465    */
2466   public function newDisplay() {
2467   }
2468
2469   /**
2470    * {@inheritdoc}
2471    */
2472   public function isIdentifierUnique($id, $identifier) {
2473     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2474       foreach ($this->getHandlers($type) as $key => $handler) {
2475         if ($handler->canExpose() && $handler->isExposed()) {
2476           if ($handler->isAGroup()) {
2477             if ($id != $key && $identifier == $handler->options['group_info']['identifier']) {
2478               return FALSE;
2479             }
2480           }
2481           else {
2482             if ($id != $key && $identifier == $handler->options['expose']['identifier']) {
2483               return FALSE;
2484             }
2485           }
2486         }
2487       }
2488     }
2489     return TRUE;
2490   }
2491
2492   /**
2493    * {@inheritdoc}
2494    */
2495   public function outputIsEmpty() {
2496     if (!empty($this->view->result)) {
2497       return FALSE;
2498     }
2499
2500     // Check whether all of the area handlers are empty.
2501     foreach (['empty', 'footer', 'header'] as $type) {
2502       $handlers = $this->getHandlers($type);
2503       foreach ($handlers as $handler) {
2504         // If one is not empty, return FALSE now.
2505         if (!$handler->isEmpty()) {
2506           return FALSE;
2507         }
2508       }
2509     }
2510
2511     return TRUE;
2512   }
2513
2514   /**
2515    * {@inheritdoc}
2516    */
2517   public function getSpecialBlocks() {
2518     $blocks = [];
2519
2520     if ($this->usesExposedFormInBlock()) {
2521       $delta = '-exp-' . $this->view->storage->id() . '-' . $this->display['id'];
2522       $desc = $this->t('Exposed form: @view-@display_id', ['@view' => $this->view->storage->id(), '@display_id' => $this->display['id']]);
2523
2524       $blocks[$delta] = [
2525         'info' => $desc,
2526       ];
2527     }
2528
2529     return $blocks;
2530   }
2531
2532   /**
2533    * {@inheritdoc}
2534    */
2535   public function viewExposedFormBlocks() {
2536     // Avoid interfering with the admin forms.
2537     $route_name = \Drupal::routeMatch()->getRouteName();
2538     if (strpos($route_name, 'views_ui.') === 0) {
2539       return;
2540     }
2541     $this->view->initHandlers();
2542
2543     if ($this->usesExposed() && $this->getOption('exposed_block')) {
2544       /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */
2545       $exposed_form = $this->getPlugin('exposed_form');
2546       return $exposed_form->renderExposedForm(TRUE);
2547     }
2548   }
2549
2550   /**
2551    * {@inheritdoc}
2552    */
2553   public function getArgumentText() {
2554     return [
2555       'filter value not present' => $this->t('When the filter value is <em>NOT</em> available'),
2556       'filter value present' => $this->t('When the filter value <em>IS</em> available or a default is provided'),
2557       'description' => $this->t("This display does not have a source for contextual filters, so no contextual filter value will be available unless you select 'Provide default'."),
2558     ];
2559   }
2560
2561   /**
2562    * {@inheritdoc}
2563    */
2564   public function getPagerText() {
2565     return [
2566       'items per page title' => $this->t('Items to display'),
2567       'items per page description' => $this->t('Enter 0 for no limit.')
2568     ];
2569   }
2570
2571   /**
2572    * {@inheritdoc}
2573    */
2574   public function mergeDefaults() {
2575     $defined_options = $this->defineOptions();
2576
2577     // Build a map of plural => singular for handler types.
2578     $type_map = [];
2579     foreach (ViewExecutable::getHandlerTypes() as $type => $info) {
2580       $type_map[$info['plural']] = $type;
2581     }
2582
2583     // Find all defined options, that have specified a merge_defaults callback.
2584     foreach ($defined_options as $type => $definition) {
2585       if (!isset($definition['merge_defaults']) || !is_callable($definition['merge_defaults'])) {
2586         continue;
2587       }
2588       // Switch the type to singular, if it's a plural handler.
2589       if (isset($type_map[$type])) {
2590         $type = $type_map[$type];
2591       }
2592
2593       call_user_func($definition['merge_defaults'], $type);
2594     }
2595   }
2596
2597   /**
2598    * {@inheritdoc}
2599    */
2600   public function remove() {
2601
2602   }
2603
2604   /**
2605    * Merges plugins default values.
2606    *
2607    * @param string $type
2608    *   The name of the plugin type option.
2609    */
2610   protected function mergePlugin($type) {
2611     if (($options = $this->getOption($type)) && isset($options['options'])) {
2612       $plugin = $this->getPlugin($type);
2613       $options['options'] = $options['options'] + $plugin->options;
2614       $this->setOption($type, $options);
2615     }
2616   }
2617
2618   /**
2619    * Merges handlers default values.
2620    *
2621    * @param string $type
2622    *   The name of the handler type option.
2623    */
2624   protected function mergeHandler($type) {
2625     $types = ViewExecutable::getHandlerTypes();
2626
2627     $options = $this->getOption($types[$type]['plural']);
2628     foreach ($this->getHandlers($type) as $id => $handler) {
2629       if (isset($options[$id])) {
2630         $options[$id] = $options[$id] + $handler->options;
2631       }
2632     }
2633
2634     $this->setOption($types[$type]['plural'], $options);
2635   }
2636
2637   /**
2638    * {@inheritdoc}
2639    */
2640   public function getExtenders() {
2641     return $this->extenders;
2642   }
2643
2644   /**
2645    * Returns the available rendering strategies for language-aware entities.
2646    *
2647    * @return array
2648    *   An array of available entity row renderers keyed by renderer identifiers.
2649    */
2650   protected function buildRenderingLanguageOptions() {
2651     // @todo Consider making these plugins. See
2652     //   https://www.drupal.org/node/2173811.
2653     // Pass the current rendering language (in this case a one element array) so
2654     // is not lost when there are language configuration changes.
2655     return $this->listLanguages(LanguageInterface::STATE_CONFIGURABLE | LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED | PluginBase::INCLUDE_ENTITY, [$this->getOption('rendering_language')]);
2656   }
2657
2658   /**
2659    * Returns whether the base table is of a translatable entity type.
2660    *
2661    * @return bool
2662    *   TRUE if the base table is of a translatable entity type, FALSE otherwise.
2663    */
2664   protected function isBaseTableTranslatable() {
2665     if ($entity_type = $this->view->getBaseEntityType()) {
2666       return $entity_type->isTranslatable();
2667     }
2668     return FALSE;
2669   }
2670
2671 }
2672
2673 /**
2674  * @}
2675  */