doghouse_menu-3.0.x-dev/src/Plugin/Block/VerticalMenu.php
src/Plugin/Block/VerticalMenu.php
<?php
namespace Drupal\doghouse_menu\Plugin\Block;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Template\Attribute;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
/**
* Provides a 'VerticalMenu' block.
*
* @Block(
* id = "doghouse_vertical_menu",
* admin_label = @Translation("Doghouse Vertical Menu"),
* )
*/
class VerticalMenu extends BlockBase implements ContainerFactoryPluginInterface {
/**
* Drupal\Core\Menu\MenuLinkTreeInterface definition.
*
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
protected $menuLinkTree;
/**
* Drupal\Core\Menu\MenuLinkManagerInterface definition.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $menuLinkManager;
/**
* Constructs a new VerticalMenu object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param string $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
MenuLinkTreeInterface $menu_link_tree,
MenuLinkManagerInterface $menuLinkManager
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->menuLinkTree = $menu_link_tree;
$this->menuLinkManager = $menuLinkManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu.link_tree'),
$container->get('plugin.manager.menu.link')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'menu' => '',
'options' => [
'menu_type' => 'slide',
],
] + parent::defaultConfiguration();
}
/**
* Returns a list of theme regions.
*
* @return array
*/
private function getRegionOptions() {
$default_theme = \Drupal::config('system.theme')->get('default');
return system_region_list($default_theme);
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$menuOptions = \Drupal::entityQuery('menu')->execute();
$form['menu'] = [
'#type' => 'select',
'#title' => $this->t('Menu'),
'#options' => $menuOptions,
'#default_value' => $this->configuration['menu'],
];
$form['options'] = [
'#type' => 'fieldset',
'#title' => $this->t('Style options'),
];
$form['options']['menu_type'] = [
'#type' => 'select',
'#title' => $this->t('Menu Type'),
'#description' => $this->t("Select how you want the sub menu to appear once a parent link has been clicked."),
'#default_value' => $this->configuration['options']['menu_type'],
'#options' => [
'slide' => $this->t('Slide'),
'accordion' => $this->t('Accordion'),
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['menu'] = $form_state->getValue('menu');
$this->configuration['options'] = $form_state->getValue('options');
}
/**
* {@inheritdoc}
*/
public function build() {
$id = Html::getUniqueId('doghouse-menu');
$tree = $this->loadMenuTrue();
$build = [];
if (!empty($tree)) {
$build = [
'#attributes' => [
'class' => [
'doghouse-menu',
'js-doghouse-menu',
],
'data-menu-type' => $this->configuration['options']['menu_type'],
],
'#attached' => [
'library' => [
'doghouse_menu/doghouse_menu',
],
'drupalSettings' => [
'doghouseMenu' => [
'id' => $id,
'options' => $this->configuration['options'],
],
],
],
];
if ($this->configuration['options']['menu_type'] === 'slide') {
$flattenedTree = $this->flattenMenuTree($tree);
$builtMenu = $this->buildMenu($flattenedTree);
} else {
$builtMenu = $this->menuLinkTree->build($tree);
$builtMenu['#theme'] = 'doghouse_menu_accordion';
}
$build[] = $builtMenu;
}
return $build;
}
private function loadMenuTrue() {
$menu_name = $this->configuration['menu'];
$parameters = $this->menuLinkTree->getCurrentRouteMenuTreeParameters($menu_name);
$tree = $this->menuLinkTree->load($menu_name, $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
return $this->menuLinkTree->transform($tree, $manipulators);
}
/**
* Builds the menu renderable array.
*
* @param array $flattenedTree
* The flattened menu tree array
*
* @return array
*/
public function buildMenu(array $flattenedTree) {
$menu = [
'#theme' => 'doghouse_menu',
'#items' => [],
];
foreach ($flattenedTree as $dataMenu => $links) {
$menu['#items'][$dataMenu]['items'] = $this->buildItems($links['items']);
$menu['#items'][$dataMenu]['depth'] = $links['depth'];
$menu['#items'][$dataMenu]['attributes'] = new Attribute();
$menu['#items'][$dataMenu]['attributes']['data-menu'] = $dataMenu;
$menu['#items'][$dataMenu]['attributes']['data-depth'] = $links['depth'];
}
return $menu;
}
/**
* Builds a list of single menu items for rendering.
*
* @param array $submenu
* An array of submenu items.
*
* @return array
*/
public function buildItems(array $submenu) {
$items = [];
foreach ($submenu as $id => $menuItem) {
$element = [];
$link = $menuItem->link;
if (!$link->isEnabled()) {
continue;
}
if ($menuItem->access !== NULL && !$menuItem->access instanceof AccessResultInterface) {
throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
}
$element['attributes'] = new Attribute();
$element['title'] = $link->getTitle();
$element['url'] = $link->getUrlObject();
$element['url']->getOption('attributes') ?: $element['url']->setOption('attributes', []);
$element['url']->setOption('set_active_class', TRUE);
if ($menuItem->subtree) {
$menuItem->options['attributes']['data-submenu'] = $id;
}
if (isset($menuItem->options)) {
$element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $menuItem->options));
if (isset($menuItem->options['is_heading'])) {
$element['attributes']->addClass('doghouse-menu__item-heading');
}
if (isset($menuItem->options['has_heading'])) {
$element['attributes']->addClass('doghouse-menu__item-has-heading');
}
}
$element['original_link'] = $link;
$items[$id] = $element;
}
return $items;
}
/**
* Check if user has access to link.
*
* @param \Drupal\Core\Menu\MenuLinkTreeElement $item
* An element in a menu link tree.
*
* @return bool
* Weather or not the user is allowed access.
*/
private function itemAllowedAccess($item) {
if (!$item->link->isEnabled()) {
return FALSE;
}
if ($item->access !== NULL && !$item->access instanceof AccessResultInterface) {
throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
}
// Only render accessible links.
if ($item->access instanceof AccessResultInterface && !$item->access->isAllowed()) {
return FALSE;
}
return TRUE;
}
/**
* Flattens a menu tree into a grouped one dimensional array.
*
* @param $menu
* @param string $id
* @param array $return
*
* @return array
*/
public function flattenMenuTree($menu, $parent = 'main', $depth = 0, $return = []) {
$return[$parent]['depth'] = $depth;
foreach ($menu as $itemId => $item) {
// Check if user is allowed access to this link.
if (!$this->itemAllowedAccess($item)) {
continue;
}
$return[$parent]['items'][$itemId] = $item;
// Filter out inaccessible links from subtree.
if (!empty($item->subtree)) {
foreach ($item->subtree as $key => $subtree_item) {
if (!$this->itemAllowedAccess($subtree_item)) {
unset($item->subtree[$key]);
}
}
}
if ($item->hasChildren && !empty($item->subtree)) {
// Add copy of parent link to submenu.
$new_parent_id = $itemId;
$return[$new_parent_id]['items'][$itemId] = clone $item;
$return[$new_parent_id]['items'][$itemId]->subtree = FALSE;
$return[$new_parent_id]['items'][$itemId]->options['attributes']['class'][] = 'doghouse-menu__parent-link';
$return = $this->flattenMenuTree($item->subtree, $new_parent_id, $depth + 1, $return);
$return[$parent]['items'][$itemId]->subtree = TRUE;
} else {
$return[$parent]['items'][$itemId]->subtree = FALSE;
}
if ($depth == 0) {
$return[$parent]['items'][$itemId]->options['attributes']['class'][] = 'doghouse-menu__top-level-link';
}
}
return $return;
}
}
