Skip to content
Snippets Groups Projects
Verified Commit 96ac98b6 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3040258 by amateescu, adityasingh, anushrikumari, larowlan, s_leu,...

Issue #3040258 by amateescu, adityasingh, anushrikumari, larowlan, s_leu, dixon_, catch, atul4drupal, alexpott, Fabianx: Menu link content changes are not visible on non-live workspaces
parent 126395fe
No related branches found
No related tags found
43 merge requests!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4594Applying patch for Views Global Text area field to allow extra HTML tags. As video, source and iframe tag is not rendering. Due to which Media embedded video and remote-video not rendering in Views Global Text area field.,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
<?php
namespace Drupal\workspaces;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Menu\MenuTreeStorage as CoreMenuTreeStorage;
/**
* Overrides the default menu storage to provide workspace-specific menu links.
*
* @internal
*/
class WorkspacesMenuTreeStorage extends CoreMenuTreeStorage {
/**
* WorkspacesMenuTreeStorage constructor.
*
* @param \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager
* The workspace manager service.
* @param \Drupal\workspaces\WorkspaceAssociationInterface $workspaceAssociation
* The workspace association service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Database\Connection $connection
* A Database connection to use for reading and writing configuration data.
* @param \Drupal\Core\Cache\CacheBackendInterface $menu_cache_backend
* Cache backend instance for the extracted tree data.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator.
* @param string $table
* A database table name to store configuration data in.
* @param array $options
* (optional) Any additional database connection options to use in queries.
*/
public function __construct(
protected readonly WorkspaceManagerInterface $workspaceManager,
protected readonly WorkspaceAssociationInterface $workspaceAssociation,
protected readonly EntityTypeManagerInterface $entityTypeManager,
Connection $connection,
CacheBackendInterface $menu_cache_backend,
CacheTagsInvalidatorInterface $cache_tags_invalidator,
string $table,
array $options = []
) {
parent::__construct($connection, $menu_cache_backend, $cache_tags_invalidator, $table, $options);
}
/**
* {@inheritdoc}
*/
public function loadTreeData($menu_name, MenuTreeParameters $parameters) {
// Add the active workspace as a menu tree condition parameter in order to
// include it in the cache ID.
if ($active_workspace = $this->workspaceManager->getActiveWorkspace()) {
$parameters->conditions['workspace'] = $active_workspace->id();
}
return parent::loadTreeData($menu_name, $parameters);
}
/**
* {@inheritdoc}
*/
protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
$links = parent::loadLinks($menu_name, $parameters);
// Replace the menu link plugin definitions with workspace-specific ones.
if ($active_workspace = $this->workspaceManager->getActiveWorkspace()) {
$tracked_revisions = $this->workspaceAssociation->getTrackedEntities($active_workspace->id());
if (isset($tracked_revisions['menu_link_content'])) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface[] $workspace_revisions */
$workspace_revisions = $this->entityTypeManager->getStorage('menu_link_content')->loadMultipleRevisions(array_keys($tracked_revisions['menu_link_content']));
foreach ($workspace_revisions as $workspace_revision) {
if (isset($links[$workspace_revision->getPluginId()])) {
$pending_plugin_definition = $workspace_revision->getPluginDefinition();
$links[$workspace_revision->getPluginId()] = [
'title' => serialize($pending_plugin_definition['title']),
'description' => serialize($pending_plugin_definition['description']),
'enabled' => (string) $pending_plugin_definition['enabled'],
'url' => $pending_plugin_definition['url'],
'route_name' => $pending_plugin_definition['route_name'],
'route_parameters' => serialize($pending_plugin_definition['route_parameters']),
'options' => serialize($pending_plugin_definition['options']),
] + $links[$workspace_revision->getPluginId()];
}
}
}
}
return $links;
}
}
...@@ -56,6 +56,7 @@ public function testBypassOwnWorkspace() { ...@@ -56,6 +56,7 @@ public function testBypassOwnWorkspace() {
// Create a new user that should be able to edit anything in the Bears // Create a new user that should be able to edit anything in the Bears
// workspace. // workspace.
$this->switchToLive();
$lombardi = $this->drupalCreateUser(array_merge($permissions, ['view any workspace'])); $lombardi = $this->drupalCreateUser(array_merge($permissions, ['view any workspace']));
$this->drupalLogin($lombardi); $this->drupalLogin($lombardi);
$this->switchToWorkspace($bears); $this->switchToWorkspace($bears);
......
<?php
namespace Drupal\Tests\workspaces\Functional;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\Tests\BrowserTestBase;
use Drupal\workspaces\Entity\Workspace;
/**
* Tests workspace integration for custom menu links.
*
* @group workspaces
* @group menu_link_content
*/
class WorkspaceMenuLinkContentIntegrationTest extends BrowserTestBase {
use WorkspaceTestUtilities;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'menu_link_content',
'node',
'workspaces',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$permissions = [
'access administration pages',
'administer site configuration',
'administer workspaces',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalPlaceBlock('system_menu_block:main');
}
/**
* Tests custom menu links in non-default workspaces.
*/
public function testWorkspacesWithCustomMenuLinks() {
$stage = Workspace::load('stage');
$this->setupWorkspaceSwitcherBlock();
$default_title = 'default';
$default_link = '#live';
$menu_link_content = MenuLinkContent::create([
'title' => $default_title,
'menu_name' => 'main',
'link' => [['uri' => 'internal:/' . $default_link]],
]);
$menu_link_content->save();
$pending_title = 'pending';
$pending_link = 'http://example.com';
$this->switchToWorkspace($stage);
$menu_link_content->set('title', $pending_title);
$menu_link_content->set('link', [['uri' => $pending_link]]);
$menu_link_content->save();
$this->drupalGet('');
$assert_session = $this->assertSession();
$assert_session->linkExists($pending_title);
$assert_session->linkByHrefExists($pending_link);
// Add a new menu link in the Stage workspace.
$menu_link_content = MenuLinkContent::create([
'title' => 'stage link',
'menu_name' => 'main',
'link' => [['uri' => 'internal:/#stage']],
]);
$menu_link_content->save();
$this->drupalGet('');
$assert_session->linkExists('stage link');
$assert_session->linkByHrefExists('#stage');
// Switch back to the Live workspace and check that the menu link has the
// default values.
$this->switchToLive();
$this->drupalGet('');
$assert_session->linkExists($default_title);
$assert_session->linkByHrefExists($default_link);
$assert_session->linkNotExists($pending_title);
$assert_session->linkByHrefNotExists($pending_link);
$assert_session->linkNotExists('stage link');
$assert_session->linkByHrefNotExists('#stage');
// Publish the workspace and check that the menu link has been updated.
$stage->publish();
$this->drupalGet('');
$assert_session->linkNotExists($default_title);
$assert_session->linkByHrefNotExists($default_link);
$assert_session->linkExists($pending_title);
$assert_session->linkByHrefExists($pending_link);
$assert_session->linkExists('stage link');
$assert_session->linkByHrefExists('#stage');
}
}
...@@ -174,6 +174,9 @@ public function testWorkspaceManagePage() { ...@@ -174,6 +174,9 @@ public function testWorkspaceManagePage() {
$this->setupWorkspaceSwitcherBlock(); $this->setupWorkspaceSwitcherBlock();
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$this->drupalCreateContentType(['type' => 'test', 'label' => 'Test']);
$vocabulary = $this->createVocabulary();
$test_1 = $this->createWorkspaceThroughUi('Test 1', 'test_1'); $test_1 = $this->createWorkspaceThroughUi('Test 1', 'test_1');
$test_2 = $this->createWorkspaceThroughUi('Test 2', 'test_2'); $test_2 = $this->createWorkspaceThroughUi('Test 2', 'test_2');
...@@ -190,10 +193,8 @@ public function testWorkspaceManagePage() { ...@@ -190,10 +193,8 @@ public function testWorkspaceManagePage() {
$assert_session->linkExists('Switch to this workspace'); $assert_session->linkExists('Switch to this workspace');
// Create some test content. // Create some test content.
$this->drupalCreateContentType(['type' => 'test', 'label' => 'Test']);
$this->createNodeThroughUi('Node 1', 'test'); $this->createNodeThroughUi('Node 1', 'test');
$this->createNodeThroughUi('Node 2', 'test'); $this->createNodeThroughUi('Node 2', 'test');
$vocabulary = $this->createVocabulary();
$edit = [ $edit = [
'name[0][value]' => 'Term 1', 'name[0][value]' => 'Term 1',
]; ];
......
...@@ -107,6 +107,8 @@ protected function switchToWorkspace(WorkspaceInterface $workspace) { ...@@ -107,6 +107,8 @@ protected function switchToWorkspace(WorkspaceInterface $workspace) {
$session->buttonExists('Activate'); $session->buttonExists('Activate');
$this->submitForm(['workspace_id' => $workspace->id()], 'Activate'); $this->submitForm(['workspace_id' => $workspace->id()], 'Activate');
$session->pageTextContains($workspace->label() . ' is now the active workspace.'); $session->pageTextContains($workspace->label() . ' is now the active workspace.');
// Keep the test runner in sync with the system under test.
\Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
} }
/** /**
...@@ -120,6 +122,8 @@ protected function switchToLive() { ...@@ -120,6 +122,8 @@ protected function switchToLive() {
$session = $this->assertSession(); $session = $this->assertSession();
$this->submitForm([], 'Switch to Live'); $this->submitForm([], 'Switch to Live');
$session->pageTextContains('You are now viewing the live version of the site.'); $session->pageTextContains('You are now viewing the live version of the site.');
// Keep the test runner in sync with the system under test.
\Drupal::service('workspaces.manager')->switchToLive();
} }
/** /**
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
use Drupal\Component\Serialization\Json; use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
...@@ -185,6 +186,22 @@ function workspaces_entity_create_access(AccountInterface $account, array $conte ...@@ -185,6 +186,22 @@ function workspaces_entity_create_access(AccountInterface $account, array $conte
->entityCreateAccess($account, $context, $entity_bundle); ->entityCreateAccess($account, $context, $entity_bundle);
} }
/**
* Implements hook_ENTITY_TYPE_update() for the 'menu_link_content' entity type.
*/
function workspaces_menu_link_content_update(EntityInterface $entity) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
if ($entity->getLoadedRevisionId() != $entity->getRevisionId()) {
// We are not updating the menu tree definitions when a custom menu link
// entity is saved as a pending revision (because the parent can not be
// changed), so we need to clear the system menu cache manually. However,
// inserting or deleting a custom menu link updates the menu tree
// definitions, so we don't have to do anything in those cases.
$cache_tags = Cache::buildTags('config:system.menu', [$entity->getMenuName()], '.');
\Drupal::service('cache_tags.invalidator')->invalidateTags($cache_tags);
}
}
/** /**
* Implements hook_views_query_alter(). * Implements hook_views_query_alter().
*/ */
......
...@@ -72,3 +72,12 @@ services: ...@@ -72,3 +72,12 @@ services:
arguments: ['@database', '@workspaces.manager'] arguments: ['@database', '@workspaces.manager']
public: false public: false
decoration_priority: 50 decoration_priority: 50
workspaces.menu.tree_storage:
decorates: menu.tree_storage
class: Drupal\workspaces\WorkspacesMenuTreeStorage
arguments: ['@workspaces.manager', '@workspaces.association', '@entity_type.manager', '@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree']
public: false
decoration_priority: 50
tags:
- { name: backend_overridable }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment