Skip to content
ViewExecutable.php 56.2 KiB
Newer Older
Earl Miles's avatar
Earl Miles committed
<?php

/**
 * @file
 * Definition of Drupal\views\ViewExecutable.
use Symfony\Component\HttpFoundation\Response;
use Drupal\views\ViewStorageInterface;
Earl Miles's avatar
Earl Miles committed
/**
 * @defgroup views_objects Objects that represent a View or part of a view
 * @{
 * These objects are the core of Views do the bulk of the direction and
 * storing of data. All database activity is in these objects.
 */

/**
 * An object to contain all of the data to generate a view, plus the member
 * functions to build the view query, execute the query and render the output.
 */
Earl Miles's avatar
Earl Miles committed
  /**
   * The config entity in which the view is stored.
   * @var Drupal\views\Plugin\Core\Entity\View
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $built = FALSE;

  /**
   * Whether the view has been executed/query has been run.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $executed = FALSE;

  /**
   * Any arguments that have been passed into the view.
   *
   * @var array
   */
  public $args = array();

  /**
   * An array of build info.
   *
   * @var array
   */
  public $build_info = array();

  /**
   * Whether this view uses AJAX.
   *
   * @var bool
   */
  /**
   * Where the results of a query will go.
   *
   * The array must use a numeric index starting at 0.
   *
   * @var array
   */
Earl Miles's avatar
Earl Miles committed

  // May be used to override the current pager info.

  /**
   * The current page. If the view uses pagination.
   *
   * @var int
   */
  public $current_page = NULL;

  /**
   * The number of items per page.
   *
   * @var int
   */
  public $items_per_page = NULL;

  /**
   * The pager offset.
   *
   * @var int
   */
  public $offset = NULL;

  /**
   * The total number of rows returned from the query.
   *
   * @var array
   */
  public $total_rows = NULL;

  /**
Earl Miles's avatar
Earl Miles committed

  // Exposed widget input

  /**
   * All the form data from $form_state['values'].
   *
   * @var array
   */
  public $exposed_data = array();
  /**
   * An array of input values from exposed forms.
   *
   * @var array
   */
  public $exposed_input = array();
  /**
   * Exposed widget input directly from the $form_state['values'].
   *
   * @var array
   */
  public $exposed_raw_input = array();

  /**
   * Used to store views that were previously running if we recurse.
   *
   * @var array
   */
  public $old_view = array();

  /**
   * To avoid recursion in views embedded into areas.
   *
   * @var array
   */
  public $parent_views = array();

  /**
   * Whether this view is an attachment to another view.
   *
   * @var bool
   */
  public $is_attachment = NULL;
Earl Miles's avatar
Earl Miles committed

  /**
   * Identifier of the current display.
   *
   * @var string
   */
   * @var Drupal\views\Plugin\query\QueryInterface
  /**
   * The used pager plugin used by the current executed view.
   *
   * @var Drupal\views\Plugin\views\pager\PagerPluginBase
   */
  public $pager = NULL;

  /**
Earl Miles's avatar
Earl Miles committed
   * The current used display plugin.
   *
   * @var Drupal\views\Plugin\views\display\DisplayPluginBase
  /**
   * The list of used displays of the view.
   *
   * An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
   * objects.
   *
   * @var array
   */
  public $displayHandlers;

Earl Miles's avatar
Earl Miles committed
  /**
   * The current used style plugin.
   *
   * @var \Drupal\views\Plugin\views\style\StylePluginBase
  /**
   * The current used row plugin, if the style plugin supports row plugins.
   *
   * @var \Drupal\views\Plugin\views\row\RowPluginBase
   */
  public $rowPlugin;

Earl Miles's avatar
Earl Miles committed
  /**
   * Stores the current active row while rendering.
   *
   * @var int
   */
Earl Miles's avatar
Earl Miles committed

   /**
   * Allow to override the url of the current view.
   *
   * @var string
   */
Earl Miles's avatar
Earl Miles committed

  /**
   * Allow to override the path used for generated urls.
   *
   * @var string
   */
Earl Miles's avatar
Earl Miles committed

  /**
   * Allow to override the used database which is used for this query.
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the field handlers which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\field\FieldPluginBase
   * objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the argument handlers which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\argument\ArgumentPluginBase
   * objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the sort handlers which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\sort\SortPluginBase objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the filter handlers which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\filter\FilterPluginBase
   * objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the relationship handlers which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\relationship\RelationshipPluginBase
   * objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the header which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the footer which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var array
Earl Miles's avatar
Earl Miles committed

  /**
   * Stores the area handlers for the empty text which are initialized on this view.
   *
   * An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
   *
   * @var array
  /**
   * Stores the current response object.
   *
   * @var Symfony\Component\HttpFoundation\Response
   */
  protected $response = NULL;

  /**
   * Does this view already have loaded it's handlers.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $inited;

  /**
   * The rendered output of the exposed form.
   *
   * @var string
   */
  public $exposed_widgets;

  /**
   * If this view has been previewed.
   *
   * @var bool
   */
  public $preview;

  /**
   * Force the query to calculate the total number of results.
   *
   * @todo Move to the query.
   *
   * @var bool
   */
  public $get_total_rows;

  /**
   * Indicates if the sorts have been built.
   *
   * @todo Group with other static properties.
   *
   * @var bool
   */
  public $build_sort;

  /**
   * Stores the many-to-one tables for performance.
   *
   * @var array
   */
  public $many_to_one_tables;

  /**
   * A unique identifier which allows to update multiple views output via js.
   *
   * @var string
   */
  public $dom_id;

  /**
   * A render array container to store render related information.
   *
   * For example you can alter the array and attach some css/js via the
   * #attached key. This is the required way to add custom css/js.
   *
   * @var array
   *
   * @see drupal_process_attached
   */
  public $element = array(
    '#attached' => array(
      'css' => array(),
      'js' => array(),
      'library' => array(),
    ),
  );

  /**
   * Should the admin links be shown on the rendered view.
   *
   * @var bool
   */
  protected $showAdminLinks;

   * @param \Drupal\views\ViewStorageInterface $storage
   *   The view config entity the actual information is stored on.
   */
  public function __construct(ViewStorageInterface $storage) {
    // Reference the storage and the executable to each other.
    $this->storage = $storage;
    $this->storage->set('executable', $this);

    // Add the default css for a view.
    $this->element['#attached']['css'][] = drupal_get_path('module', 'views') . '/css/views.base.css';
  public function save() {
    $this->storage->save();
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Set the arguments that come to this view. Usually from the URL
   * but possibly from elsewhere.
   */
Earl Miles's avatar
Earl Miles committed
    $this->args = $args;
  }

  /**
   * Change/Set the current page for the pager.
   */
Earl Miles's avatar
Earl Miles committed
    $this->current_page = $page;

    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      return $this->pager->set_current_page($page);
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Get the current page from the pager.
   */
    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      return $this->pager->get_current_page();

    if (isset($this->current_page)) {
      return $this->current_page;
    }
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Get the items per page from the pager.
   */
    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      return $this->pager->get_items_per_page();

    if (isset($this->items_per_page)) {
      return $this->items_per_page;
    }
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Set the items per page on the pager.
   */
  public function setItemsPerPage($items_per_page) {
Earl Miles's avatar
Earl Miles committed
    $this->items_per_page = $items_per_page;

    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      $this->pager->set_items_per_page($items_per_page);
Earl Miles's avatar
Earl Miles committed
    }
  }

  /**
   * Get the pager offset from the pager.
   */
    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      return $this->pager->get_offset();
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Set the offset on the pager.
   */
Earl Miles's avatar
Earl Miles committed
    $this->offset = $offset;

    // If the pager is already initialized, pass it through to the pager.
    if (!empty($this->pager)) {
      $this->pager->set_offset($offset);
Earl Miles's avatar
Earl Miles committed
    }
  }

  /**
   * Determine if the pager actually uses a pager.
   */
   * Sets whether or not AJAX should be used.
   *
   * If AJAX is used, paging, tablesorting and exposed filters will be fetched
   * via an AJAX call rather than a page refresh.
   *
   * @param bool $use_ajax
   *   TRUE if AJAX should be used, FALSE otherwise.
   */
  public function setAjaxEnabled($ajax_enabled) {
    $this->ajaxEnabled = (bool) $ajax_enabled;
  }

  /**
   * Whether or not AJAX should be used.
   *
   * @see \Drupal\views\ViewExecutable::setAjaxEnabled().
   *
   * @return bool
  public function ajaxEnabled() {
    return $this->ajaxEnabled;
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Set the exposed filters input to an array. If unset they will be taken
   * from $_GET when the time comes.
   */
Earl Miles's avatar
Earl Miles committed
    $this->exposed_input = $filters;
  }

  /**
   * Figure out what the exposed input for this view is.
   */
Earl Miles's avatar
Earl Miles committed
    // Fill our input either from $_GET or from something previously set on the
    // view.
    if (empty($this->exposed_input)) {
      $this->exposed_input = drupal_container()->get('request')->query->all();
Earl Miles's avatar
Earl Miles committed
      // unset items that are definitely not our input:
      foreach (array('page', 'q') as $key) {
        if (isset($this->exposed_input[$key])) {
          unset($this->exposed_input[$key]);
        }
      }

      // If we have no input at all, check for remembered input via session.

      // If filters are not overridden, store the 'remember' settings on the
      // default display. If they are, store them on this display. This way,
      // multiple displays in the same view can share the same filters and
      // remember settings.
      $display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
      if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
        $this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
Earl Miles's avatar
Earl Miles committed
      }
    }

    return $this->exposed_input;
  }

  /**
   * Set the display for this view and initialize the display handler.
   */
Earl Miles's avatar
Earl Miles committed
    if (isset($this->current_display)) {
      return TRUE;
    }

    $this->displayHandlers = new DisplayBag($this, Views::pluginManager('display'));
Earl Miles's avatar
Earl Miles committed

    $this->current_display = 'default';
    $this->display_handler = $this->displayHandlers->get('default');
Earl Miles's avatar
Earl Miles committed

    return TRUE;
  }

  /**
   * Get the first display that is accessible to the user.
   *
Earl Miles's avatar
Earl Miles committed
   *   Either a single display id or an array of display ids.
   *
   * @return string
   *   The first accessible display id, at least default.
Earl Miles's avatar
Earl Miles committed
    if (!is_array($displays)) {
      return $displays;
    }

Earl Miles's avatar
Earl Miles committed

    foreach ($displays as $display_id) {
      if ($this->displayHandlers->get($display_id)->access()) {
Earl Miles's avatar
Earl Miles committed
        return $display_id;
      }
    }

    return 'default';
  }

  /**
   * @param string $display_id
   *   The ID of the display to mark as current.
   *
   * @return bool
   *   TRUE if the display was correctly set, FALSE otherwise.
  public function setDisplay($display_id = NULL) {
    // If we have not already initialized the display, do so.
    if (!isset($this->current_display)) {
      // This will set the default display and instantiate the default display
      // plugin.
    // If no display ID is passed, we either have initialized the default or
    // already have a display set.
    if (!isset($display_id)) {
      return TRUE;
    $display_id = $this->chooseDisplay($display_id);
Earl Miles's avatar
Earl Miles committed

    // Ensure the requested display exists.
    if (!$this->displayHandlers->has($display_id)) {
      debug(format_string('setDisplay() called with invalid display ID "@display".', array('@display' => $display_id)));
      return FALSE;
    // Reset if the display has changed. It could be called multiple times for
    // the same display, especially in the UI.
    if ($this->current_display != $display_id) {
      // Set the current display.
      $this->current_display = $display_id;

      // Reset the style and row plugins.
      $this->style_plugin = NULL;
      $this->plugin_name = NULL;
      $this->rowPlugin = NULL;
    }

    if ($display = $this->displayHandlers->get($display_id)) {
      // Set a shortcut.
      $this->display_handler = $display;
      return TRUE;
    }
Earl Miles's avatar
Earl Miles committed
  }

  /**
   * Find and initialize the style plugin.
   *
   * Note that arguments may have changed which style plugin we use, so
   * check the view object first, then ask the display handler.
   */
