2

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
  • 1
    You need to add type key as well in annotation – miststudent2011 Jan 5 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 at 18:15
  • After adding type key did you clear cache or check by uninstalling and reinstalling the module? – miststudent2011 Jan 5 at 18:26
  • @FelixEve, are you trying to add the action only for nodes or for all entity types? – 4k4 Jan 5 at 20:28
1

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 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. – miststudent2011 Jan 6 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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