
Turning off links to entities that have been Rabbit Holed
in modern Drupal
You have used Rabbit Hole module to prevent people from being able to directly visit to select content items, but when you include those items in a reference list Drupal is still linking to them.
Here is one way to fix that.
This presumes you are listing nodes, taxonomy terms, or other content entities with some that are set to not allow direct visiting, but that the others should be linked. Essentially, if you have configured a content type, vocabulary, or other bundle in Rabbit Hole to Allow these settings to be overridden for individual entities, and are using this capability, you may be in a situation where item A links to its page, item B should not because that page will give access denied or page not found, and item C perhaps should link to its page, etc.
First, if the list is shown as labels, Rabbit Hole Links module is all you need. Install it, enable it, you are done— no configuration needed, and no need for the rest of this blog post.
However, if you are listing content rendered as view modes, that module will not handle the links for you. In our case, we have a "Person" content type and are using Entity Reference Override module to show a varied project role alongside the person's name. As some of the people should be linked, and others should not have standalone pages, we needed a way to deactivate links based on the per-node Rabbit Hole settings.
This approach follows the path laid out by Daniel Flanagan of Horizontal using bundle classes to provide control over node URLs. Laying out that path for us all to follow also involved Daniel building several bridges, in the form of four patches (which all apply to the current stable versions of these modules, but work is needed to keep up with the latest development branches and get these patches in):
- Drupal core: Path module calls getInternalPath without checking if the url is routed [#3342398]
- Pathauto: Alias should not get created if system path is [#3367043]
- Pathauto: deleteEntityPathAll calls getInternalPath on potentially unrouted url [#3357928]
- Redirect: redirect_form_node_form_alter calls getInternalPath on potentially unrouted url [#3342409]
As Daniel Flanagan's blog post also helpfully provides, here are the composer patches that need to go in the patches
section (under extra
) of your project's composer.json
file:
"patches": {
"drupal/core": {
"Path module calls getInternalPath without checking if the url is routed": "https://www.drupal.org/files/issues/2023-05-03/3342398-18-D9.patch"
},
"drupal/pathauto": {
"deleteEntityPathAll calls getInternalPath on potentially unrouted url": "https://www.drupal.org/files/issues/2023-05-03/pathauto-unrouted-3357928-2.patch",
"Alias should not get created if system path is <nolink>": "https://www.drupal.org/files/issues/2023-06-15/pathauto-nolink-3367043-2.patch"
},
"drupal/redirect": {
"redirect_form_node_form_alter calls getInternalPath on potentially unrouted url": "https://www.drupal.org/files/issues/2023-02-16/redirect-unrouted-3342409-2.patch"
},
},
And with that, you are ready for the code!
First, you may need to make the link conditional on a URL existing in your template. Here is our node--person--project-details-team.html.twig
for showing the person's name (node title) linked to their page (if applicable) followed by their project role (if any, we made this a field not shown on the Person edit form but only in the Entity Reference Override widget form used when the person is referenced from a project).
{% if url %}<a href="{{ url }}">{% endif %}{{ label }}{% if url %}</a>{% endif %}{% if node.field_project_title.value %}{{- content.field_project_title -}}{% endif %}
In a custom module, called here example
module, first in you example.module
file have your bundle class take over for the specified content type:
<?php
/**
* Implements hook_entity_bundle_info_alter().
*/
function example_entity_bundle_info_alter(array &$bundles): void {
if (isset($bundles['node']['person'])) {
$bundles['node']['person']['class'] = \Drupal\example\Entity\Bundle\PersonNode::class;
}
}
In your same custom module, make file at src/Entity/Bundle/PersonNode.php
:
<?php
namespace Drupal\example\Entity\Bundle;
use Drupal\node\Entity\Node;
use Drupal\Core\Url;
/**
* A bundle class for node entities.
*/
class PersonNode extends Node {
/**
* Use contents of field_link as canonical url.
*
* {@inheritdoc}
*/
public function toUrl($rel = 'canonical', array $options = []) {
if ($rel === 'canonical' && $this->hasField('rabbit_hole__settings')) {
if ($this->get('rabbit_hole__settings')->action === 'access_denied') {
return new Url('<nolink>');
}
}
return parent::toUrl($rel, $options);
}
/**
* {@inheritdoc}
*
* This is important to avoid accidentally having pathauto delete all url aliases.
* @see https://www.drupal.org/project/pathauto/issues/3367067
*/
public function hasLinkTemplate($rel) {
if ($rel === 'canonical' && $this->hasField('rabbit_hole__settings')) {
if ($this->get('rabbit_hole__settings')->action === 'access_denied') {
return FALSE;
}
}
return parent::hasLinkTemplate($rel);
}
}
The above works for our setup (where we only set individual items to access denied, and we know the default will always be to show the page), but the way we get the settings for rabbit hole should be updated and follow the approach in Rabbit Hole Links, which would be more robust.
Overriding this in a way a contributed module could do (so not bundle classes) should be possible but the need to unset canonical links to use uri_callback
and then reproduce the canonical link logic for all bundles that you do not want to affect—all while Drupal wants to deprecate uri_callback in routes for entities—makes this a not-fun contrib experience. But possibly Rabbit Hole Links module will be up for the task!
I'd also love to hear from Typed Entity aficionados if this is possible with that alternative to bundle classes, and if so how!
Comments
Add new comment