Earl Miles's avatar
Earl Miles committed
    if (isset($this->style_plugin)) {
    $this->style_plugin = $this->display_handler->getPlugin('style');
Earl Miles's avatar
Earl Miles committed

    if (empty($this->style_plugin)) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Acquire and attach all of the handlers.
   */
Earl Miles's avatar
Earl Miles committed
    if (empty($this->inited)) {
      foreach ($this::viewsHandlerTypes() as $key => $info) {
Earl Miles's avatar
Earl Miles committed
      }
      $this->inited = TRUE;
    }
  }

  /**
   * Initialize the pager
   *
   * Like style initialization, pager initialization is held until late
   * to allow for overrides.
   */
      $this->pager = $this->display_handler->getPlugin('pager');
        $this->pager->set_current_page($this->current_page);
Earl Miles's avatar
Earl Miles committed
      }

      // These overrides may have been set earlier via $view->set_*
      // functions.
      if (isset($this->items_per_page)) {
        $this->pager->set_items_per_page($this->items_per_page);
Earl Miles's avatar
Earl Miles committed
      }

      if (isset($this->offset)) {
  public function renderPager($exposed_input) {
    if (!empty($this->pager) && $this->pager->usePager()) {
Earl Miles's avatar
Earl Miles committed
  /**
   * Create a list of base tables eligible for this view. Used primarily
   * for the UI. Display must be already initialized.
   */
Earl Miles's avatar
Earl Miles committed
    $base_tables = array(
      $this->storage->get('base_table') => TRUE,
Earl Miles's avatar
Earl Miles committed
      '#global' => TRUE,
    );

    foreach ($this->display_handler->getHandlers('relationship') as $handler) {
Earl Miles's avatar
Earl Miles committed
      $base_tables[$handler->definition['base']] = TRUE;
    }
    return $base_tables;
  }

  /**
   * Run the preQuery() on all active handlers.
    foreach ($this::viewsHandlerTypes() as $key => $info) {
Earl Miles's avatar
Earl Miles committed
      $handlers = &$this->$key;
      $position = 0;
      foreach ($handlers as $id => $handler) {
        $handlers[$id]->position = $position;
   * Run the postExecute() on all active handlers.
    foreach ($this::viewsHandlerTypes() as $key => $info) {
Earl Miles's avatar
Earl Miles committed
      $handlers = &$this->$key;
      foreach ($handlers as $id => $handler) {
        $handlers[$id]->postExecute($this->result);
Earl Miles's avatar
Earl Miles committed
      }
    }
  }

  /**
   * Attach all of the handlers for each type.
   *
   * @param $key
   *   One of 'argument', 'field', 'sort', 'filter', 'relationship'
   * @param $info
   *   The $info from viewsHandlerTypes for this object.
  protected function _initHandler($key, $info) {
Earl Miles's avatar
Earl Miles committed
    // Load the requested items from the display onto the object.
    $this->$key = $this->display_handler->getHandlers($key);
Earl Miles's avatar
Earl Miles committed

    // This reference deals with difficult PHP indirection.
    $handlers = &$this->$key;

    // Run through and test for accessibility.
    foreach ($handlers as $id => $handler) {
      if (!$handler->access()) {
        unset($handlers[$id]);
      }
    }
  }

  /**
   * Build all the arguments.
   */
Earl Miles's avatar
Earl Miles committed
    // Initially, we want to build sorts and fields. This can change, though,
    // if we get a summary view.
    if (empty($this->argument)) {
      return TRUE;
    }

    // build arguments.
    $position = -1;

    // Create a title for use in the breadcrumb trail.
    $title = $this->display_handler->getOption('title');
Earl Miles's avatar
Earl Miles committed

    $this->build_info['breadcrumb'] = array();
    $breadcrumb_args = array();
    $substitutions = array();

    $status = TRUE;

    // Iterate through each argument and process.
    foreach ($this->argument as $id => $arg) {
      $position++;
      $argument = &$this->argument[$id];

      if ($argument->broken()) {
        continue;
      }

Earl Miles's avatar
Earl Miles committed

      $arg = isset($this->args[$position]) ? $this->args[$position] : NULL;
      $argument->position = $position;

      if (isset($arg) || $argument->hasDefaultArgument()) {
Earl Miles's avatar
Earl Miles committed
        if (!isset($arg)) {
Earl Miles's avatar
Earl Miles committed
          // make sure default args get put back.
          if (isset($arg)) {
            $this->args[$position] = $arg;
          }
          // remember that this argument was computed, not passed on the URL.
          $argument->is_default = TRUE;
        }

        // Set the argument, which will also validate that the argument can be set.
          $status = $argument->validateFail($arg);
Earl Miles's avatar
Earl Miles committed
          break;
        }

        if ($argument->is_exception()) {
          $arg_title = $argument->exception_title();
        }
        else {
          $arg_title = $argument->getTitle();
          $argument->query($this->display_handler->useGroupBy());
Earl Miles's avatar
Earl Miles committed
        }

        // Add this argument's substitution
        $substitutions['%' . ($position + 1)] = $arg_title;
        $substitutions['!' . ($position + 1)] = strip_tags(decode_entities($arg));

        // Since we're really generating the breadcrumb for the item above us,
        // check the default action of this argument.
        if ($this->display_handler->usesBreadcrumb() && $argument->usesBreadcrumb()) {
Earl Miles's avatar
Earl Miles committed
          if (strpos($path, '%') === FALSE) {
            if (!empty($argument->options['breadcrumb_enable']) && !empty($argument->options['breadcrumb'])) {
              $breadcrumb = $argument->options['breadcrumb'];
            }
            else {
              $breadcrumb = $title;
            }
            $this->build_info['breadcrumb'][$path] = str_replace(array_keys($substitutions), $substitutions, $breadcrumb);
          }
        }

        // Allow the argument to muck with this breadcrumb.
        $argument->set_breadcrumb($this->build_info['breadcrumb']);

        // Test to see if we should use this argument's title
        if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) {
          $title = $argument->options['title'];
        }

        $breadcrumb_args[] = $arg;
      }
      else {
        // determine default condition and handle.
        $status = $argument->default_action();
        break;
      }

      // Be safe with references and loops:
      unset($argument);
    }

    // set the title in the build info.
    if (!empty($title)) {
      $this->build_info['title'] = $title;
    }

    // Store the arguments for later use.
    $this->build_info['substitutions'] = $substitutions;

    return $status;
  }

  /**
   * Do some common building initialization.
   */
