Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / views_ui / src / ViewUI.php
1 <?php
2
3 namespace Drupal\views_ui;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\Timer;
7 use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\views\Views;
10 use Drupal\Core\Entity\EntityStorageInterface;
11 use Drupal\views\ViewExecutable;
12 use Drupal\Core\Database\Database;
13 use Drupal\Core\Session\AccountInterface;
14 use Drupal\views\Plugin\views\query\Sql;
15 use Drupal\views\Entity\View;
16 use Drupal\views\ViewEntityInterface;
17 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
18 use Symfony\Component\HttpFoundation\ParameterBag;
19 use Symfony\Component\HttpFoundation\Request;
20
21 /**
22  * Stores UI related temporary settings.
23  */
24 class ViewUI implements ViewEntityInterface {
25
26   /**
27    * Indicates if a view is currently being edited.
28    *
29    * @var bool
30    */
31   public $editing = FALSE;
32
33   /**
34    * Stores an array of displays that have been changed.
35    *
36    * @var array
37    */
38   public $changed_display;
39
40   /**
41    * How long the view takes to render in microseconds.
42    *
43    * @var float
44    */
45   public $render_time;
46
47   /**
48    * If this view is locked for editing.
49    *
50    * If this view is locked it will contain the result of
51    * \Drupal\user\SharedTempStore::getMetadata(). Which can be a stdClass or
52    * NULL.
53    *
54    * @var stdClass
55    */
56   public $lock;
57
58   /**
59    * If this view has been changed.
60    *
61    * @var bool
62    */
63   public $changed;
64
65   /**
66    * Stores options temporarily while editing.
67    *
68    * @var array
69    */
70   public $temporary_options;
71
72   /**
73    * Stores a stack of UI forms to display.
74    *
75    * @var array
76    */
77   public $stack;
78
79   /**
80    * Is the view run in a context of the preview in the admin interface.
81    *
82    * @var bool
83    */
84   public $live_preview;
85
86   public $renderPreview = FALSE;
87
88   /**
89    * The View storage object.
90    *
91    * @var \Drupal\views\ViewEntityInterface
92    */
93   protected $storage;
94
95   /**
96    * Stores a list of database queries run beside the main one from views.
97    *
98    * @var array
99    *
100    * @see \Drupal\Core\Database\Log
101    */
102   protected $additionalQueries;
103
104   /**
105    * Contains an array of form keys and their respective classes.
106    *
107    * @var array
108    */
109   public static $forms = [
110     'add-handler' => '\Drupal\views_ui\Form\Ajax\AddItem',
111     'analyze' => '\Drupal\views_ui\Form\Ajax\Analyze',
112     'handler' => '\Drupal\views_ui\Form\Ajax\ConfigHandler',
113     'handler-extra' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerExtra',
114     'handler-group' => '\Drupal\views_ui\Form\Ajax\ConfigHandlerGroup',
115     'display' => '\Drupal\views_ui\Form\Ajax\Display',
116     'edit-details' => '\Drupal\views_ui\Form\Ajax\EditDetails',
117     'rearrange' => '\Drupal\views_ui\Form\Ajax\Rearrange',
118     'rearrange-filter' => '\Drupal\views_ui\Form\Ajax\RearrangeFilter',
119     'reorder-displays' => '\Drupal\views_ui\Form\Ajax\ReorderDisplays',
120   ];
121
122   /**
123    * Whether the config is being created, updated or deleted through the
124    * import process.
125    *
126    * @var bool
127    */
128   private $isSyncing = FALSE;
129
130   /**
131    * Whether the config is being deleted through the uninstall process.
132    *
133    * @var bool
134    */
135   private $isUninstalling = FALSE;
136
137   /**
138    * Constructs a View UI object.
139    *
140    * @param \Drupal\views\ViewEntityInterface $storage
141    *   The View storage object to wrap.
142    */
143   public function __construct(ViewEntityInterface $storage) {
144     $this->entityType = 'view';
145     $this->storage = $storage;
146   }
147
148   /**
149    * {@inheritdoc}
150    */
151   public function get($property_name, $langcode = NULL) {
152     if (property_exists($this->storage, $property_name)) {
153       return $this->storage->get($property_name, $langcode);
154     }
155
156     return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function setStatus($status) {
163     return $this->storage->setStatus($status);
164   }
165
166   /**
167    * {@inheritdoc}
168    */
169   public function set($property_name, $value, $notify = TRUE) {
170     if (property_exists($this->storage, $property_name)) {
171       $this->storage->set($property_name, $value);
172     }
173     else {
174       $this->{$property_name} = $value;
175     }
176   }
177
178   /**
179    * {@inheritdoc}
180    */
181   public function setSyncing($syncing) {
182     $this->isSyncing = $syncing;
183   }
184
185   /**
186    * {@inheritdoc}
187    */
188   public function setUninstalling($isUninstalling) {
189     $this->isUninstalling = $isUninstalling;
190   }
191
192   /**
193    * {@inheritdoc}
194    */
195   public function isSyncing() {
196     return $this->isSyncing;
197   }
198
199   /**
200    * {@inheritdoc}
201    */
202   public function isUninstalling() {
203     return $this->isUninstalling;
204   }
205
206   /**
207    * Basic submit handler applicable to all 'standard' forms.
208    *
209    * This submit handler determines whether the user wants the submitted changes
210    * to apply to the default display or to the current display, and dispatches
211    * control appropriately.
212    */
213   public function standardSubmit($form, FormStateInterface $form_state) {
214     // Determine whether the values the user entered are intended to apply to
215     // the current display or the default display.
216
217     list($was_defaulted, $is_defaulted, $revert) = $this->getOverrideValues($form, $form_state);
218
219     // Based on the user's choice in the display dropdown, determine which display
220     // these changes apply to.
221     $display_id = $form_state->get('display_id');
222     if ($revert) {
223       // If it's revert just change the override and return.
224       $display = &$this->getExecutable()->displayHandlers->get($display_id);
225       $display->optionsOverride($form, $form_state);
226
227       // Don't execute the normal submit handling but still store the changed view into cache.
228       $this->cacheSet();
229       return;
230     }
231     elseif ($was_defaulted === $is_defaulted) {
232       // We're not changing which display these form values apply to.
233       // Run the regular submit handler for this form.
234     }
235     elseif ($was_defaulted && !$is_defaulted) {
236       // We were using the default display's values, but we're now overriding
237       // the default display and saving values specific to this display.
238       $display = &$this->getExecutable()->displayHandlers->get($display_id);
239       // optionsOverride toggles the override of this section.
240       $display->optionsOverride($form, $form_state);
241       $display->submitOptionsForm($form, $form_state);
242     }
243     elseif (!$was_defaulted && $is_defaulted) {
244       // We used to have an override for this display, but the user now wants
245       // to go back to the default display.
246       // Overwrite the default display with the current form values, and make
247       // the current display use the new default values.
248       $display = &$this->getExecutable()->displayHandlers->get($display_id);
249       // optionsOverride toggles the override of this section.
250       $display->optionsOverride($form, $form_state);
251       $display->submitOptionsForm($form, $form_state);
252     }
253
254     $submit_handler = [$form_state->getFormObject(), 'submitForm'];
255     call_user_func_array($submit_handler, [&$form, $form_state]);
256   }
257
258   /**
259    * Submit handler for cancel button
260    */
261   public function standardCancel($form, FormStateInterface $form_state) {
262     if (!empty($this->changed) && isset($this->form_cache)) {
263       unset($this->form_cache);
264       $this->cacheSet();
265     }
266
267     $form_state->setRedirectUrl($this->urlInfo('edit-form'));
268   }
269
270   /**
271    * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
272    * a hidden op operator because the forms plugin doesn't seem to properly
273    * provide which button was clicked.
274    *
275    * TODO: Is the hidden op operator still here somewhere, or is that part of the
276    * docblock outdated?
277    */
278   public function getStandardButtons(&$form, FormStateInterface $form_state, $form_id, $name = NULL) {
279     $form['actions'] = [
280       '#type' => 'actions',
281     ];
282
283     if (empty($name)) {
284       $name = t('Apply');
285       if (!empty($this->stack) && count($this->stack) > 1) {
286         $name = t('Apply and continue');
287       }
288       $names = [t('Apply'), t('Apply and continue')];
289     }
290
291     // Forms that are purely informational set an ok_button flag, so we know not
292     // to create an "Apply" button for them.
293     if (!$form_state->get('ok_button')) {
294       $form['actions']['submit'] = [
295         '#type' => 'submit',
296         '#value' => $name,
297         '#id' => 'edit-submit-' . Html::getUniqueId($form_id),
298         // The regular submit handler ($form_id . '_submit') does not apply if
299         // we're updating the default display. It does apply if we're updating
300         // the current display. Since we have no way of knowing at this point
301         // which display the user wants to update, views_ui_standard_submit will
302         // take care of running the regular submit handler as appropriate.
303         '#submit' => [[$this, 'standardSubmit']],
304         '#button_type' => 'primary',
305       ];
306       // Form API button click detection requires the button's #value to be the
307       // same between the form build of the initial page request, and the
308       // initial form build of the request processing the form submission.
309       // Ideally, the button's #value shouldn't change until the form rebuild
310       // step. However, \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm()
311       // implements a different multistep form workflow than the Form API does,
312       // and adjusts $view->stack prior to form processing, so we compensate by
313       // extending button click detection code to support any of the possible
314       // button labels.
315       if (isset($names)) {
316         $form['actions']['submit']['#values'] = $names;
317         $form['actions']['submit']['#process'] = array_merge(['views_ui_form_button_was_clicked'], \Drupal::service('element_info')->getInfoProperty($form['actions']['submit']['#type'], '#process', []));
318       }
319       // If a validation handler exists for the form, assign it to this button.
320       $form['actions']['submit']['#validate'][] = [$form_state->getFormObject(), 'validateForm'];
321     }
322
323     // Create a "Cancel" button. For purely informational forms, label it "OK".
324     $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : [$this, 'standardCancel'];
325     $form['actions']['cancel'] = [
326       '#type' => 'submit',
327       '#value' => !$form_state->get('ok_button') ? t('Cancel') : t('Ok'),
328       '#submit' => [$cancel_submit],
329       '#validate' => [],
330       '#limit_validation_errors' => [],
331     ];
332
333     // Compatibility, to be removed later: // TODO: When is "later"?
334     // We used to set these items on the form, but now we want them on the $form_state:
335     if (isset($form['#title'])) {
336       $form_state->set('title', $form['#title']);
337     }
338     if (isset($form['#section'])) {
339       $form_state->set('#section', $form['#section']);
340     }
341     // Finally, we never want these cached -- our object cache does that for us.
342     $form['#no_cache'] = TRUE;
343   }
344
345   /**
346    * Return the was_defaulted, is_defaulted and revert state of a form.
347    */
348   public function getOverrideValues($form, FormStateInterface $form_state) {
349     // Make sure the dropdown exists in the first place.
350     if ($form_state->hasValue(['override', 'dropdown'])) {
351       // #default_value is used to determine whether it was the default value or not.
352       // So the available options are: $display, 'default' and 'default_revert', not 'defaults'.
353       $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
354       $dropdown = $form_state->getValue(['override', 'dropdown']);
355       $is_defaulted = ($dropdown === 'default');
356       $revert = ($dropdown === 'default_revert');
357
358       if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
359         // We're changing which display these values apply to.
360         // Update the #section so it knows what to mark changed.
361         $form['#section'] = str_replace('default-', $form_state->get('display_id') . '-', $form['#section']);
362       }
363     }
364     else {
365       // The user didn't get the dropdown for overriding the default display.
366       $was_defaulted = FALSE;
367       $is_defaulted = FALSE;
368       $revert = FALSE;
369     }
370
371     return [$was_defaulted, $is_defaulted, $revert];
372   }
373
374   /**
375    * Add another form to the stack; clicking 'apply' will go to this form
376    * rather than closing the ajax popup.
377    */
378   public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALSE, $rebuild_keys = FALSE) {
379     // Reset the cache of IDs. Drupal rather aggressively prevents ID
380     // duplication but this causes it to remember IDs that are no longer even
381     // being used.
382     Html::resetSeenIds();
383
384     if (empty($this->stack)) {
385       $this->stack = [];
386     }
387
388     $stack = [implode('-', array_filter([$key, $this->id(), $display_id, $type, $id])), $key, $display_id, $type, $id];
389     // If we're being asked to add this form to the bottom of the stack, no
390     // special logic is required. Our work is equally easy if we were asked to add
391     // to the top of the stack, but there's nothing in it yet.
392     if (!$top || empty($this->stack)) {
393       $this->stack[] = $stack;
394     }
395     // If we're adding to the top of an existing stack, we have to maintain the
396     // existing integer keys, so they can be used for the "2 of 3" progress
397     // indicator (which will now read "2 of 4").
398     else {
399       $keys = array_keys($this->stack);
400       $first = current($keys);
401       $last = end($keys);
402       for ($i = $last; $i >= $first; $i--) {
403         if (!isset($this->stack[$i])) {
404           continue;
405         }
406         // Move form number $i to the next position in the stack.
407         $this->stack[$i + 1] = $this->stack[$i];
408         unset($this->stack[$i]);
409       }
410       // Now that the previously $first slot is free, move the new form into it.
411       $this->stack[$first] = $stack;
412       ksort($this->stack);
413
414       // Start the keys from 0 again, if requested.
415       if ($rebuild_keys) {
416         $this->stack = array_values($this->stack);
417       }
418     }
419   }
420
421   /**
422    * Submit handler for adding new item(s) to a view.
423    */
424   public function submitItemAdd($form, FormStateInterface $form_state) {
425     $type = $form_state->get('type');
426     $types = ViewExecutable::getHandlerTypes();
427     $section = $types[$type]['plural'];
428     $display_id = $form_state->get('display_id');
429
430     // Handle the override select.
431     list($was_defaulted, $is_defaulted) = $this->getOverrideValues($form, $form_state);
432     if ($was_defaulted && !$is_defaulted) {
433       // We were using the default display's values, but we're now overriding
434       // the default display and saving values specific to this display.
435       $display = &$this->getExecutable()->displayHandlers->get($display_id);
436       // setOverride toggles the override of this section.
437       $display->setOverride($section);
438     }
439     elseif (!$was_defaulted && $is_defaulted) {
440       // We used to have an override for this display, but the user now wants
441       // to go back to the default display.
442       // Overwrite the default display with the current form values, and make
443       // the current display use the new default values.
444       $display = &$this->getExecutable()->displayHandlers->get($display_id);
445       // optionsOverride toggles the override of this section.
446       $display->setOverride($section);
447     }
448
449     if (!$form_state->isValueEmpty('name') && is_array($form_state->getValue('name'))) {
450       // Loop through each of the items that were checked and add them to the view.
451       foreach (array_keys(array_filter($form_state->getValue('name'))) as $field) {
452         list($table, $field) = explode('.', $field, 2);
453
454         if ($cut = strpos($field, '$')) {
455           $field = substr($field, 0, $cut);
456         }
457         $id = $this->getExecutable()->addHandler($display_id, $type, $table, $field);
458
459         // check to see if we have group by settings
460         $key = $type;
461         // Footer,header and empty text have a different internal handler type(area).
462         if (isset($types[$type]['type'])) {
463           $key = $types[$type]['type'];
464         }
465         $item = [
466           'table' => $table,
467           'field' => $field,
468         ];
469         $handler = Views::handlerManager($key)->getHandler($item);
470         if ($this->getExecutable()->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) {
471           $this->addFormToStack('handler-group', $display_id, $type, $id);
472         }
473
474         // check to see if this type has settings, if so add the settings form first
475         if ($handler && $handler->hasExtraOptions()) {
476           $this->addFormToStack('handler-extra', $display_id, $type, $id);
477         }
478         // Then add the form to the stack
479         $this->addFormToStack('handler', $display_id, $type, $id);
480       }
481     }
482
483     if (isset($this->form_cache)) {
484       unset($this->form_cache);
485     }
486
487     // Store in cache
488     $this->cacheSet();
489   }
490
491   /**
492    * Set up query capturing.
493    *
494    * \Drupal\Core\Database\Database stores the queries that it runs, if logging
495    * is enabled.
496    *
497    * @see ViewUI::endQueryCapture()
498    */
499   public function startQueryCapture() {
500     Database::startLog('views');
501   }
502
503   /**
504    * Add the list of queries run during render to buildinfo.
505    *
506    * @see ViewUI::startQueryCapture()
507    */
508   public function endQueryCapture() {
509     $queries = Database::getLog('views');
510
511     $this->additionalQueries = $queries;
512   }
513
514   public function renderPreview($display_id, $args = []) {
515     // Save the current path so it can be restored before returning from this function.
516     $request_stack = \Drupal::requestStack();
517     $current_request = $request_stack->getCurrentRequest();
518     $executable = $this->getExecutable();
519
520     // Determine where the query and performance statistics should be output.
521     $config = \Drupal::config('views.settings');
522     $show_query = $config->get('ui.show.sql_query.enabled');
523     $show_info = $config->get('ui.show.preview_information');
524     $show_location = $config->get('ui.show.sql_query.where');
525
526     $show_stats = $config->get('ui.show.performance_statistics');
527     if ($show_stats) {
528       $show_stats = $config->get('ui.show.sql_query.where');
529     }
530
531     $combined = $show_query && $show_stats;
532
533     $rows = ['query' => [], 'statistics' => []];
534
535     $errors = $executable->validate();
536     $executable->destroy();
537     if (empty($errors)) {
538       $this->ajax = TRUE;
539       $executable->live_preview = TRUE;
540
541       // AJAX happens via HTTP POST but everything expects exposed data to
542       // be in GET. Copy stuff but remove ajax-framework specific keys.
543       // If we're clicking on links in a preview, though, we could actually
544       // have some input in the query parameters, so we merge request() and
545       // query() to ensure we get it all.
546       $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all());
547       foreach (['view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token'] as $key) {
548         if (isset($exposed_input[$key])) {
549           unset($exposed_input[$key]);
550         }
551       }
552       $executable->setExposedInput($exposed_input);
553
554       if (!$executable->setDisplay($display_id)) {
555         return [
556           '#markup' => t('Invalid display id @display', ['@display' => $display_id]),
557         ];
558       }
559
560       $executable->setArguments($args);
561
562       // Store the current view URL for later use:
563       if ($executable->hasUrl() && $executable->display_handler->getOption('path')) {
564         $path = $executable->getUrl();
565       }
566
567       // Make view links come back to preview.
568
569       // Also override the current path so we get the pager, and make sure the
570       // Request object gets all of the proper values from $_SERVER.
571       $request = Request::createFromGlobals();
572       $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form');
573       $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')->getRouteByName('entity.view.preview_form'));
574       $request->attributes->set('view', $this->storage);
575       $request->attributes->set('display_id', $display_id);
576       $raw_parameters = new ParameterBag();
577       $raw_parameters->set('view', $this->id());
578       $raw_parameters->set('display_id', $display_id);
579       $request->attributes->set('_raw_variables', $raw_parameters);
580
581       foreach ($args as $key => $arg) {
582         $request->attributes->set('arg_' . $key, $arg);
583       }
584       $request_stack->push($request);
585
586       // Suppress contextual links of entities within the result set during a
587       // Preview.
588       // @todo We'll want to add contextual links specific to editing the View, so
589       //   the suppression may need to be moved deeper into the Preview pipeline.
590       views_ui_contextual_links_suppress_push();
591
592       $show_additional_queries = $config->get('ui.show.additional_queries');
593
594       Timer::start('entity.view.preview_form');
595
596       if ($show_additional_queries) {
597         $this->startQueryCapture();
598       }
599
600       // Execute/get the view preview.
601       $preview = $executable->preview($display_id, $args);
602
603       if ($show_additional_queries) {
604         $this->endQueryCapture();
605       }
606
607       $this->render_time = Timer::stop('entity.view.preview_form')['time'];
608
609       views_ui_contextual_links_suppress_pop();
610
611       // Prepare the query information and statistics to show either above or
612       // below the view preview.
613       // Initialise the empty rows arrays so we can safely merge them later.
614       $rows['query'] = [];
615       $rows['statistics'] = [];
616       if ($show_info || $show_query || $show_stats) {
617         // Get information from the preview for display.
618         if (!empty($executable->build_info['query'])) {
619           if ($show_query) {
620             $query_string = $executable->build_info['query'];
621             // Only the sql default class has a method getArguments.
622             $quoted = [];
623
624             if ($executable->query instanceof Sql) {
625               $quoted = $query_string->getArguments();
626               $connection = Database::getConnection();
627               foreach ($quoted as $key => $val) {
628                 if (is_array($val)) {
629                   $quoted[$key] = implode(', ', array_map([$connection, 'quote'], $val));
630                 }
631                 else {
632                   $quoted[$key] = $connection->quote($val);
633                 }
634               }
635             }
636             $rows['query'][] = [
637               [
638                 'data' => [
639                   '#type' => 'inline_template',
640                   '#template' => "<strong>{% trans 'Query' %}</strong>",
641                 ],
642               ],
643               [
644                 'data' => [
645                   '#type' => 'inline_template',
646                   '#template' => '<pre>{{ query }}</pre>',
647                   '#context' => ['query' => strtr($query_string, $quoted)],
648                 ],
649               ],
650             ];
651             if (!empty($this->additionalQueries)) {
652               $queries[] = [
653                 '#prefix' => '<strong>',
654                 '#markup' => t('These queries were run during view rendering:'),
655                 '#suffix' => '</strong>',
656               ];
657               foreach ($this->additionalQueries as $query) {
658                 $query_string = strtr($query['query'], $query['args']);
659                 $queries[] = [
660                   '#prefix' => "\n",
661                   '#markup' => t('[@time ms] @query', ['@time' => round($query['time'] * 100000, 1) / 100000.0, '@query' => $query_string]),
662                 ];
663               }
664
665               $rows['query'][] = [
666                 [
667                   'data' => [
668                     '#type' => 'inline_template',
669                     '#template' => "<strong>{% trans 'Other queries' %}</strong>",
670                   ],
671                 ],
672                 [
673                   'data' => [
674                     '#prefix' => '<pre>',
675                      'queries' => $queries,
676                      '#suffix' => '</pre>',
677                     ],
678                 ],
679               ];
680             }
681           }
682           if ($show_info) {
683             $rows['query'][] = [
684               [
685                 'data' => [
686                   '#type' => 'inline_template',
687                   '#template' => "<strong>{% trans 'Title' %}</strong>",
688                 ],
689               ],
690               [
691                 'data' => [
692                   '#markup' => $executable->getTitle(),
693                 ],
694               ],
695             ];
696             if (isset($path)) {
697               // @todo Views should expect and store a leading /. See:
698               //   https://www.drupal.org/node/2423913
699               $path = \Drupal::l($path->toString(), $path);
700             }
701             else {
702               $path = t('This display has no path.');
703             }
704             $rows['query'][] = [
705               [
706                 'data' => [
707                   '#prefix' => '<strong>',
708                   '#markup' => t('Path'),
709                   '#suffix' => '</strong>',
710                 ],
711               ],
712               [
713                 'data' => [
714                   '#markup' => $path,
715                 ],
716               ]
717             ];
718           }
719           if ($show_stats) {
720             $rows['statistics'][] = [
721               [
722                 'data' => [
723                   '#type' => 'inline_template',
724                   '#template' => "<strong>{% trans 'Query build time' %}</strong>",
725                 ],
726               ],
727               t('@time ms', ['@time' => intval($executable->build_time * 100000) / 100]),
728             ];
729
730             $rows['statistics'][] = [
731               [
732                 'data' => [
733                   '#type' => 'inline_template',
734                   '#template' => "<strong>{% trans 'Query execute time' %}</strong>",
735                 ],
736               ],
737               t('@time ms', ['@time' => intval($executable->execute_time * 100000) / 100]),
738             ];
739
740             $rows['statistics'][] = [
741               [
742                 'data' => [
743                   '#type' => 'inline_template',
744                   '#template' => "<strong>{% trans 'View render time' %}</strong>",
745                 ],
746               ],
747               t('@time ms', ['@time' => intval($this->render_time * 100) / 100]),
748             ];
749           }
750           \Drupal::moduleHandler()->alter('views_preview_info', $rows, $executable);
751         }
752         else {
753           // No query was run. Display that information in place of either the
754           // query or the performance statistics, whichever comes first.
755           if ($combined || ($show_location === 'above')) {
756             $rows['query'][] = [
757               [
758                 'data' => [
759                   '#prefix' => '<strong>',
760                   '#markup' => t('Query'),
761                   '#suffix' => '</strong>',
762                 ],
763               ],
764               [
765                 'data' => [
766                   '#markup' => t('No query was run'),
767                 ],
768               ],
769             ];
770           }
771           else {
772             $rows['statistics'][] = [
773               [
774                 'data' => [
775                   '#prefix' => '<strong>',
776                   '#markup' => t('Query'),
777                   '#suffix' => '</strong>',
778                 ],
779               ],
780               [
781                 'data' => [
782                   '#markup' => t('No query was run'),
783                 ],
784               ],
785             ];
786           }
787         }
788       }
789     }
790     else {
791       foreach ($errors as $display_errors) {
792         foreach ($display_errors as $error) {
793           drupal_set_message($error, 'error');
794         }
795       }
796       $preview = ['#markup' => t('Unable to preview due to validation errors.')];
797     }
798
799     // Assemble the preview, the query info, and the query statistics in the
800     // requested order.
801     $table = [
802       '#type' => 'table',
803       '#prefix' => '<div class="views-query-info">',
804       '#suffix' => '</div>',
805       '#rows' => array_merge($rows['query'], $rows['statistics']),
806     ];
807
808     if ($show_location == 'above') {
809       $output = [
810         'table' => $table,
811         'preview' => $preview,
812       ];
813     }
814     else {
815       $output = [
816         'preview' => $preview,
817         'table' => $table,
818       ];
819     }
820
821     // Ensure that we just remove an additional request we pushed earlier.
822     // This could happen if $errors was not empty.
823     if ($request_stack->getCurrentRequest() != $current_request) {
824       $request_stack->pop();
825     }
826     return $output;
827   }
828
829   /**
830    * Get the user's current progress through the form stack.
831    *
832    * @return
833    *   FALSE if the user is not currently in a multiple-form stack. Otherwise,
834    *   an associative array with the following keys:
835    *   - current: The number of the current form on the stack.
836    *   - total: The total number of forms originally on the stack.
837    */
838   public function getFormProgress() {
839     $progress = FALSE;
840     if (!empty($this->stack)) {
841       // The forms on the stack have integer keys that don't change as the forms
842       // are completed, so we can see which ones are still left.
843       $keys = array_keys($this->stack);
844       // Add 1 to the array keys for the benefit of humans, who start counting
845       // from 1 and not 0.
846       $current = reset($keys) + 1;
847       $total = end($keys) + 1;
848       if ($total > 1) {
849         $progress = [];
850         $progress['current'] = $current;
851         $progress['total'] = $total;
852       }
853     }
854     return $progress;
855   }
856
857   /**
858    * Sets a cached view object in the user tempstore.
859    */
860   public function cacheSet() {
861     if ($this->isLocked()) {
862       drupal_set_message(t('Changes cannot be made to a locked view.'), 'error');
863       return;
864     }
865
866     // Let any future object know that this view has changed.
867     $this->changed = TRUE;
868
869     $executable = $this->getExecutable();
870     if (isset($executable->current_display)) {
871       // Add the knowledge of the changed display, too.
872       $this->changed_display[$executable->current_display] = TRUE;
873       $executable->current_display = NULL;
874     }
875
876     // Unset handlers. We don't want to write these into the cache.
877     $executable->display_handler = NULL;
878     $executable->default_display = NULL;
879     $executable->query = NULL;
880     $executable->displayHandlers = NULL;
881     \Drupal::service('user.shared_tempstore')->get('views')->set($this->id(), $this);
882   }
883
884   /**
885    * Returns whether the current view is locked.
886    *
887    * @return bool
888    *   TRUE if the view is locked, FALSE otherwise.
889    */
890   public function isLocked() {
891     return is_object($this->lock) && ($this->lock->owner != \Drupal::currentUser()->id());
892   }
893
894   /**
895    * Passes through all unknown calls onto the storage object.
896    */
897   public function __call($method, $args) {
898     return call_user_func_array([$this->storage, $method], $args);
899   }
900
901   /**
902    * {@inheritdoc}
903    */
904   public function &getDisplay($display_id) {
905     return $this->storage->getDisplay($display_id);
906   }
907
908   /**
909    * {@inheritdoc}
910    */
911   public function id() {
912     return $this->storage->id();
913   }
914
915   /**
916    * {@inheritdoc}
917    */
918   public function uuid() {
919     return $this->storage->uuid();
920   }
921
922   /**
923    * {@inheritdoc}
924    */
925   public function isNew() {
926     return $this->storage->isNew();
927   }
928
929   /**
930    * {@inheritdoc}
931    */
932   public function getEntityTypeId() {
933     return $this->storage->getEntityTypeId();
934   }
935
936   /**
937    * {@inheritdoc}
938    */
939   public function bundle() {
940     return $this->storage->bundle();
941   }
942
943   /**
944    * {@inheritdoc}
945    */
946   public function getEntityType() {
947     return $this->storage->getEntityType();
948   }
949
950   /**
951    * {@inheritdoc}
952    */
953   public function createDuplicate() {
954     return $this->storage->createDuplicate();
955   }
956
957   /**
958    * {@inheritdoc}
959    */
960   public static function load($id) {
961     return View::load($id);
962   }
963
964   /**
965    * {@inheritdoc}
966    */
967   public static function loadMultiple(array $ids = NULL) {
968     return View::loadMultiple($ids);
969   }
970
971   /**
972    * {@inheritdoc}
973    */
974   public static function create(array $values = []) {
975     return View::create($values);
976   }
977
978   /**
979    * {@inheritdoc}
980    */
981   public function delete() {
982     return $this->storage->delete();
983   }
984
985   /**
986    * {@inheritdoc}
987    */
988   public function save() {
989     return $this->storage->save();
990   }
991
992   /**
993    * {@inheritdoc}
994    */
995   public function urlInfo($rel = 'edit-form', array $options = []) {
996     return $this->storage->urlInfo($rel, $options);
997   }
998
999   /**
1000    * {@inheritdoc}
1001    */
1002   public function toUrl($rel = 'edit-form', array $options = []) {
1003     return $this->storage->toUrl($rel, $options);
1004   }
1005
1006   /**
1007    * {@inheritdoc}
1008    */
1009   public function link($text = NULL, $rel = 'edit-form', array $options = []) {
1010     return $this->storage->link($text, $rel, $options);
1011   }
1012
1013   /**
1014    * {@inheritdoc}
1015    */
1016   public function toLink($text = NULL, $rel = 'edit-form', array $options = []) {
1017     return $this->storage->toLink($text, $rel, $options);
1018   }
1019
1020   /**
1021    * {@inheritdoc}
1022    */
1023   public function label() {
1024     return $this->storage->label();
1025   }
1026
1027   /**
1028    * {@inheritdoc}
1029    */
1030   public function enforceIsNew($value = TRUE) {
1031     return $this->storage->enforceIsNew($value);
1032   }
1033
1034   /**
1035    * {@inheritdoc}
1036    */
1037   public function toArray() {
1038     return $this->storage->toArray();
1039   }
1040
1041   /**
1042    * {@inheritdoc}
1043    */
1044   public function language() {
1045     return $this->storage->language();
1046   }
1047
1048   /**
1049    * {@inheritdoc}
1050    */
1051   public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
1052     return $this->storage->access($operation, $account, $return_as_object);
1053   }
1054
1055   /**
1056    * {@inheritdoc}
1057    */
1058   public function enable() {
1059     return $this->storage->enable();
1060   }
1061
1062   /**
1063    * {@inheritdoc}
1064    */
1065   public function disable() {
1066     return $this->storage->disable();
1067   }
1068
1069   /**
1070    * {@inheritdoc}
1071    */
1072   public function status() {
1073     return $this->storage->status();
1074   }
1075
1076   /**
1077    * {@inheritdoc}
1078    */
1079   public function getOriginalId() {
1080     return $this->storage->getOriginalId();
1081   }
1082
1083   /**
1084    * {@inheritdoc}
1085    */
1086   public function setOriginalId($id) {
1087     return $this->storage->setOriginalId($id);
1088   }
1089
1090   /**
1091    * {@inheritdoc}
1092    */
1093   public function preSave(EntityStorageInterface $storage) {
1094     $this->storage->presave($storage);
1095   }
1096
1097   /**
1098    * {@inheritdoc}
1099    */
1100   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
1101     $this->storage->postSave($storage, $update);
1102   }
1103
1104   /**
1105    * {@inheritdoc}
1106    */
1107   public static function preCreate(EntityStorageInterface $storage, array &$values) {
1108   }
1109
1110   /**
1111    * {@inheritdoc}
1112    */
1113   public function postCreate(EntityStorageInterface $storage) {
1114     $this->storage->postCreate($storage);
1115   }
1116
1117   /**
1118    * {@inheritdoc}
1119    */
1120   public static function preDelete(EntityStorageInterface $storage, array $entities) {
1121   }
1122
1123   /**
1124    * {@inheritdoc}
1125    */
1126   public static function postDelete(EntityStorageInterface $storage, array $entities) {
1127   }
1128
1129   /**
1130    * {@inheritdoc}
1131    */
1132   public static function postLoad(EntityStorageInterface $storage, array &$entities) {
1133   }
1134
1135   /**
1136    * {@inheritdoc}
1137    */
1138   public function getExecutable() {
1139     return $this->storage->getExecutable();
1140   }
1141
1142   /**
1143    * {@inheritdoc}
1144    */
1145   public function duplicateDisplayAsType($old_display_id, $new_display_type) {
1146     return $this->storage->duplicateDisplayAsType($old_display_id, $new_display_type);
1147   }
1148
1149   /**
1150    * {@inheritdoc}
1151    */
1152   public function mergeDefaultDisplaysOptions() {
1153     $this->storage->mergeDefaultDisplaysOptions();
1154   }
1155
1156   /**
1157    * {@inheritdoc}
1158    */
1159   public function uriRelationships() {
1160     return $this->storage->uriRelationships();
1161   }
1162
1163   /**
1164    * {@inheritdoc}
1165    */
1166   public function referencedEntities() {
1167     return $this->storage->referencedEntities();
1168   }
1169
1170   /**
1171    * {@inheritdoc}
1172    */
1173   public function url($rel = 'edit-form', $options = []) {
1174     return $this->storage->url($rel, $options);
1175   }
1176
1177   /**
1178    * {@inheritdoc}
1179    */
1180   public function hasLinkTemplate($key) {
1181     return $this->storage->hasLinkTemplate($key);
1182   }
1183
1184   /**
1185    * {@inheritdoc}
1186    */
1187   public function calculateDependencies() {
1188     $this->storage->calculateDependencies();
1189     return $this;
1190   }
1191
1192   /**
1193    * {@inheritdoc}
1194    */
1195   public function getConfigDependencyKey() {
1196     return $this->storage->getConfigDependencyKey();
1197   }
1198
1199   /**
1200    * {@inheritdoc}
1201    */
1202   public function getConfigDependencyName() {
1203     return $this->storage->getConfigDependencyName();
1204   }
1205
1206   /**
1207    * {@inheritdoc}
1208    */
1209   public function getConfigTarget() {
1210     return $this->storage->getConfigTarget();
1211   }
1212
1213   /**
1214    * {@inheritdoc}
1215    */
1216   public function onDependencyRemoval(array $dependencies) {
1217     return $this->storage->onDependencyRemoval($dependencies);
1218   }
1219
1220   /**
1221    * {@inheritdoc}
1222    */
1223   public function getDependencies() {
1224     return $this->storage->getDependencies();
1225   }
1226
1227   /**
1228    * {@inheritdoc}
1229    */
1230   public function getCacheContexts() {
1231     return $this->storage->getCacheContexts();
1232   }
1233
1234   /**
1235    * {@inheritdoc}
1236    */
1237   public function getCacheTags() {
1238     return $this->storage->getCacheTags();
1239   }
1240
1241   /**
1242    * {@inheritdoc}
1243    */
1244   public function getCacheMaxAge() {
1245     return $this->storage->getCacheMaxAge();
1246   }
1247
1248   /**
1249    * {@inheritdoc}
1250    */
1251   public function getTypedData() {
1252     $this->storage->getTypedData();
1253   }
1254
1255   /**
1256    * {@inheritdoc}
1257    */
1258   public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
1259     return $this->storage->addDisplay($plugin_id, $title, $id);
1260   }
1261
1262   /**
1263    * {@inheritdoc}
1264    */
1265   public function isInstallable() {
1266     return $this->storage->isInstallable();
1267   }
1268
1269   /**
1270    * {@inheritdoc}
1271    */
1272   public function setThirdPartySetting($module, $key, $value) {
1273     return $this->storage->setThirdPartySetting($module, $key, $value);
1274   }
1275
1276   /**
1277    * {@inheritdoc}
1278    */
1279   public function getThirdPartySetting($module, $key, $default = NULL) {
1280     return $this->storage->getThirdPartySetting($module, $key, $default);
1281   }
1282
1283   /**
1284    * {@inheritdoc}
1285    */
1286   public function getThirdPartySettings($module) {
1287     return $this->storage->getThirdPartySettings($module);
1288   }
1289
1290   /**
1291    * {@inheritdoc}
1292    */
1293   public function unsetThirdPartySetting($module, $key) {
1294     return $this->storage->unsetThirdPartySetting($module, $key);
1295   }
1296
1297   /**
1298    * {@inheritdoc}
1299    */
1300   public function getThirdPartyProviders() {
1301     return $this->storage->getThirdPartyProviders();
1302   }
1303
1304   /**
1305    * {@inheritdoc}
1306    */
1307   public function trustData() {
1308     return $this->storage->trustData();
1309   }
1310
1311   /**
1312    * {@inheritdoc}
1313    */
1314   public function hasTrustedData() {
1315     return $this->storage->hasTrustedData();
1316   }
1317
1318   /**
1319    * {@inheritdoc}
1320    */
1321   public function addCacheableDependency($other_object) {
1322     $this->storage->addCacheableDependency($other_object);
1323     return $this;
1324   }
1325
1326   /**
1327    * {@inheritdoc}
1328    */
1329   public function addCacheContexts(array $cache_contexts) {
1330     return $this->storage->addCacheContexts($cache_contexts);
1331   }
1332
1333   /**
1334    * {@inheritdoc}
1335    */
1336   public function mergeCacheMaxAge($max_age) {
1337     return $this->storage->mergeCacheMaxAge($max_age);
1338   }
1339
1340   /**
1341    * {@inheritdoc}
1342    */
1343   public function getCacheTagsToInvalidate() {
1344     return $this->storage->getCacheTagsToInvalidate();
1345   }
1346
1347   /**
1348    * {@inheritdoc}
1349    */
1350   public function addCacheTags(array $cache_tags) {
1351     return $this->storage->addCacheTags($cache_tags);
1352   }
1353
1354 }