Skip to content
Snippets Groups Projects

Draft: Issue #3447727: Add JSON API Menu Tree submodule with resource for hierarchical menu structure

5 files
+ 329
7
Compare changes
  • Side-by-side
  • Inline
Files
5
<?php
namespace Drupal\jsonapi_menu_items_tree\Resource;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\GeneratedUrl;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\system\MenuInterface;
use Drupal\jsonapi_menu_items\Resource\MenuItemsResource;
/**
* Processes a request for a menu and delivers a processed menu tree structure.
*
* @internal
*/
class MenuTreeResource extends MenuItemsResource {
/**
* Overrides the parent method to provide a custom menu tree structure.
*
* @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
* The menu tree.
* @param array $items
* The already created items.
* @param \Drupal\Core\Cache\CacheableMetadata $cache
* The cacheable metadata.
* @param \Drupal\system\MenuInterface $menu
* The menu that the links belong to.
*/
protected function getMenuItems(array $tree, array &$items, CacheableMetadata $cache, MenuInterface $menu) {
$items = [];
foreach ($tree as $menu_link) {
if ($menu_link->access !== NULL && !$menu_link->access instanceof AccessResultInterface) {
throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
}
if ($menu_link->access instanceof AccessResultInterface) {
$cache->merge(CacheableMetadata::createFromObject($menu_link->access));
}
// Only return accessible links.
if ($menu_link->access instanceof AccessResultInterface && !$menu_link->access->isAllowed()) {
continue;
}
$id = $menu_link->link->getPluginId();
[$plugin] = explode(':', $id, 2);
if ($plugin === 'menu_link_config') {
$resource_type = $this->resourceTypeRepository->get('menu_link_config', 'menu_link_config');
}
else {
$resource_type = $this->resourceTypeRepository->get('menu_link_content', $menu_link->link->getMenuName());
if ($resource_type === NULL) {
$resource_type = $this->resourceTypeRepository->get('menu_link_content', 'menu_link_content');
}
}
$url = $menu_link->link->getUrlObject()->toString(TRUE);
assert($url instanceof GeneratedUrl);
$cache->addCacheableDependency($url);
$links = new LinkCollection([]);
$resource_object_cacheability = new CacheableMetadata();
$resource_object_cacheability->addCacheableDependency($menu_link->access);
$resource_object_cacheability->addCacheableDependency($cache);
$fields = $this->buildMenuLinkSubtree($menu_link, $resource_object_cacheability);
$items[$id] = new ResourceObject($resource_object_cacheability, $resource_type, $id, NULL, $fields, $links);
}
}
/**
* Recursively generates the data structure for a menu link and its children.
*
* Each child link is nested under the `subTree` key of its parent link,
* creating a tree-like representation of the menu.
*
* @param array $menuLink
* The menu link data to process.
* @param \Drupal\Core\Cache\CacheableMetadata $cache
* The cacheable metadata object to merge with the menu link's cache.
* @param int $depth
* The current depth in the menu tree.
*
* @return array
* A structured array representing the menu link and its nested children.
*/
public function buildMenuLinkSubtree($menu_link, CacheableMetadata $cache, int $depth = 0) {
if ($menu_link->access !== NULL && !$menu_link->access instanceof AccessResultInterface) {
throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
}
if ($menu_link->access instanceof AccessResultInterface) {
$cache->merge(CacheableMetadata::createFromObject($menu_link->access));
}
// Only build data for accessible links.
if ($menu_link->access instanceof AccessResultInterface && !$menu_link->access->isAllowed()) {
return [];
}
$url = $menu_link->link->getUrlObject()->toString(TRUE);
assert($url instanceof GeneratedUrl);
$cache->addCacheableDependency($url);
$cache->addCacheableDependency($menu_link->access);
$fields = [
'description' => $menu_link->link->getDescription(),
'enabled' => $menu_link->link->isEnabled(),
'expanded' => $menu_link->link->isExpanded(),
'menu_name' => $menu_link->link->getMenuName(),
'meta' => $menu_link->link->getMetaData(),
'options' => $menu_link->link->getOptions(),
'parent' => $menu_link->link->getParent(),
'provider' => $menu_link->link->getProvider(),
'route' => [
'name' => $menu_link->link->getRouteName(),
'parameters' => $menu_link->link->getRouteParameters(),
],
'title' => (string) $menu_link->link->getTitle(),
'url' => $url->getGeneratedUrl(),
'weight' => (int) $menu_link->link->getWeight(),
'depth' => $depth,
];
if ($menu_link->subtree) {
$depth++;
foreach ($menu_link->subtree as $link) {
/** @var \Drupal\Core\Menu\MenuLinkTreeElement $link */
if ($link->access instanceof AccessResultInterface && !$link->access->isAllowed()) {
continue;
}
$provider = $link->link->getProvider();
$child_data = [
"type" => $provider . '--' . $provider,
"id" => $link->link->getPluginId(),
"attributes" => $this->buildMenuLinkSubtree($link, $cache, $depth),
];
$fields['subTree'][] = $child_data;
}
}
return $fields;
}
}
Loading