Commit 174fde37 by Manzar Hussain

add taxnomy menu

parent 28e89119
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
Hierarchical Taxonomy Menu is a Drupal 8/9 module for creating menus from
taxonomy terms. You can display an image next to a menu item if your terms have
an image field, and there is also an option to make the menu collapsible. This
module comes with a Twig template, so you can customize the HTML structure any
way you want.
* For a full description of the module, visit the project page:
https://www.drupal.org/project/hierarchical_taxonomy_menu
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/hierarchical_taxonomy_menu
REQUIREMENTS
------------
This module requires no modules outside of Drupal core.
INSTALLATION
------------
* Install the Hierarchical Taxonomy Menu module as you would normally install a
contributed Drupal module. Visit https://www.drupal.org/node/1897420 for
further information.
CONFIGURATION
-------------
After you install the module go to the block layout '/admin/structure/block' and
place 'Hierarchical Taxonomy Menu' block to any region you want. In block
settings, you can choose a vocabulary from which you want to create a menu, and
if that vocabulary has image fields you will see multiple options in a select
box. You can limit your menu to a part of taxonomy terms, by selecting a base
term. In this case, menu items will be generated only for its children's terms.
MAINTAINERS
-----------
Current maintainers:
* Goran Nikolovski (gnikolovski) - https://www.drupal.org/u/gnikolovski
This project has been sponsored by:
* Studio Present - https://www.drupal.org/studio-present
{
"name": "drupal/hierarchical_taxonomy_menu",
"description": "Create menus from taxonomy terms.",
"type": "drupal-module",
"homepage": "https://www.drupal.org/project/hierarchical_taxonomy_menu",
"authors": [
{
"name": "Goran Nikolovski",
"email": "goran@gorannikolovski.com",
"homepage": "https://gorannikolovski.com",
"role": "Developer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/hierarchical_taxonomy_menu",
"email": "goran@gorannikolovski.com"
},
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"drupal/core": "^8.6 || ^9"
}
}
block.settings.hierarchical_taxonomy_menu:
type: block_settings
label: 'Hierarchical Taxonomy Menu settings'
mapping:
vocabulary:
type: string
label: 'Use taxonomy terms from this vocabulary to create a menu'
max_depth:
type: integer
label: 'Number of sublevels to display'
dynamic_block_title:
type: boolean
label: 'Make the block title match the current taxonomy term name'
collapsible:
type: boolean
label: 'Make the menu collapsed by default'
stay_open:
type: boolean
label: 'Stay open at the current taxonomy term'
interactive_parent:
type: boolean
label: 'Allow parent items to be collapsible and selectable'
hide_block:
type: boolean
label: 'Hide block if the output is empty'
use_image_style:
type: boolean
label: 'Use image style'
image_height:
type: integer
label: 'Image height (px)'
image_width:
type: integer
label: 'Image width (px)'
image_style:
type: string
label: 'Image style'
max_age:
type: string
label: 'Cache'
base_term:
type: string
label: 'Base term'
dynamic_base_term:
type: boolean
label: 'Dynamic Base term'
show_count:
type: integer
label: 'Show count of referencing entities'
referencing_field:
type: string
label: 'Referencing field'
calculate_count_recursively:
type: boolean
label: 'Calculate count recursively'
.arrow-right {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBoCg+AAAADnRSTlMAAQsWHyMnQk9hg7bU1uPWfIEAAABZSURBVHgBZY9NGoAgCERHI4gM73/csOlzw1ugPH8AEDETbJrHTMIb8z7mz+jfOXOadce5P58VPUVQ4FgmAMnlVlXQCCzjhYTGiihPyqe7rLJsbay0XoYr478zTgfmno75KQAAAABJRU5ErkJggg==);
background-size: 100% auto;
display: inline-block;
width: 14px;
height: 14px;
cursor: pointer;
}
.arrow-down {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBoCg+AAAADnRSTlMAAQsWHyMnQk9hg7bU1uPWfIEAAABlSURBVHjaZY9bEgARDAQH2Y143f+4K0Op2jI/dBMSrIiq4CRYHzPdwuJYx06NPCdv43fMdyXn4qtNwfoXePkOIOMIj0D/QqFE5nHBkpacU2MJOs3mzm9pyMNOY41cw9X6Pdw1/gc85Qfmz9nu9wAAAABJRU5ErkJggg==);
background-size: 100% auto;
display: inline-block;
width: 14px;
height: 14px;
cursor: pointer;
}
/*Hide all submenus by default.*/
.block-taxonomymenu__submenu.collapsed-submenu {
display: none;
}
name: 'Hierarchical Taxonomy Menu'
type: module
description: 'Create menus from taxonomy terms.'
core: 8.x
core_version_requirement: ^8 || ^9
package: Menu
dependencies:
- drupal:taxonomy
- drupal:system (>=8.6.0)
# Information added by Drupal.org packaging script on 2020-11-11
version: '8.x-1.42'
project: 'hierarchical_taxonomy_menu'
datestamp: 1605088406
<?php
/**
* @file
* Installation file for Hierarchical Taxonomy Menu module.
*/
/**
* Convert config values.
*/
function hierarchical_taxonomy_menu_update_8001() {
$config_factory = \Drupal::configFactory();
foreach ($config_factory->listAll('block.block.hierarchicaltaxonomymenu') as $block_config_name) {
$block = $config_factory->getEditable($block_config_name);
$settings = $block->get('settings');
$settings['collapsible'] = (bool) $settings['collapsible'];
$settings['interactive_parent'] = (bool) $settings['interactive_parent'];
$settings['dynamic_base_term'] = (bool) $settings['dynamic_base_term'];
$block->set('settings', $settings);
$block->save();
}
}
/**
* Convert show_count config values from bool to integer.
*/
function hierarchical_taxonomy_menu_update_8002() {
$config_factory = \Drupal::configFactory();
foreach ($config_factory->listAll('block.block.hierarchicaltaxonomymenu') as $block_config_name) {
$block = $config_factory->getEditable($block_config_name);
$settings = $block->get('settings');
$settings['show_count'] = (int) $settings['show_count'];
$block->set('settings', $settings);
$block->save();
}
}
hierarchical_taxonomy_menu:
version: VERSION
css:
theme:
css/hierarchical-taxonomy-menu.css: {}
js:
js/hierarchical-taxonomy-menu.js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
<?php
/**
* @file
* Contains hierarchical_taxonomy_menu.module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function hierarchical_taxonomy_menu_help($route_name, RouteMatchInterface $route_match) {
if ($route_name === 'help.page.hierarchical_taxonomy_menu') {
$readme_file = file_exists(__DIR__ . '/README.md') ? __DIR__ . '/README.md' : __DIR__ . '/README.txt';
if (!file_exists($readme_file)) {
return NULL;
}
$text = file_get_contents($readme_file);
if ($text && !\Drupal::moduleHandler()->moduleExists('markdown')) {
return '<pre>' . $text . '</pre>';
}
else {
// 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');
}
}
return NULL;
}
/**
* Implements hook_theme().
*/
function hierarchical_taxonomy_menu_theme($existing, $type, $theme, $path) {
return [
'hierarchical_taxonomy_menu' => [
'variables' => [
'menu_tree' => [],
'route_tid' => NULL,
'vocabulary' => NULL,
'current_depth' => 0,
'max_depth' => 0,
'collapsible' => NULL,
],
],
];
}
/**
* Implements template_preprocess_block().
*/
function hierarchical_taxonomy_menu_preprocess_block(&$variables) {
if ($variables['plugin_id'] == 'hierarchical_taxonomy_menu' &&
$variables['configuration']['label_display'] == 'visible' &&
$variables['configuration']['dynamic_block_title']
) {
$term = \Drupal::routeMatch()
->getParameter('taxonomy_term');
if (!$term) {
return NULL;
}
$langcode = \Drupal::languageManager()
->getCurrentLanguage()
->getId();
$translation_languages = $term->getTranslationLanguages();
if (isset($translation_languages[$langcode])) {
$term = $term->getTranslation($langcode);
}
$variables['label'] = $term->getName();
}
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function hierarchical_taxonomy_menu_theme_suggestions_hierarchical_taxonomy_menu(array $variables) {
if (isset($variables['vocabulary'])) {
return ['hierarchical_taxonomy_menu__' . $variables['vocabulary']];
}
}
<?php
/**
* @file
* Post update functions for hierarchical_taxonomy_menu.
*/
/**
* Clear caches due to config schema changes.
*/
function hierarchical_taxonomy_menu_post_update_updated_config_schema() {
// Empty post-update hook.
}
/**
* @file
* Contains hierarchical-taxonomy-menu.js.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
$(document).ready(function () {
// Show all submenus which have list items with 'menu-item--active' class.
if (drupalSettings.stayOpen === true) {
$('.hierarchical-taxonomy-menu ul.menu').has('.menu-item--active').show();
}
$('.menu-item.menu-item--expanded').each(function (i, obj) {
let self = $(this);
if (self.find('a.active').length) {
self.addClass('active');
if (drupalSettings.interactiveParentMenu) {
if (!self.hasClass('menu-item--active')) {
self.children('i').toggleClass('arrow-right arrow-down');
}
}
}
});
if (drupalSettings.interactiveParentMenu === false) {
$('.hierarchical-taxonomy-menu .menu-item--expanded > a').on('click', function (e) {
e.preventDefault();
let isChildVisible = $(this).parent().children('.menu').is(':visible');
if (isChildVisible) {
$(this).parent().children('.menu').slideUp();
$(this).parent().removeClass('active');
}
else {
$(this).parent().children('.menu').slideDown();
$(this).parent().addClass('active');
}
});
}
else {
$('.hierarchical-taxonomy-menu .menu-item--expanded > .parent-toggle').on('click', function (e) {
e.preventDefault();
$(this).closest('i').toggleClass('arrow-right arrow-down');
let isChildVisible = $(this).parent().children('.menu').is(':visible');
if (isChildVisible) {
$(this).parent().children('.menu').slideUp();
$(this).parent().removeClass('active');
}
else {
$(this).parent().children('.menu').slideDown();
$(this).parent().addClass('active');
}
});
}
});
})(jQuery, Drupal, drupalSettings);
{% macro menu_links(menu_tree, route_tid, current_depth, max_depth, collapsible) %}
{% import _self as macros %}
{% for item in menu_tree %}
{%
set liClass = [
item.subitem and current_depth < max_depth ? 'menu-item menu-item--expanded block-taxonomymenu__menu-item block-taxonomymenu__menu-item--expanded' : 'menu-item block-taxonomymenu__menu-item',
route_tid == item.tid ? 'menu-item--active block-taxonomymenu__menu-item--active' : ''
]
%}
<li class="{{ liClass|join(' ') }}">
{% if item.image %}
<img class="menu-item-image block-taxonomymenu__image" src="{{ item.image }}" alt="{{ item.name }}" {% if item.use_image_style == false %}height="{{ item.height }}" width="{{ item.width }}"{% endif %} />
{% endif %}
<a href="{{ item.url }}" class="block-taxonomymenu__link {% if route_tid == item.tid %}active block-taxonomymenu__link--active{% endif %}">{{ item.name }}{% if item.show_count == true %} [{{ item.entities|length }}]{% endif %}</a>
{% if item.subitem and current_depth < max_depth %}
{% if item.interactive_parent %}
<i class="arrow-right parent-toggle" aria-hidden="true"></i><span class="visually-hidden">Expand Secondary Navigation Menu</span>
{% endif %}
{% if collapsible == TRUE %}
<ul class="menu block-taxonomymenu__submenu collapsed-submenu">
{% else %}
<ul class="menu block-taxonomymenu__submenu">
{% endif %}
{{ macros.menu_links(item.subitem, route_tid, current_depth + 1, max_depth, collapsible) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macros %}
<ul class="menu hierarchical-taxonomy-menu block-taxonomymenu__menu">
{{ macros.menu_links(menu_tree, route_tid, 0, max_depth, collapsible) }}
</ul>
<?php
namespace Drupal\Tests\hierarchical_taxonomy_menu\Functional;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests the Hierarchical Taxonomy Menu block caching.
*
* @group hierarchical_taxonomy_menu
*/
class HierarchicalTaxonomyMenuCacheTest extends BrowserTestBase {
use BlockCreationTrait;
use TaxonomyTestTrait;
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'image',
'hierarchical_taxonomy_menu',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The block.
*
* @var \Drupal\block\Entity\Block
*/
protected $block;
/**
* The user.
*
* @var \Drupal\user\Entity\User
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$vocabulary = $this->createVocabulary();
$this->createTerm($vocabulary, [
'name' => 'Term 1',
]);
$this->createTerm($vocabulary, [
'name' => 'Term 2',
]);
$block = $this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
$block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $vocabulary->id() . '|',
'dynamic_block_title' => TRUE,
]);
$block->save();
$this->block = $block;
$user = $this->drupalCreateUser([
'access content',
]);
$this->user = $user;
}
/**
* Tests cache context for anonymous users.
*/
public function testBlockCacheContextAnonymous() {
$this->drupalGet('<front>');
$this->assertCacheContext('url.path');
$this->drupalGet('taxonomy/term/1');
$this->assertCacheContext('url.path');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Term 1');
$this->drupalGet('taxonomy/term/2');
$this->assertCacheContext('url.path');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Term 2');
}
/**
* Tests cache context for authenticated users.
*/
public function testBlockCacheContextAuthenticated() {
$this->drupalLogin($this->user);
$this->drupalGet('<front>');
$this->assertCacheContext('url.path');
$this->drupalGet('taxonomy/term/1');
$this->assertCacheContext('url.path');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Term 1');
$this->drupalGet('taxonomy/term/2');
$this->assertCacheContext('url.path');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Term 2');
}
/**
* Tests that 'taxonomy_term_list' tag is working for anonymous users.
*/
public function testBlockCacheTagsAnonymous() {
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertCacheTags(array_merge($this->block->getCacheTags(), [
'block_view',
'config:block_list',
'config:system.site',
'http_response',
'rendered',
'taxonomy_term_list',
]));
$this->assertStringContainsString('Term 1', $block_element->getText());
$this->assertStringContainsString('Term 2', $block_element->getText());
$term1 = Term::load(1);
$term1->name->value = 'Renamed 1';
$term1->save();
$term2 = Term::load(2);
$term2->name->value = 'Renamed 2';
$term2->save();
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertCacheTags(array_merge($this->block->getCacheTags(), [
'block_view',
'config:block_list',
'config:system.site',
'http_response',
'rendered',
'taxonomy_term_list',
]));
$this->assertStringContainsString('Renamed 1', $block_element->getText());
$this->assertStringContainsString('Renamed 2', $block_element->getText());
}
/**
* Tests that 'taxonomy_term_list' tag is working for authenticated users.
*/
public function testBlockCacheTagsAuthenticated() {
$this->drupalLogin($this->user);
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertCacheTags(array_merge($this->block->getCacheTags(), [
'block_view',
'config:block_list',
'http_response',
'rendered',
'taxonomy_term_list',
'user_view',
'user:' . $this->user->id(),
]));
$this->assertStringContainsString('Term 1', $block_element->getText());
$this->assertStringContainsString('Term 2', $block_element->getText());
$term1 = Term::load(1);
$term1->name->value = 'Re-renamed 1';
$term1->save();
$term2 = Term::load(2);
$term2->name->value = 'Re-renamed 2';
$term2->save();
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertCacheTags(array_merge($this->block->getCacheTags(), [
'block_view',
'config:block_list',
'http_response',
'rendered',
'taxonomy_term_list',
'user_view',
'user:' . $this->user->id(),
]));
$this->assertStringContainsString('Re-renamed 1', $block_element->getText());
$this->assertStringContainsString('Re-renamed 2', $block_element->getText());
}
}
<?php
namespace Drupal\Tests\hierarchical_taxonomy_menu\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the Hierarchical Taxonomy Menu images.
*
* @group hierarchical_taxonomy_menu
*/
class HierarchicalTaxonomyMenuImageTest extends BrowserTestBase {
use BlockCreationTrait;
use TaxonomyTestTrait;
use TestFileCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'image',
'hierarchical_taxonomy_menu',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The block.
*
* @var \Drupal\block\Entity\Block
*/
protected $block;
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$vocabulary = $this->createVocabulary();
$this->vocabulary = $vocabulary;
FieldStorageConfig::create([
'field_name' => 'field_icon',
'entity_type' => 'taxonomy_term',
'type' => 'image',
'settings' => [],
'cardinality' => 1,
])->save();
$field_config = FieldConfig::create([
'field_name' => 'field_icon',
'label' => 'Icon',
'entity_type' => 'taxonomy_term',
'bundle' => $vocabulary->id(),
'required' => TRUE,
'settings' => [],
'description' => '',
]);
$field_config->save();
$images = $this->getTestFiles('image');
$file1 = File::create([
'uri' => $images[0]->uri,
'status' => FILE_STATUS_PERMANENT,
]);
$file1->save();
$file2 = File::create([
'uri' => $images[1]->uri,
'status' => FILE_STATUS_PERMANENT,
]);
$file2->save();
$this->createTerm($vocabulary, [
'name' => 'Term 1',
'field_icon' => [
'target_id' => $file1->id(),
'alt' => 'First image',
],
]);
$this->createTerm($vocabulary, [
'name' => 'Term 2',
'field_icon' => [
'target_id' => $file2->id(),
'alt' => 'Second image',
],
]);
$block = $this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
$block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $vocabulary->id() . '|field_icon',
'collapsible' => FALSE,
]);
$block->save();
$this->block = $block;
}
/**
* Tests that images are displayed.
*/
public function testBlockImagesEnabled() {
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Term 1', $block_element->getText());
$this->assertStringContainsString('Term 2', $block_element->getText());
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:first-child img', 'alt', 'Term 1');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:first-child img', 'height', '16');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:first-child img', 'width', '16');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:last-child img', 'alt', 'Term 2');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:last-child img', 'height', '16');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:last-child img', 'width', '16');
}
/**
* Tests image dimensions.
*/
public function testBlockImageDimensions() {
$this->block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $this->vocabulary->id() . '|field_icon',
'collapsible' => FALSE,
'image_height' => 128,
'image_width' => 256,
])->save();
$this->drupalGet('<front>');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:first-child img', 'height', '128');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:first-child img', 'width', '256');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:last-child img', 'height', '128');
$this->assertSession()->elementAttributeContains('css', '.hierarchical-taxonomy-menu li:last-child img', 'width', '256');
}
/**
* Tests image styles.
*/
public function testBlockImageStyles() {
$this->block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $this->vocabulary->id() . '|field_icon',
'collapsible' => FALSE,
'use_image_style' => TRUE,
'image_style' => 'medium',
])->save();
$this->drupalGet('<front>');
$image1 = $this->getSession()->getPage()->find('css', '.hierarchical-taxonomy-menu li:first-child img');
$this->assertStringContainsString('files/styles/medium', $image1->getAttribute('src'));
$image2 = $this->getSession()->getPage()->find('css', '.hierarchical-taxonomy-menu li:last-child img');
$this->assertStringContainsString('files/styles/medium', $image2->getAttribute('src'));
}
/**
* Tests that images are not displayed.
*/
public function testBlockImagesDisabled() {
$this->block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $this->vocabulary->id() . '|',
'collapsible' => FALSE,
])->save();
$this->drupalGet('<front>');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Term 1', $block_element->getText());
$this->assertStringContainsString('Term 2', $block_element->getText());
$this->assertSession()->elementNotExists('css', '.hierarchical-taxonomy-menu li:first-child img');
$this->assertSession()->elementNotExists('css', '.hierarchical-taxonomy-menu li:last-child img');
}
}
<?php
namespace Drupal\Tests\hierarchical_taxonomy_menu\Functional;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the Hierarchical Taxonomy Menu.
*
* @group hierarchical_taxonomy_menu
*/
class HierarchicalTaxonomyMenuTest extends BrowserTestBase {
use BlockCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'image',
'hierarchical_taxonomy_menu',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer blocks',
'administer site configuration',
'access administration pages',
]);
$this->drupalLogin($admin_user);
}
/**
* Test that the block is available.
*/
public function testBlockAvailability() {
$this->drupalGet('/admin/structure/block');
$this->clickLink('Place block');
$this->assertSession()->pageTextContains('Hierarchical Taxonomy Menu');
$this->assertSession()->linkByHrefExists('admin/structure/block/add/hierarchical_taxonomy_menu/', 0);
}
/**
* Test that the block can be placed.
*/
public function testBlockPlacement() {
$this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
$this->drupalGet('admin/structure/block');
$this->assertSession()->pageTextContains('Hierarchical Taxonomy Menu');
$this->drupalGet('<front>');
$this->assertSession()->pageTextContains('Hierarchical Taxonomy Menu');
}
/**
* Test the block config form integrity.
*/
public function testBlockConfigForm() {
$this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
$this->drupalGet('admin/structure/block/manage/hierarchicaltaxonomymenu');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists('settings[basic][vocabulary]');
$this->assertSession()->fieldExists('settings[basic][max_depth]');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '0');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '1');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '2');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '3');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '4');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '5');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '6');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '7');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '8');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '9');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '10');
$this->assertSession()->optionExists('edit-settings-basic-max-depth', '100');
$this->assertSession()->fieldExists('settings[basic][dynamic_block_title]');
$this->assertSession()->fieldExists('settings[basic][collapsible]');
$this->assertSession()->fieldExists('settings[basic][stay_open]');
$this->assertSession()->fieldExists('settings[basic][interactive_parent]');
$this->assertSession()->fieldExists('settings[basic][hide_block]');
$this->assertSession()->fieldExists('settings[image][use_image_style]');
$this->assertSession()->fieldExists('settings[image][image_style]');
$this->assertSession()->fieldExists('settings[image][image_height]');
$this->assertSession()->fieldExists('settings[image][image_width]');
$this->assertSession()->optionExists('edit-settings-image-image-style', 'large');
$this->assertSession()->optionExists('edit-settings-image-image-style', 'medium');
$this->assertSession()->optionExists('edit-settings-image-image-style', 'thumbnail');
$this->assertSession()->fieldExists('settings[advanced][max_age]');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '0');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '1800');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '3600');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '21600');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '43200');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '86400');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', '604800');
$this->assertSession()->optionExists('edit-settings-advanced-max-age', 'PERMANENT');
$this->assertSession()->fieldExists('settings[advanced][base_term]');
$this->assertSession()->fieldExists('settings[advanced][dynamic_base_term]');
$this->assertSession()->fieldExists('settings[advanced][show_count]');
$this->assertSession()->fieldExists('settings[advanced][calculate_count_recursively]');
}
}
<?php
namespace Drupal\Tests\hierarchical_taxonomy_menu\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests the Hierarchical Taxonomy Menu block with translated taxonomy terms.
*
* @group hierarchical_taxonomy_menu
*/
class HierarchicalTaxonomyMenuTranslationTest extends BrowserTestBase {
use BlockCreationTrait;
use TaxonomyTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'image',
'hierarchical_taxonomy_menu',
'taxonomy',
'locale',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$language = ConfigurableLanguage::createFromLangcode('sr');
$language->save();
$admin_user = $this->drupalCreateUser([
'administer blocks',
'administer site configuration',
'access administration pages',
]);
$this->drupalLogin($admin_user);
$vocabulary = $this->createVocabulary();
$parent_term = $this->createTerm($vocabulary, [
'name' => 'Parent',
'langcode' => 'en',
'status' => TRUE,
]);
$child_term = $this->createTerm($vocabulary, [
'name' => 'Child',
'langcode' => 'en',
'status' => TRUE,
]);
$child_term->parent = $parent_term->id();
$child_term->save();
$parent_term_sr = $parent_term->addTranslation('sr');
$parent_term_sr->name = 'Roditelj';
$parent_term_sr->langcode = 'sr';
$parent_term_sr->status = TRUE;
$parent_term_sr->save();
$child_term_sr = $child_term->addTranslation('sr');
$child_term_sr->name = 'Dete';
$child_term_sr->langcode = 'sr';
$child_term_sr->status = TRUE;
$child_term_sr->save();
$block = $this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
$block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $vocabulary->id() . '|',
'dynamic_block_title' => TRUE,
]);
$block->save();
}
/**
* Test translated block content.
*/
public function testTranslatedBlockContent() {
$this->drupalGet('taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Parent', $block_element->getText());
$this->assertStringContainsString('Child', $block_element->getText());
$this->drupalGet('sr/taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Roditelj', $block_element->getText());
$this->assertStringContainsString('Dete', $block_element->getText());
}
/**
* Test translated terms status.
*/
public function testTranslatedBlockContentWithDisabledChild() {
$this->drupalGet('taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Parent', $block_element->getText());
$this->assertStringContainsString('Child', $block_element->getText());
$parent_term = Term::load(1);
$parent_term->status = FALSE;
$parent_term->save();
$this->drupalGet('taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringNotContainsString('Parent', $block_element->getText());
$child_term = Term::load(2);
$child_term->status = FALSE;
$child_term->save();
$this->drupalGet('taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringNotContainsString('Child', $block_element->getText());
$this->drupalGet('sr/taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringContainsString('Roditelj', $block_element->getText());
$this->assertStringContainsString('Dete', $block_element->getText());
$parent_term = Term::load(1);
$parent_term_sr = \Drupal::service('entity.repository')
->getTranslationFromContext($parent_term, 'sr');
$parent_term_sr->status = FALSE;
$parent_term_sr->save();
$this->drupalGet('sr/taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringNotContainsString('Roditelj', $block_element->getText());
$child_term = Term::load(2);
$child_term_sr = \Drupal::service('entity.repository')
->getTranslationFromContext($child_term, 'sr');
$child_term_sr->status = FALSE;
$child_term_sr->save();
$this->drupalGet('sr/taxonomy/term/1');
$block_element = $this->getSession()->getPage()->find('css', '.block-taxonomymenu__menu');
$this->assertStringNotContainsString('Dete', $block_element->getText());
}
/**
* Test dynamic block title.
*/
public function testDynamicBlockTitle() {
$this->drupalGet('taxonomy/term/1');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Parent');
$this->drupalGet('sr/taxonomy/term/1');
$block_title_element = $this->getSession()->getPage()->find('css', '#block-hierarchicaltaxonomymenu h2');
$this->assertEqual($block_title_element->getText(), 'Roditelj');
}
}
<?php
namespace Drupal\Tests\hierarchical_taxonomy_menu\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests the Hierarchical Taxonomy Menu dynamic behaviour.
*
* @group hierarchical_taxonomy_menu
*/
class HierarchicalTaxonomyMenuDynamicTest extends WebDriverTestBase {
use BlockCreationTrait;
use TaxonomyTestTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'image',
'hierarchical_taxonomy_menu',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* The parent term ID.
*
* @var int
*/
protected $childParent;
/**
* The placed Hierarchical Taxonomy Menu block.
*
* @var \Drupal\block\Entity\Block
*/
protected $block;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer blocks',
'administer site configuration',
'access administration pages',
]);
$this->drupalLogin($admin_user);
$this->vocabulary = $this->createVocabulary();
// Generate taxonomy tree with 3 parents. Each parent has 11 descendants.
// @codingStandardsIgnoreStart
// Parent term 1 (Term ID: 1)
// Child term 1-1 (Term ID: 2)
// Child term 1-2 (Term ID: 3)
// Child term 1-3 (Term ID: 4)
// Child term 1-4 (Term ID: 5)
// Child term 1-5 (Term ID: 6)
// Child term 1-6 (Term ID: 7)
// Child term 1-7 (Term ID: 8)
// Child term 1-8 (Term ID: 9)
// Child term 1-9 (Term ID: 10)
// Child term 1-10 (Term ID: 11)
// Child term 1-11 (Term ID: 12)
// Parent term 2 (Term ID: 13)
// Child term 2-1 (Term ID: 14)
// Child term 2-2 (Term ID: 15)
// Child term 2-3 (Term ID: 16)
// Child term 2-4 (Term ID: 17)
// Child term 2-5 (Term ID: 18)
// Child term 2-6 (Term ID: 19)
// Child term 2-7 (Term ID: 20)
// Child term 2-8 (Term ID: 21)
// Child term 2-9 (Term ID: 22)
// Child term 2-10 (Term ID: 23)
// Child term 2-11 (Term ID: 24)
// Parent term 3 (Term ID: 25)
// Child term 3-1 (Term ID: 26)
// Child term 3-2 (Term ID: 27)
// Child term 3-3 (Term ID: 28)
// Child term 3-4 (Term ID: 29)
// Child term 3-5 (Term ID: 30)
// Child term 3-6 (Term ID: 31)
// Child term 3-7 (Term ID: 32)
// Child term 3-8 (Term ID: 33)
// Child term 3-9 (Term ID: 34)
// Child term 3-10 (Term ID: 35)
// Child term 3-11 (Term ID: 36)
// @codingStandardsIgnoreEnd
for ($i = 1; $i < 4; $i++) {
$parent = Term::create([
'vid' => $this->vocabulary->id(),
'name' => 'Parent term ' . $i,
]);
$parent->save();
for ($n = 1; $n < 12; $n++) {
$child = Term::create([
'vid' => $this->vocabulary->id(),
'name' => 'Child term ' . $i . '-' . $n,
]);
if ($n == 1) {
$child->parent = $parent->id();
}
else {
$child->parent = $this->childParent;
}
$child->save();
$this->childParent = $child->id();
}
}
$this->block = $this->drupalPlaceBlock('hierarchical_taxonomy_menu', [
'region' => 'content',
'label' => 'Hierarchical Taxonomy Menu',
'id' => 'hierarchicaltaxonomymenu',
]);
}
/**
* Tests dynamic behaviour when menu items are collapsed.
*/
public function testDynamicBehaviourWhenCollapsed() {
$this->block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $this->vocabulary->id() . '|',
'collapsible' => TRUE,
]);
$this->block->save();
$this->drupalGet('<front>');
// We should see just parents. All descendants must be hidden.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
$this->clickLink('Parent term 1');
// We should see just parents and first descendant of Parent term 1.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
for ($n = 1; $n < 12; $n++) {
if ($i == 1 && $n == 1) {
$this->assertSession()->pageTextContains('Child term ' . $i . '-' . $n);
}
else {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
}
$this->clickLink('Child term 1-1');
// We should see just parents and first descendant of Child term 1-1.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
for ($n = 1; $n < 12; $n++) {
if ($i == 1 && ($n == 1 || $n == 2)) {
$this->assertSession()->pageTextContains('Child term ' . $i . '-' . $n);
}
else {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
}
}
/**
* Tests dynamic behaviour when menu items are not collapsed.
*/
public function testDynamicBehaviourWhenNotCollapsed() {
$this->block->set('settings', [
'label' => 'Hierarchical Taxonomy Menu',
'label_display' => 'visible',
'vocabulary' => $this->vocabulary->id() . '|',
'collapsible' => FALSE,
]);
$this->block->save();
$this->drupalGet('<front>');
// We should see all items.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextContains('Child term ' . $i . '-' . $n);
}
}
$this->clickLink('Parent term 1');
// We should see parents and Parent term 2 and 3 descendants.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
if ($i == 1) {
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
else {
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextContains('Child term ' . $i . '-' . $n);
}
}
}
$this->clickLink('Parent term 2');
// We should see parents and Parent term 3 descendants.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
if ($i == 1 || $i == 2) {
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
else {
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextContains('Child term ' . $i . '-' . $n);
}
}
}
$this->clickLink('Parent term 3');
// We should see just parents. All descendants must be hidden.
for ($i = 1; $i < 4; $i++) {
$this->assertSession()->pageTextContains('Parent term ' . $i);
for ($n = 1; $n < 12; $n++) {
$this->assertSession()->pageTextNotContains('Child term ' . $i . '-' . $n);
}
}
}
}
language: php
php:
- 5.5
- 5.6
sudo: false
env:
global:
- PATH="$PATH:$HOME/.composer/vendor/bin"
- TESTDIR=$(pwd)
- DRUPAL_TI_MODULE_NAME="taxonomy_menu"
- DRUPAL_TI_SIMPLETEST_GROUP="taxonomy_menu"
- DRUPAL_TI_DB="drupal_travis_$$"
- DRUPAL_TI_DB_URL="mysql://root@127.0.0.1/$DRUPAL_TI_DB"
- DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1"
- DRUPAL_TI_WEBSERVER_PORT="8080"
- DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT"
# - DRUPAL_TI_PHPUNIT_CORE_SRC_DIRECTORY="./tests/src"
- DRUPAL_TI_ENVIRONMENT="drupal-8"
matrix:
# - DRUPAL_TI_RUNNERS="simpletest phpunit-core"
- DRUPAL_TI_RUNNERS="simpletest"
before_install:
- composer self-update
- composer global require "lionsad/drupal_ti:1.*"
- drupal-ti before_install
install:
- drupal-ti install
before_script:
- drupal-ti before_script
# - DRUPAL_TI_PHPUNIT_ARGS="-c $DRUPAL_TI_DRUPAL_DIR/modules/key/phpunit.xml --coverage-text"
script:
- drupal-ti script
after_script:
- drupal-ti after_script
notifications:
email: false
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Taxonomy Menu transforms your taxonomy vocabularies into menus.
* For a full description of the module visit:
https://www.drupal.org/project/taxonomy_menu
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/project/issues/taxonomy_menu
* If you prefer GitHub or GitLab, feel free to send a pull/merge request:
https://github.com/unn/taxonomy_menu https://gitlab.com/dstol/taxonomy_menu
REQUIREMENTS
------------
This module requires no modules outside of Drupal core.
INSTALLATION
------------
* Install the Taxonomy Menu module as you would normally install a contributed
Drupal module. Visit https://www.drupal.org/node/1897420 for further
information.
CONFIGURATION
-------------
1. Navigate to Administration > Extend and enable the module.
2. Navigate to Administration > Structure > Taxonomy menu to add a new
taxonomy menu.
3. From the appropriate dropdown, assign a vocabulary.
4. From the appropriate dropdown, assign a menu.
5. Save.
6. Clear caches.
Modify the menu:
Please note - once the taxonomy menu is created, the menu items are decoupled
from the taxonomy.
You can adjust the weight/order of the menu items, the ability to expand, and if
the item is enabled or disabled.
We have built some constraints to ensure the menu items resemble it's associated
taxonomy. First, you cannot adjust the parents. This ensures the original
taxonomy tree stays somewhat in tact. Second, you cannot change the title or
description for taxonomy-generated menu items. This is rendered dynamically from
the original taxonomy.
Caching:
Menu items are heavily cached. Upon making changes to menus and/or taxonomy,
please clear the cache before submitting an issue.
MAINTAINERS
-----------
* Adam Bergstein (nerdstein) - https://www.drupal.org/u/nerdstein
* Andrey Troeglazov (andrey.troeglazov) -
https://www.drupal.org/u/andreytroeglazov
* David Stoline (dstol) - https://www.drupal.org/u/dstol
* Nick Wilde (NickWilde) - https://www.drupal.org/u/nickwilde
Supporting organization
* Hook 42 - https://www.drupal.org/hook-42
Development proudly supported through a PhpStorm license from JetBrains.
<?php
namespace Drupal\taxonomy_menu\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Provides automated tests for the taxonomy_menu module.
*/
class TaxonomyMenuTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return [
'name' => "taxonomy_menu TaxonomyMenu's controller functionality",
'description' => 'Test Unit for module taxonomy_menu and controller TaxonomyMenu.',
'group' => 'Other',
];
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
}
/**
* Tests taxonomy_menu functionality.
*/
public function testTaxonomyMenu() {
// Check that the basic functions of module taxonomy_menu.
$this->assertEqual(TRUE, TRUE, 'Test Unit Generated via App Console.');
}
}
{
"name": "drupal/taxonomy_menu",
"type": "drupal-module",
"description": "Creates menu items based on associations to taxonomy vocabularies",
"keywords": ["Drupal"],
"license": "GPL-2.0+",
"homepage": "http://drupal.org/project/taxonomy_menu",
"minimum-stability": "dev",
"support": {
"issues": "http://drupal.org/project/taxonomy_menu",
"source": "http://cgit.drupalcode.org/taxonomy_menu"
},
"require": { }
}
taxonomy_menu.taxonomy_menu.*:
type: config_entity
label: 'Taxonomy Menu config'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
vocabulary:
type: string
label: 'Vocabulary'
menu:
type: string
label: 'Menu'
expanded:
type: boolean
label: 'Expanded'
depth:
type: integer
label: 'Depth'
menu_parent:
type: string
label: 'Menu parent'
description_field_name:
type: string
label: 'Description field name'
use_term_weight_order:
type: boolean
label: 'Use term weight order'
<?php
namespace Drupal\taxonomy_menu\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Class TaxonomyMenu.
*
* @package Drupal\taxonomy_menu\Controller
*/
class TaxonomyMenu extends ControllerBase {
/**
* Render taxonomy links.
*
* @return string
* Return Hello string.
*/
public function renderTaxonomyLinks() {
$markup = '';
// Load taxonomy menus.
$storage = \Drupal::entityTypeManager()->getStorage('taxonomy_menu');
$taxonomy_menus = $storage->loadMultiple();
$links = [];
// Get taxonomy and create menu links from vocabularies.
foreach ($taxonomy_menus as $taxonomy_menu) {
$links += $taxonomy_menu->generateTaxonomyLinks([]);
}
return [
'#type' => 'markup',
'#markup' => $this->t($markup),
];
}
}
<?php
namespace Drupal\taxonomy_menu\Controller;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
/**
* Provides a listing of TaxonomyMenu.
*/
class TaxonomyMenuListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Taxonomy Menu');
$header['id'] = $this->t('Machine name');
$header['expanded'] = $this->t('Expanded');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
$row['id'] = $entity->id();
$row['expanded'] = ($entity->expanded) ? $this->t('Yes') : $this->t('No');
// You probably want a few more properties here...
return $row + parent::buildRow($entity);
}
}
<?php
namespace Drupal\taxonomy_menu\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy_menu\TaxonomyMenuInterface;
/**
* Defines the TaxonomyMenu entity.
*
* @ConfigEntityType(
* id = "taxonomy_menu",
* label = @Translation("Taxonomy menu"),
* handlers = {
* "list_builder" = "Drupal\taxonomy_menu\Controller\TaxonomyMenuListBuilder",
* "form" = {
* "add" = "Drupal\taxonomy_menu\Form\TaxonomyMenuForm",
* "edit" = "Drupal\taxonomy_menu\Form\TaxonomyMenuForm",
* "delete" = "Drupal\Core\Entity\EntityDeleteForm"
* }
* },
* config_prefix = "taxonomy_menu",
* admin_permission = "administer site configuration",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* links = {
* "edit-form" = "/admin/structure/taxonomy_menu/{taxonomy_menu}",
* "delete-form" = "/admin/structure/taxonomy_menu/{taxonomy_menu}/delete",
* "collection" = "/admin/structure/taxonomy_menu"
* }
* )
*/
class TaxonomyMenu extends ConfigEntityBase implements TaxonomyMenuInterface {
/**
* The TaxonomyMenu ID.
*
* @var string
*/
protected $id;
/**
* The TaxonomyMenu label.
*
* @var string
*/
protected $label;
/**
* The taxonomy vocabulary.
*
* @var string
* The machine name of the vocabulary this entity represents.
*/
protected $vocabulary;
/**
* The depth to generate menu items.
*
* @var int
*/
protected $depth;
/**
* The menu to embed the vocabulary.
*
* @var string
* The machine name of the menu entity.
*/
protected $menu;
/**
* The expanded mode.
*
* @var bool
*/
public $expanded;
/**
* The menu parent.
*
* @var string
*/
protected $menu_parent;
/**
* The name of the description field.
*
* @var string
* The machine name of the field to be used as the description.
*/
protected $description_field_name;
/**
* {@inheritdoc}
*/
public function getVocabulary() {
return $this->vocabulary;
}
/**
* {@inheritdoc}
*/
public function getDepth() {
return $this->depth;
}
/**
* {@inheritdoc}
*/
public function getMenu() {
return $this->menu;
}
/**
* {@inheritdoc}
*/
public function getMenuParent() {
return $this->menu_parent;
}
/**
* Return if menu items should be ordered by the terms weight.
*
* Default value is TRUE.
*
* @return bool
* True or false.
*/
public function useTermWeightOrder() {
return isset($this->use_term_weight_order) ? $this->use_term_weight_order : TRUE;
}
/**
* {@inheritdoc}
*/
public function getDescriptionFieldName() {
return $this->description_field_name;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
if (!$this->isNew()) {
foreach (array_keys($this->getLinks([], TRUE)) as $link_key) {
$this->getMenuLinkManager()->removeDefinition($link_key, FALSE);
}
}
$this->addDependency('config', 'system.menu.' . $this->getMenu());
$this->addDependency('config', 'taxonomy.vocabulary.' . $this->getVocabulary());
}
/**
* {@inheritdoc}
*/
public function save() {
// Make sure we don't have any save exceptions before building menu
// definitions.
$return = parent::save();
foreach ($this->getLinks([], TRUE) as $link_key => $link_def) {
$this->getMenuLinkManager()->addDefinition($link_key, $link_def);
}
return $return;
}
/**
* {@inheritdoc}
*/
public function delete() {
foreach (array_keys($this->getLinks([], TRUE)) as $link_key) {
$this->getMenuLinkManager()->removeDefinition($link_key, FALSE);
}
parent::delete();
}
/**
* {@inheritdoc}
*/
public function getLinks($base_plugin_definition = [], $include_base_plugin_id = FALSE) {
/** @var $termStorage \Drupal\taxonomy\TermStorageInterface */
$termStorage = $this->entityTypeManager()->getStorage('taxonomy_term');
// Load taxonomy terms for tax menu vocab.
$terms = $termStorage->loadTree($this->getVocabulary(), 0, $this->getDepth() + 1);
$links = [];
// Create menu links for each term in the vocabulary.
foreach ($terms as $term) {
if (!$term instanceof TermInterface) {
$term = Term::load($term->tid);
}
$mlid = $this->buildMenuPluginId($term, $include_base_plugin_id);
$links[$mlid] = $this->buildMenuDefinition($term, $base_plugin_definition);
}
return $links;
}
/**
* Get the Menu Link Manager
*
* @return \Drupal\Core\Menu\MenuLinkManagerInterface
* The Menu Link Manager Service
*/
protected function getMenuLinkManager() {
return \Drupal::service('plugin.manager.menu.link');
}
/**
* {@inheritdoc}
*/
public function buildMenuPluginId(TermInterface $term, $include_base_plugin_id = TRUE) {
$plugin_id = '';
if ($include_base_plugin_id) {
$plugin_id .= 'taxonomy_menu.menu_link:';
}
$plugin_id .= 'taxonomy_menu.menu_link.' . $this->id() . '.' . $term->id();
return $plugin_id;
}
/**
* Generate a menu link plugin definition for a taxonomy term.
*
* @param \Drupal\taxonomy\TermInterface $term
* The taxonomy term for which to build a menu link render array.
* @param array $base_plugin_definition
* The base plugin definition to merge the link with.
*
* @return array
* The menu link plugin definition.
*/
protected function buildMenuDefinition(TermInterface $term, array $base_plugin_definition) {
$term_id = $term->id();
$term_url = $term->toUrl();
$taxonomy_menu_id = $this->id();
$menu_id = $this->getMenu();
// Determine parent link.
// TODO: Evaluate use case of multiple parents (should we make many menu items?)
$menu_parent_id = NULL;
/* @var $termStorage \Drupal\taxonomy\TermStorageInterface */
$termStorage = $this->entityTypeManager()->getStorage('taxonomy_term');
$parents = $termStorage->loadParents($term_id);
$parents = array_values($parents);
if (is_array($parents) && count($parents) && !is_null($parents[0]) && $parents[0] != '0') {
$menu_parent_id = $this->buildMenuPluginId($parents[0]);
}
// Note: if menu_parent_id is NULL, it will not update the hierarchy properly.
if (empty($menu_parent_id)) {
$menu_parent_id = str_replace($this->getMenu() . ':', '', $this->getMenuParent());
}
// TODO: Consider implementing a forced weight based on taxonomy tree.
// Generate link.
$arguments = ['taxonomy_term' => $term_id];
$link = $base_plugin_definition;
$link += [
'id' => $this->buildMenuPluginId($term),
'title' => $term->label(),
'description' => $term->getDescription(),
'menu_name' => $menu_id,
'expanded' => $this->expanded,
'metadata' => [
'taxonomy_menu_id' => $taxonomy_menu_id,
'taxonomy_term_id' => $term_id,
],
'route_name' => $term_url->getRouteName(),
'route_parameters' => $term_url->getRouteParameters(),
'load arguments' => $arguments,
'parent' => $menu_parent_id,
'provider' => 'taxonomy_menu',
'class' => 'Drupal\taxonomy_menu\Plugin\Menu\TaxonomyMenuMenuLink',
];
// Order by terms weight if configured for this taxonomy_menu.
if ($this->useTermWeightOrder()) {
$link['weight'] = $term->getWeight();
}
\Drupal::moduleHandler()->alter('taxonomy_menu_link', $link, $term);
return $link;
}
}
<?php
namespace Drupal\taxonomy_menu\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\system\Entity\Menu;
/**
* Class TaxonomyMenuForm.
*
* @package Drupal\taxonomy_menu\Form
*/
class TaxonomyMenuForm extends EntityForm {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
/* @var $taxonomy_menu \Drupal\taxonomy_menu\Entity\TaxonomyMenu */
$taxonomy_menu = $this->entity;
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $taxonomy_menu->label(),
'#description' => $this->t("Label for the Taxonomy Menu."),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $taxonomy_menu->id(),
'#machine_name' => [
'exists' => '\Drupal\taxonomy_menu\Entity\TaxonomyMenu::load',
],
'#disabled' => !$taxonomy_menu->isNew(),
];
// Vocabulary selection.
$options = [];
$vocabulary_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary');
foreach ($vocabulary_storage->loadMultiple() as $vocabulary) {
$options[$vocabulary->id()] = $vocabulary->label();
}
$form['vocabulary'] = [
'#type' => 'select',
'#title' => $this->t('Vocabulary'),
'#options' => $options,
'#default_value' => $taxonomy_menu->getVocabulary(),
'#ajax' => [
'callback' => '::ajaxReplaceDescriptionFieldForm',
'wrapper' => 'description-field-container',
'method' => 'replace',
],
];
// Description field selection.
$form['description_container'] = [
'#type' => 'container',
'#prefix' => '<div id="description-field-container">',
'#suffix' => '</div>',
];
$selected_vocabulary = $taxonomy_menu->getVocabulary();
if ($selected_vocabulary) {
$field_definitions = $this->entityManager->getFieldDefinitions('taxonomy_term', $selected_vocabulary);
// Build a field options array.
$field_options = ['' => $this->t('none')];
if (count($field_definitions)) {
foreach ($field_definitions as $field_name => $field_definition) {
$field_options[$field_name] = $field_definition->getName();
}
}
if (count($field_options)) {
$form['description_container']['description_field_name'] = [
'#type' => 'select',
'#title' => $this->t('Description field'),
'#description' => $this->t('Select the field to be used for the menu item description.'),
'#options' => $field_options,
'#default_value' => $taxonomy_menu->getDescriptionFieldName(),
];
}
}
// Menu selection.
$options = [];
$menu_storage = \Drupal::entityTypeManager()->getStorage('menu');
foreach ($menu_storage->loadMultiple() as $menu) {
$options[$menu->id()] = $menu->label();
}
$form['menu'] = [
'#type' => 'select',
'#title' => $this->t('Menu'),
'#options' => $options,
'#default_value' => $taxonomy_menu->getMenu(),
];
$form['expanded'] = [
'#type' => 'checkbox',
'#title' => $this->t('All menus entries expanded'),
'#default_value' => $taxonomy_menu->expanded,
];
$form['depth'] = [
'#type' => 'select',
'#title' => $this->t('Depth'),
'#default_value' => $taxonomy_menu->getDepth(),
'#options' => range(1, 9),
];
// Menu selection.
$custom_menus = Menu::loadMultiple();
foreach ($custom_menus as $menu_name => $menu) {
$custom_menus[$menu_name] = $menu->label();
}
asort($custom_menus);
$menu_parent_selector = \Drupal::service('menu.parent_form_selector');
$available_menus = $custom_menus;
$menu_options = $menu_parent_selector->getParentSelectOptions(NULL, $available_menus);
$form['menu_parent'] = [
'#type' => 'select',
'#title' => $this->t('Parent menu link'),
'#options' => $menu_options,
'#default_value' => $taxonomy_menu->getMenuParent(),
];
// If this checkbox is active, use the term weight for the menu item order.
// Otherwise the menu items will be sorted alphabetically.
// The default is order by weight.
$form['use_term_weight_order'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use term weight order'),
'#default_value' => isset($taxonomy_menu->use_term_weight_order) ? $taxonomy_menu->use_term_weight_order : TRUE,
];
return $form;
}
/**
* AJAX callback; Builds the description field selector.
*/
public static function ajaxReplaceDescriptionFieldForm(array &$form, FormStateInterface $form_state) {
return $form['description_container'];
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$taxonomy_menu = $this->entity;
$status = $taxonomy_menu->save();
if ($status) {
drupal_set_message($this->t('Saved the %label Taxonomy Menu.', [
'%label' => $taxonomy_menu->label(),
]));
}
else {
drupal_set_message($this->t('The %label Taxonomy Menu was not saved.', [
'%label' => $taxonomy_menu->label(),
]));
}
$form_state->setRedirectUrl($taxonomy_menu->toUrl('collection'));
}
}
<?php
namespace Drupal\taxonomy_menu\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides menu links for Taxonomy Menus.
*
* @see \Drupal\taxonomy_menu\Plugin\Menu\TaxonomyMenuMenuLink
*/
class TaxonomyMenuMenuLink extends DeriverBase implements ContainerDeriverInterface {
/**
* The taxonomy menu storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $taxonomyMenuStorage;
/**
* Sets up the storage handler.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $taxonomy_menu_storage
* The taxonomy menu storage.
*/
public function __construct(EntityStorageInterface $taxonomy_menu_storage) {
$this->taxonomyMenuStorage = $taxonomy_menu_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')->getStorage('taxonomy_menu')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$links = [];
/* @var $taxonomy_menus \Drupal\taxonomy_menu\TaxonomyMenuInterface[] */
$taxonomy_menus = $this->taxonomyMenuStorage->loadMultiple();
//MenuLinkContent entity, menulinkcontent table, look for data
foreach ($taxonomy_menus as $taxonomy_menu) {
/* @var $taxonomy_menu \Drupal\taxonomy_menu\TaxonomyMenuInterface */
$taxonomy_menu->getMenu();
$links = array_merge($links, $taxonomy_menu->getLinks($base_plugin_definition));
}
return $links;
}
}
<?php
namespace Drupal\taxonomy_menu\Plugin\Menu;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkBase;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines menu links provided by taxonomy menu.
*
* @see \Drupal\taxonony_menu\Plugin\Derivative\TaxonomyMenuMenuLink
*/
class TaxonomyMenuMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*
* Other possible overrides:
* - 'menu_name' => 1,
* - 'parent' => 1,
* - 'title' => 1,
* - 'description' => 1,
* - 'metadata' => 1,
*/
protected $overrideAllowed = [
'weight' => 1,
'expanded' => 1,
'enabled' => 1,
'parent' => 1,
];
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The static menu link service used to store updates to weight/parent etc.
*
* @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
*/
protected $staticOverride;
/**
* Constructs a new TaxonomyMenuMenuLink.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
* The static menu override.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
EntityTypeManagerInterface $entity_type_manager,
StaticMenuLinkOverridesInterface $static_override
) {
$this->configuration = $configuration;
$this->pluginId = $plugin_id;
$this->pluginDefinition = $plugin_definition;
$this->entityTypeManager = $entity_type_manager;
$this->staticOverride = $static_override;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('menu_link.static.overrides')
);
}
/**
* {@inheritdoc}
*/
public function getTitle() {
/* @var $link \Drupal\taxonomy\Entity\Term. */
$link = $this->entityTypeManager->getStorage('taxonomy_term')
->load($this->pluginDefinition['metadata']['taxonomy_term_id']);
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
if ($link->hasTranslation($language)) {
$translation = $link->getTranslation($language);
return $translation->label();
}
else {
return $link->label();
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getDescription() {
/* @var $link \Drupal\taxonomy\Entity\Term. */
$link = $this->entityTypeManager->getStorage('taxonomy_term')
->load($this->pluginDefinition['metadata']['taxonomy_term_id']);
// Get the description field name.
$taxonomy_menu = $this->entityTypeManager->getStorage('taxonomy_menu')->load($this->pluginDefinition['metadata']['taxonomy_menu_id']);
$description_field_name = $taxonomy_menu->getDescriptionFieldName();
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
if ($link->hasTranslation($language)) {
$translation = $link->getTranslation($language);
if (!empty($translation) && $translation->hasField($description_field_name)) {
return $translation->{$description_field_name}->value;
}
}
elseif (!empty($link) && $link->hasField($description_field_name)) {
return $link->{$description_field_name}->value;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function updateLink(array $new_definition_values, $persist) {
$overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
// Update the definition.
$this->pluginDefinition = $overrides + $this->pluginDefinition;
if ($persist) {
// TODO - consider any "persistence" back to TaxonomyMenu and/or Taxonomy
// upon menu link update.
// Always save the menu name as an override to avoid defaulting to tools.
$overrides['menu_name'] = $this->pluginDefinition['menu_name'];
$this->staticOverride->saveOverride($this->getPluginId(), $this->pluginDefinition);
}
return $this->pluginDefinition;
}
/**
* {@inheritdoc}
*/
public function isDeletable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function deleteLink() {
}
}
<?php
namespace Drupal\taxonomy_menu;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuLinkManagerInterface;
use Drupal\taxonomy\TermInterface;
/**
* Class TaxonomyMenu.
*
* @package Drupal\taxonomy_menu
*/
class TaxonomyMenuHelper {
/**
* Taxonomy Menu storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuStorage;
/**
* Menu Link Manager.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
*/
protected $manager;
/**
* Constructor.
*
* @param EntityTypeManagerInterface $entity_type_manager
* @param \Drupal\Core\Menu\MenuLinkManagerInterface $manager
* The menu link manager.
* @internal param EntityTypeManagerInterface $entity_manager The storage interface.* The storage interface.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, MenuLinkManagerInterface $manager) {
$this->menuStorage = $entity_type_manager->getStorage('taxonomy_menu');
$this->manager = $manager;
}
/**
* A reverse lookup of a taxonomy term menus by vocabulary.
*
* @param string $vid
* The vocabulary id.
*
* @return \Drupal\taxonomy_menu\TaxonomyMenuInterface[]
* The Taxonomy Menu
*/
public function getTermMenusByVocabulary($vid) {
return $this->menuStorage->loadByProperties(['vocabulary'=>$vid]);
}
/**
* Create menu entries associate with the vocabulary of this term.
*
* @param \Drupal\taxonomy\TermInterface $term
* Term
*/
public function generateTaxonomyMenuEntries(TermInterface $term, $rebuild_all = TRUE) {
// Load relevant taxonomy menus.
$tax_menus = $this->getTermMenusByVocabulary($term->getVocabularyId());
foreach ($tax_menus as $menu) {
foreach ($menu->getLinks([], TRUE) as $plugin_id => $plugin_def) {
if (!$rebuild_all) {
$plugin_id_parts = explode('.', $plugin_id);
$term_id = array_pop($plugin_id_parts);
if ($term->id() != $term_id) {
continue;
}
}
if ($this->manager->hasDefinition($plugin_id)) {
$this->manager->updateDefinition($plugin_id, $plugin_def);
}
else {
$this->manager->addDefinition($plugin_id, $plugin_def);
}
}
}
}
/**
* Update menu entries associate with the vocabulary of this term.
*
* @param \Drupal\taxonomy\TermInterface $term
* Term
*/
public function updateTaxonomyMenuEntries(TermInterface $term, $rebuild_all = TRUE) {
// Load relevant taxonomy menus.
$tax_menus = $this->getTermMenusByVocabulary($term->getVocabularyId());
/** @var $menu \Drupal\taxonomy_menu\TaxonomyMenuInterface */
foreach ($tax_menus as $menu) {
$links = $menu->getLinks([], TRUE);
foreach ($links as $plugin_id => $plugin_def) {
if (!$rebuild_all) {
$plugin_id_explode = explode('.', $plugin_id);
$term_id = array_pop($plugin_id_explode);
if ($term->id() != $term_id) {
continue;
}
}
if ($this->manager->hasDefinition($plugin_id)) {
$this->manager->updateDefinition($plugin_id, $plugin_def, FALSE);
}
else {
// Remove specific menu link if vid term is different to this old vid.
if ($term->original->getVocabularyId() != $term->getVocabularyId()) {
$this->removeTaxonomyMenuEntries($term->original);
}
$this->manager->addDefinition($plugin_id, $plugin_def);
}
}
}
}
/**
* Remove menu entries associate with the vocabulary of this term.
*
* @param \Drupal\taxonomy\TermInterface $term
* Term.
* @param bool $rebuild_all
* Whether to rebuild all links or not.
*/
public function removeTaxonomyMenuEntries(TermInterface $term, $rebuild_all = TRUE) {
// Load relevant taxonomy menus.
$tax_menus = $this->getTermMenusByVocabulary($term->getVocabularyId());
/** @var $menu \Drupal\taxonomy_menu\TaxonomyMenuInterface */
foreach ($tax_menus as $menu) {
// Remove all links.
if ($rebuild_all) {
$links = array_keys($menu->getLinks([], TRUE));
foreach ($links as $plugin_id) {
$this->manager->removeDefinition($plugin_id, FALSE);
}
// Remove specific term link. Note - this link does not exist in the taxonomy menu and is not in $links.
} else if (!empty($term)) {
$this->manager->removeDefinition($menu->buildMenuPluginId($term), FALSE);
}
}
}
}
<?php
namespace Drupal\taxonomy_menu;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\taxonomy\TermInterface;
/**
* Provides an interface defining a TaxonomyMenu entity.
*/
interface TaxonomyMenuInterface extends ConfigEntityInterface {
/**
* Get the menu that the menu items are being created in.
*
* @return string
* The machine name of the menu entity holding the vocabulary's menu items.
*/
public function getMenu();
/**
* Get the vocabulary being used.
*
* @return string
* The vocabulary whose terms will be used to generate a menu.
*/
public function getVocabulary();
/**
* Get the depth of terms to generate menu items for.
*
* @return int
* The depth.
*/
public function getDepth();
/**
* Get the menu item to use as the parent for the taxonomy menu.
*
* @return string
* The menu item id string.
*/
public function getMenuParent();
/**
* @return string
* The machine name of the field to be used as the description.
*/
public function getDescriptionFieldName();
/**
* Get menu link plugin definitions
*
* @param array $base_plugin_definition
*
* @param bool $include_base_plugin_id
* If true, 'taxonomy_menu.menu_link:' will be prepended to the returned
* plugin ids.
*
* @return array
* Associative array of menu links ids and definitions.
*/
public function getLinks($base_plugin_definition = [], $include_base_plugin_id = FALSE);
/**
* Generates a menu link id for the taxonomy term.
*
* @param \Drupal\taxonomy\TermInterface $term
* Term to build menu item for.
* @param bool $include_base_plugin_id
* Include base plugin id in menu item id.
* @return string
* A unique string id for the menu item.
*/
public function buildMenuPluginId(TermInterface $term, $include_base_plugin_id = TRUE);
}
<?php
namespace Drupal\taxonomy_menu\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the operations of Taxonomy Menu.
*
* @group taxonomy_menu
*/
class TaxonomyMenuOperations extends WebTestBase {
public static $modules = ['taxonomy_menu', 'system', 'menu_ui', 'taxonomy', 'dblog'];
/**
* Set up for all tests.
*/
function setUp() {
parent::setUp();
// Create user with permission to create policy.
$user1 = $this->drupalCreateUser(['administer site configuration', 'administer taxonomy']);
$this->drupalLogin($user1);
// Create a testing taxonomy vocabulary.
$this->drupalGet('admin/structure/taxonomy/add');
$edit = [
'vid' => 'test_tax_vocab',
'name' => 'Test',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
// Create logged in user.
$perms = [
'administer site configuration',
'administer taxonomy',
'administer menu'
//'delete terms in test',
//'edit terms in test'
];
$admin_user = $this->drupalCreateUser($perms);
$this->drupalLogin($admin_user);
// Add sample terms to the vocabulary.
$this->drupalGet('admin/structure/taxonomy/manage/test_tax_vocab/add');
$edit = [
'name[0][value]' => 'test term 1',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/structure/taxonomy/manage/test_tax_vocab/add');
$edit = [
'name[0][value]' => 'test term 1-A',
'parent[]' => '1',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/structure/taxonomy/manage/test_tax_vocab/add');
$edit = [
'name[0][value]' => 'test term 2',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
// Create a testing menu.
$this->drupalGet('admin/structure/menu/add');
$edit = [
'id' => 'test-menu',
'label' => 'Test',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
// Create new taxonomy menu.
$this->drupalGet('admin/structure/taxonomy_menu/add');
$edit = [
'id' => 'test_tax_menu',
'label' => 'test tax menu',
'vocabulary' => 'test_tax_vocab',
'menu' => 'test-menu',
'expanded' => 1,
'depth' => '1',
'menu_parent' => 'test-menu:',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
}
/**
* Test creation of taxonomy menu functions.
*/
function testTaxMenuCreate() {
// Check menu for taxonomy-based menu items keyed 1, 2, and 3.
$this->drupalGet('admin/structure/menu/manage/test-menu');
$this->assertFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.1][enabled]',
NULL,
'I should expect to see enabled field for taxonomy term 1'
);
$this->assertFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.2][enabled]',
NULL,
'I should expect to see enabled field for taxonomy term 2'
);
$this->assertFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.3][enabled]',
NULL,
'I should expect to see enabled field for taxonomy term 3'
);
// Check 2 is a parent of 1.
$this->assertFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.2][parent]',
'taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.1',
'I should expect to see taxonomy term 2 have a parent of taxonomy term 1'
);
}
/**
* Test creation of taxonomy term.
*/
function testTaxTermCreate() {
// Create a new term.
$this->drupalGet('admin/structure/taxonomy/manage/test_tax_vocab/add');
$edit = [
'name[0][value]' => 'test term 3',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalGet('admin/structure/menu/manage/test-menu');
// Check for it within the menu.
$this->assertFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.4][enabled]',
NULL,
'I should expect to see enabled field for taxonomy term 4'
);
}
/**
* Test deletion of taxonomy term.
*/
function testTaxTermDelete() {
// Delete a term.
$this->drupalGet('taxonomy/term/3/delete');
$edit = [
];
$this->drupalPostForm(NULL, $edit, t('Delete'));
// Check for it within the menu.
$this->assertNoFieldByName(
'links[menu_plugin_id:taxonomy_menu.menu_link.test.3][enabled]',
NULL,
'I should not expect to see enabled field for taxonomy term 3'
);
}
/**
* Tests if of menu links from taxonony_menu is expanded.
*/
function testTaxMenuLinkExpanded() {
$this->drupalGet('admin/structure/menu/link/taxonomy_menu.menu_link:taxonomy_menu.menu_link.test_tax_menu.1/edit');
$this->assertFieldByName(
'expanded',
1,
'I should expect to see expanded value for menu based on taxonomy term 1'
);
}
}
name: Taxonomy Menu
type: module
description: Embed a taxonomy tree into a menu
# core: 8.x
package: Menu
configure: entity.taxonomy_menu.collection
# Information added by Drupal.org packaging script on 2018-12-22
version: '8.x-3.4'
core: '8.x'
project: 'taxonomy_menu'
datestamp: 1545492795
entity.taxonomy_menu.add_form:
route_name: 'entity.taxonomy_menu.add_form'
title: 'Add Taxonomy menu'
appears_on:
- entity.taxonomy_menu.collection
# Taxonomy Menu menu items definition
entity.taxonomy_menu.collection:
title: 'Taxonomy Menu'
route_name: entity.taxonomy_menu.collection
description: 'List Taxonomy Menus'
parent: system.admin_structure
taxonomy_menu.menu_link:
class: Drupal\taxonomy_menu\Plugin\Menu\TaxonomyMenuMenuLink
deriver: Drupal\taxonomy_menu\Plugin\Derivative\TaxonomyMenuMenuLink
<?php
/**
* @file
* Contains taxonomy_menu.module.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements taxonomy_menu_help().
*/
function taxonomy_menu_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.taxonomy_menu':
return t('<p>The Taxonomy Menu module transforms your taxonomy vocabularies into menus.</p>
<p>See the <a href=":project_page">project page on Drupal.org</a> for more details.</p>',
[
':project_page' => 'https://www.drupal.org/project/taxonomy_menu',
]);
}
}
/**
* Implements hook_entity_insert().
*
* Check for taxonomy term insert.
*/
function taxonomy_menu_taxonomy_term_insert(EntityInterface $entity) {
\Drupal::service('taxonomy_menu.helper')->generateTaxonomyMenuEntries($entity, FALSE);
}
/**
* Implements hook_entity_delete().
*
* Check for taxonomy term deletion.
*/
function taxonomy_menu_taxonomy_term_delete(EntityInterface $entity) {
\Drupal::service('taxonomy_menu.helper')->removeTaxonomyMenuEntries($entity, FALSE);
}
/**
* Implements hook_entity_update().
*
* Check for taxonomy term updates.
*/
function taxonomy_menu_taxonomy_term_update(EntityInterface $entity) {
\Drupal::service('taxonomy_menu.helper')->updateTaxonomyMenuEntries($entity, FALSE);
}
# Taxonomy Menu Entity routing definition
entity.taxonomy_menu.collection:
path: '/admin/structure/taxonomy_menu'
defaults:
_entity_list: 'taxonomy_menu'
_title: 'Taxonomy menu Configuration'
requirements:
_permission: 'administer site configuration'
entity.taxonomy_menu.add_form:
path: '/admin/structure/taxonomy_menu/add'
defaults:
_entity_form: 'taxonomy_menu.add'
_title: 'Add Taxonomy menu'
requirements:
_permission: 'administer site configuration'
entity.taxonomy_menu.edit_form:
path: '/admin/structure/taxonomy_menu/{taxonomy_menu}'
defaults:
_entity_form: 'taxonomy_menu.edit'
_title: 'Edit Taxonomy menu'
requirements:
_permission: 'administer site configuration'
entity.taxonomy_menu.delete_form:
path: '/admin/structure/taxonomy_menu/{taxonomy_menu}/delete'
defaults:
_entity_form: 'taxonomy_menu.delete'
_title: 'Delete Taxonomy menu'
requirements:
_permission: 'administer site configuration'
taxonomy_menu.test:
path: '/taxonomy-menu/render-taxonomy-links'
defaults:
_controller: '\Drupal\taxonomy_menu\Controller\TaxonomyMenu::renderTaxonomyLinks'
_title: 'Debugging'
requirements:
_permission: 'administer site configuration'
services:
taxonomy_menu.helper:
class: Drupal\taxonomy_menu\TaxonomyMenuHelper
arguments: ['@entity.manager', '@plugin.manager.menu.link']
----------------------
-- Taxonomy Menu UI --
----------------------
This module provides to create menu items on a Taxonomy term page in Drupal 8.
This functionality similar like you create menu items for node. This module
enables the "Available Menus" options on the Taxonomy vocabulary page.
Then on the Taxonomy term add/edit form you can set the menu item for that
term, just as you can do on the node add/edit form.
-----------
-- Usage --
-----------
Configurable per Taxonomy vocabulary on the "Menu Settings" tab, editing your
available menus and default parent item.
Configurable per Taxonomy term on the "Menu Settings" tab, editing your menu
link title, parent item and weight.
Functionality similar with "Menu Settings" tab for node.
# Schema for configuration files of the Taxonomy Menu UI module.
taxonomy.vocabulary.*.third_party.menu_ui:
type: mapping
label: 'Per-content type menu settings'
mapping:
available_menus:
type: sequence
label: 'Available menus'
sequence:
type: string
label: 'Menu machine name'
parent:
type: string
label: 'Parent'
name: Taxonomy Menu UI
type: module
description: 'Add ability to create menu links for taxonomy terms.'
core: 8.x
core_version_requirement: ^8 || ^9
package: Taxonomy
dependencies:
- drupal:taxonomy
- drupal:menu_ui
# Information added by Drupal.org packaging script on 2020-07-17
version: '8.x-1.4'
project: 'taxonomy_menu_ui'
datestamp: 1594974827
<?php
/**
* @file
* Taxonomy Menu UI installation file.
*/
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Implements hook_uninstall().
*/
function taxonomy_menu_ui_uninstall() {
$logger = \Drupal::logger('taxonomy_menu_ui');
/** @var \Drupal\Core\Entity\EntityInterface $bundle */
foreach (Vocabulary::loadMultiple() as $bundle) {
$config_name = 'core.entity_form_display.taxonomy_term.' . $bundle->id() . '.default';
try {
\Drupal::service('config.factory')
->getEditable($config_name)
->clear('content.menu')
->save();
}
catch (\Exception $e) {
$logger->warning(sprintf('Unable to uninstall config: %s.', $config_name));
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment