diff --git a/core/modules/node/src/Tests/Views/BulkFormTest.php b/core/modules/node/src/Tests/Views/BulkFormTest.php index 42dc34a..b823e76 100644 --- a/core/modules/node/src/Tests/Views/BulkFormTest.php +++ b/core/modules/node/src/Tests/Views/BulkFormTest.php @@ -62,7 +62,6 @@ public function testBulkForm() { $this->assertTrue($node->isPublished(), 'Node has been published'); // Make sticky action. - $node->setPublished(FALSE); $node->save(); $this->assertFalse($node->isSticky(), 'Node is not sticky'); $edit = array( @@ -70,10 +69,9 @@ public function testBulkForm() { 'action' => 'node_make_sticky_action', ); $this->drupalPostForm(NULL, $edit, t('Apply')); - // Re-load the node and check the status and sticky flag. + // Re-load the node and check the sticky flag. $node_storage->resetCache(array($node->id())); $node = $node_storage->load($node->id()); - $this->assertTrue($node->isPublished(), 'Node has been published'); $this->assertTrue($node->isSticky(), 'Node has been made sticky'); // Make unsticky action. @@ -88,7 +86,6 @@ public function testBulkForm() { $this->assertFalse($node->isSticky(), 'Node is not sticky anymore'); // Promote to front page. - $node->setPublished(FALSE); $node->save(); $this->assertFalse($node->isPromoted(), 'Node is not promoted to the front page'); $edit = array( @@ -96,10 +93,9 @@ public function testBulkForm() { 'action' => 'node_promote_action', ); $this->drupalPostForm(NULL, $edit, t('Apply')); - // Re-load the node and check the status and promoted flag. + // Re-load the node and check the promoted flag. $node_storage->resetCache(array($node->id())); $node = $node_storage->load($node->id()); - $this->assertTrue($node->isPublished(), 'Node has been published'); $this->assertTrue($node->isPromoted(), 'Node has been promoted to the front page'); // Demote from front page. diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php index ecd541e..63d3ba1 100644 --- a/core/modules/views/src/Entity/View.php +++ b/core/modules/views/src/Entity/View.php @@ -433,4 +433,13 @@ public function isInstallable() { return (bool) \Drupal::service('views.views_data')->get($this->base_table); } + /** + * {@inheritdoc} + */ + public function __sleep() { + $keys = parent::__sleep(); + unset($keys[array_search('executable', $keys)]); + return $keys; + } + } diff --git a/core/modules/views/src/Form/ViewsExposedForm.php b/core/modules/views/src/Form/ViewsExposedForm.php index 1b1aa35..c87113c 100644 --- a/core/modules/views/src/Form/ViewsExposedForm.php +++ b/core/modules/views/src/Form/ViewsExposedForm.php @@ -121,7 +121,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . String::checkPlain($view->storage->id()) . '-' . String::checkPlain($display['id'])); /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */ - $exposed_form_plugin = $form_state->get('exposed_form_plugin'); + $exposed_form_plugin = $view->display_handler->getPlugin('exposed_form'); $exposed_form_plugin->exposedFormAlter($form, $form_state); // Save the form. @@ -134,15 +134,17 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { + $view = $form_state->get('view'); + foreach (array('field', 'filter') as $type) { /** @var \Drupal\views\Plugin\views\ViewsHandlerInterface[] $handlers */ - $handlers = &$form_state->get('view')->$type; + $handlers = &$view->$type; foreach ($handlers as $key => $handler) { $handlers[$key]->validateExposed($form, $form_state); } } /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */ - $exposed_form_plugin = $form_state->get('exposed_form_plugin'); + $exposed_form_plugin = $view->display_handler->getPlugin('exposed_form'); $exposed_form_plugin->exposedFormValidate($form, $form_state); } @@ -157,13 +159,14 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $handlers[$key]->submitExposed($form, $form_state); } } + $view = $form_state->get('view'); $view->exposed_data = $form_state->getValues(); $view->exposed_raw_input = []; - $exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset'); + $exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', 'reset'); /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */ - $exposed_form_plugin = $form_state->get('exposed_form_plugin'); + $exposed_form_plugin = $view->display_handler->getPlugin('exposed_form'); $exposed_form_plugin->exposedFormSubmit($form, $form_state, $exclude); foreach ($form_state->getValues() as $key => $value) { diff --git a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php index f9ba904..7f0caa9 100644 --- a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php +++ b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php @@ -150,7 +150,6 @@ public function renderExposedForm($block = FALSE) { $form_state->set('ajax', TRUE); } - $form_state->set('exposed_form_plugin', $this); $form = \Drupal::formBuilder()->buildForm('\Drupal\views\Form\ViewsExposedForm', $form_state); if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) { diff --git a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php index bb6b803..34a2a9a 100644 --- a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php +++ b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php @@ -55,6 +55,13 @@ abstract class RelationshipPluginBase extends HandlerBase { /** + * The relationship alias. + * + * @var string + */ + public $alias; + + /** * Overrides \Drupal\views\Plugin\views\HandlerBase::init(). * * Init handler to let relationships live on tables other than diff --git a/core/modules/views/src/Tests/ViewExecutableTest.php b/core/modules/views/src/Tests/ViewExecutableTest.php index ecbe857..ed1b47a 100644 --- a/core/modules/views/src/Tests/ViewExecutableTest.php +++ b/core/modules/views/src/Tests/ViewExecutableTest.php @@ -449,6 +449,7 @@ public function testValidateNestedLoops() { $errors = $executable->validate(); $total_error_count = array_reduce($errors, function ($carry, $item) { $carry += count($item); + return $carry; }); // Assert that there were 9 total errors across 3 displays. @@ -456,4 +457,29 @@ public function testValidateNestedLoops() { $this->assertIdentical(3, count($errors)); } + /** + * Tests serialization of the ViewExecutable object. + */ + public function testSerialization() { + $view = Views::getView('test_executable_displays'); + $view->setDisplay('page_1'); + $view->setArguments(['test']); + $view->setCurrentPage(2); + + $serialized = serialize($view); + + // Test the view storage object is not present in the actual serialized + // string. + $this->assertIdentical(strpos($serialized, '"Drupal\views\Entity\View"'), FALSE, 'The Drupal\views\Entity\View class was not found in the serialized string.'); + + /** @var \Drupal\views\ViewExecutable $unserialized */ + $unserialized = unserialize($serialized); + + $this->assertTrue($unserialized instanceof ViewExecutable); + $this->assertIdentical($view->storage->id(), $unserialized->storage->id(), 'The expected storage entity was loaded on the unserialized view.'); + $this->assertIdentical($unserialized->current_display, 'page_1', 'The expected display was set on the unserialized view.'); + $this->assertIdentical($unserialized->args, ['test'], 'The expected argument was set on the unserialized view.'); + $this->assertIdentical($unserialized->getCurrentPage(), 2, 'The expected current page was set on the unserialized view.'); + } + } diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index d74fe31..0eb445d 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -26,7 +26,7 @@ * 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. */ -class ViewExecutable { +class ViewExecutable implements \Serializable { use DependencySerializationTrait; /** @@ -2295,4 +2295,49 @@ public function calculateDependencies() { return $this->storage->calculateDependencies(); } + /** + * {@inheritdoc} + */ + public function serialize() { + return serialize([ + // Only serialize the storage entity ID. + $this->storage->id(), + $this->current_display, + $this->args, + $this->current_page, + $this->exposed_input, + $this->exposed_raw_input, + $this->exposed_data, + $this->dom_id, + $this->executed, + ]); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { + list($storage, $current_display, $args, $current_page, $exposed_input, $exposed_raw_input, $exposed_data, $dom_id, $executed) = unserialize($serialized); + + $this->setRequest(\Drupal::request()); + $this->user = \Drupal::currentUser(); + + $this->storage = \Drupal::entityManager()->getStorage('view')->load($storage); + + $this->setDisplay($current_display); + $this->setArguments($args); + $this->setCurrentPage($current_page); + $this->setExposedInput($exposed_input); + $this->exposed_data = $exposed_data; + $this->exposed_raw_input = $exposed_raw_input; + $this->dom_id = $dom_id; + + $this->initHandlers(); + + // If the display was previously executed, execute it now. + if ($executed) { + $this->execute($this->current_display); + } + } + } diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 5d27d30..28b1175 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -266,6 +266,7 @@ public function validate(array $form, FormStateInterface $form_state) { public function save(array $form, FormStateInterface $form_state) { $view = $this->entity; $executable = $view->getExecutable(); + $executable->initDisplay(); // Go through and remove displayed scheduled for removal. $displays = $view->get('display'); diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 68eb0e8..3dcb45b 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -173,10 +173,10 @@ class ViewUI implements ViewEntityInterface { public function __construct(ViewEntityInterface $storage, ViewExecutable $executable = NULL) { $this->entityType = 'view'; $this->storage = $storage; - if (!isset($executable)) { - $executable = Views::executableFactory()->get($this); + + if (isset($executable)) { + $this->executable = $executable; } - $this->executable = $executable; } /** @@ -259,7 +259,7 @@ public function standardSubmit($form, FormStateInterface $form_state) { $display_id = $form_state->get('display_id'); if ($revert) { // If it's revert just change the override and return. - $display = &$this->executable->displayHandlers->get($display_id); + $display = &$this->getExecutable()->displayHandlers->get($display_id); $display->optionsOverride($form, $form_state); // Don't execute the normal submit handling but still store the changed view into cache. @@ -273,7 +273,7 @@ public function standardSubmit($form, FormStateInterface $form_state) { elseif ($was_defaulted && !$is_defaulted) { // We were using the default display's values, but we're now overriding // the default display and saving values specific to this display. - $display = &$this->executable->displayHandlers->get($display_id); + $display = &$this->getExecutable()->displayHandlers->get($display_id); // optionsOverride toggles the override of this section. $display->optionsOverride($form, $form_state); $display->submitOptionsForm($form, $form_state); @@ -283,7 +283,7 @@ public function standardSubmit($form, FormStateInterface $form_state) { // to go back to the default display. // Overwrite the default display with the current form values, and make // the current display use the new default values. - $display = &$this->executable->displayHandlers->get($display_id); + $display = &$this->getExecutable()->displayHandlers->get($display_id); // optionsOverride toggles the override of this section. $display->optionsOverride($form, $form_state); $display->submitOptionsForm($form, $form_state); @@ -481,7 +481,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) { if ($was_defaulted && !$is_defaulted) { // We were using the default display's values, but we're now overriding // the default display and saving values specific to this display. - $display = &$this->executable->displayHandlers->get($display_id); + $display = &$this->getExecutable()->displayHandlers->get($display_id); // setOverride toggles the override of this section. $display->setOverride($section); } @@ -490,7 +490,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) { // to go back to the default display. // Overwrite the default display with the current form values, and make // the current display use the new default values. - $display = &$this->executable->displayHandlers->get($display_id); + $display = &$this->getExecutable()->displayHandlers->get($display_id); // optionsOverride toggles the override of this section. $display->setOverride($section); } @@ -503,7 +503,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) { if ($cut = strpos($field, '$')) { $field = substr($field, 0, $cut); } - $id = $this->executable->addHandler($display_id, $type, $table, $field); + $id = $this->getExecutable()->addHandler($display_id, $type, $table, $field); // check to see if we have group by settings $key = $type; @@ -516,7 +516,7 @@ public function submitItemAdd($form, FormStateInterface $form_state) { 'field' => $field, ); $handler = Views::handlerManager($key)->getHandler($item); - if ($this->executable->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) { + if ($this->getExecutable()->displayHandlers->get('default')->useGroupBy() && $handler->usesGroupBy()) { $this->addFormToStack('handler-group', $display_id, $type, $id); } @@ -580,11 +580,11 @@ public function renderPreview($display_id, $args = array()) { $rows = array('query' => array(), 'statistics' => array()); - $errors = $this->executable->validate(); - $this->executable->destroy(); + $errors = $this->getExecutable()->validate(); + $this->getExecutable()->destroy(); if (empty($errors)) { $this->ajax = TRUE; - $this->executable->live_preview = TRUE; + $this->getExecutable()->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. @@ -597,19 +597,19 @@ public function renderPreview($display_id, $args = array()) { unset($exposed_input[$key]); } } - $this->executable->setExposedInput($exposed_input); + $this->getExecutable()->setExposedInput($exposed_input); - if (!$this->executable->setDisplay($display_id)) { + if (!$this->getExecutable()->setDisplay($display_id)) { return [ '#markup' => t('Invalid display id @display', array('@display' => $display_id)), ]; } - $this->executable->setArguments($args); + $this->getExecutable()->setArguments($args); // Store the current view URL for later use: - if ($this->executable->display_handler->getOption('path')) { - $path = $this->executable->getUrl(); + if ($this->getExecutable()->display_handler->getOption('path')) { + $path = $this->getExecutable()->getUrl(); } // Make view links come back to preview. @@ -646,7 +646,7 @@ public function renderPreview($display_id, $args = array()) { } // Execute/get the view preview. - $preview = $this->executable->preview($display_id, $args); + $preview = $this->getExecutable()->preview($display_id, $args); if ($show_additional_queries) { $this->endQueryCapture(); @@ -660,13 +660,13 @@ public function renderPreview($display_id, $args = array()) { // below the view preview. if ($show_info || $show_query || $show_stats) { // Get information from the preview for display. - if (!empty($this->executable->build_info['query'])) { + if (!empty($this->getExecutable()->build_info['query'])) { if ($show_query) { - $query_string = $this->executable->build_info['query']; + $query_string = $this->getExecutable()->build_info['query']; // Only the sql default class has a method getArguments. $quoted = array(); - if ($this->executable->query instanceof Sql) { + if ($this->getExecutable()->query instanceof Sql) { $quoted = $query_string->getArguments(); $connection = Database::getConnection(); foreach ($quoted as $key => $val) { @@ -722,7 +722,7 @@ public function renderPreview($display_id, $args = array()) { '#template' => "{% trans 'Title' %}", ), ), - Xss::filterAdmin($this->executable->getTitle()), + Xss::filterAdmin($this->getExecutable()->getTitle()), ); if (isset($path)) { // @todo Views should expect and store a leading /. See: @@ -743,7 +743,7 @@ public function renderPreview($display_id, $args = array()) { '#template' => "{% trans 'Query build time' %}", ), ), - t('@time ms', array('@time' => intval($this->executable->build_time * 100000) / 100)), + t('@time ms', array('@time' => intval($this->getExecutable()->build_time * 100000) / 100)), ); $rows['statistics'][] = array( @@ -753,7 +753,7 @@ public function renderPreview($display_id, $args = array()) { '#template' => "{% trans 'Query execute time' %}", ), ), - t('@time ms', array('@time' => intval($this->executable->execute_time * 100000) / 100)), + t('@time ms', array('@time' => intval($this->getExecutable()->execute_time * 100000) / 100)), ); $rows['statistics'][] = array( @@ -763,7 +763,7 @@ public function renderPreview($display_id, $args = array()) { '#template' => "{% trans 'View render time' %}", ), ), - t('@time ms', array('@time' => intval($this->executable->render_time * 100000) / 100)), + t('@time ms', array('@time' => intval($this->getExecutable()->render_time * 100000) / 100)), ); } \Drupal::moduleHandler()->alter('views_preview_info', $rows, $this->executable); @@ -873,14 +873,14 @@ public function cacheSet() { if (isset($executable->current_display)) { // Add the knowledge of the changed display, too. $this->changed_display[$executable->current_display] = TRUE; - unset($executable->current_display); + $executable->current_display = NULL; } - // Unset handlers; we don't want to write these into the cache. - unset($executable->display_handler); - unset($executable->default_display); + // Unset handlers. We don't want to write these into the cache. + $executable->display_handler = NULL; + $executable->default_display = NULL; $executable->query = NULL; - unset($executable->displayHandlers); + $executable->displayHandlers = NULL; \Drupal::service('user.shared_tempstore')->get('views')->set($this->id(), $this); } @@ -1132,7 +1132,11 @@ public static function postLoad(EntityStorageInterface $storage, array &$entitie * {@inheritdoc} */ public function getExecutable() { - return $this->storage->getExecutable(); + if (!isset($this->executable)) { + $this->executable = Views::executableFactory()->get($this); + } + + return $this->executable; } /** @@ -1242,13 +1246,6 @@ public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) { /** * {@inheritdoc} */ - public function getViewExecutable() { - return $this->storage->getViewExecutable(); - } - - /** - * {@inheritdoc} - */ public function isInstallable() { return $this->storage->isInstallable(); } @@ -1287,4 +1284,14 @@ public function unsetThirdPartySetting($module, $key) { public function getThirdPartyProviders() { return $this->storage->getThirdPartyProviders(); } + + /** + * {@inheritdoc} + */ + public function __sleep() { + $vars = get_object_vars($this); + unset($vars['executable']); + return array_keys($vars); + } + } diff --git a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php index 60a9d16..506bd32 100644 --- a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php +++ b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\UnitTestCase; +use Drupal\views\Entity\View; use Drupal\views\ViewExecutable; use Drupal\views_ui\ViewUI; use Symfony\Component\DependencyInjection\Container; @@ -113,4 +114,32 @@ public function testIsLocked() { $this->assertFalse($view_ui->isLocked()); } + /** + * Tests serialization of the ViewUI object. + */ + public function testSerialization() { + // Set a container so the DependencySerializationTrait has it. + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + + $storage = new View([], 'view'); + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->setConstructorArgs([$storage]) + ->getMock(); + + $view_ui = new ViewUI($storage, $executable); + + // Make sure the executable is returned before serializing. + $this->assertInstanceOf('Drupal\views\ViewExecutable', $view_ui->getExecutable()); + + $serialized = serialize($view_ui); + + // Make sure the ViewExecutable class is not found in the serialized string. + $this->assertSame(strpos($serialized, '"Drupal\views\ViewExecutable"'), FALSE); + + $unserialized = unserialize($serialized); + $this->assertInstanceOf('Drupal\views_ui\ViewUI', $unserialized); + } + }