Smart way to add "Add" and "Remove" links to node pages

Created on 8 July 2024, 4 months ago

Problem/Motivation

You're viewing a node. You have access to an entityqueue called "Favourites".
With the existing Entityqueue module you have to click on the Entityqueue tab, then add it to your queue called "Favourites"

Proposed resolution

Why not supply people with a block showing the queue names they have access to with a "+" or "-" in front?

I seem to recall this existed in the D7 version of either Nodequeue or Entityqueue.

Claude assures me it'll be as easy as 1, 2 3 (see the Claude solution further down).

But having an official solution, handling language and made by intelligent humans, might be preferable.

In Claude voice:

You're absolutely right, and I apologize for overlooking that crucial detail. Entity Queue indeed requires a CSRF token for security reasons in recent versions. Let's modify our approach to incorporate this token. Here's an updated implementation:

Update the route and controller:

In my_queue_module.routing.yml:
yamlCopymy_queue_module.add_to_queue:
  path: '/node/{node}/add-to-queue/{queue_id}/{token}'
  defaults:
    _controller: '\Drupal\my_queue_module\Controller\QueueController::addToQueue'
    _title: 'Add to Queue'
  requirements:
    _csrf_token: 'TRUE'
    _permission: 'manipulate queues'
Update the QueueController.php:
phpCopy<?php

namespace Drupal\my_queue_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Access\CsrfTokenGenerator;

class QueueController extends ControllerBase {

  protected $entityTypeManager;
  protected $csrfToken;

  public function __construct(EntityTypeManagerInterface $entity_type_manager, CsrfTokenGenerator $csrf_token) {
    $this->entityTypeManager = $entity_type_manager;
    $this->csrfToken = $csrf_token;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('csrf_token')
    );
  }

  public function addToQueue(NodeInterface $node, $queue_id, $token) {
    if (!$this->csrfToken->validate($token, "add_to_queue:$queue_id:$node->id()")) {
      $this->messenger()->addError($this->t('Invalid token.'));
      return new RedirectResponse($node->toUrl()->toString());
    }

    $queue = $this->entityTypeManager->getStorage('entity_queue')->load($queue_id);
    
    if ($queue) {
      $queue->addItem($node);
      $queue->save();
      $this->messenger()->addMessage($this->t('Node added to queue.'));
    }
    else {
      $this->messenger()->addError($this->t('Queue not found.'));
    }

    return new RedirectResponse($node->toUrl()->toString());
  }
}

Create a custom Twig function to generate the token:

Create a new file src/TwigExtension/QueueTokenExtension.php:
phpCopy<?php

namespace Drupal\my_queue_module\TwigExtension;

use Drupal\Core\Access\CsrfTokenGenerator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class QueueTokenExtension extends AbstractExtension {

  protected $csrfToken;

  public function __construct(CsrfTokenGenerator $csrf_token) {
    $this->csrfToken = $csrf_token;
  }

  public function getFunctions() {
    return [
      new TwigFunction('generate_queue_token', [$this, 'generateQueueToken']),
    ];
  }

  public function generateQueueToken($queue_id, $node_id) {
    return $this->csrfToken->get("add_to_queue:$queue_id:$node_id");
  }
}

Register the Twig extension:

Create a new file my_queue_module.services.yml:
yamlCopyservices:
  my_queue_module.twig.queue_token_extension:
    class: Drupal\my_queue_module\TwigExtension\QueueTokenExtension
    arguments: ['@csrf_token']
    tags:
      - { name: twig.extension }

Update the Twig template:

In your node.html.twig:
twigCopy{% if content.field_your_entityqueue_reference is empty %}
  {% set queue_id = 'your_queue_machine_name' %}
  {% set token = generate_queue_token(queue_id, node.id) %}
  {{ link('Add to Queue', 'internal:/node/' ~ node.id ~ '/add-to-queue/' ~ queue_id ~ '/' ~ token, {'class':['button']}) }}
{% endif %}
These changes will:

Include a CSRF token in the URL.
Validate the token in the controller before adding the node to the queue.
Provide a custom Twig function to generate the token in the template.

Remember to:

Replace your_queue_machine_name with your actual queue machine name.
Clear the cache after making these changes.
Ensure the user still has the 'manipulate queues' permission.

This implementation should now work with the token requirement of Entity Queue. Would you like me to explain any part of this updated code in more detail?
Feature request
Status

Active

Version

1.0

Component

User interface

Created by

🇩🇰Denmark Steven Snedker

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @Steven Snedker
  • 🇩🇰Denmark Steven Snedker

    I had Claude make the great Entityqueue Buttons module. You can find it at https://vertikal.dk/entityqueue_buttons.zip.

    My hope is that @amateescu, @quicksketch or @jojonaloha will try it out and make it a submodule of Entityqueue. Or maybe publish it as a distinct project and link to it from the Entityqueue project page.

    What makes the Entityqueue Buttons module useful? You only have to click once to add or remove a node. It'll also cut down on the confusion a very junior editor may feel.

    Drawbacks? You'll get an add/remove button on your nodes, that only editors can see. Shouldn't be that ugly or confusing.

Production build 0.71.5 2024