Commit 42505613 authored by alexpott's avatar alexpott

Issue #2387157 by dawehner, Gábor Hojtsy: Cloning display into another display...

Issue #2387157 by dawehner, Gábor Hojtsy: Cloning display into another display also stores options that are not supported by the new display type
parent 4efd487c
......@@ -7,6 +7,7 @@
namespace Drupal\views\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\views\Views;
......@@ -239,6 +240,38 @@ public function &getDisplay($display_id) {
return $this->display[$display_id];
}
/**
* {@inheritdoc}
*/
public function duplicateDisplayAsType($old_display_id, $new_display_type) {
$executable = $this->getExecutable();
$display = $executable->newDisplay($new_display_type);
$new_display_id = $display->display['id'];
$displays = $this->get('display');
// Let the display title be generated by the addDisplay method and set the
// right display plugin, but keep the rest from the original display.
$display_duplicate = $displays[$old_display_id];
unset($display_duplicate['display_title']);
unset($display_duplicate['display_plugin']);
$displays[$new_display_id] = NestedArray::mergeDeep($displays[$new_display_id], $display_duplicate);
$displays[$new_display_id]['id'] = $new_display_id;
// First set the displays.
$this->set('display', $displays);
// Ensure that we just copy display options, which are provided by the new
// display plugin.
$executable->setDisplay($new_display_id);
$executable->display_handler->filterByDefinedOptions($displays[$new_display_id]['display_options']);
// Update the display settings.
$this->set('display', $displays);
return $new_display_id;
}
/**
* {@inheritdoc}
*/
......
......@@ -176,6 +176,34 @@ protected function setOptionDefaults(array &$storage, array $options) {
}
}
/**
* {@inheritdoc}
*/
public function filterByDefinedOptions(array &$storage) {
$this->doFilterByDefinedOptions($storage, $this->defineOptions());
}
/**
* Do the work to filter out stored options depending on the defined options.
*
* @param array $storage
* The stored options.
*
* @param array $options
* The defined options.
*/
protected function doFilterByDefinedOptions(array &$storage, array $options) {
foreach ($storage as $key => $sub_storage) {
if (!isset($options[$key])) {
unset($storage[$key]);
}
if (isset($options[$key]['contains'])) {
$this->doFilterByDefinedOptions($storage[$key], $options[$key]['contains']);
}
}
}
/**
* {@inheritdoc}
*/
......
......@@ -38,6 +38,14 @@ public function pluginTitle();
*/
public function usesOptions();
/**
* Filter out stored options depending on the defined options.
*
* @param array $storage
* The stored options.
*/
public function filterByDefinedOptions(array &$storage);
/**
* Validate the options form.
*/
......
......@@ -16,6 +16,13 @@
*/
class TestHelperPlugin extends PluginBase {
/**
* Stores the defined options.
*
* @var array
*/
protected $definedOptions = [];
/**
* Calls the protected method setOptionDefaults().
*
......@@ -25,4 +32,26 @@ public function testSetOptionDefaults(&$storage, $options, $level = 0) {
$this->setOptionDefaults($storage, $options, $level);
}
/**
* Allows to set the defined options.
*
* @param array $options
*
* @return $this
*/
public function setDefinedOptions($options) {
$this->definedOptions = $options;
return $this;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
// Normally we provide a limited set of options, but for testing purposes we
// make it possible to set the defined options statically.
return $this->definedOptions;
}
}
......@@ -14,6 +14,14 @@
*/
interface ViewStorageInterface extends ConfigEntityInterface {
/**
* Gets an executable instance for this view.
*
* @return \Drupal\views\ViewExecutable
* A view executable instance.
*/
public function getExecutable();
/**
* Retrieves a specific display's configuration by reference.
*
......@@ -30,4 +38,19 @@ public function &getDisplay($display_id);
*/
public function mergeDefaultDisplaysOptions();
/**
* Duplicates an existing display into a new display type.
*
* For example clone to display a page display as a block display.
*
* @param string $old_display_id
* The origin of the duplicated display.
* @param string $new_display_type
* The display type of the new display.
*
* @return string
* The display ID of the new display.
*/
public function duplicateDisplayAsType($old_display_id, $new_display_type);
}
......@@ -285,4 +285,39 @@ public function providerTestSetOptionDefault() {
return $test_parameters;
}
/**
* Tests filterByDefinedOptions().
*
* @dataProvider providerTestFilterByDefinedOptions
*/
public function testFilterByDefinedOptions($storage, $options, $expected_storage) {
$this->testHelperPlugin->setDefinedOptions($options);
$this->testHelperPlugin->filterByDefinedOptions($storage);
$this->assertEquals($expected_storage, $storage);
}
public function providerTestFilterByDefinedOptions() {
$data = [];
// A simple defined option.
$values_1 = ['key1' => 'value1'];
$options_1 = ['key1' => ['default' => '']];
$data[] = [$values_1, $options_1, $values_1];
// Multiple defined options .
$values_2 = ['key1' => 'value1', 'key2' => 'value2'];
$options_2 = ['key1' => ['default' => ''], 'key2' => ['default' => '']];
$data[] = [$values_2, $options_2, $values_2];
// Multiple options, just one defined.
$data[] = [$values_2, $options_1, $values_1];
// Nested options, all properly defined.
$data[] = [['sub1' => $values_2, 'sub2' => $values_2], ['sub1' => ['contains' => $options_2], 'sub2' => ['contains' => $options_2]], ['sub1' => $values_2, 'sub2' => $values_2]];
// Nested options, not all properly defined.
$data[] = [['sub1' => $values_2, 'sub2' => $values_2], ['sub1' => ['contains' => $options_2], 'sub2' => ['contains' => $options_1]], ['sub1' => $values_2, 'sub2' => $values_1]];
return $data;
}
}
......@@ -16,17 +16,6 @@
*/
class DisplayCRUDTest extends UITestBase {
/**
* Set to TRUE to strict check all configuration saved.
*
* @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
*
* @todo https://www.drupal.org/node/2387157
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
/**
* Views used by this test.
*
......@@ -114,6 +103,7 @@ public function testDefaultDisplay() {
public function testDuplicateDisplay() {
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] .'/edit';
$path = $view['page[path]'];
$this->drupalGet($path_prefix);
$this->drupalPostForm(NULL, array(), 'Duplicate Page');
......@@ -140,10 +130,12 @@ public function testDuplicateDisplay() {
$page_2 = $view->displayHandlers->get('page_2');
$this->assertTrue($page_2, 'The new page display got saved.');
$this->assertEqual($page_2->display['display_title'], 'Page');
$this->assertEqual($page_2->display['display_options']['path'], $path);
$block_1 = $view->displayHandlers->get('block_1');
$this->assertTrue($block_1, 'The new block display got saved.');
$this->assertEqual($block_1->display['display_plugin'], 'block');
$this->assertEqual($block_1->display['display_title'], 'Block', 'The new display title got generated as expected.');
$this->assertFalse(isset($block_1->display['display_options']['path']));
$this->assertEqual($block_1->getOption('title'), $random_title, 'The overridden title option from the display got copied into the duplicate');
$this->assertEqual($block_1->getOption('css_class'), $random_css, 'The overridden css_class option from the display got copied into the duplicate');
}
......
......@@ -855,25 +855,15 @@ public function submitDisplayAdd($form, FormStateInterface $form_state) {
* Submit handler to Duplicate a display as another display type.
*/
public function submitDuplicateDisplayAsType($form, FormStateInterface $form_state) {
/** @var \Drupal\views\ViewStorageInterface $view */
$view = $this->entity;
$display_id = $this->displayID;
// Create the new display.
$parents = $form_state->getTriggeringElement()['#parents'];
$display_type = array_pop($parents);
$display = $view->getExecutable()->newDisplay($display_type);
$new_display_id = $display->display['id'];
$displays = $view->get('display');
// Let the display title be generated by the addDisplay method and set the
// right display plugin, but keep the rest from the original display.
$display_duplicate = $displays[$display_id];
unset($display_duplicate['display_title']);
unset($display_duplicate['display_plugin']);
$displays[$new_display_id] = NestedArray::mergeDeep($displays[$new_display_id], $display_duplicate);
$displays[$new_display_id]['id'] = $new_display_id;
$view->set('display', $displays);
$new_display_id = $view->duplicateDisplayAsType($display_id, $display_type);
// By setting the current display the changed marker will appear on the new
// display.
......
......@@ -1071,6 +1071,20 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
}
/**
* {@inheritdoc}
*/
public function getExecutable() {
return $this->storage->getExecutable();
}
/**
* {@inheritdoc}
*/
public function duplicateDisplayAsType($old_display_id, $new_display_type) {
return $this->storage->duplicateDisplayAsType($old_display_id, $new_display_type);
}
/**
* {@inheritdoc}
*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment