-
Matthieu Scarset authoredMatthieu Scarset authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
menu_manipulator.module 9.42 KiB
<?php
/**
* @file
* Contains menu_manipulator.module.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Routing\CurrentRouteMatch;
/**
* Implements hook_help().
*
* @inheritdoc
*/
function menu_manipulator_help($route_name, CurrentRouteMatch $route_match) {
if ($route_name == 'help.page.menu_manipulator') {
$text = file_get_contents(dirname(__FILE__) . '/README.md');
if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
return '<pre>' . $text . '</pre>';
}
// Use the Markdown filter to render the README.
$filter_manager = \Drupal::service('plugin.manager.filter');
$settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
$config = ['settings' => $settings];
$filter = $filter_manager->createInstance('markdown', $config);
return $filter->process($text, 'en');
}
}
/**
* Get a menu tree filtered by the current language.
*
* Based on our custom MenuTreeManipulators services.
* This is pretty useful as of now (8.4.x) as Drupal doesn't
* provide a way to filer MenuLinkContent entity by their language.
*
* @param string $menu_name
* The menu machine name.
* @param \Drupal\Core\Menu\MenuTreeParameters $parameters
* Extra parameters for the menu tree.
*
* @return array
* The filtered MenuTree renderable array.
*
* @code
* // Implements theme_preprocess_menu().
* function yourtheme_preprocess_menu(&$variables, $hook) {
* if (isset($variables['menu_name']) && $menu_name = $variables['menu_name']) {
* $moduleHandler = \Drupal::service('module_handler');
* if ($moduleHandler->moduleExists('menu_manipulator')) {
* $menu_tree_translated = menu_manipulator_get_multilingual_menu($menu_name);
* $variables['items'] = isset($menu_tree_translated['#items']) ? $menu_tree_translated['#items'] : [];
* }
* }
* }
* @endcode
*/
function menu_manipulator_get_multilingual_menu(string $menu_name, MenuTreeParameters $parameters = NULL) {
$menu_tree = \Drupal::menuTree();
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
['callable' => 'menu_manipulator.menu_tree_manipulators:filterTreeByCurrentLanguage'],
];
if ($menu_name == 'admin') {
// Specific manipulation for the "Admin Toolbar" menu.
// See admin_toolbar_prerender_toolbar_administration_tray()
$parameters = $parameters ?: new MenuTreeParameters();
$parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth(4)->onlyEnabledLinks();
}
else {
// Default method to retrieve menu items.
// See MenuLinkTreeInteface::getCurrentRouteMenuTreeParameters().
$parameters = $parameters ?: $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
}
// Manipulate the menu tree to filter by current language.
$menu = $menu_tree->load($menu_name, $parameters);
$menu = $menu_tree->transform($menu, $manipulators);
return $menu_tree->build($menu);
}
/**
* Implements theme_preprocess_menu().
*/
function menu_manipulator_preprocess_menu(&$variables, $hook) {
if (!isset($variables['menu_name'])) {
return;
}
$config = \Drupal::config('menu_manipulator.settings');
$menu_name = $variables['menu_name'];
$menu_config = \Drupal::config('system.menu.' . $menu_name);
// Expose menu title and description for easier theming.
$variables['menu_title'] = ['#markup' => $menu_config->get('label')];
$variables['menu_description'] = ['#markup' => $menu_config->get('description')];
// Display icons.
$selected_menus = $config->get('preprocess_menus_icon_list') ?? [];
$selected_menus = array_filter($selected_menus, function ($v) {
return !empty($v);
});
if (($selected_menus[$menu_name] ?? 0) !== 0) {
foreach ($variables['items'] ?? [] as $key => $item) {
$item_options = $item['original_link']->getOptions();
if (isset($item_options['icon']) && $icon = $item_options['icon']) {
$variables['items'][$key]['#icon'] = $icon;
$variables['items'][$key]['#attributes']['class'][] = 'menu-item-with-icon';
$variables['items'][$key]['#attributes']['class'][] = 'menu-item-with-icon--' . $icon;
}
}
}
// Filter links now, if possible.
if (menu_manipulator_menu_is_filterable_by_language($menu_name)) {
\Drupal::service('menu_manipulator.menu_tree_manipulators')
->filterItemsByCurrentLanguage($variables['items']);
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for the Menu Link Content form.
*/
function menu_manipulator_form_menu_link_content_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
$config = \Drupal::config('menu_manipulator.settings');
$menu_link = $form_state->getFormObject()->getEntity();
$menu_link_options = $menu_link->link->first()->options ?: [];
$menu_name = $menu_link->getMenuName();
// Save/load custom options in entity builder.
$form['#entity_builders']['menu_manipulator'] = 'menu_manipulator_menu_link_content_form_entity_builder';
// Skip untranslatable custom link options.
$not_translatable = !$menu_link->isTranslatable() || !$menu_link->getEntityType()->isRevisionable();
$hide_untranslatable_fields = $menu_link->isDefaultTranslationAffectedOnly() && !$menu_link->isDefaultTranslation();
$hide_options = $not_translatable || $hide_untranslatable_fields;
$filterable_by_language = !$hide_options && menu_manipulator_menu_is_filterable_by_language($menu_name);
// Check if menu can have icons (all menu can by default).
$menus_with_icon = $config->get('preprocess_menus_icon_list') ?? [];
$selected_menus_with_icons = array_filter($menus_with_icon, function ($v) {
return !empty($v);
});
$menus_with_icon = !empty($selected_menus_with_icons) ? $selected_menus_with_icons : array_keys($menus_with_icon);
$can_have_icon = in_array($menu_link->getMenuName(), $menus_with_icon);
// Custom options container.
$form['menu_manipulator'] = [
'#type' => 'container',
'#weight' => -2,
];
// Icons.
$icon_list_value = $config->get('menu_link_icon_list') ?: '';
$icon_list = menu_manipulator_prepare_associative_list($icon_list_value);
$form['menu_manipulator']['menu_link_icon'] = [
'#type' => 'select',
'#title' => t('Icon'),
'#description' => t('Select icon for this menu link.'),
'#tree' => TRUE,
'#empty_option' => t('- Select -'),
'#options' => $icon_list,
'#default_value' => $menu_link_options['icon'] ?? NULL,
'#access' => $can_have_icon,
];
$language_use_entity_default = $config->get('preprocess_menus_language_use_entity') ?? 1;
$form['menu_manipulator']['menu_link_language_use_entity'] = [
'#type' => 'checkbox',
'#title' => t('Use referenced entity to filter by current language'),
'#description' => t("If menu item's route targets an entity, check if it has translation"),
'#tree' => TRUE,
'#default_value' => $menu_link_options['language_use_entity'] ?? $language_use_entity_default,
'#disabled' => !$language_use_entity_default,
'#access' => $filterable_by_language,
];
}
/**
* Entity builder for menu item entity form.
*/
function menu_manipulator_menu_link_content_form_entity_builder($entity_type, EntityInterface $menu_link, &$form, FormStateInterface $form_state) {
// Don't do anything when we have no link.
if (!$menu_link->link || $menu_link->link->isEmpty()) {
return;
}
// Attach extra options to Menu Link Content entity.
$menu_link_options = $menu_link->link->first()->options ?: [];
$menu_link_options['icon'] = $form_state->getValue('menu_link_icon');
$menu_link_options['language_use_entity'] = $form_state->getValue('menu_link_language_use_entity');
$menu_link->link->first()->options = $menu_link_options;
}
/**
* Helper function to filter the associative key|label configuration.
*
* @param string $string
* The original value.
*
* @return array
* An associative array of values.
*
* @see Drupal\options\Plugin\Field\FieldType\ListItemBase::extractedAllowedValues();
*/
function menu_manipulator_prepare_associative_list($string) {
$values = [];
$list = explode("\n", $string);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
$generated_keys = $explicit_keys = FALSE;
foreach ($list as $text) {
// Check for an explicit key.
$matches = [];
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
// Trim key and value to avoid unwanted spaces issues.
$key = trim($matches[1]);
$value = trim($matches[2]);
$explicit_keys = TRUE;
}
$values[$key] = $value;
}
// We generate keys only if the list contains no explicit key at all.
if ($explicit_keys && $generated_keys) {
return [];
}
return $values;
}
/**
* Helper function to detect if a menu is to be filtered by language.
*
* @param string $string
* The menu name.
*
* @return bool
* Yes or no.
*/
function menu_manipulator_menu_is_filterable_by_language(string $menu_name) {
$config = \Drupal::config('menu_manipulator.settings');
if (!$config->get('preprocess_menus_language')) {
return FALSE;
}
$selected_menus = $config->get('preprocess_menus_language_list') ?? [];
$selected_menus = array_filter($selected_menus, function ($v) {
return !empty($v);
});
// Filter this menu by default.
if (empty($selected_menus)) {
$selected_menus[$menu_name] = $menu_name;
}
// Filter links.
return ($selected_menus[$menu_name] ?? 0) !== 0;
}