diff --git a/action_example/CHANGELOG.txt b/action_example/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..02b5f165e4509ceab3a7a80b282f664f3fb4f7fa --- /dev/null +++ b/action_example/CHANGELOG.txt @@ -0,0 +1,2 @@ +# $Id$ + diff --git a/action_example/action_example.info b/action_example/action_example.info new file mode 100644 index 0000000000000000000000000000000000000000..d5f373ed2270f242088b43a8607198b116837427 --- /dev/null +++ b/action_example/action_example.info @@ -0,0 +1,8 @@ +; $Id$ +name = Action example +description = Demonstrates providing actions that can be associated to triggers. +package = Example modules +core = 7.x +dependencies[] = trigger +files[] = action_example.module +files[] = action_example.test diff --git a/action_example/action_example.module b/action_example/action_example.module new file mode 100644 index 0000000000000000000000000000000000000000..2cacb2caf285ef882a6432a0a9dfbea90ca95c75 --- /dev/null +++ b/action_example/action_example.module @@ -0,0 +1,361 @@ + array( + 'label' => t('Action Example: A basic example action that does nothing'), + 'type' => 'system', + 'configurable' => FALSE, + 'triggers' => array('any'), + ), + 'action_example_unblock_user_action' => array( + 'label' => t('Action Example: Unblock a user'), + 'type' => 'user', + 'configurable' => FALSE, + 'triggers' => array('any'), + ), + 'action_example_node_sticky_action' => array( + 'type' => 'node', + 'label' => t('Action Example: Promote to frontpage and sticky on top any content created by :'), + 'configurable' => TRUE, + 'behavior' => array('changes_property'), + 'triggers' => array('node_presave', 'node_insert', 'node_update'), + ), + ); +} + + + +/** + * Implements hook_menu(). + * + * Simply provide a menu entry which explains what the module does. + */ +function action_example_menu() { + $items['examples/action_example'] = array( + 'title' => 'Action Example', + 'description' => 'Provides a basic information page.', + 'page callback' => '_action_example_page', + 'access callback' => TRUE, + ); + return $items; +} + + +/** + * A simple page to explain to the developer what to do. + */ +function _action_example_page() { + return t("The Action Example provides three example actions which can be configured on the Actions configuration page and assigned to triggers on the Triggers configuration page.", array('@actions_url' => url('admin/config/system/actions'), '@triggers_url' => url('admin/structure/trigger/node'))); +} + +/* + * Most basic action. + * + * This action is not expecting any type of entity object, and can be used with + * any trigger type or any event. + */ + +/** + * Basic example action. + * + * @param $entity + * An optional entity object. + * @param array $context + * Array with parameters for this action: depends on the trigger. + * + * @ingroup actions + */ +function action_example_basic_action(&$entity, $context = array()) { + // + // In this case we are ignoring the entity and the context. This case of + // action is useful when your action does not depend on the context, and + // the function must do something regardless the scope of the trigger. + // Simply announces that the action was executed using a messages. + + drupal_set_message(t('action_example_basic_action fired')); + watchdog('action_example', 'action_example_basic_action fired.'); +} + +// --------------------------------------------------------------------------- +/* + * A complex action for different trigger types. + * + * This action is expecting an entity object user, node or comment. If none of + * the above is provided (because it was not called from an user/node/comment + * trigger event, then the action will be taken on the current logged in user. + * + */ + +/** + * Unblock an user. This action can be fired from different trigger types: + * - User trigger: this user will be unblocked. + * - Node/Comment trigger: the author of the node or comment will be unblocked. + * - Other: (including system or custom defined types), current user will be + * unblocked. (Yes, this seems like an incomprehensible use-case.) + * + * @param $entity + * An optional user object (could be a user, or an author if context is + * node or comment) + * @param array $context + * Array with parameters for this action: depends on the trigger. The context + * is not used in this example. + * + * @ingroup actions + */ +function action_example_unblock_user_action(&$entity, $context = array()) { + + // First we check that entity is a user object. If this is the case, then this + // is a user-type trigger. + if (isset($entity->uid)) { + $uid = $entity->uid; + } + elseif (isset($context['uid'])) { + $uid = $context['uid']; + } + // If neither of those are valid, then block the current user. + else { + $uid = $GLOBALS['user']->uid; + } + $account = user_load($uid); + $account = user_save($account, array('status' => 1)); + watchdog('action_example', 'Unblocked user %name.', array('%name' => $account->name)); + drupal_set_message(t('Unblocked user %name', array('%name' => $account->name))); +} + +// --------------------------------------------------------------------------- +/* + * A complex action using customization. + * + * The next action requires a configuration form to create/configure the action. + * In Drupal these are called 'advanced actions', because they must be + * customized to define their functionality. + * + * The 'action_example_node_sticky_action' allows creating rules to promote and + * set sticky content created by selected users on certain events. A form is + * used to configure which user is affected by this action, and this form + * includes the stanard _validate and _submit hooks. + */ + + +/** + * Generates settings form for action_example_node_sticky_action(). + * + * @param array $context + * An array of options of this action (in case it is being edited) + * @return array $form + * + */ +function action_example_node_sticky_action_form($context) { + /* + * We return a configuration form to set the requirements that will + * match this action before being executed. This is a regular Drupal form and + * may include any type of information you want, but all the fields of the + * form will be saved into the $context variable. + * + * In this case we are promoting all content types submited by this user, but + * it is possible to extend these conditions providing more options in the + * settings form. + */ + $form['author'] = array( + '#title' => t('Author name'), + '#type' => 'textfield', + '#description' => t('Any content created, presaved or updated by this user will be promoted to front page and set as sticky.'), + '#default_value' => isset($context['author']) ? $context['author'] : '', + ); + // Verify user permissions and provide an easier way to fill this field. + if (user_access('access user profiles')) { + $form['author']['#autocomplete_path'] = 'user/autocomplete'; + } + // No more options, return the form. + return $form; +} + +/** + * Validate settings form for action_example_node_sticky_action(). + * Verify that user exists before continuing. + */ +function action_example_node_sticky_action_validate($form, $form_state) { + if (! $account = user_load_by_name($form_state['values']['author']) ) { + form_set_error('author', t('Please, provide a valid username')); + } +} + +/** + * Submit handler for action_example_node_sticky_action. + * + * Returns an associative array of values which will be available in the + * $context when an action is executed. + */ +function action_example_node_sticky_action_submit($form, $form_state) { + return array('author' => $form_state['values']['author']); +} + +/** + * Promote and set sticky flag action. This is the special action that has been + * customized using the configuration form. + * + * @param $node + * A node object provided by the associated trigger. + * @param $context + * Array with the following elements: + * - 'author': username of the author's content this function will promote and + * set as sticky. + * + * @ingroup actions + */ +function action_example_node_sticky_action($node, $context) { + if (function_exists('dsm')) { + dsm($node, 'action_example_node_sticky_action is firing. Here is the $node'); + dsm($context, 'action_example_node_sticky_action is firing. Here is the $context'); + } + // Get the user configured for this special action. + $account = user_load_by_name($context['author']); + // Is the node created by this user? then promote and set as sticky. + if ($account->uid == $node->uid) { + $node->promote = NODE_PROMOTED; + $node->sticky = NODE_STICKY; + watchdog('action', 'Set @type %title to sticky and promoted by special action for user %username.', array('@type' => node_type_get_name($node), '%title' => $node->title, '%username' => $account->name)); + drupal_set_message(t('Set @type %title to sticky and promoted by special action for user %username.', array('@type' => node_type_get_name($node), '%title' => $node->title, '%username' => $account->name))); + } +} diff --git a/action_example/action_example.test b/action_example/action_example.test new file mode 100644 index 0000000000000000000000000000000000000000..c22b9112b658b86e9c87b8dd140f2bfcaef75f8d --- /dev/null +++ b/action_example/action_example.test @@ -0,0 +1,84 @@ + 'Action example', + 'description' => 'Perform various tests on action_example module.' , + 'group' => 'Examples', + ); + } + + function setUp() { + parent::setUp('trigger', 'action_example'); + } + + /** + * Test Action Example. + * + * 1. action_example_basic_action: Configure a action_example_basic_action to + * happen when user logs in. + * 2. action_example_unblock_user_action: When a user's profile is being + * viewed, unblock that user. + * 3. action_example_node_sticky_action: Create a user, configure that user + * to always be stickied using advanced configuration. Have the user + * create content; verify that it gets stickied. + */ + function testActionExample() { + // Create an administrative user. + $admin_user = $this->drupalCreateUser(array('administer actions', 'access comments', 'access content', 'post comments', 'skip comment approval', 'create article content', 'access user profiles', 'administer users')); + $this->drupalLogin($admin_user); + + // 1. Assign basic action; then logout and login user and see if it puts + // the message on the screen. + $hash = drupal_hash_base64('action_example_basic_action'); + $edit = array('aid' => $hash); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-login-assign-form'); + + $this->drupalLogout(); + $this->drupalLogin($admin_user); + $this->assertText(t('action_example_basic_action fired')); + + // 2. Unblock: When a user's profile is being viewed, unblock. + $normal_user = $this->drupalCreateUser(); + user_save($normal_user, array('status' => 0)); // Blocked user. + $normal_user = user_load($normal_user->uid, TRUE); + $this->assertFalse($normal_user->status, t('Normal user status has been set to blocked')); + + $hash = drupal_hash_base64('action_example_unblock_user_action'); + $edit = array('aid' => $hash); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-view-assign-form'); + + $this->drupalGet("user/$normal_user->uid"); + $normal_user = user_load($normal_user->uid, TRUE); + $this->assertTrue($normal_user->status, t('Normal user status has been set to unblocked')); + $this->assertRaw(t('Unblocked user %name', array('%name' => $normal_user->name))); + + // 3. Create a user whose posts are always to be stickied. + $sticky_user = $this->drupalCreateUser(array('access comments', 'access content', 'post comments', 'skip comment approval', 'create article content')); + + $action_label = $this->randomName(); + $edit = array( + 'actions_label' => $action_label, + 'author' => $sticky_user -> name, + ); + $aid = $this->configureAdvancedAction('action_example_node_sticky_action', $edit); + $edit = array('aid' => drupal_hash_base64($aid)); + $this->drupalPost('admin/structure/trigger/node', $edit, t('Assign'), array(), array(), 'trigger-node-insert-assign-form'); + // Now create a node and verify that it gets stickied. + $this->drupalLogout(); + $this->drupalLogin($sticky_user); + $node = $this->drupalCreateNode(); + $this->assertTrue($node->sticky, t('Node was set to sticky on creation')); + } +} diff --git a/trigger_example/trigger_example.module b/trigger_example/trigger_example.module index 53c194e9038980484eef164580168b20357b8ac4..087cd5821d521033ec8d9374dc2a0e5e6800882c 100644 --- a/trigger_example/trigger_example.module +++ b/trigger_example/trigger_example.module @@ -65,6 +65,7 @@ * * @see hook_trigger_info() * @see hook_trigger_info_alter() + * Also see the @link action_example.module Action Example @endlink. */ /** @@ -149,7 +150,7 @@ function trigger_example_triggersomething($options = array()) { */ function trigger_example_user_login(&$edit, $account, $category = NULL) { // Verify user has never accessed the site: last access was creation date. - if ($account->created == $account->access) { + if ($account->access == 0) { // Call the aproppriate trigger function _trigger_example_first_time_login('user_first_time_login', $edit, $account, $category); } diff --git a/trigger_example/trigger_example.test b/trigger_example/trigger_example.test index 1d4df1a75206cad450261b0ef38fadb4b4cb2026..552cd0281cfd6d8090b305fe58616fc3778f9f60 100644 --- a/trigger_example/trigger_example.test +++ b/trigger_example/trigger_example.test @@ -69,115 +69,11 @@ class TriggerExampleTestCase extends DrupalWebTestCase { $aid = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'system_message_action'))->fetchField(); $edit = array('aid' => drupal_hash_base64($aid)); - // Unfortunately there is no current way in simpletest to choose the correct - // form to submit to, since all the forms on this page have the same name - // for their submit button. See http://drupal.org/node/709852 // Find the correct trigger -// $this->drupalGet('admin/structure/trigger/user'); -// $form = $this->xpath("//*[@id='edit-user-first-time-login']"); -// $button_id = (string)$form[0]->div->input->attributes()->id; -// -// $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign')); -// -// -// $test_user = $this->drupalCreateUser(); -// $this->drupalLogin($test_user); -// $this->assertText($action_label, "Found the random message on the page after login."); - } - - protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { - $submit_matches = FALSE; - $ajax = is_array($submit); - if (isset($path)) { - $this->drupalGet($path, $options); - } - if ($this->parse()) { - $edit_save = $edit; - // Let's iterate over all the forms. - $xpath = "//form"; - if (!empty($form_html_id)) { - $xpath .= "[@id='" . $form_html_id . "']"; - } - $forms = $this->xpath($xpath); - foreach ($forms as $form) { - // We try to set the fields of this form as specified in $edit. - $edit = $edit_save; - $post = array(); - $upload = array(); - $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form); - $action = isset($form['action']) ? $this->getAbsoluteUrl($form['action']) : $this->getUrl(); - if ($ajax) { - $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax'); - // AJAX callbacks verify the triggering element if necessary, so while - // we may eventually want extra code that verifies it in the - // handleForm() function, it's not currently a requirement. - $submit_matches = TRUE; - } - - // We post only if we managed to handle every field in edit and the - // submit button matches. - if (!$edit && $submit_matches) { - $post_array = $post; - if ($upload) { - // TODO: cURL handles file uploads for us, but the implementation - // is broken. This is a less than elegant workaround. Alternatives - // are being explored at #253506. - foreach ($upload as $key => $file) { - $file = drupal_realpath($file); - if ($file && is_file($file)) { - $post[$key] = '@' . $file; - } - } - } - else { - foreach ($post as $key => $value) { - // Encode according to application/x-www-form-urlencoded - // Both names and values needs to be urlencoded, according to - // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 - $post[$key] = urlencode($key) . '=' . urlencode($value); - } - if ($ajax && isset($submit['triggering_element'])) { - $post['ajax_triggering_element'] = 'ajax_triggering_element=' . urlencode($submit['triggering_element']); - } - $post = implode('&', $post); - } - $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers)); - // Ensure that any changes to variables in the other thread are picked up. - $this->refreshVariables(); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-first-time-login-assign-form'); - // Replace original page output with new output from redirected page(s). - if ($new = $this->checkForMetaRefresh()) { - $out = $new; - } - $this->verbose('POST request to: ' . $path . - '
Ending URL: ' . $this->getUrl() . - '
Fields: ' . highlight_string('' . $out); - return $out; - } - } - // We have not found a form which contained all fields of $edit. - foreach ($edit as $name => $value) { - $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); - } - if (!$ajax) { - $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); - } - $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); - } - } - - /** - * Test assigning a configurable action to the triggersomething event. - */ - function testTriggerhookinfoalter() { - // Create an administrative user. - $test_user = $this->drupalCreateUser(array('administer actions')); + $test_user = $this->drupalCreateUser(); $this->drupalLogin($test_user); - - $this->drupalGet('admin/structure/trigger/system'); - // Verify the new 'On cron execution' label of the cron trigger is shown, - $this->assertText( t('On cron execution') , t('The hook_trigger_info_alter() event changed a trigger name.')); - $this->assertNoText( t('When cron runs') , t('The hook_trigger_info_alter() event changed a trigger name.')); + $this->assertText($action_label); } }