Skip to content
Snippets Groups Projects
Commit f38eef1c authored by Steve Wirt's avatar Steve Wirt
Browse files

Issue #3405786: Add a method to insert a menu item into a menu in a specific location

parent e1f6c1fd
No related branches found
No related tags found
No related merge requests found
<?php
namespace Drupal\codit_menu_tools;
use Drupal\menu_link_content\Entity\MenuLinkContent;
/**
* Class for inserting menu items into a menu.
*/
class MenuInsert extends MenuManipulatorBase implements MenuInsertInterface {
/**
* {@inheritdoc}
*/
public function addMenuItem(
string $item_title,
string $item_destination,
string $item_description = '',
bool $enabled = TRUE,
string $parent_title = '',
string $sibling_title = '',
bool $below = TRUE
): int {
$this->preCheckRequiredItemValues($item_title, $item_destination);
$parent_details = $this->findMenuItemDetailsByName($parent_title);
$parent_id = $parent_details['id'] ?? '';
$sibling_weight = $this->findMenuItemWeight($sibling_title, $parent_id);
$item_weight = ($below) ? $sibling_weight + 1 : $sibling_weight - 1;
return MenuLinkContent::create([
'title' => $item_title,
'description' => $item_description,
'link' => ['uri' => $item_destination],
'menu_name' => $this->menuName,
'enabled' => $enabled,
'parent' => $parent_id,
'weight' => $item_weight,
])->save();
}
}
<?php
namespace Drupal\codit_menu_tools;
/**
* Interface for MenuInsert.
*/
interface MenuInsertInterface extends MenuManipulatorBaseInterface {
/**
* Adds menu item to menu relative to parent and or sibling.
*
* @param string $item_title
* (required) The title of the menu item.
* @param string $item_destination
* (required) The destination of the menu item.
* @param string $item_description
* The description of the menu item.
* @param bool $enabled
* TRUE to enable or FALSE to disable the menu item.
* @param string $parent_title
* The title of the parent to lookup.
* @param string $sibling_title
* The title of the nearest sibling.
* @param bool $below
* TRUE to place the new menu item below the sibling, FALSE to place above.
*
* @return int
* The value SAVED_NEW since it is a menu item add.
*/
public function addMenuItem(
string $item_title,
string $item_destination,
string $item_description = '',
bool $enabled = TRUE,
string $parent_title = '',
string $sibling_title = '',
bool $below = TRUE
);
}
<?php
namespace Drupal\codit_menu_tools;
use Drupal\Core\Menu\MenuTreeParameters;
/**
* Base class for Menu manipulation.
*/
abstract class MenuManipulatorBase implements MenuManipulatorBaseInterface {
/**
* The machine name of the menu.
*
* @var string
*/
protected $menuName;
/**
* The menu storage service.
*
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
*/
protected $menuStorage;
/**
* The menu storage service.
*
* @var \Drupal\Core\Menu\MenuLinkTreeElement[]
*/
protected $menuTree;
/**
* Constructor for MenuManipulatorBase.
*
* @param string $menu_name
* The name of the menu to load.
*/
public function __construct(string $menu_name) {
$this->getMenuTree($menu_name);
// Can't use it with Dependency Injection because runs from script or
// hook_update_N where DI is not available.
$this->menuStorage = \Drupal::entityTypeManager()->getStorage('menu_link_content');
}
/**
* {@inheritdoc}
*/
public function getMenuTree(string $menu_name = '') {
if (empty($menu_name) && empty($this->menuTree)) {
throw new \Exception('MenuManipulatorBase::getMenuTree must have a menu name to load.');
}
if (empty($this->menuTree)) {
// Try to load the menu since it has not been loaded previously.
$this->menuName = $menu_name;
$tree = \Drupal::menuTree()->load($menu_name, new MenuTreeParameters());
if (empty($tree)) {
throw new \Exception("The Menu manipulator found no menu by the name '$menu_name'.");
}
$this->menuTree = $tree;
}
return $this->menuTree;
}
/**
* Validate that the title and destination are provided.
*
* @param string $item_title
* The title of the intended menu item.
* @param string $item_destination
* The destination of the intended menu item.
*
* @throws \Exception
* If any of the required menu details are not provided.
*/
protected function preCheckRequiredItemValues(string $item_title, string $item_destination) : void {
// Should throw exception if any of these values are empty.
if (empty($item_title) && empty($item_destination)) {
throw new \Exception("In order for MenuManipulatorBase to add a menu item, it must have a title and destination.");
}
elseif (empty($item_title)) {
throw new \Exception("In order for MenuManipulatorBase to add a menu item, it must have a title.");
}
elseif (empty($item_destination)) {
throw new \Exception("In order for MenuManipulatorBase to add a menu item, it must have a destination.");
}
}
/**
* {@inheritdoc}
*/
public function findMenuItemDetailsByName(string $menu_item_title = '', $parent_id = ''): array {
$item_details = [];
if (!empty($menu_item_title)) {
$query = $this->menuStorage->getQuery()
->condition('menu_name', $this->menuName)
->condition('title', $menu_item_title, '=');
if (!empty($parent_id)) {
// There is a parent id, use it for more certainty.
$query->condition('parent', $parent_id, '=');
}
// We are just looking for something, no need for accesscheck.
$query->accessCheck(FALSE);
$mids = $query->execute();
$mids = array_values($mids);
// Grab the last instance in case multiple exist.
// This is a little risky, but it is an edge case.
$mid = end($mids);
/** @var Drupal\menu_item_extras\Entity\MenuItemExtrasMenuLinkContent $menuItem */
if ($menuItem = $this->menuStorage->load($mid)) {
$item_details['mid'] = $mid;
$item_details['id'] = "menu_link_content:{$menuItem->uuid->value}";
$item_details['weight'] = $menuItem->getWeight();
$item_details['parent_id'] = $menuItem->getParentId();
}
}
return $item_details;
}
/**
* {@inheritdoc}
*/
public function findMenuItemWeight(string $menu_item_title, $parent_id = '') {
$menu_item_details = $this->findMenuItemDetailsByName($menu_item_title, $parent_id);
return $menu_item_details['weight'] ?? 0;
}
}
<?php
namespace Drupal\codit_menu_tools;
/**
* Interface for MenuMainpulatorBase.
*/
interface MenuManipulatorBaseInterface {
/**
* Instantiates the menuTree and assigns the menuName to the class.
*
* Can be used to get a new menuTree or retrieve the one already loaded.
*
* @param string $menu_name
* The machine name of the menu tree to load.
*
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
* The menu tree that was loaded.
*
* @throws \Exception
* If the menu does not exist.
*/
public function getMenuTree(string $menu_name = '');
/**
* Gets information about menu item by name.
*
* @param string $menu_item_title
* The title of the menu item to lookup.
* @param string $parent_id
* The optional id of the parent to help identify the child.
*
* @return array
* An array containing mid, id, weight and parent of the menu item.
*/
public function findMenuItemDetailsByName(string $menu_item_title = '', $parent_id = '');
/**
* Find the weight of a menu item by title and optional parent.
*
* @param string $menu_item_title
* The title of the item to look up.
* @param string $parent_id
* Only needed if you have muliple menu items with the same title.
*
* @return int
* The weight of the menu item. [-n, 0, +n].
*/
public function findMenuItemWeight(string $menu_item_title, $parent_id = '');
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment