Skip to content

Commit

Permalink
Merge pull request #1244 from UN-OCHA/berliner/HPC-9954
Browse files Browse the repository at this point in the history
HPC-9954: Prevent errors with mega menu block if menu link content entities disappear for some reason
  • Loading branch information
berliner authored Dec 4, 2024
2 parents d9bca77 + 2c68fab commit c932507
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 0 deletions.
9 changes: 9 additions & 0 deletions html/modules/custom/ghi_menu/ghi_menu.deploy.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,12 @@ function ghi_menu_deploy_add_pages_item_to_admin_menu(&$sandbox) {
$menu_link->set('parent', 'system.admin_content');
$menu_link->save();
}

/**
* Fix broken menu items.
*/
function ghi_menu_deploy_cleanup_broken_menu_items(&$sandbox) {
/** @var \Drupal\ghi_menu\GhiMenuStorageHelper $menu_tree_storage_helper */
$menu_tree_storage_helper = \Drupal::service('ghi_menu.menu_tree_storage_helper');
$menu_tree_storage_helper->cleanupMenuStorage();
}
8 changes: 8 additions & 0 deletions html/modules/custom/ghi_menu/ghi_menu.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ services:
ghi_menu.entity_operations_manager:
class: Drupal\ghi_menu\GhiEntityOperationsManager
arguments: ['@publishcontent.access', '@csrf_token', '@redirect.destination', '@current_user']
ghi_menu.menu_tree_storage_helper:
class: Drupal\ghi_menu\GhiMenuStorageHelper
arguments: ['@menu.link_tree', '@menu.tree_storage']
ghi_menu.route_subscriber:
class: Drupal\ghi_menu\EventSubscriber\RouteSubscriber
tags:
- { name: event_subscriber }
ghi_menu.menu_rebuild_subscriber:
class: Drupal\ghi_menu\EventSubscriber\MenuRouterRebuildSubscriber
arguments: ['@ghi_menu.menu_tree_storage_helper']
tags:
- { name: event_subscriber }
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Drupal\ghi_menu\EventSubscriber;

use Drupal\Core\Routing\RoutingEvents;
use Drupal\ghi_menu\GhiMenuStorageHelper;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Rebuilds the default menu links and runs menu-specific code if necessary.
*/
class MenuRouterRebuildSubscriber implements EventSubscriberInterface {

/**
* The menu storage helper service.
*
* @var \Drupal\ghi_menu\GhiMenuStorageHelper
*/
protected $menuStorageHelper;

/**
* Constructs the MenuRouterRebuildSubscriber object.
*
* @param \Drupal\ghi_menu\GhiMenuStorageHelper $menu_storage_helper
* The menu storage helper service.
*/
public function __construct(GhiMenuStorageHelper $menu_storage_helper) {
$this->menuStorageHelper = $menu_storage_helper;
}

/**
* Cleanup the menu tree storage.
*
* @param \Drupal\Component\EventDispatcher\Event $event
* The event object.
*/
public function onRouterRebuild($event) {
$this->menuStorageHelper->cleanupMenuStorage();
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// Run after CachedRouteRebuildSubscriber.
$events[RoutingEvents::FINISHED][] = ['onRouterRebuild', 120];
return $events;
}

}
87 changes: 87 additions & 0 deletions html/modules/custom/ghi_menu/src/GhiMenuStorageHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Drupal\ghi_menu;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Menu\MenuTreeStorageInterface;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;

/**
* Helper service for menu storage.
*/
class GhiMenuStorageHelper {

/**
* The menu tree service.
*
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
protected $menuTree;

/**
* The menu link tree storage.
*
* @var \Drupal\Core\Menu\MenuTreeStorageInterface
*/
protected $treeStorage;

/**
* Constructs a \Drupal\Core\Menu\MenuLinkManager object.
*
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
* The menu tree service.
* @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
* The menu link tree storage.
*/
public function __construct(MenuLinkTreeInterface $menu_tree, MenuTreeStorageInterface $tree_storage) {
$this->menuTree = $menu_tree;
$this->treeStorage = $tree_storage;
}

/**
* Cleanup the menu storage.
*
* While it seems like an edge case, we had situations where an item in the
* menu storage referenced a menu content link that was already deleted,
* resulting in a WSOD due to an unhandled PluginException. This logic here
* removes those broken items in the menu tree storage.
*/
public function cleanupMenuStorage() {
$parameters = new MenuTreeParameters();
$menu_names = $this->treeStorage->getMenuNames();
foreach ($menu_names as $menu_name) {
$menu_tree = $this->menuTree->load($menu_name, $parameters);
$this->purgeBrokenItems($menu_tree);
}
}

/**
* Purge broken menu links from the tree.
*
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $menu_tree
* The menu tree to filter.
*/
private function purgeBrokenItems(array $menu_tree) {
foreach ($menu_tree as $id => $item) {
if (!empty($item->subtree)) {
$this->purgeBrokenItems($item->subtree);
}
$link = $item->link;
if (!$link instanceof MenuLinkContent) {
continue;
}
try {
// Just try to get the entity. If this throws an exception, this menu
// item is broken.
$link->getEntity();
}
catch (PluginException $e) {
$this->treeStorage->delete($id);
}

}
}

}
37 changes: 37 additions & 0 deletions html/modules/custom/ghi_menu/src/Plugin/Block/MegaMenuBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Drupal\ghi_menu\Plugin\Block;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Utility\Html;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
Expand All @@ -13,6 +14,7 @@
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Core\Render\Element\VerticalTabs;
use Drupal\ghi_blocks\Traits\VerticalTabsTrait;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
Expand Down Expand Up @@ -56,6 +58,13 @@ class MegaMenuBlock extends BlockBase implements ContainerFactoryPluginInterface
*/
protected $menuTree;

/**
* The menu tree manipulators.
*
* @var \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
*/
protected $menuTreeManipulators;

/**
* {@inheritdoc}
*/
Expand All @@ -66,6 +75,7 @@ public static function create(ContainerInterface $container, array $configuratio
$instance->entityFieldManager = $container->get('entity_field.manager');
$instance->moduleHandler = $container->get('module_handler');
$instance->menuTree = $container->get('menu.link_tree');
$instance->menuTreeManipulators = $container->get('menu.default_tree_manipulators');
return $instance;
}

Expand Down Expand Up @@ -206,6 +216,8 @@ public function getMenuItems() {
$parameters = $this->menuTree->getCurrentRouteMenuTreeParameters('main');
$parameters->expandedParents = [];
$menu_tree = $this->menuTree->load($menu->id(), $parameters);
$this->filterBrokenItems($menu_tree);

// Transform the tree using the manipulators you want.
$manipulators = [
// Only show links that are accessible for the current user.
Expand All @@ -217,6 +229,31 @@ public function getMenuItems() {
return $menu_tree;
}

/**
* Filter broken menu links from the tree.
*
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $menu_tree
* The menu tree to filter.
*/
private function filterBrokenItems(array &$menu_tree) {
foreach ($menu_tree as $key => &$item) {
$link = $item->link;
if (!$link instanceof MenuLinkContent) {
continue;
}
try {
$this->menuTreeManipulators->checkAccess([$item]);
}
catch (PluginException $e) {
unset($menu_tree[$key]);
continue;
}
if ($item->hasChildren) {
$this->filterBrokenItems($item->subtree);
}
}
}

/**
* {@inheritdoc}
*/
Expand Down

0 comments on commit c932507

Please sign in to comment.