Earl Miles's avatar
Earl Miles committed
    if (!empty($this->query)) {
      $class = get_class($this->query);
      if ($class && $class != 'stdClass') {
        // return if query is already initialized.
        return TRUE;
      }
    }

    // Create and initialize the query object.
    $views_data = Views::viewsData()->get($this->storage->get('base_table'));
    $this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
Earl Miles's avatar
Earl Miles committed
    if (!empty($views_data['table']['base']['database'])) {
      $this->base_database = $views_data['table']['base']['database'];
    }

    $this->query = $this->display_handler->getPlugin('query');
Earl Miles's avatar
Earl Miles committed
    return TRUE;
  }

  /**
   * Build the query for the view.
   */
Earl Miles's avatar
Earl Miles committed
    if (!empty($this->built)) {
      return;
    }

    if (empty($this->current_display) || $display_id) {
Earl Miles's avatar
Earl Miles committed
        return FALSE;
      }
    }

    // Let modules modify the view just prior to building it.
    $module_handler = \Drupal::moduleHandler();
    $module_handler->invokeAll('views_pre_build', array($this));
Earl Miles's avatar
Earl Miles committed

    // Attempt to load from cache.
    // @todo Load a build_info from cache.

    $start = microtime(TRUE);
    // If that fails, let's build!
    $this->build_info = array(
      'query' => '',
      'count_query' => '',
      'query_args' => array(),
    );

Earl Miles's avatar
Earl Miles committed

    // Call a module hook and see if it wants to present us with a
    // pre-built query or instruct us not to build the query for
    // some reason.
    // @todo: Implement this. Use the same mechanism Panels uses.

    // Run through our handlers and ensure they have necessary information.
Earl Miles's avatar
Earl Miles committed

    // Let the handlers interact with each other if they really want.