Newer
Older
Daniel Wehner
committed
* Definition of Drupal\views\ViewExecutable.
namespace Drupal\views;
Dries Buytaert
committed
use Drupal;
Daniel Wehner
committed
use Symfony\Component\HttpFoundation\Response;
use Drupal\views\ViewStorageInterface;
Daniel Wehner
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.
*/
Daniel Wehner
committed
class ViewExecutable {
Tim Plunkett
committed
Daniel Wehner
committed
* The config entity in which the view is stored.
Angie Byron
committed
* @var Drupal\views\Plugin\Core\Entity\View
Tim Plunkett
committed
public $storage;
Damian Lee
committed
/**
* Whether or not the view has been built.
*
Tim Plunkett
committed
* @todo Group with other static properties.
*
Damian Lee
committed
* @var bool
*/
public $built = FALSE;
/**
* Whether the view has been executed/query has been run.
*
Tim Plunkett
committed
* @todo Group with other static properties.
*
Damian Lee
committed
* @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
*/
protected $ajaxEnabled = FALSE;
Peter Philipp
committed
/**
* Where the results of a query will go.
*
* The array must use a numeric index starting at 0.
*
* @var array
*/
Damian Lee
committed
public $result = array();
// May be used to override the current pager info.
Damian Lee
committed
/**
* 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;
/**
Dries Buytaert
committed
* Attachments to place before the view.
Damian Lee
committed
*
Dries Buytaert
committed
* @var array()
Damian Lee
committed
*/
Dries Buytaert
committed
public $attachment_before = array();
Damian Lee
committed
/**
Dries Buytaert
committed
* Attachments to place after the view.
Damian Lee
committed
*
Dries Buytaert
committed
* @var array
Damian Lee
committed
*/
Dries Buytaert
committed
public $attachment_after = array();
Damian Lee
committed
/**
* All the form data from $form_state['values'].
*
* @var array
*/
public $exposed_data = array();
Damian Lee
committed
/**
* An array of input values from exposed forms.
*
* @var array
*/
public $exposed_input = array();
Damian Lee
committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
* 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;
/**
* Identifier of the current display.
*
* @var string
*/
Damian Lee
committed
public $current_display;
Damian Lee
committed
* Where the $query object will reside.
Damian Lee
committed
* @var Drupal\views\Plugin\query\QueryInterface
Damian Lee
committed
public $query = NULL;
Daniel Wehner
committed
/**
* The used pager plugin used by the current executed view.
*
* @var Drupal\views\Plugin\views\pager\PagerPluginBase
*/
public $pager = NULL;
/**
Damian Lee
committed
* @var Drupal\views\Plugin\views\display\DisplayPluginBase
Damian Lee
committed
public $display_handler;
Daniel Wehner
committed
/**
* The list of used displays of the view.
*
* An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
* objects.
*
* @var array
*/
public $displayHandlers;
/**
* The current used style plugin.
*
Alex Pott
committed
* @var \Drupal\views\Plugin\views\style\StylePluginBase
Damian Lee
committed
public $style_plugin;
Angie Byron
committed
/**
* The current used row plugin, if the style plugin supports row plugins.
*
* @var \Drupal\views\Plugin\views\row\RowPluginBase
*/
public $rowPlugin;
/**
* Stores the current active row while rendering.
*
* @var int
*/
Damian Lee
committed
public $row_index;
/**
* Allow to override the url of the current view.
*
* @var string
*/
Damian Lee
committed
public $override_url = NULL;
/**
* Allow to override the path used for generated urls.
*
* @var string
*/
Damian Lee
committed
public $override_path = NULL;
/**
* Allow to override the used database which is used for this query.
Damian Lee
committed
*
* @var bool
Damian Lee
committed
public $base_database = NULL;
Damian Lee
committed
// Handlers which are active on this view.
/**
* Stores the field handlers which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\field\FieldPluginBase
* objects.
*
* @var array
Damian Lee
committed
public $field;
/**
* Stores the argument handlers which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\argument\ArgumentPluginBase
* objects.
*
* @var array
Damian Lee
committed
public $argument;
/**
* Stores the sort handlers which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\sort\SortPluginBase objects.
*
* @var array
Damian Lee
committed
public $sort;
/**
* Stores the filter handlers which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\filter\FilterPluginBase
* objects.
*
* @var array
Damian Lee
committed
public $filter;
/**
* Stores the relationship handlers which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\relationship\RelationshipPluginBase
* objects.
*
* @var array
Damian Lee
committed
public $relationship;
/**
* Stores the area handlers for the header which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
*
* @var array
Damian Lee
committed
public $header;
/**
* Stores the area handlers for the footer which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
*
* @var array
Damian Lee
committed
public $footer;
/**
* Stores the area handlers for the empty text which are initialized on this view.
Damian Lee
committed
*
* An array containing Drupal\views\Plugin\views\area\AreaPluginBase objects.
*
* @var array
Damian Lee
committed
public $empty;
Daniel Wehner
committed
/**
* Stores the current response object.
*
* @var Symfony\Component\HttpFoundation\Response
*/
protected $response = NULL;
Tim Plunkett
committed
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
/**
* 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;
catch
committed
/**
* 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(),
),
);
Angie Byron
committed
/**
* Should the admin links be shown on the rendered view.
*
* @var bool
*/
protected $showAdminLinks;
Daniel Wehner
committed
/**
* Constructs a new ViewExecutable object.
*
* @param \Drupal\views\ViewStorageInterface $storage
Daniel Wehner
committed
* The view config entity the actual information is stored on.
*/
public function __construct(ViewStorageInterface $storage) {
Daniel Wehner
committed
// Reference the storage and the executable to each other.
$this->storage = $storage;
$this->storage->set('executable', $this);
catch
committed
// Add the default css for a view.
$this->element['#attached']['css'][] = drupal_get_path('module', 'views') . '/css/views.base.css';
Daniel Wehner
committed
}
/**
Tim Plunkett
committed
* @todo.
Daniel Wehner
committed
*/
Tim Plunkett
committed
public function save() {
$this->storage->save();
}
/**
* Set the arguments that come to this view. Usually from the URL
* but possibly from elsewhere.
*/
Damian Lee
committed
public function setArguments($args) {
$this->args = $args;
}
/**
* Change/Set the current page for the pager.
*/
Damian Lee
committed
public function setCurrentPage($page) {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->set_current_page($page);
Daniel Wehner
committed
}
}
/**
* Get the current page from the pager.
*/
Damian Lee
committed
public function getCurrentPage() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->get_current_page();
Daniel Wehner
committed
if (isset($this->current_page)) {
return $this->current_page;
}
}
/**
* Get the items per page from the pager.
*/
Damian Lee
committed
public function getItemsPerPage() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->get_items_per_page();
Daniel Wehner
committed
if (isset($this->items_per_page)) {
return $this->items_per_page;
}
}
/**
* Set the items per page on the pager.
*/
Damian Lee
committed
public function setItemsPerPage($items_per_page) {
$this->items_per_page = $items_per_page;
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
$this->pager->set_items_per_page($items_per_page);
}
}
/**
* Get the pager offset from the pager.
*/
Damian Lee
committed
public function getOffset() {
Daniel Wehner
committed
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
return $this->pager->get_offset();
Daniel Wehner
committed
if (isset($this->offset)) {
return $this->offset;
}
}
/**
* Set the offset on the pager.
*/
Damian Lee
committed
public function setOffset($offset) {
$this->offset = $offset;
// If the pager is already initialized, pass it through to the pager.
Daniel Wehner
committed
if (!empty($this->pager)) {
$this->pager->set_offset($offset);
}
}
/**
* Determine if the pager actually uses a pager.
*/
Damian Lee
committed
public function usePager() {
Daniel Wehner
committed
if (!empty($this->pager)) {
Alex Pott
committed
return $this->pager->usePager();
* 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;
}
/**
* Set the exposed filters input to an array. If unset they will be taken
* from $_GET when the time comes.
*/
Damian Lee
committed
public function setExposedInput($filters) {
$this->exposed_input = $filters;
}
/**
* Figure out what the exposed input for this view is.
*/
Damian Lee
committed
public function getExposedInput() {
// Fill our input either from $_GET or from something previously set on the
// view.
if (empty($this->exposed_input)) {
Daniel Wehner
committed
$this->exposed_input = drupal_container()->get('request')->query->all();
// 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.
Daniel Wehner
committed
$display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
Angie Byron
committed
if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->storage->id()][$display_id])) {
$this->exposed_input = $_SESSION['views'][$this->storage->id()][$display_id];
}
}
return $this->exposed_input;
}
/**
* Set the display for this view and initialize the display handler.
*/
Damian Lee
committed
public function initDisplay() {
if (isset($this->current_display)) {
return TRUE;
}
catch
committed
// Initialize the display cache array.
Dries Buytaert
committed
$this->displayHandlers = new DisplayBag($this, Views::pluginManager('display'));
$this->display_handler = $this->displayHandlers->get('default');
return TRUE;
}
/**
* Get the first display that is accessible to the user.
*
Daniel Wehner
committed
* @param array|string $displays
* Either a single display id or an array of display ids.
Daniel Wehner
committed
*
* @return string
* The first accessible display id, at least default.
Damian Lee
committed
public function chooseDisplay($displays) {
if (!is_array($displays)) {
return $displays;
}
Damian Lee
committed
$this->initDisplay();
if ($this->displayHandlers->get($display_id)->access()) {
return $display_id;
}
}
return 'default';
}
/**
Dries Buytaert
committed
* Sets the current display.
Dries Buytaert
committed
* @param string $display_id
* The ID of the display to mark as current.
*
* @return bool
* TRUE if the display was correctly set, FALSE otherwise.
Damian Lee
committed
public function setDisplay($display_id = NULL) {
Dries Buytaert
committed
// 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.
Damian Lee
committed
$this->initDisplay();
Dries Buytaert
committed
}
Dries Buytaert
committed
// If no display ID is passed, we either have initialized the default or
// already have a display set.
if (!isset($display_id)) {
return TRUE;
Damian Lee
committed
$display_id = $this->chooseDisplay($display_id);
if (!$this->displayHandlers->has($display_id)) {
Dries Buytaert
committed
debug(format_string('setDisplay() called with invalid display ID "@display".', array('@display' => $display_id)));
return FALSE;
Angie Byron
committed
// 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;
}
Dries Buytaert
committed
if ($display = $this->displayHandlers->get($display_id)) {
// Set a shortcut.
$this->display_handler = $display;
return TRUE;
}
Dries Buytaert
committed
return FALSE;
}
/**
* 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.
*/
Damian Lee
committed
public function initStyle() {
Alex Pott
committed
return TRUE;
Alex Pott
committed
$this->style_plugin = $this->display_handler->getPlugin('style');
if (empty($this->style_plugin)) {
return FALSE;
}
return TRUE;
}
/**
* Acquire and attach all of the handlers.
*/
Damian Lee
committed
public function initHandlers() {
Tim Plunkett
committed
$this->initDisplay();
Tim Plunkett
committed
foreach ($this::viewsHandlerTypes() as $key => $info) {
Damian Lee
committed
$this->_initHandler($key, $info);
}
$this->inited = TRUE;
}
}
/**
* Initialize the pager
*
* Like style initialization, pager initialization is held until late
* to allow for overrides.
*/
Damian Lee
committed
public function initPager() {
Daniel Wehner
committed
if (!isset($this->pager)) {
Daniel Wehner
committed
$this->pager = $this->display_handler->getPlugin('pager');
Alex Pott
committed
if ($this->pager->usePager()) {
Daniel Wehner
committed
$this->pager->set_current_page($this->current_page);
}
// These overrides may have been set earlier via $view->set_*
// functions.
if (isset($this->items_per_page)) {
Tim Plunkett
committed
$this->pager->set_items_per_page($this->items_per_page);
Daniel Wehner
committed
$this->pager->set_offset($this->offset);
Daniel Wehner
committed
/**
* Render the pager, if necessary.
*/
Damian Lee
committed
public function renderPager($exposed_input) {
Alex Pott
committed
if (!empty($this->pager) && $this->pager->usePager()) {
Daniel Wehner
committed
return $this->pager->render($exposed_input);
}
return '';
}
/**
* Create a list of base tables eligible for this view. Used primarily
* for the UI. Display must be already initialized.
*/
Damian Lee
committed
public function getBaseTables() {
Angie Byron
committed
$this->storage->get('base_table') => TRUE,
Daniel Wehner
committed
foreach ($this->display_handler->getHandlers('relationship') as $handler) {
$base_tables[$handler->definition['base']] = TRUE;
}
return $base_tables;
}
/**
* Run the preQuery() on all active handlers.
Damian Lee
committed
protected function _preQuery() {
Tim Plunkett
committed
foreach ($this::viewsHandlerTypes() as $key => $info) {
$handlers = &$this->$key;
$position = 0;
foreach ($handlers as $id => $handler) {
$handlers[$id]->position = $position;
$handlers[$id]->preQuery();
* Run the postExecute() on all active handlers.
Damian Lee
committed
protected function _postExecute() {
Tim Plunkett
committed
foreach ($this::viewsHandlerTypes() as $key => $info) {
$handlers = &$this->$key;
foreach ($handlers as $id => $handler) {
$handlers[$id]->postExecute($this->result);
}
}
}
/**
* Attach all of the handlers for each type.
*
* @param $key
* One of 'argument', 'field', 'sort', 'filter', 'relationship'
* @param $info
Daniel Wehner
committed
* The $info from viewsHandlerTypes for this object.
Damian Lee
committed
protected function _initHandler($key, $info) {
// Load the requested items from the display onto the object.
Daniel Wehner
committed
$this->$key = $this->display_handler->getHandlers($key);
// 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.
*/
Damian Lee
committed
protected function _buildArguments() {
// 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.
Daniel Wehner
committed
$title = $this->display_handler->getOption('title');
$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;
}
$argument->setRelationship();
$arg = isset($this->args[$position]) ? $this->args[$position] : NULL;
$argument->position = $position;
Alex Pott
committed
if (isset($arg) || $argument->hasDefaultArgument()) {
Alex Pott
committed
$arg = $argument->getDefaultArgument();
// 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.
Alex Pott
committed
if (!$argument->setArgument($arg)) {
$status = $argument->validateFail($arg);
break;
}
if ($argument->is_exception()) {
$arg_title = $argument->exception_title();
}
else {
$arg_title = $argument->getTitle();
Daniel Wehner
committed
$argument->query($this->display_handler->useGroupBy());
}
// 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()) {
Damian Lee
committed
$path = $this->getUrl($breadcrumb_args);
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
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.
*/
Damian Lee
committed
public function initQuery() {
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.
Dries Buytaert
committed
$views_data = Views::viewsData()->get($this->storage->get('base_table'));
Angie Byron
committed
$this->storage->set('base_field', !empty($views_data['table']['base']['field']) ? $views_data['table']['base']['field'] : '');
if (!empty($views_data['table']['base']['database'])) {
$this->base_database = $views_data['table']['base']['database'];
}
catch
committed
$this->query = $this->display_handler->getPlugin('query');
return TRUE;
}
/**
* Build the query for the view.
*/
Damian Lee
committed
public function build($display_id = NULL) {
if (!empty($this->built)) {
return;
}
if (empty($this->current_display) || $display_id) {
Damian Lee
committed
if (!$this->setDisplay($display_id)) {
return FALSE;
}
}
// Let modules modify the view just prior to building it.
Alex Pott
committed
$module_handler = \Drupal::moduleHandler();
$module_handler->invokeAll('views_pre_build', array($this));
// 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(),
);
Damian Lee
committed
$this->initQuery();
// 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.
Damian Lee
committed
$this->initHandlers();
// Let the handlers interact with each other if they really want.
Damian Lee
committed
$this->_preQuery();