From 82e8a950a8e43d1a1a1205f94d0e6f6813460393 Mon Sep 17 00:00:00 2001 From: lpeidro <luis.ruiz@metadrop.net> Date: Thu, 13 Mar 2025 18:12:04 +0100 Subject: [PATCH 1/7] Issue #3512462: Fix the false not acess for taxonomy terms without alias --- src/EntityRender.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/EntityRender.php b/src/EntityRender.php index 7aa5866..9cf7f1c 100644 --- a/src/EntityRender.php +++ b/src/EntityRender.php @@ -283,8 +283,12 @@ class EntityRender extends Entity { * The target. */ protected function processInternalHref(TargetInterface $target) { - $path = (string) $target->getPath(); + + if ($path === '') { + return; + } + $alias = $this->entityMeshRepository->getPathWithoutLangPrefix($path); $target->setEntityLangcode($this->entityMeshRepository->getLangcodeFromPath($path)); @@ -468,8 +472,13 @@ class EntityRender extends Entity { return; } - // It is a view route. - if (isset($route_match['view_id'])) { + if (empty($route_match['_route'])) { + $target->setSubcategory('broken-link'); + return; + } + + // @todo Maybe here is possible to get as well the entity object + if (str_starts_with($route_match['_route'], 'view.')) { $target->setEntityType('view'); $target->setEntityId($route_match['view_id'] . '.' . $route_match['display_id']); return; @@ -477,11 +486,12 @@ class EntityRender extends Entity { // This case apply with entity canonical routes. $route_parts = explode('.', $route_match['_route']); - if (count($route_parts) === 3 && $route_parts[0] === 'entity' && $route_parts[2] === 'canonical') { + if (count($route_parts) > 1) { $entity_id = ''; $entity = $route_parts[1]; if (isset($route_match[$entity]) && $route_match[$entity] instanceof EntityInterface) { $entity_id = $route_match[$entity]->id(); + $entity = $route_match[$entity]->getEntityTypeId(); } $target->setEntityType($entity); $target->setEntityId((string) $entity_id); -- GitLab From 81d8f46c555abe0b174600ad6f4b195df35eac60 Mon Sep 17 00:00:00 2001 From: lpeidro <luis.ruiz@metadrop.net> Date: Thu, 13 Mar 2025 18:31:30 +0100 Subject: [PATCH 2/7] Issue #3512462: Avoid to check access for views, needs specific logic --- src/EntityRender.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EntityRender.php b/src/EntityRender.php index 9cf7f1c..90c58f8 100644 --- a/src/EntityRender.php +++ b/src/EntityRender.php @@ -580,9 +580,11 @@ class EntityRender extends Entity { */ public function accessCheckTarget(Target $target) { // Only check internal targets. + // @todo check access for views. if ($target->getLinkType() !== 'internal' || empty($target->getEntityType()) - || empty($target->getEntityId())) { + || empty($target->getEntityId()) + || $target->getEntityType() === 'view') { return TRUE; } -- GitLab From d4020b2f5cbff727b29abe879c436a677ee0fca6 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Fri, 14 Mar 2025 12:27:48 +0100 Subject: [PATCH 3/7] Issue #3512879 by lpeidro, eduardo morales alberti: False access-denied-link with some links of Taxonomies --- config/optional/views.view.entity_mesh.yml | 16 +- .../optional/views.view.entity_mesh_node.yml | 68 ++-- config/schema/entity_mesh.views.schema.yml | 38 ++ entity_mesh.services.yml | 1 + src/EntityRender.php | 44 ++- tests/src/Kernel/EntityMeshViewsTest.php | 334 ++++++++++++++++++ 6 files changed, 439 insertions(+), 62 deletions(-) create mode 100644 tests/src/Kernel/EntityMeshViewsTest.php diff --git a/config/optional/views.view.entity_mesh.yml b/config/optional/views.view.entity_mesh.yml index b08bbcb..fbc4968 100644 --- a/config/optional/views.view.entity_mesh.yml +++ b/config/optional/views.view.entity_mesh.yml @@ -1490,10 +1490,7 @@ display: empty: false content: 'Showing @start - @end de @total' footer: { } - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } cache_metadata: max-age: -1 contexts: @@ -1522,10 +1519,7 @@ display: encoding: utf8 utf8_bom: '0' use_serializer_encode_only: false - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } path: admin/reports/entity-mesh/table displays: table: table @@ -2527,9 +2521,6 @@ display: use_ajax: false display_description: '' display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false ajax_history: { } path: admin/reports/entity-mesh/links menu: @@ -3331,9 +3322,6 @@ display: use_ajax: false display_description: '' display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false ajax_history: enable_history: false path: admin/reports/entity-mesh/table diff --git a/config/optional/views.view.entity_mesh_node.yml b/config/optional/views.view.entity_mesh_node.yml index 7a6c88e..0374ae5 100644 --- a/config/optional/views.view.entity_mesh_node.yml +++ b/config/optional/views.view.entity_mesh_node.yml @@ -833,7 +833,7 @@ display: support_desk: '0' contributor: '0' api_client: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -875,7 +875,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -917,7 +917,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -959,7 +959,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -1043,7 +1043,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -1065,7 +1065,7 @@ display: admin_label: '' plugin_id: string operator: in - value: { } + value: '' group: 1 exposed: true expose: @@ -1086,7 +1086,6 @@ display: administrator: '0' editor: '0' placeholder: '' - reduce: 0 is_grouped: false group_info: label: '' @@ -1212,7 +1211,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -1254,7 +1253,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -1338,7 +1337,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -1490,10 +1489,7 @@ display: empty: false content: 'Showing @start - @end de @total' footer: { } - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } cache_metadata: max-age: -1 contexts: @@ -2514,7 +2510,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -2556,7 +2552,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -2640,7 +2636,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -2724,7 +2720,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -2761,10 +2757,7 @@ display: use_ajax: true display_description: '' header: { } - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } displays: node_source_table: node_source_table inherit_exposed_filters: true @@ -3376,7 +3369,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -3418,7 +3411,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -3502,7 +3495,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -3544,7 +3537,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -3622,10 +3615,7 @@ display: plugin_id: result empty: true content: 'Displaying @start - @end of @total' - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } path: node/%node/entity_mesh menu: type: none @@ -4708,7 +4698,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -4750,7 +4740,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -4829,10 +4819,7 @@ display: use_ajax: true display_description: '' header: { } - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } displays: node_target_table: node_target_table inherit_exposed_filters: true @@ -5342,7 +5329,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -5384,7 +5371,7 @@ display: anonymous: '0' administrator: '0' editor: '0' - reduce: 0 + reduce: false is_grouped: false group_info: label: '' @@ -5461,10 +5448,7 @@ display: plugin_id: result empty: true content: 'Displaying @start - @end of @total' - display_extenders: - metatag_display_extender: - metatags: { } - tokenize: false + display_extenders: { } path: node/%node/entity_mesh/target menu: type: none diff --git a/config/schema/entity_mesh.views.schema.yml b/config/schema/entity_mesh.views.schema.yml index 96f6fb3..1188f06 100644 --- a/config/schema/entity_mesh.views.schema.yml +++ b/config/schema/entity_mesh.views.schema.yml @@ -6,3 +6,41 @@ views.filter.source_entity_type_filter: type: views.filter.in_operator label: 'Source entity type filter' +views.filter.category_filter: + type: views.filter.in_operator + label: 'Category filter' + +views.filter.subcategory_filter: + type: views.filter.in_operator + label: 'Subcategory filter' + +views.filter.source_bundle_filter: + type: views.filter.in_operator + label: 'Source bundle filter' + +views.filter.source_langcode_filter: + type: views.filter.in_operator + label: 'Source langcode filter' + +views.filter.target_bundle_filter: + type: views.filter.in_operator + label: 'Target bundle filter' + +views.filter.target_langcode_filter: + type: views.filter.in_operator + label: 'Target langcode filter' + +views.filter.target_schema_filter: + type: views.filter.in_operator + label: 'Target schema filter' + mapping: + value: + type: sequence + label: 'Values' + sequence: + type: string + label: 'Value' + +views.filter.target_type_link_filter: + type: views.filter.in_operator + label: 'Target type link filter' diff --git a/entity_mesh.services.yml b/entity_mesh.services.yml index c47337c..cb7b429 100644 --- a/entity_mesh.services.yml +++ b/entity_mesh.services.yml @@ -26,6 +26,7 @@ services: - '@account_switcher' - '@entity_mesh.language_negotiator_switcher' - '@module_handler' + - '@access_manager' entity_mesh.language_negotiator_switcher: class: Drupal\entity_mesh\Language\LanguageNegotiatorSwitcher arguments: ['@language_manager', '@module_handler', '@string_translation', '@entity_mesh.static_language_negotiator'] diff --git a/src/EntityRender.php b/src/EntityRender.php index 90c58f8..6fc6e8a 100644 --- a/src/EntityRender.php +++ b/src/EntityRender.php @@ -4,6 +4,7 @@ namespace Drupal\entity_mesh; use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Component\Utility\DeprecationHelper; +use Drupal\Core\Access\AccessManager; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Entity\EntityInterface; @@ -15,6 +16,7 @@ use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountSwitcherInterface; use Drupal\Core\Session\AnonymousUserSession; use Drupal\entity_mesh\Language\LanguageNegotiatorSwitcher; +use Drupal\taxonomy\TermInterface; /** * Service description. @@ -51,6 +53,13 @@ class EntityRender extends Entity { */ protected ModuleHandlerInterface $moduleHandler; + /** + * Access manager. + * + * @var \Drupal\Core\Access\AccessManager + */ + protected AccessManager $accessManager; + /** * Constructs a Menu object. * @@ -70,6 +79,8 @@ class EntityRender extends Entity { * The language negotiator switcher service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * Module handler. + * @param \Drupal\Core\Access\AccessManager $access_manager + * Access manager. */ public function __construct( RepositoryInterface $entity_mesh_repository, @@ -80,6 +91,7 @@ class EntityRender extends Entity { AccountSwitcherInterface $account_switcher, LanguageNegotiatorSwitcher $language_negotiator_switcher, ModuleHandlerInterface $module_handler, + AccessManager $access_manager, ) { parent::__construct($entity_mesh_repository, $entity_type_manager, $language_manager, $config_factory); $this->renderer = $renderer; @@ -87,6 +99,7 @@ class EntityRender extends Entity { $this->type = 'entity_render'; $this->languageNegotiatorSwitcher = $language_negotiator_switcher; $this->moduleHandler = $module_handler; + $this->accessManager = $access_manager; } /** @@ -478,9 +491,20 @@ class EntityRender extends Entity { } // @todo Maybe here is possible to get as well the entity object - if (str_starts_with($route_match['_route'], 'view.')) { - $target->setEntityType('view'); - $target->setEntityId($route_match['view_id'] . '.' . $route_match['display_id']); + if (str_starts_with($route_match['_route'], 'view.') + || isset($route_match['view_id']) && isset($route_match['display_id'])) { + if ($route_match['view_id'] === 'taxonomy_term' + && isset($route_match['taxonomy_term']) + && $route_match['taxonomy_term'] instanceof TermInterface + ) { + $target->setEntityType('taxonomy_term'); + $target->setEntityId($route_match['taxonomy_term']->id()); + } + else { + $target->setEntityType('view'); + $target->setEntityId($route_match['view_id'] . '.' . $route_match['display_id']); + } + return; } @@ -580,14 +604,22 @@ class EntityRender extends Entity { */ public function accessCheckTarget(Target $target) { // Only check internal targets. - // @todo check access for views. if ($target->getLinkType() !== 'internal' || empty($target->getEntityType()) - || empty($target->getEntityId()) - || $target->getEntityType() === 'view') { + || empty($target->getEntityId())) { return TRUE; } + if ($target->getEntityType() === 'view') { + $route_view = 'view.' . $target->getEntityId(); + + $anonymous_account = new AnonymousUserSession(); + + // Check access. + return $this->accessManager->checkNamedRoute($route_view, [], $anonymous_account); + + } + try { $storage = $this->entityTypeManager->getStorage($target->getEntityType()); } diff --git a/tests/src/Kernel/EntityMeshViewsTest.php b/tests/src/Kernel/EntityMeshViewsTest.php new file mode 100644 index 0000000..e9ad2a0 --- /dev/null +++ b/tests/src/Kernel/EntityMeshViewsTest.php @@ -0,0 +1,334 @@ +<?php + +namespace Drupal\Tests\entity_mesh\Kernel; + +use Drupal\Core\Session\AccountInterface; +use Drupal\filter\Entity\FilterFormat; +use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\Node; +use Drupal\taxonomy\Entity\Term; +use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\Tests\node\Traits\ContentTypeCreationTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; +use Drupal\user\Entity\Role; + +/** + * Tests the Entity Mesh link auditing for taxonomy terms and views. + * + * @group entity_mesh + */ +class EntityMeshViewsTest extends KernelTestBase { + + use ContentTypeCreationTrait; + use UserCreationTrait; + + /** + * Modules to enable. + * + * @var array<string> + */ + protected static $modules = [ + 'system', + 'node', + 'user', + 'field', + 'filter', + 'text', + 'language', + 'entity_mesh', + 'taxonomy', + 'views', + 'path_alias', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + // Install the necessary schemas. + $this->installEntitySchema('configurable_language'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installEntitySchema('path_alias'); + $this->installEntitySchema('taxonomy_term'); + + // Install schemas. + $this->installSchema('entity_mesh', ['entity_mesh']); + $this->installSchema('node', ['node_access']); + + // Install configs but skip entity_mesh to avoid schema validation errors. + $this->installConfig(['filter', 'node', 'system', 'language', 'taxonomy', 'entity_mesh']); + + // Create content type. + $this->createContentType(['type' => 'page', 'name' => 'Page']); + + // Set up language configuration. + $config = $this->config('language.negotiation'); + $config->set('url.prefixes', ['en' => 'en']) + ->save(); + + // Enable the body field in the default view mode. + $this->container->get('entity_display.repository') + ->getViewDisplay('node', 'page', 'full') + ->setComponent('body', [ + // Show label above the body content. + 'label' => 'above', + // Render as basic text. + 'type' => 'text_default', + ]) + ->save(); + + // Create filter format. + $filter_format = FilterFormat::load('basic_html'); + if (!$filter_format) { + $filter_format = FilterFormat::create([ + 'format' => 'basic_html', + 'name' => 'Basic HTML', + 'filters' => [], + ]); + $filter_format->save(); + } + + // Create anonymous role if it doesn't exist. + if (!Role::load(AccountInterface::ANONYMOUS_ROLE)) { + Role::create(['id' => AccountInterface::ANONYMOUS_ROLE, 'label' => 'Anonymous user'])->save(); + } + + // Grant permissions to anonymous users. + $this->grantPermissions(Role::load(AccountInterface::ANONYMOUS_ROLE), [ + 'access content', + ]); + + // Create vocabulary and taxonomy terms. + $this->createTaxonomyTerms(); + + // Set up path aliases for taxonomy terms. + $this->createPathAliases(); + + // Rebuild router and container. + $this->container->get('kernel')->rebuildContainer(); + $this->container->get('router.builder')->rebuild(); + + // Create example nodes with links to taxonomy terms and views. + $this->createExampleNodes(); + } + + /** + * Creates taxonomy terms for testing. + */ + protected function createTaxonomyTerms() { + // Create vocabulary. + Vocabulary::create([ + 'vid' => 'tags', + 'name' => 'Tags', + ])->save(); + + // Create term without alias. + Term::create([ + 'tid' => 1, + 'vid' => 'tags', + 'name' => 'Term without alias', + ])->save(); + + // Create term with alias. + Term::create([ + 'tid' => 2, + 'vid' => 'tags', + 'name' => 'Term with alias', + ])->save(); + + // Create unpublished term without alias. + Term::create([ + 'tid' => 3, + 'vid' => 'tags', + 'name' => 'Unpublished term without alias', + // Set as unpublished. + 'status' => 0, + ])->save(); + } + + /** + * Creates path aliases for taxonomy terms. + */ + protected function createPathAliases() { + // Create alias for the second taxonomy term. + $path_alias = \Drupal::entityTypeManager()->getStorage('path_alias')->create([ + 'path' => '/taxonomy/term/2', + 'alias' => '/example-category', + 'langcode' => 'en', + ]); + $path_alias->save(); + } + + /** + * {@inheritdoc} + */ + protected function createExampleNodes() { + // Create a node with links to taxonomy terms and views. + $html_content = ' + <p>Taxonomy term without alias: <a href="/taxonomy/term/1">Term without alias</a></p> + <p>Taxonomy term with alias: <a href="/example-category">Term with alias</a></p> + <p>View with access denied: <a href="/admin/content">Admin content</a></p> + <p>Unpublished taxonomy term without alias: <a href="/taxonomy/term/3">Unpublished term without alias</a></p> + '; + + $node = Node::create([ + 'type' => 'page', + 'title' => 'Test Node with Links', + 'nid' => 1, + 'body' => [ + 'value' => $html_content, + 'format' => 'basic_html', + ], + ]); + $node->save(); + } + + /** + * Fetches records from the 'entity_mesh' table. + */ + protected function fetchEntityMeshRecords() { + $connection = $this->container->get('database'); + $query = $connection->select('entity_mesh', 'em') + ->fields('em', [ + 'id', + 'type', + 'category', + 'subcategory', + 'source_entity_id', + 'source_entity_type', + 'source_entity_bundle', + 'source_entity_langcode', + 'source_title', + 'target_href', + 'target_path', + 'target_scheme', + 'target_link_type', + 'target_entity_type', + 'target_entity_bundle', + 'target_entity_id', + 'target_title', + 'target_entity_langcode', + ]); + $result = $query->execute(); + return $result ? $result->fetchAllAssoc('id', \PDO::FETCH_ASSOC) : []; + } + + /** + * Tests different types of links. + * + * @dataProvider linkCasesProvider + */ + public function testLinks( + $source_entity_id, + $target_href, + $expected_target_link_type, + $excepted_target_subcategory = NULL, + $expected_target_title = NULL, + $expected_target_scheme = NULL, + $expected_target_entity_type = NULL, + $expected_target_entity_bundle = NULL, + $expected_target_entity_id = NULL, + $excepted_target_entity_langcode = NULL, + $excepted_source_entity_type = NULL, + $excepted_source_entity_bundle = NULL, + $excepted_source_entity_langcode = NULL, + $excepted_source_title = NULL, + ) { + // Fetch records from entity_mesh table for assertions. + $records = $this->fetchEntityMeshRecords(); + + // Filter records based on node ID and link type. + $filtered = array_filter($records, function ($record) use ($source_entity_id, $target_href) { + return $record['source_entity_id'] == $source_entity_id && + $record['target_href'] === $target_href; + }); + + // Extract the record by matching 'target_href' with $expected_href. + $record = reset($filtered); + + $this->assertNotEmpty($record, "No record found with target_href: $target_href"); + + $checks = [ + 'target_link_type' => $expected_target_link_type, + 'subcategory' => $excepted_target_subcategory, + 'target_title' => $expected_target_title, + 'target_scheme' => $expected_target_scheme, + 'target_entity_type' => $expected_target_entity_type, + 'target_entity_bundle' => $expected_target_entity_bundle, + 'target_entity_id' => $expected_target_entity_id, + 'target_entity_langcode' => $excepted_target_entity_langcode, + 'source_entity_type' => $excepted_source_entity_type, + 'source_entity_bundle' => $excepted_source_entity_bundle, + 'source_entity_langcode' => $excepted_source_entity_langcode, + 'source_title' => $excepted_source_title, + ]; + + foreach ($checks as $key => $expectedValue) { + if ($expectedValue !== NULL) { + $this->assertEquals($expectedValue, $record[$key]); + } + } + } + + /** + * Provides test cases for different types of links. + */ + public function linkCasesProvider() { + $defaults = [ + 'source_entity_id' => NULL, + 'target_href' => NULL, + 'expected_target_link_type' => NULL, + 'excepted_target_subcategory' => NULL, + 'expected_target_title' => NULL, + 'expected_target_scheme' => NULL, + 'expected_target_entity_type' => NULL, + 'expected_target_entity_bundle' => NULL, + 'expected_target_entity_id' => NULL, + 'excepted_target_entity_langcode' => NULL, + 'excepted_source_entity_type' => NULL, + 'excepted_source_entity_bundle' => NULL, + 'excepted_source_entity_langcode' => NULL, + 'excepted_source_title' => NULL, + ]; + + return [ + 'Taxonomy term without alias' => array_merge($defaults, [ + 'source_entity_id' => 1, + 'target_href' => '/taxonomy/term/1', + 'expected_target_link_type' => 'internal', + 'excepted_target_subcategory' => 'link', + 'expected_target_entity_type' => 'taxonomy_term', + 'expected_target_entity_id' => '1', + ]), + + 'Taxonomy term with alias' => array_merge($defaults, [ + 'source_entity_id' => 1, + 'target_href' => '/example-category', + 'expected_target_link_type' => 'internal', + 'excepted_target_subcategory' => 'link', + 'expected_target_entity_type' => 'taxonomy_term', + 'expected_target_entity_id' => '2', + ]), + + 'View with access denied' => array_merge($defaults, [ + 'source_entity_id' => 1, + 'target_href' => '/admin/content', + 'expected_target_link_type' => 'internal', + 'excepted_target_subcategory' => 'access-denied-link', + ]), + + 'Unpublished taxonomy term without alias' => array_merge($defaults, [ + 'source_entity_id' => 1, + 'target_href' => '/taxonomy/term/3', + 'expected_target_link_type' => 'internal', + 'excepted_target_subcategory' => 'access-denied-link', + 'expected_target_entity_type' => 'taxonomy_term', + 'expected_target_entity_id' => '3', + ]), + ]; + } + +} -- GitLab From 938e41373014831bc57a5126c926aa97a99dd279 Mon Sep 17 00:00:00 2001 From: Eduardo Morales <eduardo.morales@metadrop.net> Date: Fri, 14 Mar 2025 12:36:56 +0100 Subject: [PATCH 4/7] Issue #3512879 by lpeidro, eduardo morales alberti: False access-denied-link with some links of Taxonomies --- src/EntityRender.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EntityRender.php b/src/EntityRender.php index 6fc6e8a..305687a 100644 --- a/src/EntityRender.php +++ b/src/EntityRender.php @@ -491,8 +491,7 @@ class EntityRender extends Entity { } // @todo Maybe here is possible to get as well the entity object - if (str_starts_with($route_match['_route'], 'view.') - || isset($route_match['view_id']) && isset($route_match['display_id'])) { + if (isset($route_match['view_id']) && isset($route_match['display_id'])) { if ($route_match['view_id'] === 'taxonomy_term' && isset($route_match['taxonomy_term']) && $route_match['taxonomy_term'] instanceof TermInterface -- GitLab From 9a03fa7c0a521bdb2f4236667d28846f2a1ced68 Mon Sep 17 00:00:00 2001 From: lpeidro <luis.ruiz@metadrop.net> Date: Fri, 14 Mar 2025 17:36:03 +0100 Subject: [PATCH 5/7] =?UTF-8?q?Issue=20#3512879:=20Add=20administer=20perm?= =?UTF-8?q?isi=C3=B3n=20to=20the=20batch=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entity_mesh.routing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entity_mesh.routing.yml b/entity_mesh.routing.yml index f05b035..ecebd30 100644 --- a/entity_mesh.routing.yml +++ b/entity_mesh.routing.yml @@ -4,7 +4,7 @@ entity_mesh.batch_form: _title: 'Entity Mesh batch' _form: 'Drupal\entity_mesh\Form\BatchForm' requirements: - _permission: 'access entity_mesh report' + _permission: 'administer entity_mesh configuration' entity_mesh.settings_form: path: '/admin/config/system/entity-mesh/config' -- GitLab From 1141beb979bb5c9b8949b4fb94caf41f8d73dc30 Mon Sep 17 00:00:00 2001 From: lpeidro <luis.ruiz@metadrop.net> Date: Fri, 14 Mar 2025 17:52:53 +0100 Subject: [PATCH 6/7] Issue #3512879: Add suncategory field and filter in entity mesh node view --- .../optional/views.view.entity_mesh_node.yml | 183 +++++++++++++++++- entity_mesh.install | 14 ++ 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/config/optional/views.view.entity_mesh_node.yml b/config/optional/views.view.entity_mesh_node.yml index 0374ae5..d185c93 100644 --- a/config/optional/views.view.entity_mesh_node.yml +++ b/config/optional/views.view.entity_mesh_node.yml @@ -4,7 +4,6 @@ dependencies: module: - entity_mesh - user - id: entity_mesh_node label: 'Entity mesh node' module: views @@ -1505,6 +1504,55 @@ display: position: 1 display_options: fields: + subcategory: + id: subcategory + table: entity_mesh + field: subcategory + relationship: none + group_type: group + admin_label: '' + plugin_id: standard + label: '' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true type: id: type table: entity_mesh @@ -2442,6 +2490,48 @@ display: fail: 'not found' validate_options: { } filters: + subcategory: + id: subcategory + table: entity_mesh + field: subcategory + relationship: none + group_type: group + admin_label: '' + plugin_id: subcategory_filter + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: subcategory_op + label: 'Subcategory' + description: '' + use_operator: false + operator: subcategory_op + operator_limit_selection: false + operator_list: { } + identifier: subcategory + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + editor: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } source_entity_type: id: source_entity_type table: entity_mesh @@ -2776,6 +2866,55 @@ display: position: 5 display_options: fields: + subcategory: + id: subcategory + table: entity_mesh + field: subcategory + relationship: none + group_type: group + admin_label: '' + plugin_id: standard + label: 'Subcategory' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true target_link_type: id: target_link_type table: entity_mesh @@ -3301,6 +3440,48 @@ display: fail: 'not found' validate_options: { } filters: + subcategory: + id: subcategory + table: entity_mesh + field: subcategory + relationship: none + group_type: group + admin_label: '' + plugin_id: subcategory_filter + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: subcategory_op + label: 'Subcategory' + description: '' + use_operator: false + operator: subcategory_op + operator_limit_selection: false + operator_list: { } + identifier: subcategory + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + editor: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } source_entity_type: id: source_entity_type table: entity_mesh diff --git a/entity_mesh.install b/entity_mesh.install index ba84ac0..c118021 100644 --- a/entity_mesh.install +++ b/entity_mesh.install @@ -255,3 +255,17 @@ function entity_mesh_update_10006() { \Drupal::service('config.installer')->installOptionalConfig($config_source); return (string) new TranslatableMarkup("Views data export installed and configured on Entity Mesh report."); } + +/** + * Add field and filter succategory in entity mesh node views. + */ +function entity_mesh_update_10007() { + // Update view. + \Drupal::configFactory()->getEditable('views.view.entity_mesh_node')->delete(); + + $path_to_module = \Drupal::service('extension.path.resolver')->getPath('module', 'entity_mesh'); + $config_path = $path_to_module . '/config/optional'; + $config_source = new FileStorage($config_path); + \Drupal::service('config.installer')->installOptionalConfig($config_source); + return (string) new TranslatableMarkup("Views data export installed and configured on Entity Mesh report."); +} -- GitLab From bf1fa0b7748d7a799b24456b2784345826fbaa3a Mon Sep 17 00:00:00 2001 From: lpeidro <luis.ruiz@metadrop.net> Date: Fri, 14 Mar 2025 18:06:07 +0100 Subject: [PATCH 7/7] Issue #3512879: Fix spell --- entity_mesh.install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entity_mesh.install b/entity_mesh.install index c118021..96f7b17 100644 --- a/entity_mesh.install +++ b/entity_mesh.install @@ -257,7 +257,7 @@ function entity_mesh_update_10006() { } /** - * Add field and filter succategory in entity mesh node views. + * Add field and filter subcategory in entity mesh node views. */ function entity_mesh_update_10007() { // Update view. -- GitLab