Issue #2858434 by amateescu, vijaycs85, plach, timmillwood, catch: Menu changes from node form leak into live site when creating draft revision
							parent
							
								
									478ddd3118
								
							
						
					
					
						commit
						7755d437e0
					
				| 
						 | 
				
			
			@ -71,6 +71,10 @@ function menu_ui_entity_type_build(array &$entity_types) {
 | 
			
		|||
    ->setLinkTemplate('edit-form', '/admin/structure/menu/manage/{menu}')
 | 
			
		||||
    ->setLinkTemplate('add-link-form', '/admin/structure/menu/manage/{menu}/add')
 | 
			
		||||
    ->setLinkTemplate('collection', '/admin/structure/menu');
 | 
			
		||||
 | 
			
		||||
  if (isset($entity_types['node'])) {
 | 
			
		||||
    $entity_types['node']->addConstraint('MenuSettings', []);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -357,6 +361,15 @@ function menu_ui_form_node_form_alter(&$form, FormStateInterface $form_state) {
 | 
			
		|||
      $form['actions'][$action]['#submit'][] = 'menu_ui_form_node_form_submit';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $form['#entity_builders'][] = 'menu_ui_node_builder';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Entity form builder to add the menu information to the node.
 | 
			
		||||
 */
 | 
			
		||||
function menu_ui_node_builder($entity_type, NodeInterface $entity, &$form, FormStateInterface $form_state) {
 | 
			
		||||
  $entity->menu = $form_state->getValue('menu');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\menu_ui\Plugin\Validation\Constraint;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Validator\Constraint;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Validation constraint for changing the menu settings in forward revisions.
 | 
			
		||||
 *
 | 
			
		||||
 * @Constraint(
 | 
			
		||||
 *   id = "MenuSettings",
 | 
			
		||||
 *   label = @Translation("Menu settings.", context = "Validation"),
 | 
			
		||||
 * )
 | 
			
		||||
 */
 | 
			
		||||
class MenuSettingsConstraint extends Constraint {
 | 
			
		||||
 | 
			
		||||
  public $message = 'You can only change the menu settings for the <em>published</em> version of this content.';
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\menu_ui\Plugin\Validation\Constraint;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Validator\Constraint;
 | 
			
		||||
use Symfony\Component\Validator\ConstraintValidator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constraint validator for changing the menu settings in forward revisions.
 | 
			
		||||
 */
 | 
			
		||||
class MenuSettingsConstraintValidator extends ConstraintValidator {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validate($entity, Constraint $constraint) {
 | 
			
		||||
    if (isset($entity) && !$entity->isNew() && !$entity->isDefaultRevision()) {
 | 
			
		||||
      $defaults = menu_ui_get_menu_link_defaults($entity);
 | 
			
		||||
      $values = $entity->menu;
 | 
			
		||||
      $violation_path = NULL;
 | 
			
		||||
 | 
			
		||||
      if (trim($values['title']) && !empty($values['menu_parent'])) {
 | 
			
		||||
        list($menu_name, $parent) = explode(':', $values['menu_parent'], 2);
 | 
			
		||||
        $values['menu_name'] = $menu_name;
 | 
			
		||||
        $values['parent'] = $parent;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Handle the case when a menu link is added to a forward revision.
 | 
			
		||||
      if ($defaults['entity_id'] != $values['entity_id']) {
 | 
			
		||||
        $violation_path = 'menu';
 | 
			
		||||
      }
 | 
			
		||||
      // Handle the case when the menu link is deleted in a forward revision.
 | 
			
		||||
      elseif (empty($values['enabled'] && $values['entity_id'])) {
 | 
			
		||||
        $violation_path = 'menu';
 | 
			
		||||
      }
 | 
			
		||||
      // Handle all the other menu link changes in a forward revision.
 | 
			
		||||
      elseif (($values['title'] != $defaults['title'])) {
 | 
			
		||||
        $violation_path = 'menu.title';
 | 
			
		||||
      }
 | 
			
		||||
      elseif (($values['description'] != $defaults['description'])) {
 | 
			
		||||
        $violation_path = 'menu.description';
 | 
			
		||||
      }
 | 
			
		||||
      elseif (($values['menu_name'] != $defaults['menu_name'])) {
 | 
			
		||||
        $violation_path = 'menu.menu_parent';
 | 
			
		||||
      }
 | 
			
		||||
      elseif (($values['parent'] != $defaults['parent'])) {
 | 
			
		||||
        $violation_path = 'menu.menu_parent';
 | 
			
		||||
      }
 | 
			
		||||
      elseif (($values['weight'] != $defaults['weight'])) {
 | 
			
		||||
        $violation_path = 'menu.weight';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($violation_path) {
 | 
			
		||||
        $this->context->buildViolation($constraint->message)
 | 
			
		||||
          ->atPath($violation_path)
 | 
			
		||||
          ->setInvalidValue($entity)
 | 
			
		||||
          ->addViolation();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\menu_ui\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
use Drupal\workflows\Entity\Workflow;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests Menu UI and Content Moderation integration.
 | 
			
		||||
 *
 | 
			
		||||
 * @group menu_ui
 | 
			
		||||
 */
 | 
			
		||||
class MenuUiContentModerationTest extends BrowserTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Modules to install.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  public static $modules = ['block', 'content_moderation', 'node', 'menu_ui', 'test_page_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->drupalPlaceBlock('system_menu_block:main');
 | 
			
		||||
 | 
			
		||||
    // Create a 'page' content type.
 | 
			
		||||
    $this->drupalCreateContentType([
 | 
			
		||||
      'type' => 'page',
 | 
			
		||||
      'name' => 'Basic page',
 | 
			
		||||
      'display_submitted' => FALSE,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $workflow = Workflow::load('editorial');
 | 
			
		||||
    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
 | 
			
		||||
    $workflow->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that node drafts can not modify the menu settings.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMenuUiWithForwardRevisions() {
 | 
			
		||||
    $editor = $this->drupalCreateUser([
 | 
			
		||||
      'administer nodes',
 | 
			
		||||
      'administer menu',
 | 
			
		||||
      'create page content',
 | 
			
		||||
      'edit any page content',
 | 
			
		||||
      'use editorial transition create_new_draft',
 | 
			
		||||
      'use editorial transition publish',
 | 
			
		||||
      'view latest version',
 | 
			
		||||
      'view any unpublished content',
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->drupalLogin($editor);
 | 
			
		||||
 | 
			
		||||
    // Create a node.
 | 
			
		||||
    $node = $this->drupalCreateNode();
 | 
			
		||||
 | 
			
		||||
    // Add a menu link and save a new default (published) revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[enabled]' => 1,
 | 
			
		||||
      'menu[title]' => 'Test menu link',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Publish'));
 | 
			
		||||
 | 
			
		||||
    $this->assertSession()->linkExists('Test menu link');
 | 
			
		||||
 | 
			
		||||
    // Try to change the menu link title and save a new non-default (draft)
 | 
			
		||||
    // revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[title]' => 'Test menu link draft',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
    $this->assertSession()->linkExists('Test menu link');
 | 
			
		||||
    $this->assertSession()->linkNotExists('Test menu link draft');
 | 
			
		||||
 | 
			
		||||
    // Try to change the menu link description and save a new non-default
 | 
			
		||||
    // (draft) revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[description]' => 'Test menu link description',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
 | 
			
		||||
    // Try to change the menu link weight and save a new non-default (draft)
 | 
			
		||||
    // revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[weight]' => 1,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
 | 
			
		||||
    // Try to change the menu link parent and save a new non-default (draft)
 | 
			
		||||
    // revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[menu_parent]' => 'main:test_page_test.front_page',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
 | 
			
		||||
    // Try to delete the menu link and save a new non-default (draft) revision.
 | 
			
		||||
    $edit = [
 | 
			
		||||
      'menu[enabled]' => 0,
 | 
			
		||||
    ];
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
    $this->assertSession()->linkExists('Test menu link');
 | 
			
		||||
 | 
			
		||||
    // Try to save a new non-default (draft) revision without any changes and
 | 
			
		||||
    // check that the error message is not shown.
 | 
			
		||||
    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and Create New Draft'));
 | 
			
		||||
 | 
			
		||||
    // Check that the menu settings were not applied.
 | 
			
		||||
    $this->assertSession()->pageTextNotContains('You can only change the menu settings for the published version of this content.');
 | 
			
		||||
    $this->assertSession()->linkExists('Test menu link');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue