diff --git a/core/modules/views/lib/Drupal/views/DisplayArray.php b/core/modules/views/lib/Drupal/views/DisplayArray.php new file mode 100644 index 0000000..08bb2b6 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/DisplayArray.php @@ -0,0 +1,157 @@ +view->storage->display. + * + * @var array + */ + protected $displayIDs; + + /** + * Constructs a DisplayArray object. + * + * @param \Drupal\views\ViewExecutable + * The view which has this displays attached. + */ + public function __construct(ViewExecutable $view) { + $this->view = $view; + + $this->initializeDisplay('default'); + + // Store all display IDs to access them easy and fast. + $this->displayIDs = drupal_map_assoc(array_keys($this->view->storage->display)); + } + + /** + * Destructs a DisplayArray object. + */ + public function __destruct() { + foreach ($this->displayHandlers as $display_id => $display) { + $display->destroy(); + unset($this->displayHandlers[$display_id]); + } + } + + /** + * Initializes a single display and stores the result in $this->displayHandlers. + * + * @param string $display_id + * The name of the display to initialize. + */ + protected function initializeDisplay($display_id) { + // If the display was initialize before just return. + if (isset($this->displayHandlers[$display_id])) { + return; + } + + // Retrieve and initialize the new display handler with data. + $this->displayHandlers[$display_id] = views_get_plugin('display', $this->view->storage->display[$display_id]['display_plugin']); + $this->displayHandlers[$display_id]->init($this->view, $this->view->storage->display[$display_id]); + // If this is not the default display handler, let it know which is since + // it may well utilize some data from the default. + if ($display_id != 'default') { + $this->displayHandlers[$display_id]->default_display = $this->displayHandlers['default']; + } + } + + /** + * Implements \ArrayAccess::offsetExists(). + */ + public function offsetExists($offset) { + return isset($this->displayHandlers[$offset]) || isset($this->displayIDs[$offset]); + } + + /** + * Implements \ArrayAccess::offsetGet(). + */ + public function offsetGet($offset) { + if (!isset($this->displayHandlers[$offset])) { + $this->initializeDisplay($offset); + } + return $this->displayHandlers[$offset]; + } + + /** + * Implements \ArrayAccess::offsetSet(). + */ + public function offsetSet($offset, $value) { + $this->displayHandlers[$offset] = $value; + } + + /** + * Implements \ArrayAccess::offsetUnset(). + */ + public function offsetUnset($offset) { + unset($this->displayHandlers[$offset]); + } + + /** + * Implements \Iterator::current(). + */ + public function current() { + return $this->offsetGet($this->key()); + } + + /** + * Implements \Iterator::next(). + */ + public function next() { + next($this->displayIDs); + } + + /** + * Implements \Iterator::key(). + */ + public function key() { + return key($this->displayIDs); + } + + /** + * Implements \Iterator::valid(). + */ + public function valid() { + $key = key($this->displayIDs); + return $key !== NULL && $key !== FALSE; + } + + /** + * Implements \Iterator::rewind(). + */ + public function rewind() { + reset($this->displayIDs); + } + + /** + * Implements \Countable::count(). + */ + public function count() { + return count($this->displayIDs); + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php b/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php index 3f39af8..cc3e608 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php +++ b/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php @@ -304,7 +304,7 @@ protected function generateDisplayId($plugin_id) { * @return Drupal\views\Plugin\views\display\DisplayPluginBase * A reference to the new handler object. */ - public function &newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) { + public function newDisplay($plugin_id = 'page', $title = NULL, $id = NULL) { $id = $this->addDisplay($plugin_id, $title, $id); return $this->get('executable')->newDisplay($id); } diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php index cf68a49..528fce1 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php @@ -662,6 +662,29 @@ public function usesLinkDisplay() { return !$this->hasPath(); } public function usesExposedFormInBlock() { return $this->hasPath(); } /** + * Find out all displays which are attached to this display. + * + * The method is just using the pure storage object to avoid loading of the + * sub displays which would kill lazy loading. + */ + public function getAttachedDisplays() { + $current_display_id = $this->display['id']; + $attached_displays = array(); + + // Go through all displays and search displays which link to this one. + foreach ($this->view->storage->display as $display_id => $display) { + if (isset($display['display_options']['displays'])) { + $displays = $display['display_options']['displays']; + if (isset($displays[$current_display_id])) { + $attached_displays[] = $display_id; + } + } + } + + return $attached_displays; + } + + /** * Check to see which display to use when creating links within * a view using this display. */ diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php index b071b9e..ddb44a0 100644 --- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php @@ -120,4 +120,18 @@ public function testFilterGroupsOverriding() { $this->assertFalse($view->displayHandlers['page']->isDefaulted('filters'), "Take sure that 'filters'' is marked as overridden."); } + /** + * Tests the getAttachedDisplays method. + */ + public function testGetAttachedDisplays() { + $view = views_get_view('test_get_attach_displays'); + + // Both the feed_1 and the feed_2 display are attached to the page display. + $view->setDisplay('page_1'); + $this->assertEqual($view->display_handler->getAttachedDisplays(), array('feed_1', 'feed_2')); + + $view->setDisplay('feed_1'); + $this->assertEqual($view->display_handler->getAttachedDisplays(), array()); + } + } diff --git a/core/modules/views/lib/Drupal/views/Tests/UI/DisplayTest.php b/core/modules/views/lib/Drupal/views/Tests/UI/DisplayTest.php index 0fd4bc3..9ab4ab4 100644 --- a/core/modules/views/lib/Drupal/views/Tests/UI/DisplayTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/UI/DisplayTest.php @@ -55,7 +55,8 @@ public function testRemoveDisplay() { // Delete the page, so we can test the undo process. $this->drupalPost($path_prefix . '/page_1', array(), 'delete Page'); $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete', 'undo delete of Page', 'Make sure there a undo button on the page display after deleting.'); - $this->assertTrue($this->xpath('//a[contains(@class, :class)]', array(':class' => 'views-display-deleted-link')), 'Make sure the display link is marked as to be deleted.'); + $element = $this->xpath('//a[contains(@href, :href) and contains(@class, :class)]', array(':href' => $path_prefix . '/page_1', ':class' => 'views-display-deleted-link')); + $this->assertTrue(!empty($element), 'Make sure the display link is marked as to be deleted.'); // Undo the deleting of the display. $this->drupalPost($path_prefix . '/page_1', array(), 'undo delete of Page'); diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php index 9fb3fbc..a2d7f8a 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewExecutableTest.php @@ -8,6 +8,7 @@ namespace Drupal\views\Tests; use Drupal\views\ViewExecutable; +use Drupal\views\DisplayArray; use Drupal\views\Plugin\views\display\DefaultDisplay; use Drupal\views\Plugin\views\display\Page; @@ -120,8 +121,7 @@ public function testDisplays() { // Tests Drupal\views\ViewExecutable::initDisplay(). $view->initDisplay(); - $count = count($view->displayHandlers); - $this->assertEqual($count, 3, format_string('Make sure all display handlers got instantiated (@count of @count_expected)', array('@count' => $count, '@count_expected' => 3))); + $this->assertTrue($view->displayHandlers instanceof DisplayArray, 'The displayHandlers property has the right class.'); // Tests the classes of the instances. $this->assertTrue($view->displayHandlers['default'] instanceof DefaultDisplay); $this->assertTrue($view->displayHandlers['page'] instanceof Page); diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php index 0f9837d..ed7dd55 100644 --- a/core/modules/views/lib/Drupal/views/ViewExecutable.php +++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php @@ -575,22 +575,8 @@ public function initDisplay() { return TRUE; } - // Instantiate all displays - foreach ($this->storage->get('display') as $id => $display) { - $this->displayHandlers[$id] = views_get_plugin('display', $display['display_plugin']); - if (!empty($this->displayHandlers[$id])) { - // Initialize the new display handler with data. - // @todo Refactor display to not need the handler data by reference. - $this->displayHandlers[$id]->init($this, $this->storage->getDisplay($id)); - // If this is NOT the default display handler, let it know which is - // since it may well utilize some data from the default. - // This assumes that the 'default' handler is always first. It always - // is. Make sure of it. - if ($id != 'default') { - $this->displayHandlers[$id]->default_display =& $this->displayHandlers['default']; - } - } - } + // Initialize the display cache array. + $this->displayHandlers = new DisplayArray($this); $this->current_display = 'default'; $this->display_handler = $this->displayHandlers['default']; @@ -1469,11 +1455,11 @@ public function attachDisplays() { } $this->is_attachment = TRUE; - // Give other displays an opportunity to attach to the view. - foreach ($this->displayHandlers as $id => $display) { - if (!empty($this->displayHandlers[$id])) { - $this->displayHandlers[$id]->attachTo($this->current_display); - } + // Find out which other displays attach to the current one. + $attachments = $this->display_handler->getAttachedDisplays(); + + foreach ($attachments as $display_id) { + $this->displayHandlers[$display_id]->attachTo($this->current_display); } $this->is_attachment = FALSE; } @@ -1831,12 +1817,7 @@ public function cloneView() { * collected. */ public function destroy() { - foreach (array_keys($this->displayHandlers) as $display_id) { - if (isset($this->displayHandlers[$display_id])) { - $this->displayHandlers[$display_id]->destroy(); - unset($this->displayHandlers[$display_id]); - } - } + unset($this->displayHandlers); foreach ($this::viewsHandlerTypes() as $type => $info) { if (isset($this->$type)) { @@ -2212,7 +2193,7 @@ public function setItemOption($display_id, $type, $id, $option, $value) { * @return Drupal\views\Plugin\views\display\DisplayPluginBase * A reference to the new handler object. */ - public function &newDisplay($id) { + public function newDisplay($id) { // Create a handler. $display = $this->storage->get('display'); $this->displayHandlers[$id] = views_get_plugin('display', $display[$id]['display_plugin']); diff --git a/core/modules/views/tests/views_test_config/config/views.view.test_get_attach_displays.yml b/core/modules/views/tests/views_test_config/config/views.view.test_get_attach_displays.yml new file mode 100644 index 0000000..96a1be7 --- /dev/null +++ b/core/modules/views/tests/views_test_config/config/views.view.test_get_attach_displays.yml @@ -0,0 +1,104 @@ +base_table: node +name: test_get_attach_displays +description: '' +tag: '' +human_name: test_get_attach_displays +core: 8.x +api_version: '3.0' +display: + default: + display_plugin: default + id: default + display_title: Master + position: '' + display_options: + access: + type: perm + cache: + type: none + query: + type: views_query + exposed_form: + type: basic + pager: + type: full + options: + items_per_page: '10' + style: + type: default + row: + type: node + options: + build_mode: teaser + links: '1' + comments: '0' + fields: + title: + id: title + table: node + field: title + label: '' + alter: + alter_text: '0' + make_link: '0' + absolute: '0' + trim: '0' + word_boundary: '0' + ellipsis: '0' + strip_tags: '0' + html: '0' + hide_empty: '0' + empty_zero: '0' + link_to_node: '1' + filters: + status: + value: '1' + table: node + field: status + id: status + expose: + operator: '0' + group: '1' + sorts: + created: + id: created + table: node + field: created + order: DESC + title: test_get_attach_displays + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: '' + display_options: + path: test-get-attach-displays + feed_1: + display_plugin: feed + id: feed_1 + display_title: Feed + position: '' + display_options: + pager: + type: some + style: + type: rss + row: + type: node_rss + path: test-get-attach-displays.xml + displays: + default: default + page_1: page_1 + feed_2: + display_plugin: feed + id: feed_2 + display_title: 'Feed 2' + position: '' + display_options: + displays: + default: default + page_1: page_1 +base_field: nid +disabled: '0' +module: views +langcode: und