You are here

public function ViewUI::renderPreview in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/views_ui/src/ViewUI.php \Drupal\views_ui\ViewUI::renderPreview()

File

core/modules/views_ui/src/ViewUI.php, line 515

Class

ViewUI
Stores UI related temporary settings.

Namespace

Drupal\views_ui

Code

public function renderPreview($display_id, $args = []) {

  // Save the current path so it can be restored before returning from this function.
  $request_stack = \Drupal::requestStack();
  $current_request = $request_stack
    ->getCurrentRequest();
  $executable = $this
    ->getExecutable();

  // Determine where the query and performance statistics should be output.
  $config = \Drupal::config('views.settings');
  $show_query = $config
    ->get('ui.show.sql_query.enabled');
  $show_info = $config
    ->get('ui.show.preview_information');
  $show_location = $config
    ->get('ui.show.sql_query.where');
  $show_stats = $config
    ->get('ui.show.performance_statistics');
  if ($show_stats) {
    $show_stats = $config
      ->get('ui.show.sql_query.where');
  }
  $combined = $show_query && $show_stats;
  $rows = [
    'query' => [],
    'statistics' => [],
  ];
  $errors = $executable
    ->validate();
  $executable
    ->destroy();
  if (empty($errors)) {
    $this->ajax = TRUE;
    $executable->live_preview = TRUE;

    // AJAX happens via HTTP POST but everything expects exposed data to
    // be in GET. Copy stuff but remove ajax-framework specific keys.
    // If we're clicking on links in a preview, though, we could actually
    // have some input in the query parameters, so we merge request() and
    // query() to ensure we get it all.
    $exposed_input = array_merge(\Drupal::request()->request
      ->all(), \Drupal::request()->query
      ->all());
    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) {
      if (isset($exposed_input[$key])) {
        unset($exposed_input[$key]);
      }
    }
    $executable
      ->setExposedInput($exposed_input);
    if (!$executable
      ->setDisplay($display_id)) {
      return [
        '#markup' => t('Invalid display id @display', [
          '@display' => $display_id,
        ]),
      ];
    }
    $executable
      ->setArguments($args);

    // Store the current view URL for later use:
    if ($executable
      ->hasUrl() && $executable->display_handler
      ->getOption('path')) {
      $path = $executable
        ->getUrl();
    }

    // Make view links come back to preview.
    // Also override the current path so we get the pager, and make sure the
    // Request object gets all of the proper values from $_SERVER.
    $request = Request::createFromGlobals();
    $request->attributes
      ->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form');
    $request->attributes
      ->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')
      ->getRouteByName('entity.view.preview_form'));
    $request->attributes
      ->set('view', $this->storage);
    $request->attributes
      ->set('display_id', $display_id);
    $raw_parameters = new ParameterBag();
    $raw_parameters
      ->set('view', $this
      ->id());
    $raw_parameters
      ->set('display_id', $display_id);
    $request->attributes
      ->set('_raw_variables', $raw_parameters);
    foreach ($args as $key => $arg) {
      $request->attributes
        ->set('arg_' . $key, $arg);
    }
    $request_stack
      ->push($request);

    // Suppress contextual links of entities within the result set during a
    // Preview.
    // @todo We'll want to add contextual links specific to editing the View, so
    //   the suppression may need to be moved deeper into the Preview pipeline.
    views_ui_contextual_links_suppress_push();
    $show_additional_queries = $config
      ->get('ui.show.additional_queries');
    Timer::start('entity.view.preview_form');
    if ($show_additional_queries) {
      $this
        ->startQueryCapture();
    }

    // Execute/get the view preview.
    $preview = $executable
      ->preview($display_id, $args);
    if ($show_additional_queries) {
      $this
        ->endQueryCapture();
    }
    $this->render_time = Timer::stop('entity.view.preview_form')['time'];
    views_ui_contextual_links_suppress_pop();

    // Prepare the query information and statistics to show either above or
    // below the view preview.
    // Initialize the empty rows arrays so we can safely merge them later.
    $rows['query'] = [];
    $rows['statistics'] = [];
    if ($show_info || $show_query || $show_stats) {

      // Get information from the preview for display.
      if (!empty($executable->build_info['query'])) {
        if ($show_query) {
          $query_string = $executable->build_info['query'];

          // Only the sql default class has a method getArguments.
          $quoted = [];
          if ($executable->query instanceof Sql) {
            $quoted = $query_string
              ->getArguments();
            $connection = Database::getConnection();
            foreach ($quoted as $key => $val) {
              if (is_array($val)) {
                $quoted[$key] = implode(', ', array_map([
                  $connection,
                  'quote',
                ], $val));
              }
              else {
                $quoted[$key] = $connection
                  ->quote($val);
              }
            }
          }
          $rows['query'][] = [
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => "<strong>{% trans 'Query' %}</strong>",
              ],
            ],
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => '<pre>{{ query }}</pre>',
                '#context' => [
                  'query' => strtr($query_string, $quoted),
                ],
              ],
            ],
          ];
          if (!empty($this->additionalQueries)) {
            $queries[] = [
              '#prefix' => '<strong>',
              '#markup' => t('These queries were run during view rendering:'),
              '#suffix' => '</strong>',
            ];
            foreach ($this->additionalQueries as $query) {
              $query_string = strtr($query['query'], $query['args']);
              $queries[] = [
                '#prefix' => "\n",
                '#markup' => t('[@time ms] @query', [
                  '@time' => round($query['time'] * 100000, 1) / 100000.0,
                  '@query' => $query_string,
                ]),
              ];
            }
            $rows['query'][] = [
              [
                'data' => [
                  '#type' => 'inline_template',
                  '#template' => "<strong>{% trans 'Other queries' %}</strong>",
                ],
              ],
              [
                'data' => [
                  '#prefix' => '<pre>',
                  'queries' => $queries,
                  '#suffix' => '</pre>',
                ],
              ],
            ];
          }
        }
        if ($show_info) {
          $rows['query'][] = [
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => "<strong>{% trans 'Title' %}</strong>",
              ],
            ],
            [
              'data' => [
                '#markup' => $executable
                  ->getTitle(),
              ],
            ],
          ];
          if (isset($path)) {

            // @todo Views should expect and store a leading /. See:
            //   https://www.drupal.org/node/2423913
            $path = Link::fromTextAndUrl($path
              ->toString(), $path)
              ->toString();
          }
          else {
            $path = t('This display has no path.');
          }
          $rows['query'][] = [
            [
              'data' => [
                '#prefix' => '<strong>',
                '#markup' => t('Path'),
                '#suffix' => '</strong>',
              ],
            ],
            [
              'data' => [
                '#markup' => $path,
              ],
            ],
          ];
        }
        if ($show_stats) {
          $rows['statistics'][] = [
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => "<strong>{% trans 'Query build time' %}</strong>",
              ],
            ],
            t('@time ms', [
              '@time' => intval($executable->build_time * 100000) / 100,
            ]),
          ];
          $rows['statistics'][] = [
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => "<strong>{% trans 'Query execute time' %}</strong>",
              ],
            ],
            t('@time ms', [
              '@time' => intval($executable->execute_time * 100000) / 100,
            ]),
          ];
          $rows['statistics'][] = [
            [
              'data' => [
                '#type' => 'inline_template',
                '#template' => "<strong>{% trans 'View render time' %}</strong>",
              ],
            ],
            t('@time ms', [
              '@time' => intval($this->render_time * 100) / 100,
            ]),
          ];
        }
        \Drupal::moduleHandler()
          ->alter('views_preview_info', $rows, $executable);
      }
      else {

        // No query was run. Display that information in place of either the
        // query or the performance statistics, whichever comes first.
        if ($combined || $show_location === 'above') {
          $rows['query'][] = [
            [
              'data' => [
                '#prefix' => '<strong>',
                '#markup' => t('Query'),
                '#suffix' => '</strong>',
              ],
            ],
            [
              'data' => [
                '#markup' => t('No query was run'),
              ],
            ],
          ];
        }
        else {
          $rows['statistics'][] = [
            [
              'data' => [
                '#prefix' => '<strong>',
                '#markup' => t('Query'),
                '#suffix' => '</strong>',
              ],
            ],
            [
              'data' => [
                '#markup' => t('No query was run'),
              ],
            ],
          ];
        }
      }
    }
  }
  else {
    foreach ($errors as $display_errors) {
      foreach ($display_errors as $error) {
        \Drupal::messenger()
          ->addError($error);
      }
    }
    $preview = [
      '#markup' => t('Unable to preview due to validation errors.'),
    ];
  }

  // Assemble the preview, the query info, and the query statistics in the
  // requested order.
  $table = [
    '#type' => 'table',
    '#prefix' => '<div class="views-query-info">',
    '#suffix' => '</div>',
    '#rows' => array_merge($rows['query'], $rows['statistics']),
  ];
  if ($show_location == 'above') {
    $output = [
      'table' => $table,
      'preview' => $preview,
    ];
  }
  else {
    $output = [
      'preview' => $preview,
      'table' => $table,
    ];
  }

  // Ensure that we just remove an additional request we pushed earlier.
  // This could happen if $errors was not empty.
  if ($request_stack
    ->getCurrentRequest() != $current_request) {
    $request_stack
      ->pop();
  }
  return $output;
}