4

I want to add a custom action to the dropdown on the admin content page (/admin/content) as shown in the screenshot below using Drupal 9. The views module is enabled so that form is part of the view.

enter image description here

I have found that actions are now plugins so I should just need to create a new plugin for my custom action.

I've used the actions found in /core/lib/Drupal/Core/Action/Plugin/Action/ as an example for my new action so just created a new plugin that extends EntityActionBase add added the relevant methods.

I have a custom module called shop_scrape. I created the file shop_scrape/src/Plugin/Action/ScrapeAction.php

That file contains:

<?php

/**
 * @file
 * Contains \Drupal\shop_scrape\Plugin\Action\ScrapeAction.
 */

namespace Drupal\shop_scrape\Plugin\Action;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Annotation\Action;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Scrape a page.
 *
 * @Action(
 *   id = "entity:scrape_action",
 *   label = @Translation("Scrape a page"),
 *   deriver = "Drupal\shop_scrape\Plugin\Action\EntityScrapeActionDeriver",
 * )
 */
class ScrapeAction extends EntityActionBase
{

  /**'
   * ScrapeAction constructor.
   * @param array $configuration
   * @param $plugin_id
   * @param $plugin_definition
   * @param EntityTypeManagerInterface $entity_type_manager
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager)
  {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
  }

  /**
   * {@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')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function executeMultiple(array $entities)
  {
    foreach ($entities as $entity) {
      \Drupal::messenger()->addMessage('Scrape ' . $entity->id());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function execute($object = NULL)
  {
    $this->executeMultiple([$object]);
  }

  /**
   * {@inheritdoc}
   */
  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE)
  {
    $access = AccessResult::allowed();
    return $return_as_object ? $access : $access->isAllowed();
  }

}

Then I also created a EntityScrapeActionDeriver that for now just returns true:


<?php

namespace Drupal\shop_scrape\Plugin\Action;

use Drupal\Core\Entity\EntityTypeInterface;

/**
 * Provides an action deriver that finds scrapable entity types.
 *
 * @see \Drupal\Core\Action\Plugin\Action\PublishAction
 * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
 */
class EntityScrapeActionDeriver extends EntityActionDeriverBase {

  /**
   * {@inheritdoc}
   */
  protected function isApplicable(EntityTypeInterface $entity_type) {
    return true;
    //return $entity_type->getLabel() == 'page';
  }

}

But my custom action is not appearing. Are there other steps I need to take or have I done something wrong?

8
  • does this help drupal.stackexchange.com/questions/295329/… Jan 5, 2021 at 18:03
  • 1
    You need to add type key as well in annotation Jan 5, 2021 at 18:05
  • I wasn't sure about the type key, the core Email action has one, but Delete and Publish don't. I just tried adding a type but didn't change anything. And yes, that question looks like it will help lots!
    – Felix Eve
    Jan 5, 2021 at 18:15
  • After adding type key did you clear cache or check by uninstalling and reinstalling the module? Jan 5, 2021 at 18:26
  • @FelixEve, are you trying to add the action only for nodes or for all entity types?
    – 4uk4
    Jan 5, 2021 at 20:28

1 Answer 1

2

Here is a sample code which creates a custom action plugin which modifies node title.

action_plugin_examples/src/Plugin/Action/ArchiveNode.php

<?php

namespace Drupal\action_plugin_examples\Plugin\Action;

use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\pathauto\PathautoState;

/**
 * Provides an Archive Node Action.
 *
 * @Action(
 *   id = "action_plugin_examples_archive_node",
 *   label = @Translation("Archive Node"),
 *   type = "node",
 *   category = @Translation("Custom")
 * )
 */
class ArchiveNode extends ActionBase implements ContainerFactoryPluginInterface {


  /**
   * The Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Logger service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * The path alias manager.
   *
   * @var \Drupal\path_alias\AliasManagerInterface
   */
  protected $aliasManager;

  /**
   * Language manager for retrieving the default Langcode.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = new static($configuration, $plugin_id, $plugin_definition);
    $instance->logger = $container->get('logger.factory')->get('action_plugin_examples');
    $instance->messenger = $container->get('messenger');
    $instance->aliasManager = $container->get('path_alias.manager');
    $instance->languageManager = $container->get('language_manager');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function access($node, AccountInterface $account = NULL, $return_as_object = FALSE) {
    /** @var \Drupal\node\NodeInterface $node */
    $access = $node->access('update', $account, TRUE)
      ->andIf($node->title->access('edit', $account, TRUE));
    return $return_as_object ? $access : $access->isAllowed();
  }

  /**
   * {@inheritdoc}
   */
  public function execute($node = NULL) {

    /** @var \Drupal\node\NodeInterface $node */

    $language = $this->languageManager->getCurrentLanguage()->getId();

    $old_alias = $this->aliasManager->getAliasByPath('/node/' . $node->id(), $language);

    $title = $node->getTitle();
    $date = $node->created->value;
    $year = date('Y', $date);
    // $old_alias = $node->path->alias;
    $new_title = $this->t('[Archive] | @title', ['@title' => $title]);
    $node->setTitle($new_title);
    $node->setSticky(FALSE);
    $node->setPromoted(FALSE);

    $new_alias = '/archive/' . $year . $old_alias;
    $node->set("path", [
      'alias' => $new_alias,
      'langcode' => $language,
      'pathauto' => PathautoState::SKIP,
    ]);

    $node->save();

    $message = $this->t('Node with NID : @id Archived.', ['@id' => $node->id()]);

    $this->logger->notice($message);
    $this->messenger->addMessage($message);

  }

}

action_plugin_examples/config/optional/system.action.action_plugin_examples_archive_node.yml

langcode: en
status: true
dependencies:
  enforced:
    module:
      - action_plugin_examples
  module:
    - node
    - pathauto
id: action_plugin_examples_archive_node
label: 'Archive Node'
type: node
plugin: action_plugin_examples_archive_node
configuration: {  }

Note : plugin yml file needs to be in the below format.

system.action.<plugin_id>.yml

2
  • I placed this code in a plugin but it didn't create an action, however the code from the question you linked to has created an action so I'm now on the right path. Thanks for your help.
    – Felix Eve
    Jan 5, 2021 at 20:28
  • you need to create action_plugin_examples/config/optional/system.action.action_plugin_examples_archive_node.yml file as well to get it detected. Added to answer. Jan 6, 2021 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.