comparison core/modules/views_ui/src/ViewUI.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 }