diff --git a/core/modules/action/action.info.yml b/core/modules/action_ui/action_ui.info.yml similarity index 100% rename from core/modules/action/action.info.yml rename to core/modules/action_ui/action_ui.info.yml diff --git a/core/modules/action_ui/action_ui.local_tasks.yml b/core/modules/action_ui/action_ui.local_tasks.yml new file mode 100644 index 0000000000..0ca44138bf --- /dev/null +++ b/core/modules/action_ui/action_ui.local_tasks.yml @@ -0,0 +1,4 @@ +action.admin: + route_name: action.admin + title: 'Manage actions' + base_route: action.admin diff --git a/core/modules/action/action.module b/core/modules/action_ui/action_ui.module similarity index 100% rename from core/modules/action/action.module rename to core/modules/action_ui/action_ui.module diff --git a/core/modules/action/action.routing.yml b/core/modules/action_ui/action_ui.routing.yml similarity index 100% rename from core/modules/action/action.routing.yml rename to core/modules/action_ui/action_ui.routing.yml diff --git a/core/modules/action_ui/action_ui.views.inc b/core/modules/action_ui/action_ui.views.inc new file mode 100644 index 0000000000..5650b14164 --- /dev/null +++ b/core/modules/action_ui/action_ui.views.inc @@ -0,0 +1,27 @@ + [], + ]; + $data['action']['action_bulk_form'] = [ + 'title' => t('Bulk update'), + 'help' => t('Allows users to apply an action to one or more items.'), + 'field' => [ + 'id' => 'action_bulk_form', + ], + ]; + return $data; +} diff --git a/core/modules/action_ui/action_ui.views_execution.inc b/core/modules/action_ui/action_ui.views_execution.inc new file mode 100644 index 0000000000..1c348e74ed --- /dev/null +++ b/core/modules/action_ui/action_ui.views_execution.inc @@ -0,0 +1,22 @@ +'); + $select_all = [ + '#type' => 'checkbox', + '#default_value' => FALSE, + '#attributes' => ['class' => ['action-table-select-all']], + ]; + return [ + $select_all_placeholder => drupal_render($select_all), + ]; +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/ActionAddFormController.php b/core/modules/action_ui/lib/Drupal/action_ui/ActionAddFormController.php new file mode 100644 index 0000000000..7ffa6f095f --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/ActionAddFormController.php @@ -0,0 +1,69 @@ +actionManager = $action_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorageController('action'), + $container->get('plugin.manager.action') + ); + } + + /** + * {@inheritdoc} + * + * @param string $action_id + * The hashed version of the action ID. + */ + public function buildForm(array $form, array &$form_state, $action_id = NULL) { + // In \Drupal\action_ui\Form\ActionAdminManageForm::buildForm() the action + // are hashed. Here we have to decrypt it to find the desired action ID. + foreach ($this->actionManager->getDefinitions() as $id => $definition) { + $key = Crypt::hashBase64($id); + if ($key === $action_id) { + $this->entity->setPlugin($id); + // Derive the label and type from the action definition. + $this->entity->set('label', $definition['label']); + $this->entity->set('type', $definition['type']); + break; + } + } + + return parent::buildForm($form, $form_state); + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/ActionEditFormController.php b/core/modules/action_ui/lib/Drupal/action_ui/ActionEditFormController.php new file mode 100644 index 0000000000..acfd85ed4f --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/ActionEditFormController.php @@ -0,0 +1,10 @@ +storageController = $storage_controller; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorageController('action') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + $this->plugin = $this->entity->getPlugin(); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, array &$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 ($this->plugin instanceof PluginFormInterface) { + $form += $this->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->storageController->load($id); + return !empty($action); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, array &$form_state) { + $actions = parent::actions($form, $form_state); + unset($actions['delete']); + return $actions; + } + + /** + * {@inheritdoc} + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + + if ($this->plugin instanceof PluginFormInterface) { + $this->plugin->validateConfigurationForm($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + parent::submit($form, $form_state); + + if ($this->plugin instanceof PluginFormInterface) { + $this->plugin->submitConfigurationForm($form, $form_state); + } + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, array &$form_state) { + $this->entity->save(); + drupal_set_message($this->t('The action has been successfully saved.')); + + $form_state['redirect_route'] = [ + 'route_name' => 'action.admin', + ]; + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/ActionListController.php b/core/modules/action_ui/lib/Drupal/action_ui/ActionListController.php new file mode 100644 index 0000000000..2b8f4abc12 --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/ActionListController.php @@ -0,0 +1,122 @@ +actionManager = $action_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) { + return new static( + $entity_info, + $container->get('entity.manager')->getStorageController($entity_info->id()), + $container->get('plugin.manager.action'), + $container->get('module_handler') + ); + } + + /** + * {@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'] = $this->getLabel($entity); + 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 getOperations(EntityInterface $entity) { + $operations = $entity->isConfigurable() ? parent::getOperations($entity) : []; + if (isset($operations['edit'])) { + $operations['edit']['title'] = t('Configure'); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + public function render() { + $build['action_header']['#markup'] = '

