diff --git a/src/MenuInsert.php b/src/MenuInsert.php new file mode 100644 index 0000000000000000000000000000000000000000..8b15a4c3b65736c0c533bcc2bb77d94efbfa12c3 --- /dev/null +++ b/src/MenuInsert.php @@ -0,0 +1,40 @@ +<?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(); + } + +} diff --git a/src/MenuInsertInterface.php b/src/MenuInsertInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..71d0ff15a29cf6f367fcd5dcb2e3b22a08c48923 --- /dev/null +++ b/src/MenuInsertInterface.php @@ -0,0 +1,41 @@ +<?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 + ); + +} diff --git a/src/MenuManipulatorBase.php b/src/MenuManipulatorBase.php new file mode 100644 index 0000000000000000000000000000000000000000..848ffa51a46ff70b788ce1bd37f507ade5bbd02b --- /dev/null +++ b/src/MenuManipulatorBase.php @@ -0,0 +1,131 @@ +<?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; + } + +} diff --git a/src/MenuManipulatorBaseInterface.php b/src/MenuManipulatorBaseInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bd0f6b3c807cca1849cfbe4adb99e1b3b64377fb --- /dev/null +++ b/src/MenuManipulatorBaseInterface.php @@ -0,0 +1,52 @@ +<?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 = ''); + +}