first commit

This commit is contained in:
2020-06-08 23:57:36 +02:00
commit 6277454f7a
16057 changed files with 1715382 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
name: Actions
type: module
description: 'Allows configuration of tasks to be executed in response to events.'
package: Core
version: VERSION
core: 8.x
configure: entity.action.collection
@@ -0,0 +1,5 @@
action.admin:
title: Actions
description: 'Create tasks that the system can execute.'
route_name: entity.action.collection
parent: system.admin_config_system
@@ -0,0 +1,4 @@
action.admin:
route_name: entity.action.collection
title: 'Manage actions'
base_route: entity.action.collection
+54
View File
@@ -0,0 +1,54 @@
<?php
/**
* @file
* This is the Actions module for executing stored actions.
*/
use Drupal\Core\Url;
use Drupal\action\Form\ActionAddForm;
use Drupal\action\Form\ActionEditForm;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function action_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.action':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Actions module provides tasks that can be executed by the site such as unpublishing content, sending email messages, or blocking a user. Other modules can trigger these actions when specific system events happen; for example, when new content is posted or when a user logs in. Modules can also provide additional actions. For more information, see the <a href=":documentation">online documentation for the Actions module</a>.', [':documentation' => 'https://www.drupal.org/documentation/modules/action']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Using simple actions') . '</dt>';
$output .= '<dd>' . t('<em>Simple actions</em> do not require configuration and are listed automatically as available on the <a href=":actions">Actions page</a>.', [':actions' => Url::fromRoute('entity.action.collection')->toString()]) . '</dd>';
$output .= '<dt>' . t('Creating and configuring advanced actions') . '</dt>';
$output .= '<dd>' . t('<em>Advanced actions</em> are user-created and have to be configured individually. Create an advanced action on the <a href=":actions">Actions page</a> by selecting an action type from the drop-down list. Then configure your action, for example by specifying the recipient of an automated email message.', [':actions' => Url::fromRoute('entity.action.collection')->toString()]) . '</dd>';
$output .= '</dl>';
return $output;
case 'entity.action.collection':
$output = '<p>' . t('There are two types of actions: simple and advanced. Simple actions do not require any additional configuration and are listed here automatically. Advanced actions need to be created and configured before they can be used because they have options that need to be specified; for example, sending an email to a specified address or unpublishing content containing certain words. To create an advanced action, select the action from the drop-down list in the advanced action section below and click the <em>Create</em> button.') . '</p>';
return $output;
case 'action.admin_add':
case 'entity.action.edit_form':
return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Label</em> field is recommended in order to better identify the precise action taking place.');
}
}
/**
* Implements hook_entity_type_build().
*/
function action_entity_type_build(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['action']
->setFormClass('add', ActionAddForm::class)
->setFormClass('edit', ActionEditForm::class)
->setFormClass('delete', 'Drupal\action\Form\ActionDeleteForm')
->setListBuilderClass('Drupal\action\ActionListBuilder')
->setLinkTemplate('delete-form', '/admin/config/system/actions/configure/{action}/delete')
->setLinkTemplate('edit-form', '/admin/config/system/actions/configure/{action}')
->setLinkTemplate('collection', '/admin/config/system/actions');
}
@@ -0,0 +1,3 @@
administer actions:
title: 'Administer actions'
restrict access: true
@@ -0,0 +1,31 @@
<?php
/**
* @file
* Post update functions for Action module.
*/
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\system\ActionConfigEntityInterface;
/**
* Moves action plugins to core.
*/
function action_post_update_move_plugins(&$sandbox = NULL) {
$resave_ids = [
'action_goto_action',
'action_message_action',
'action_send_email_action',
];
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'action', function (ActionConfigEntityInterface $action) use ($resave_ids) {
// Save entity to recalculate dependencies.
return $action->isConfigurable() && in_array($action->getPlugin()->getPluginId(), $resave_ids, TRUE);
});
}
/**
* Removes action settings.
*/
function action_post_update_remove_settings() {
\Drupal::configFactory()->getEditable('action.settings')->delete();
}
@@ -0,0 +1,31 @@
entity.action.collection:
path: '/admin/config/system/actions'
defaults:
_title: 'Actions'
_entity_list: 'action'
requirements:
_permission: 'administer actions'
action.admin_add:
path: '/admin/config/system/actions/add/{action_id}'
defaults:
_entity_form: 'action.add'
_title: 'Add action'
requirements:
_permission: 'administer actions'
entity.action.edit_form:
path: '/admin/config/system/actions/configure/{action}'
defaults:
_entity_form: 'action.edit'
_title: 'Edit action'
requirements:
_permission: 'administer actions'
entity.action.delete_form:
path: '/admin/config/system/actions/configure/{action}/delete'
defaults:
_entity_form: 'action.delete'
_title: 'Delete action'
requirements:
_permission: 'administer actions'
@@ -0,0 +1,20 @@
id: action_settings
label: Action configuration
migration_tags:
- Drupal 6
- Drupal 7
- Configuration
source:
plugin: variable
variables:
- actions_max_stack
source_module: system
process:
recursion_limit:
plugin: skip_on_empty
method: row
source: empty
destination:
plugin: config
config_name: null
destination_module: action
@@ -0,0 +1,46 @@
id: d6_action
label: Actions
migration_tags:
- Drupal 6
- Configuration
source:
plugin: action
process:
id:
-
plugin: machine_name
source: aid
label: description
type: type
plugin:
-
plugin: static_map
source: callback
map:
system_goto_action: action_goto_action
system_send_email_action: action_send_email_action
system_message_action: action_message_action
user_block_ip_action: 0
imagecache_flush_action: 0
imagecache_generate_all_action: 0
imagecache_generate_action: 0
comment_publish_action: entity:publish_action:comment
comment_unpublish_action: entity:unpublish_action:comment
comment_save_action: entity:save_action:comment
node_publish_action: entity:publish_action:node
node_unpublish_action: entity:unpublish_action:node
node_save_action: entity:save_action:node
bypass: true
-
plugin: skip_on_empty
method: row
configuration:
-
plugin: default_value
source: parameters
default_value: "a:0:{}"
-
plugin: callback
callable: unserialize
destination:
plugin: entity:action
@@ -0,0 +1,43 @@
id: d7_action
label: Actions
migration_tags:
- Drupal 7
- Configuration
source:
plugin: action
process:
id:
-
plugin: machine_name
source: aid
label: label
type: type
plugin:
-
plugin: static_map
source: callback
map:
system_goto_action: action_goto_action
system_send_email_action: action_send_email_action
system_message_action: action_message_action
system_block_ip_action: 0
comment_publish_action: entity:publish_action:comment
comment_unpublish_action: entity:unpublish_action:comment
comment_save_action: entity:save_action:comment
node_publish_action: entity:publish_action:node
node_unpublish_action: entity:unpublish_action:node
node_save_action: entity:save_action:node
bypass: true
-
plugin: skip_on_empty
method: row
configuration:
-
plugin: default_value
source: parameters
default_value: "a:0:{}"
-
plugin: callback
callable: unserialize
destination:
plugin: entity:action
@@ -0,0 +1,5 @@
finished:
6:
system: action
7:
system: action
@@ -0,0 +1,21 @@
<?php
namespace Drupal\action;
use Drupal\action\Form\ActionAddForm as ActionAddFormCurrent;
@trigger_error('The ' . __NAMESPACE__ . '\ActionAddForm is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use ' . __NAMESPACE__ . '\Form\ActionAddForm. See https://www.drupal.org/node/3033540', E_USER_DEPRECATED);
/**
* Provides a form for action add forms.
*
* @internal
*
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
* \Drupal\action\Form\ActionEditForm instead.
*
* @see https://www.drupal.org/node/3033540
*/
class ActionAddForm extends ActionAddFormCurrent {
}
@@ -0,0 +1,21 @@
<?php
namespace Drupal\action;
use Drupal\action\Form\ActionEditForm as ActionEditFormCurrent;
@trigger_error('The ' . __NAMESPACE__ . '\ActionEditForm is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use ' . __NAMESPACE__ . '\Form\ActionEditForm. See https://www.drupal.org/node/3033540', E_USER_DEPRECATED);
/**
* Provides a form for action edit forms.
*
* @internal
*
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
* \Drupal\action\Form\ActionEditForm instead.
*
* @see https://www.drupal.org/node/3033540
*/
class ActionEditForm extends ActionEditFormCurrent {
}
@@ -0,0 +1,19 @@
<?php
namespace Drupal\action;
use Drupal\action\Form\ActionFormBase as ActionFormBaseCurrent;
@trigger_error('The ' . __NAMESPACE__ . '\ActionFormBase is deprecated in Drupal 8.8.0 and will be removed before Drupal 9.0.0. Use ' . __NAMESPACE__ . '\Form\ActionFormBase. See https://www.drupal.org/node/3033540', E_USER_DEPRECATED);
/**
* Provides a base form for action forms.
*
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
* \Drupal\action\Form\ActionFormBase instead.
*
* @see https://www.drupal.org/node/3033540
*/
abstract class ActionFormBase extends ActionFormBaseCurrent {
}
@@ -0,0 +1,120 @@
<?php
namespace Drupal\action;
use Drupal\Core\Action\ActionManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class to build a listing of action entities.
*
* @see \Drupal\system\Entity\Action
* @see action_entity_type_build()
*/
class ActionListBuilder extends ConfigEntityListBuilder {
/**
* @var bool
*/
protected $hasConfigurableActions = FALSE;
/**
* The action plugin manager.
*
* @var \Drupal\Core\Action\ActionManager
*/
protected $actionManager;
/**
* Constructs a new ActionListBuilder object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The action storage.
* @param \Drupal\Core\Action\ActionManager $action_manager
* The action plugin manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ActionManager $action_manager) {
parent::__construct($entity_type, $storage);
$this->actionManager = $action_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager')->getStorage($entity_type->id()),
$container->get('plugin.manager.action')
);
}
/**
* {@inheritdoc}
*/
public function load() {
$entities = parent::load();
foreach ($entities as $entity) {
if ($entity->isConfigurable()) {
$this->hasConfigurableActions = TRUE;
continue;
}
}
return $entities;
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['type'] = $entity->getType();
$row['label'] = $entity->label();
if ($this->hasConfigurableActions) {
$row += parent::buildRow($entity);
}
return $row;
}
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header = [
'type' => t('Action type'),
'label' => t('Label'),
] + parent::buildHeader();
return $header;
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : [];
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Configure');
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function render() {
$build['action_admin_manage_form'] = \Drupal::formBuilder()->getForm('Drupal\action\Form\ActionAdminManageForm');
$build['action_header']['#markup'] = '<h3>' . $this->t('Available actions:') . '</h3>';
$build['action_table'] = parent::render();
if (!$this->hasConfigurableActions) {
unset($build['action_table']['table']['#header']['operations']);
}
return $build;
}
}
@@ -0,0 +1,31 @@
<?php
namespace Drupal\action\Form;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a form for action add forms.
*
* @internal
*/
class ActionAddForm extends ActionFormBase {
/**
* {@inheritdoc}
*
* @param string $action_id
* The action ID.
*/
public function buildForm(array $form, FormStateInterface $form_state, $action_id = NULL) {
$this->entity->setPlugin($action_id);
// Derive the label and type from the action definition.
$definition = $this->entity->getPluginDefinition();
$this->entity->set('label', $definition['label']);
$this->entity->set('type', $definition['type']);
return parent::buildForm($form, $form_state);
}
}
@@ -0,0 +1,96 @@
<?php
namespace Drupal\action\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Action\ActionManager;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a configuration form for configurable actions.
*
* @internal
*/
class ActionAdminManageForm extends FormBase {
/**
* The action plugin manager.
*
* @var \Drupal\Core\Action\ActionManager
*/
protected $manager;
/**
* Constructs a new ActionAdminManageForm.
*
* @param \Drupal\Core\Action\ActionManager $manager
* The action plugin manager.
*/
public function __construct(ActionManager $manager) {
$this->manager = $manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.action')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'action_admin_manage';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$actions = [];
foreach ($this->manager->getDefinitions() as $id => $definition) {
if (is_subclass_of($definition['class'], '\Drupal\Core\Plugin\PluginFormInterface')) {
$actions[$id] = $definition['label'];
}
}
asort($actions);
$form['parent'] = [
'#type' => 'details',
'#title' => $this->t('Create an advanced action'),
'#attributes' => ['class' => ['container-inline']],
'#open' => TRUE,
];
$form['parent']['action'] = [
'#type' => 'select',
'#title' => $this->t('Action'),
'#title_display' => 'invisible',
'#options' => $actions,
'#empty_option' => $this->t('- Select -'),
];
$form['parent']['actions'] = [
'#type' => 'actions',
];
$form['parent']['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Create'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('action')) {
$form_state->setRedirect(
'action.admin_add',
['action_id' => $form_state->getValue('action')]
);
}
}
}
@@ -0,0 +1,22 @@
<?php
namespace Drupal\action\Form;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Url;
/**
* Builds a form to delete an action.
*
* @internal
*/
class ActionDeleteForm extends EntityDeleteForm {
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.action.collection');
}
}
@@ -0,0 +1,12 @@
<?php
namespace Drupal\action\Form;
/**
* Provides a form for action edit forms.
*
* @internal
*/
class ActionEditForm extends ActionFormBase {
}
@@ -0,0 +1,153 @@
<?php
namespace Drupal\action\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base form for action forms.
*/
abstract class ActionFormBase extends EntityForm {
/**
* The action storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The action entity.
*
* @var \Drupal\system\ActionConfigEntityInterface
*/
protected $entity;
/**
* Constructs a new action form.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The action storage.
*/
public function __construct(EntityStorageInterface $storage) {
$this->storage = $storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')->getStorage('action')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#default_value' => $this->entity->label(),
'#maxlength' => '255',
'#description' => $this->t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions.'),
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $this->entity->id(),
'#disabled' => !$this->entity->isNew(),
'#maxlength' => 64,
'#description' => $this->t('A unique name for this action. It must only contain lowercase letters, numbers and underscores.'),
'#machine_name' => [
'exists' => [$this, 'exists'],
],
];
$form['plugin'] = [
'#type' => 'value',
'#value' => $this->entity->get('plugin'),
];
$form['type'] = [
'#type' => 'value',
'#value' => $this->entity->getType(),
];
if ($plugin = $this->getPlugin()) {
$form += $plugin->buildConfigurationForm($form, $form_state);
}
return parent::form($form, $form_state);
}
/**
* Determines if the action already exists.
*
* @param string $id
* The action ID.
*
* @return bool
* TRUE if the action exists, FALSE otherwise.
*/
public function exists($id) {
$action = $this->storage->load($id);
return !empty($action);
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
unset($actions['delete']);
return $actions;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
if ($plugin = $this->getPlugin()) {
$plugin->validateConfigurationForm($form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
if ($plugin = $this->getPlugin()) {
$plugin->submitConfigurationForm($form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$this->entity->save();
$this->messenger()->addStatus($this->t('The action has been successfully saved.'));
$form_state->setRedirect('entity.action.collection');
}
/**
* Gets the action plugin while ensuring it implements configuration form.
*
* @return \Drupal\Core\Action\ActionInterface|\Drupal\Core\Plugin\PluginFormInterface|null
* The action plugin, or NULL if it does not implement configuration forms.
*/
protected function getPlugin() {
if ($this->entity->getPlugin() instanceof PluginFormInterface) {
return $this->entity->getPlugin();
}
return NULL;
}
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\action\Plugin\migrate\source;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
use Drupal\migrate\Row;
/**
* Drupal action source from database.
*
* @MigrateSource(
* id = "action",
* source_module = "system"
* )
*/
class Action extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('actions', 'a')
->fields('a');
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'aid' => $this->t('Action ID'),
'type' => $this->t('Module'),
'callback' => $this->t('Callback function'),
'parameters' => $this->t('Action configuration'),
];
if ($this->getModuleSchemaVersion('system') >= 7000) {
$fields['label'] = $this->t('Label of the action');
}
else {
$fields['description'] = $this->t('Action description');
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['aid']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$aid = $row->getSourceProperty('aid');
if (is_numeric($aid)) {
if ($this->getModuleSchemaVersion('system') >= 7000) {
$label = $row->getSourceProperty('label');
}
else {
$label = $row->getSourceProperty('description');
}
$row->setSourceProperty('aid', $label);
}
}
}
@@ -0,0 +1,7 @@
name: action_form_ajax_test
type: module
description: 'module used for testing ajax in action config entity forms.'
package: Core
version: VERSION
core: 8.x
hidden: true
@@ -0,0 +1,7 @@
action.configuration.action_form_ajax_test:
type: action_configuration_default
label: 'action_form_ajax_test action'
mapping:
party_time:
type: string
label: 'The time of the party.'
@@ -0,0 +1,89 @@
<?php
namespace Drupal\action_form_ajax_test\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Plugin used for testing AJAX in action config entity forms.
*
* @Action(
* id = "action_form_ajax_test",
* label = @Translation("action_form_ajax_test"),
* type = "system"
* )
*/
class ActionAjaxTest extends ConfigurableActionBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'party_time' => '',
];
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowed();
return $return_as_object ? $result : $result->isAllowed();
}
/**
* {@inheritdoc}
*/
public function execute() {
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$having_a_party = $form_state->getValue('having_a_party', !empty($this->configuration['party_time']));
$form['having_a_party'] = [
'#type' => 'checkbox',
'#title' => $this->t('Are we having a party?'),
'#ajax' => [
'wrapper' => 'party-container',
'callback' => [$this, 'partyCallback'],
],
'#default_value' => $having_a_party,
];
$form['container'] = [
'#type' => 'container',
'#prefix' => '<div id="party-container">',
'#suffix' => '</div>',
];
if ($having_a_party) {
$form['container']['party_time'] = [
'#type' => 'textfield',
'#title' => $this->t('Party time'),
'#default_value' => $this->configuration['party_time'],
];
}
return $form;
}
/**
* Callback for party checkbox.
*/
public function partyCallback(array $form, FormStateInterface $form_state) {
return $form['container'];
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['party_time'] = $form_state->getValue('party_time');
}
}
@@ -0,0 +1 @@
recursion_limit: 35
@@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains database additions to for testing upgrade path for action settings.
*
* @see https://www.drupal.org/node/3022401
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
$action_settings = Yaml::decode(file_get_contents(__DIR__ . '/action.settings_3022401.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'action.settings',
'data' => serialize($action_settings),
])
->execute();
// Enable action module.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$connection->update('config')
->fields([
'data' => serialize(array_merge_recursive($extensions, ['module' => ['action' => 0]])),
])
->condition('name', 'core.extension')
->execute();
@@ -0,0 +1,42 @@
<?php
namespace Drupal\Tests\action\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Test behaviors when visiting the action listing page.
*
* @group action
*/
class ActionListTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['action'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the behavior when there are no actions to list in the admin page.
*/
public function testEmptyActionList() {
// Create a user with permission to view the actions administration pages.
$this->drupalLogin($this->drupalCreateUser(['administer actions']));
// Ensure the empty text appears on the action list page.
/** @var $storage \Drupal\Core\Entity\EntityStorageInterface */
$storage = $this->container->get('entity_type.manager')->getStorage('action');
$actions = $storage->loadMultiple();
$storage->delete($actions);
$this->drupalGet('/admin/config/system/actions');
$this->assertRaw('There are no actions yet.');
}
}
@@ -0,0 +1,47 @@
<?php
namespace Drupal\Tests\action\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests that uninstalling actions does not remove other module's actions.
*
* @group action
* @see \Drupal\views\Plugin\views\field\BulkForm
* @see \Drupal\user\Plugin\Action\BlockUser
*/
class ActionUninstallTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['views', 'action'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests Action uninstall.
*/
public function testActionUninstall() {
\Drupal::service('module_installer')->uninstall(['action']);
$storage = $this->container->get('entity_type.manager')->getStorage('action');
$storage->resetCache(['user_block_user_action']);
$this->assertNotEmpty($storage->load('user_block_user_action'), 'Configuration entity \'user_block_user_action\' still exists after uninstalling action module.');
$admin_user = $this->drupalCreateUser(['administer users']);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/people');
// Ensure we have the user_block_user_action listed.
$this->assertRaw('<option value="user_block_user_action">Block the selected user(s)</option>');
}
}
@@ -0,0 +1,95 @@
<?php
namespace Drupal\Tests\action\Functional;
use Drupal\system\Entity\Action;
use Drupal\Tests\BrowserTestBase;
/**
* Tests complex actions configuration by adding, editing, and deleting a
* complex action.
*
* @group action
*/
class ConfigurationTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['action'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests configuration of advanced actions through administration interface.
*/
public function testActionConfiguration() {
// Create a user with permission to view the actions administration pages.
$user = $this->drupalCreateUser(['administer actions']);
$this->drupalLogin($user);
// Make a POST request to admin/config/system/actions.
$edit = [];
$edit['action'] = 'action_goto_action';
$this->drupalPostForm('admin/config/system/actions', $edit, t('Create'));
$this->assertSession()->statusCodeEquals(200);
// Make a POST request to the individual action configuration page.
$edit = [];
$action_label = $this->randomMachineName();
$edit['label'] = $action_label;
$edit['id'] = strtolower($action_label);
$edit['url'] = 'admin';
$this->drupalPostForm('admin/config/system/actions/add/action_goto_action', $edit, t('Save'));
$this->assertSession()->statusCodeEquals(200);
$action_id = $edit['id'];
// Make sure that the new complex action was saved properly.
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully saved the complex action.");
$this->assertText($action_label, "Make sure the action label appears on the configuration page after we've saved the complex action.");
// Make another POST request to the action edit page.
$this->clickLink(t('Configure'));
$edit = [];
$new_action_label = $this->randomMachineName();
$edit['label'] = $new_action_label;
$edit['url'] = 'admin';
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertSession()->statusCodeEquals(200);
// Make sure that the action updated properly.
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully updated the complex action.");
$this->assertNoText($action_label, "Make sure the old action label does NOT appear on the configuration page after we've updated the complex action.");
$this->assertText($new_action_label, "Make sure the action label appears on the configuration page after we've updated the complex action.");
$this->clickLink(t('Configure'));
$element = $this->xpath('//input[@type="text" and @value="admin"]');
$this->assertTrue(!empty($element), 'Make sure the URL appears when re-editing the action.');
// Make sure that deletions work properly.
$this->drupalGet('admin/config/system/actions');
$this->clickLink(t('Delete'));
$this->assertSession()->statusCodeEquals(200);
$edit = [];
$this->drupalPostForm(NULL, $edit, t('Delete'));
$this->assertSession()->statusCodeEquals(200);
// Make sure that the action was actually deleted.
$this->assertRaw(t('The action %action has been deleted.', ['%action' => $new_action_label]), 'Make sure that we get a delete confirmation message.');
$this->drupalGet('admin/config/system/actions');
$this->assertSession()->statusCodeEquals(200);
$this->assertNoText($new_action_label, "Make sure the action label does not appear on the overview page after we've deleted the action.");
$action = Action::load($action_id);
$this->assertNull($action, 'Make sure the action is gone after being deleted.');
}
}
@@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\action\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests removing action module's configuration.
*
* @group Update
* @group legacy
*/
class ActionConfigTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.8.0.bare.standard.php.gz',
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.action-3022401.php',
];
}
/**
* Tests upgrading action settings.
*
* @see \action_post_update_remove_settings()
*/
public function testUpdateActionPlugins() {
$config = $this->config('action.settings');
$this->assertSame(35, $config->get('recursion_limit'));
// Run updates.
$this->runUpdates();
$config = $this->config('action.settings');
$this->assertTrue($config->isNew());
}
}
@@ -0,0 +1,73 @@
<?php
namespace Drupal\Tests\action\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\system\Entity\Action;
/**
* Tests action plugins using Javascript.
*
* @group action
*/
class ActionFormAjaxTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['action', 'action_form_ajax_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$user = $this->drupalCreateUser(['administer actions']);
$this->drupalLogin($user);
}
/**
* Tests action plugins with AJAX save their configuration.
*/
public function testActionConfigurationWithAjax() {
$url = Url::fromRoute('action.admin_add', ['action_id' => 'action_form_ajax_test']);
$this->drupalGet($url);
$page = $this->getSession()->getPage();
$id = 'test_plugin';
$this->assertSession()->waitForElementVisible('named', ['button', 'Edit'])->press();
$this->assertSession()->waitForElementVisible('css', '[name="id"]')->setValue($id);
$page->find('css', '[name="having_a_party"]')
->check();
$this->assertSession()->waitForElementVisible('css', '[name="party_time"]');
$party_time = 'Evening';
$page->find('css', '[name="party_time"]')
->setValue($party_time);
$page->find('css', '[value="Save"]')
->click();
$url = Url::fromRoute('entity.action.collection');
$this->assertSession()->pageTextContains('The action has been successfully saved.');
$this->assertSession()->addressEquals($url);
// Check storage.
$instance = Action::load($id);
$configuration = $instance->getPlugin()->getConfiguration();
$this->assertEquals(['party_time' => $party_time], $configuration);
// Configuration should be shown in edit form.
$this->drupalGet($instance->toUrl('edit-form'));
$this->assertSession()->checkboxChecked('having_a_party');
$this->assertSession()->fieldValueEquals('party_time', $party_time);
}
}
@@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\action\Kernel\Migrate\d6;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade variables to null.
*
* @group migrate_drupal_6
*/
class MigrateActionConfigsTest extends MigrateDrupal6TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['action'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('action_settings');
}
/**
* Tests migration of action variables to null.
*/
public function testActionSettings() {
$config = $this->config('action.settings');
$this->assertTrue($config->isNew());
}
}
@@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\action\Kernel\Migrate\d6;
use Drupal\system\Entity\Action;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Tests migration of action items.
*
* @group migrate_drupal_6
*/
class MigrateActionsTest extends MigrateDrupal6TestBase {
public static $modules = ['action', 'comment', 'node'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('d6_action');
}
/**
* Test Drupal 6 action migration to Drupal 8.
*/
public function testActions() {
// Test default actions.
$this->assertEntity('node_publish_action', 'Publish post', 'node', []);
$this->assertEntity('node_make_sticky_action', 'Make post sticky', 'node', []);
$this->assertEntity('user_block_user_action', 'Block current user', 'user', []);
$this->assertEntity('comment_publish_action', 'Publish comment', 'comment', []);
// Test advanced actions.
$this->assertEntity('unpublish_comment_containing_keyword_s_', 'Unpublish comment containing keyword(s)', 'comment', ["keywords" => [0 => "drupal"]]);
$this->assertEntity('change_the_author_of_a_post', 'Change the author of a post', 'node', ["owner_uid" => "2"]);
$this->assertEntity('unpublish_post_containing_keyword_s_', 'Unpublish post containing keyword(s)', 'node', ["keywords" => [0 => "drupal"]]);
$this->assertEntity('display_a_message_to_the_user', 'Display a message to the user', 'system', ["message" => "Drupal migration test"]);
$this->assertEntity('send_e_mail', 'Send e-mail', 'system', [
"recipient" => "test@example.com",
"subject" => "Drupal migration test",
"message" => "Drupal migration test",
]);
$this->assertEntity('redirect_to_url', 'Redirect to URL', 'system', ["url" => "https://www.drupal.org"]);
}
/**
* Asserts various aspects of an Action entity.
*
* @param string $id
* The expected Action ID.
* @param string $label
* The expected Action label.
* @param string $type
* The expected Action type.
* @param array $configuration
* The expected Action configuration.
*/
protected function assertEntity($id, $label, $type, $configuration) {
$action = Action::load($id);
$this->assertInstanceOf(Action::class, $action);
/** @var \Drupal\system\Entity\Action $action */
$this->assertIdentical($id, $action->id());
$this->assertIdentical($label, $action->label());
$this->assertIdentical($type, $action->getType());
$this->assertIdentical($configuration, $action->get('configuration'));
}
}
@@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\action\Kernel\Migrate\d7;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Upgrade variables to null.
*
* @group migrate_drupal_7
*/
class MigrateActionConfigsTest extends MigrateDrupal7TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['action'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('action_settings');
}
/**
* Tests migration of action variables to null.
*/
public function testActionSettings() {
$config = $this->config('action.settings');
$this->assertTrue($config->isNew());
}
}
@@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\action\Kernel\Migrate\d7;
use Drupal\system\Entity\Action;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of action items.
*
* @group action
*/
class MigrateActionsTest extends MigrateDrupal7TestBase {
public static $modules = ['action', 'comment', 'node'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('d7_action');
}
/**
* Test Drupal 7 action migration to Drupal 8.
*/
public function testActions() {
// Test default actions.
$this->assertEntity('node_publish_action', 'Publish content', 'node', []);
$this->assertEntity('node_make_sticky_action', 'Make content sticky', 'node', []);
$this->assertEntity('user_block_user_action', 'Block current user', 'user', []);
$this->assertEntity('comment_publish_action', 'Publish comment', 'comment', []);
// Test advanced actions.
$this->assertEntity('unpublish_comment_containing_keyword_s_', 'Unpublish comment containing keyword(s)', 'comment', ["keywords" => [0 => "drupal"]]);
$this->assertEntity('change_the_author_of_content', 'Change the author of content', 'node', ["owner_uid" => "2"]);
$this->assertEntity('unpublish_content_containing_keyword_s_', 'Unpublish content containing keyword(s)', 'node', ["keywords" => [0 => "drupal"]]);
$this->assertEntity('display_a_message_to_the_user', 'Display a message to the user', 'system', ["message" => "Drupal migration test"]);
$this->assertEntity('send_e_mail', 'Send e-mail', 'system', [
"recipient" => "test@example.com",
"subject" => "Drupal migration test",
"message" => "Drupal migration test",
]);
$this->assertEntity('redirect_to_url', 'Redirect to URL', 'system', ["url" => "https://www.drupal.org"]);
}
/**
* Asserts various aspects of an Action entity.
*
* @param string $id
* The expected Action ID.
* @param string $label
* The expected Action label.
* @param string $type
* The expected Action type.
* @param array $configuration
* The expected Action configuration.
*/
protected function assertEntity($id, $label, $type, $configuration) {
$action = Action::load($id);
$this->assertInstanceOf(Action::class, $action);
/** @var \Drupal\system\Entity\Action $action */
$this->assertIdentical($id, $action->id());
$this->assertIdentical($label, $action->label());
$this->assertIdentical($type, $action->getType());
$this->assertIdentical($configuration, $action->get('configuration'));
}
}
@@ -0,0 +1,62 @@
<?php
namespace Drupal\Tests\action\Kernel\Plugin\migrate\source;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests actions source plugin.
*
* @covers \Drupal\action\Plugin\migrate\source\Action
* @group action
*/
class ActionTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['action', 'migrate_drupal', 'system'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
$tests[0][0]['actions'] = [
[
'aid' => 'Redirect to node list page',
'type' => 'system',
'callback' => 'system_goto_action',
'parameters' => 'a:1:{s:3:"url";s:4:"node";}',
'description' => 'Redirect to node list page',
],
[
'aid' => 'Test notice email',
'type' => 'system',
'callback' => 'system_send_email_action',
'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test',
'description' => 'Test notice email',
],
[
'aid' => 'comment_publish_action',
'type' => 'comment',
'callback' => 'comment_publish_action',
'parameters' => NULL,
'description' => NULL,
],
[
'aid' => 'node_publish_action',
'type' => 'comment',
'callback' => 'node_publish_action',
'parameters' => NULL,
'description' => NULL,
],
];
// The expected results are identical to the source data.
$tests[0][1] = $tests[0][0]['actions'];
return $tests;
}
}
@@ -0,0 +1,26 @@
<?php
namespace Drupal\Tests\action\Unit\Menu;
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
/**
* Tests action local tasks.
*
* @group action
*/
class ActionLocalTasksTest extends LocalTaskIntegrationTestBase {
protected function setUp() {
$this->directoryList = ['action' => 'core/modules/action'];
parent::setUp();
}
/**
* Tests local task existence.
*/
public function testActionLocalTasks() {
$this->assertLocalTasks('entity.action.collection', [['action.admin']]);
}
}