' . t('Available actions:') . '

'; + $build['action_table'] = parent::render(); + if (!$this->hasConfigurableActions) { + unset($build['action_table']['#header']['operations']); + } + $build['action_admin_manage_form'] = drupal_get_form('Drupal\action_ui\Form\ActionAdminManageForm'); + return $build; + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionAdminManageForm.php b/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionAdminManageForm.php new file mode 100644 index 0000000000..dedc609190 --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionAdminManageForm.php @@ -0,0 +1,93 @@ +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, array &$form_state) { + $actions = []; + foreach ($this->manager->getDefinitions() as $id => $definition) { + if (is_subclass_of($definition['class'], '\Drupal\Core\Plugin\PluginFormInterface')) { + $key = Crypt::hashBase64($id); + $actions[$key] = $definition['label'] . '...'; + } + } + $form['parent'] = [ + '#type' => 'details', + '#title' => $this->t('Create an advanced action'), + '#attributes' => ['class' => ['container-inline']], + ]; + $form['parent']['action'] = [ + '#type' => 'select', + '#title' => $this->t('Action'), + '#title_display' => 'invisible', + '#options' => $actions, + '#empty_option' => $this->t('Choose an advanced action'), + ]; + $form['parent']['actions'] = [ + '#type' => 'actions', + ]; + $form['parent']['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Create'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + if ($form_state['values']['action']) { + $form_state['redirect_route'] = [ + 'route_name' => 'action.admin_add', + 'route_parameters' => ['action_id' => $form_state['values']['action']], + ]; + } + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionDeleteForm.php b/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionDeleteForm.php new file mode 100644 index 0000000000..ee4c567a04 --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/Form/ActionDeleteForm.php @@ -0,0 +1,49 @@ +t('Are you sure you want to delete the action %action?', ['%action' => $this->entity->label()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function getCancelRoute() { + return [ + 'route_name' => 'action.admin', + ]; + } + + /** + * {@inheritdoc} + */ + public function submit(array $form, array &$form_state) { + $this->entity->delete(); + + watchdog('user', 'Deleted action %aid (%action)', ['%aid' => $this->entity->id(), '%action' => $this->entity->label()]); + drupal_set_message($this->t('Action %action was deleted', ['%action' => $this->entity->label()])); + + $form_state['redirect_route'] = [ + 'route_name' => 'action.admin', + ]; + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/Tests/ActionUninstallTest.php b/core/modules/action_ui/lib/Drupal/action_ui/Tests/ActionUninstallTest.php new file mode 100644 index 0000000000..9ab9b399ef --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/Tests/ActionUninstallTest.php @@ -0,0 +1,47 @@ + 'Uninstall Action UI test', + 'description' => "Tests that uninstalling Action UI does not remove other module's actions.", + 'group' => 'Action UI', + ]; + } + + /** + * Tests Action uninstall. + */ + public function testActionUninstall() { + \Drupal::moduleHandler()->uninstall(['action_ui']); + + $this->assertTrue(entity_load('action', 'user_block_user_action', TRUE), '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(''); + + } + +} diff --git a/core/modules/action_ui/lib/Drupal/action_ui/Tests/ConfigurationTest.php b/core/modules/action_ui/lib/Drupal/action_ui/Tests/ConfigurationTest.php new file mode 100644 index 0000000000..63adbde405 --- /dev/null +++ b/core/modules/action_ui/lib/Drupal/action_ui/Tests/ConfigurationTest.php @@ -0,0 +1,87 @@ +drupalCreateUser(['administer actions']); + $this->drupalLogin($user); + + // Make a POST request to admin/config/system/actions. + $edit = []; + $edit['action'] = Crypt::hashBase64('system_goto_action'); + $this->drupalPostForm('admin/config/system/actions', $edit, t('Create')); + $this->assertResponse(200); + + // Make a POST request to the individual action configuration page. + $edit = []; + $action_label = $this->randomName(); + $edit['label'] = $action_label; + $edit['id'] = strtolower($action_label); + $edit['url'] = 'admin'; + $this->drupalPostForm('admin/config/system/actions/add/' . Crypt::hashBase64('system_goto_action'), $edit, t('Save')); + $this->assertResponse(200); + + // 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')); + preg_match('|admin/config/system/actions/configure/(.+)|', $this->getUrl(), $matches); + $aid = $matches[1]; + $edit = []; + $new_action_label = $this->randomName(); + $edit['label'] = $new_action_label; + $edit['url'] = 'admin'; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertResponse(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->assertResponse(200); + $edit = []; + $this->drupalPostForm("admin/config/system/actions/configure/$aid/delete", $edit, t('Delete')); + $this->assertResponse(200); + + // Make sure that the action was actually deleted. + $this->assertRaw(t('Action %action was deleted', ['%action' => $new_action_label]), 'Make sure that we get a delete confirmation message.'); + $this->drupalGet('admin/config/system/actions'); + $this->assertResponse(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 = entity_load('action', $aid); + $this->assertFalse($action, 'Make sure the action is gone after being deleted.'); + } + +} diff --git a/core/modules/action_ui/tests/Drupal/action_ui/Tests/Menu/ActionLocalTasksTest.php b/core/modules/action_ui/tests/Drupal/action_ui/Tests/Menu/ActionLocalTasksTest.php new file mode 100644 index 0000000000..7f5ebfd441 --- /dev/null +++ b/core/modules/action_ui/tests/Drupal/action_ui/Tests/Menu/ActionLocalTasksTest.php @@ -0,0 +1,29 @@ +directoryList = ['action_ui' => 'core/modules/action_ui']; + parent::setUp(); + } + + /** + * Tests local task existence. + */ + public function testActionLocalTasks() { + $this->assertLocalTasks('action.admin', [['action.admin']]); + } + +} diff --git a/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.info.yml b/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.info.yml new file mode 100644 index 0000000000..6159cb8806 --- /dev/null +++ b/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.info.yml @@ -0,0 +1,10 @@ +name: 'Action bulk form test' +type: module +description: 'Support module for action bulk form testing.' +package: Testing +version: VERSION +core: 8.x +hidden: true +dependencies: + - drupal:action_ui + - drupal:views diff --git a/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.module b/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.module new file mode 100644 index 0000000000..fca6766c5e --- /dev/null +++ b/core/modules/action_ui/tests/action_bulk_test/action_bulk_test.module @@ -0,0 +1,5 @@ +drupalLogin($this->web_user); + $comment_text = $this->randomName(); + $subject = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject); + + // Unpublish a comment. + $action = entity_load('action', 'comment_unpublish_action'); + $action->execute([$comment]); + $this->assertEqual($comment->status->value, CommentInterface::NOT_PUBLISHED, 'Comment was unpublished'); + + // Publish a comment. + $action = entity_load('action', 'comment_publish_action'); + $action->execute([$comment]); + $this->assertEqual($comment->status->value, CommentInterface::PUBLISHED, 'Comment was published'); + } + + /** + * Tests the unpublish comment by keyword action. + */ + public function testCommentUnpublishByKeyword() { + $this->drupalLogin($this->admin_user); + $keyword_1 = $this->randomName(); + $keyword_2 = $this->randomName(); + $action = entity_create('action', [ + 'id' => 'comment_unpublish_by_keyword_action', + 'label' => $this->randomName(), + 'type' => 'comment', + 'configuration' => [ + 'keywords' => [$keyword_1, $keyword_2], + ], + 'plugin' => 'comment_unpublish_by_keyword_action', + ]); + $action->save(); + + $comment = $this->postComment($this->node, $keyword_2, $this->randomName()); + + // Load the full comment so that status is available. + $comment = comment_load($comment->id()); + + $this->assertTrue($comment->status->value == CommentInterface::PUBLISHED, 'The comment status was set to published.'); + + $action->execute([$comment]); + $this->assertTrue($comment->status->value == CommentInterface::NOT_PUBLISHED, 'The comment status was set to not published.'); + } + +} diff --git a/core/modules/system/lib/Drupal/system/Plugin/Action/EmailAction.php b/core/modules/system/lib/Drupal/system/Plugin/Action/EmailAction.php new file mode 100644 index 0000000000..619709ddc4 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Plugin/Action/EmailAction.php @@ -0,0 +1,157 @@ +token = $token; + $this->storageController = $entity_manager->getStorageController('user'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, + $container->get('token'), + $container->get('entity.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function execute($entity = NULL) { + if (empty($this->configuration['node'])) { + $this->configuration['node'] = $entity; + } + + $recipient = $this->token->replace($this->configuration['recipient'], $this->configuration); + + // If the recipient is a registered user with a language preference, use + // the recipient's preferred language. Otherwise, use the system default + // language. + $recipient_accounts = $this->storageController->loadByProperties(['mail' => $recipient]); + $recipient_account = reset($recipient_accounts); + if ($recipient_account) { + $langcode = $recipient_account->getPreferredLangcode(); + } + else { + $langcode = language_default()->id; + } + $params = ['context' => $this->configuration]; + + if (drupal_mail('system', 'action_send_email', $recipient, $langcode, $params)) { + watchdog('action', 'Sent email to %recipient', ['%recipient' => $recipient]); + } + else { + watchdog('error', 'Unable to send email to %recipient', ['%recipient' => $recipient]); + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'recipient' => '', + 'subject' => '', + 'message' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form['recipient'] = [ + '#type' => 'textfield', + '#title' => t('Recipient'), + '#default_value' => $this->configuration['recipient'], + '#maxlength' => '254', + '#description' => t('The e-mail address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'), + ]; + $form['subject'] = [ + '#type' => 'textfield', + '#title' => t('Subject'), + '#default_value' => $this->configuration['subject'], + '#maxlength' => '254', + '#description' => t('The subject of the message.'), + ]; + $form['message'] = [ + '#type' => 'textarea', + '#title' => t('Message'), + '#default_value' => $this->configuration['message'], + '#cols' => '80', + '#rows' => '20', + '#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, array &$form_state) { + if (!valid_email_address($form_state['values']['recipient']) && strpos($form_state['values']['recipient'], ':mail') === FALSE) { + // We want the literal %author placeholder to be emphasized in the error message. + form_set_error('recipient', $form_state, t('Enter a valid email address or use a token e-mail address such as %author.', ['%author' => '[node:author:mail]'])); + } + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, array &$form_state) { + $this->configuration['recipient'] = $form_state['values']['recipient']; + $this->configuration['subject'] = $form_state['values']['subject']; + $this->configuration['message'] = $form_state['values']['message']; + } + +} diff --git a/core/modules/system/lib/Drupal/system/Plugin/Action/GotoAction.php b/core/modules/system/lib/Drupal/system/Plugin/Action/GotoAction.php new file mode 100644 index 0000000000..2fe0822519 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Plugin/Action/GotoAction.php @@ -0,0 +1,110 @@ +dispatcher = $dispatcher; + $this->urlGenerator = $url_generator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, $container->get('event_dispatcher'), $container->get('url_generator')); + } + + /** + * {@inheritdoc} + */ + public function execute($object = NULL) { + $url = $this->urlGenerator + ->generateFromPath($this->configuration['url'], ['absolute' => TRUE]); + $response = new RedirectResponse($url); + $listener = function ($event) use ($response) { + $event->setResponse($response); + }; + // Add the listener to the event dispatcher. + $this->dispatcher->addListener(KernelEvents::RESPONSE, $listener); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'url' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form['url'] = [ + '#type' => 'textfield', + '#title' => t('URL'), + '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like @url.', ['@url' => 'http://drupal.org']), + '#default_value' => $this->configuration['url'], + '#required' => TRUE, + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, array &$form_state) { + $this->configuration['url'] = $form_state['values']['url']; + } + +} diff --git a/core/modules/system/lib/Drupal/system/Plugin/Action/MessageAction.php b/core/modules/system/lib/Drupal/system/Plugin/Action/MessageAction.php new file mode 100644 index 0000000000..9779141690 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Plugin/Action/MessageAction.php @@ -0,0 +1,86 @@ +token = $token; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, $container->get('token')); + } + + /** + * {@inheritdoc} + */ + public function execute($entity = NULL) { + if (empty($this->configuration['node'])) { + $this->configuration['node'] = $entity; + } + $message = $this->token->replace(Xss::filterAdmin($this->configuration['message']), $this->configuration); + drupal_set_message($message); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'message' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, array &$form_state) { + $form['message'] = [ + '#type' => 'textarea', + '#title' => t('Message'), + '#default_value' => $this->configuration['message'], + '#required' => TRUE, + '#rows' => '8', + '#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, array &$form_state) { + $this->configuration['message'] = $form_state['values']['message']; + unset($this->configuration['node']); + } + +} diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/BulkFormTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/BulkFormTest.php new file mode 100644 index 0000000000..ac5cd3d89d --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Tests/Handler/BulkFormTest.php @@ -0,0 +1,122 @@ +drupalCreateNode([ + 'sticky' => FALSE, + 'created' => $timestamp, + 'changed' => $timestamp, + ]); + } + + $this->drupalGet('test_bulk_form'); + + // Test that the views edit header appears first. + $first_form_element = $this->xpath('//form/div/div[1][@id = :id]', [':id' => 'edit-header']); + $this->assertTrue($first_form_element, 'The views form edit header appears first.'); + + $this->assertFieldById('edit-action', NULL, 'The action select field appears.'); + + // Make sure a checkbox appears on all rows. + $edit = []; + for ($i = 0; $i < 10; $i++) { + $this->assertFieldById('edit-action-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', ['@row' => $i])); + $edit["action_bulk_form[$i]"] = TRUE; + } + + // Set all nodes to sticky and check that. + $edit += ['action' => 'node_make_sticky_action']; + $this->drupalPostForm(NULL, $edit, t('Apply')); + + foreach ($nodes as $node) { + $changed_node = node_load($node->id()); + $this->assertTrue($changed_node->isSticky(), format_string('Node @nid got marked as sticky.', ['@nid' => $node->id()])); + } + + $this->assertText('Make content sticky was applied to 10 items.'); + + // Unpublish just one node. + $node = node_load($nodes[0]->id()); + $this->assertTrue($node->isPublished(), 'The node is published.'); + + $edit = ['action_bulk_form[0]' => TRUE, 'action' => 'node_unpublish_action']; + $this->drupalPostForm(NULL, $edit, t('Apply')); + + $this->assertText('Unpublish content was applied to 1 item.'); + + // Load the node again. + $node = node_load($node->id(), TRUE); + $this->assertFalse($node->isPublished(), 'A single node has been unpublished.'); + + // The second node should still be published. + $node = node_load($nodes[1]->id(), TRUE); + $this->assertTrue($node->isPublished(), 'An unchecked node is still published.'); + + // Set up to include just the sticky actions. + $view = views_get_view('test_bulk_form'); + $display = &$view->storage->getDisplay('default'); + $display['display_options']['fields']['action_bulk_form']['include_exclude'] = 'include'; + $display['display_options']['fields']['action_bulk_form']['selected_actions']['node_make_sticky_action'] = 'node_make_sticky_action'; + $display['display_options']['fields']['action_bulk_form']['selected_actions']['node_make_unsticky_action'] = 'node_make_unsticky_action'; + $view->save(); + + $this->drupalGet('test_bulk_form'); + $options = $this->xpath('//select[@id=:id]/option', [':id' => 'edit-action']); + $this->assertEqual(count($options), 2); + $this->assertOption('edit-action', 'node_make_sticky_action'); + $this->assertOption('edit-action', 'node_make_unsticky_action'); + + // Set up to exclude the sticky actions. + $view = views_get_view('test_bulk_form'); + $display = &$view->storage->getDisplay('default'); + $display['display_options']['fields']['action_bulk_form']['include_exclude'] = 'exclude'; + $view->save(); + + $this->drupalGet('test_bulk_form'); + $this->assertNoOption('edit-action', 'node_make_sticky_action'); + $this->assertNoOption('edit-action', 'node_make_unsticky_action'); + + // Check the default title. + $this->drupalGet('test_bulk_form'); + $result = $this->xpath('//label[@for="edit-action"]'); + $this->assertEqual('With selection', (string) $result[0]); + + // Setup up a different bulk form title. + $view = views_get_view('test_bulk_form'); + $display = &$view->storage->getDisplay('default'); + $display['display_options']['fields']['action_bulk_form']['action_title'] = 'Test title'; + $view->save(); + + $this->drupalGet('test_bulk_form'); + $result = $this->xpath('//label[@for="edit-action"]'); + $this->assertEqual('Test title', (string) $result[0]); + } + +}