You are here

sms_actions.module in SMS Framework 7

Provides a "Send SMS" action and the ability to define custom triggers for incoming messages.

File

modules/sms_actions/sms_actions.module
View source
<?php

/**
 * @file
 * Provides a "Send SMS" action and the ability to define custom triggers for
 * incoming messages.
 */

/**
 * Implementation of hook_sms_incoming().
 */
function sms_actions_sms_incoming($op, $number, $message) {
  if ($op == 'process') {
    $commands = sms_actions_get_commands();
    foreach ($commands as $command) {
      if (stripos($message, $command->discriminator) === 0) {
        $active_command = $command;
      }
    }
    if (!empty($active_command)) {
      sms_actions_do($active_command, $number, $message);
    }
    else {

      // Try to figure out what command was actually given
      // preg_match('!^([A-z0-9\-]+).+$!', $message, $matches);
      // if (variable_get('sms_actions_invalid_cmd_reply', 0)) {
      //   sms_send($number, t('The command @command was not found.', array('@command' => $matches[1])));
      // }
      // watchdog('sms', t('Command %command not found from @number.', array('%command' => $matches[1], '@number' => $number)), WATCHDOG_WARNING);
    }
  }
}

/**
 * Executes actions defined by sms_actions module.
 *
 * @param string $command
 *   The command to execute.
 * @param string $number
 *   The phone number associated with the action.
 * @param string $message
 *   The message to be sent.
 */
function sms_actions_do($command, $number, $message) {

  // Keep objects for reuse so that changes actions make to objects can persist.
  static $objects;
  $aids = trigger_get_assigned_actions($command->hook) + trigger_get_assigned_actions('sms_actions');
  if (!$aids) {
    return;
  }
  $context = array(
    'group' => 'sms_actions',
    'hook' => $command->hook,
    'from' => $number,
  );
  foreach ($aids as $aid => $action_info) {
    $type = $action_info['type'];
    if ($type != 'sms') {
      if ($type == 'node') {
        preg_match('!^[A-z0-9\\-]+\\s(.+)$!', $message, $matches);
        $params = explode(' ', $matches[1]);
        if ($node = node_load($params[0])) {
          if (!isset($objects[$type])) {
            $objects[$type] = _trigger_normalize_comment_context($type, $node);
          }
          $context['group'] = 'node';
          actions_do($aid, $objects[$type], $context);
        }
      }
    }
    else {
      if (!isset($objects[$type])) {
        $objects[$type] = (object) array(
          'message' => $message,
          'from' => $number,
        );
      }
      actions_do($aid, $objects[$type], $context);
    }
  }
}

/**
 * Implementation of hook_menu().
 */
function sms_actions_menu() {
  $items['admin/smsframework/actions'] = array(
    'title' => 'SMS Actions',
    'description' => 'Define custom triggers for incoming SMS messages.',
    'access arguments' => array(
      'administer smsframework',
    ),
    'page callback' => 'sms_actions_command_list',
  );
  $items['admin/smsframework/actions/list'] = array(
    'title' => 'List',
    'access arguments' => array(
      'administer smsframework',
    ),
    'weight' => -5,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/smsframework/actions/add'] = array(
    'title' => 'Add command',
    'access arguments' => array(
      'administer smsframework',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'sms_actions_edit_command_form',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/smsframework/actions/edit'] = array(
    'title' => 'Edit command',
    'access arguments' => array(
      'administer smsframework',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'sms_actions_edit_command_form',
      4,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_action_info().
 */
function sms_actions_action_info() {
  return array(
    'sms_actions_send_action' => array(
      'type' => 'sms',
      'label' => t('Send SMS'),
      'configurable' => TRUE,
      'triggers' => array(
        'sms_actions',
        'node_presave',
        'comment_insert',
        'comment_update',
        'comment_delete',
      ),
    ),
  );
}

/**
 * Form constructor for sms_actions_send_action form
 *
 * @param array $context
 *   Array containing contextual information for the action.
 *
 * @return array
 *   Drupal form array.
 */
function sms_actions_send_action_form($context) {

  // Set default values for form.
  if (!isset($context['number'])) {
    $context['number'] = '';
  }
  if (!isset($context['author'])) {
    $context['author'] = FALSE;
  }
  if (!isset($context['message'])) {
    $context['message'] = '';
  }
  $form = sms_send_form(NULL, NULL, FALSE);
  $form['number']['#default_value'] = $context['number'];
  $form['author'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send to author of original post'),
    '#description' => t('If checked, the message will be sent to author of the orginal post and the number field will be ignored.'),
    '#default_value' => $context['author'],
  );
  $form['message'] = array(
    '#type' => 'textarea',
    '#title' => t('Message'),
    '#default_value' => $context['message'],
    '#cols' => '80',
    '#rows' => '20',
    '#description' => t('The message that should be sent. You may include the following variables: %site_name, %username, %uid, %node_url, %node_alias, %node_type, %title, %teaser, %body. Not all variables will be available in all contexts.'),
  );
  return $form;
}

/**
 * Submit handler for sms_actions_send_action form.
 */
function sms_actions_send_action_submit($form, &$form_state) {

  // Process the HTML form to store configuration. The keyed array that
  // we return will be serialized to the database.
  $params = array(
    'number' => $form_state['values']['number'],
    'author' => $form_state['values']['author'],
    'message' => $form_state['values']['message'],
  );
  return $params;
}

/**
 * Implements the action sms_action_send_action.
 *
 * @param object $object
 *   The object initiating the action.
 * @param array $context
 *   The context in which the action is being triggered.
 */
function sms_actions_send_action($object, $context) {
  global $user;
  switch ($context['group']) {
    case 'node':
      $node = $context['node'];
      break;
    case 'comment':
      $comment = $context['comment'];
      $node = node_load($comment->nid);
      break;
    case 'user':

      // Because this is not an action of type 'user' the user
      // object is not passed as $object, but it will still be available
      // in $context. Use user_load() to pull in ->sms_user data as well.
      $account = user_load($context['account']->uid);
      $variables['%username'] = $account->name;
      if (isset($context['node'])) {
        $node = $context['node'];
      }
      elseif ($context['recipient'] == '%author') {

        // If we don't have a node, we don't have a node author.
        watchdog('error', t("Cannot use '%author' token in this context."));
        return;
      }
      break;
    case 'sms_actions':

      // We don't want the default: action for 'sms_actions' group.
      break;
    default:

      // Check context for node.
      if (!isset($object) && isset($context['node'])) {
        $node = $context['node'];
      }
      else {

        // We are being called directly.
        $node = $object;
      }
      break;
  }
  $number = $context['number'];
  $variables['%site_name'] = variable_get('site_name', 'Drupal');
  if (isset($node)) {
    if (!isset($account)) {
      $account = user_load($node->uid);
      $variables['%username'] = $account->name;
    }
    if ($context['author'] && $account->sms_user['status'] == 2) {
      $number = $account->sms_user['number'];
    }
    $names = node_type_get_names();
    $variables = array_merge($variables, array(
      '%uid' => $node->uid,
      '%node_url' => url('node/' . $node->nid, array(
        'absolute' => TRUE,
      )),
      '%node_type' => check_plain($names[$node->type]),
      '%title' => filter_xss($node->title),
      '%teaser' => filter_xss($node->teaser),
      '%body' => filter_xss($node->body),
    ));
  }
  $message = strtr($context['message'], $variables);
  if (isset($node) && $context['author'] && isset($account)) {
    sms_user_send($account->uid, $message);
  }
  else {
    sms_send($number, $message);
  }
}

/**
 * Implements hook_action_info_alter().
 */
function sms_actions_action_info_alter(&$actions) {

  // Actions to alter.
  $hooks = array(
    'node_publish_action',
    'node_unpublish_action',
    'node_make_sticky_action',
    'node_make_unsticky_action',
    'node_promote_action',
    'node_unpromote_action',
    'node_assign_owner_action',
    'node_save_action',
    'node_unpublish_by_keyword_action',
    'system_send_email_action',
    'sms_actions_send_action',
  );
  $commands = sms_actions_get_commands();
  $command_triggers[] = 'sms_actions';
  foreach ($commands as $command) {
    $command_triggers[] = $command->hook;
  }
  foreach ($actions as $hook => $action) {
    if (in_array($hook, $hooks) && $action['triggers'] != array(
      'any',
    )) {
      $actions[$hook]['triggers'] = array_unique(array_merge($actions[$hook]['triggers'], $command_triggers));
    }
  }
}

/**
 * Implementation of hook_trigger_info().
 */
function sms_actions_trigger_info() {
  $hooks = array();
  $hooks['sms_actions']['sms_actions'] = array(
    'label' => t('When an SMS message with any discriminator is received.'),
  );
  $commands = sms_actions_get_commands();
  foreach ($commands as $command) {
    $hooks['sms_actions'][$command->hook] = array(
      'label' => t('When an SMS message with the %discriminator discriminator is received.', array(
        '%discriminator' => $command->discriminator,
      )),
    );
  }
  return $hooks;
}

/**
 * Returns an array of commands objects.
 *
 * Command objects are created in the UI.
 *
 * @return array
 *   The list of commands that have been created by the UI.
 */
function sms_actions_get_commands() {
  $commands = variable_get('sms_actions_commands', array());
  foreach ($commands as $key => $command) {
    $commands[$key]->hook = 'sms_actions_' . $command->discriminator;
  }
  return $commands;
}

/**
 * Loads a specific command.
 *
 * @param string $discriminator
 *   The given name for the command.
 *
 * @return object
 *   The command object with the associated discriminator (machine_name).
 */
function sms_actions_command_load($discriminator) {
  $commands = variable_get('sms_actions_commands', array());
  return $commands[$discriminator];
}

/**
 * Saves a command.
 */
function sms_actions_command_save($command) {
  $commands = sms_actions_get_commands();
  $existing_discriminator = !empty($command->old_discriminator) ? $command->old_discriminator : $command->discriminator;
  if (array_key_exists($existing_discriminator, $commands)) {
    db_query("UPDATE {trigger_assignments} SET hook = '%s' WHERE hook = '%s'", array(
      'sms_actions_' . $command->discriminator,
      'sms_actions_' . $existing_discriminator,
    ));
  }
  unset($commands[$existing_discriminator]);
  $commands[$command->discriminator] = $command;
  variable_set('sms_actions_commands', $commands);
}

/**
 * Deletes a command.
 */
function sms_actions_command_delete($discriminator) {
  $commands = variable_get('sms_actions_commands', array());
  unset($commands[$discriminator]);
  variable_set('sms_actions_commands', $commands);
  db_query("DELETE FROM {trigger_assignments} WHERE hook = '%s'", array(
    'sms_actions_' . $discriminator,
  ));
}

/**
 * Menu callback: command listing.
 *
 * @return string
 *   HTML string for the table of commands.
 */
function sms_actions_command_list() {
  $header = array(
    t('Discriminator'),
    t('Operations'),
  );
  $commands = sms_actions_get_commands();
  $rows = array();
  foreach ($commands as $command) {
    $row = array(
      $command->discriminator,
      l(t('edit'), 'admin/smsframework/actions/edit/' . $command->discriminator),
    );
    $rows[] = $row;
  }
  return theme('table', array(
    'header' => $header,
    'rows' => $rows,
    'empty' => t('No commands available.'),
  ));
}

/**
 * Form constructor for the sms_actions command editing form.
 *
 * @param string $discriminator
 *   Determines which command is to be edited. Null for an empty form.
 *
 * @return array
 *   Drupal form array.
 *
 * @see sms_actions_edit_command_form_validate()
 * @see sms_actions_edit_command_form_submit()
 */
function sms_actions_edit_command_form($form, &$form_state, $discriminator = NULL) {

  // Check for confirmation forms.
  if (isset($form_state['confirm_delete'])) {
    return sms_actions_command_confirm_delete($form_state, $discriminator);
  }
  if (isset($discriminator)) {
    $command = sms_actions_command_load($discriminator);
  }
  $form['discriminator'] = array(
    '#type' => 'textfield',
    '#title' => t('Discriminator'),
    '#description' => t('A keyword that will be used to identify incoming messages. The discriminator may consist only of lowercase letters, numbers, and dashes.'),
    '#size' => 40,
    '#maxlength' => 16,
    '#required' => TRUE,
    '#default_value' => isset($command) ? $command->discriminator : '',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save command'),
  );
  if (isset($command)) {
    $form['old_discriminator'] = array(
      '#type' => 'value',
      '#value' => $command->discriminator,
    );
    $form['delete'] = array(
      '#type' => 'submit',
      '#value' => t('Delete'),
      '#weight' => 45,
    );
  }
  return $form;
}

/**
 * Validate handler for sms_actions_edit_command_form().
 *
 * @see sms_actions_edit_command_form()
 */
function sms_actions_edit_command_form_validate($form, &$form_state) {
  if (!preg_match('!^[a-z0-9\\-]+$!', $form_state['values']['discriminator'])) {
    form_set_error('discriminator', t('The discriminator may only consist of lowercase letters, dashes, and numbers.'));
  }
}

/**
 * Submit handler for sms_actions_edit_command_form().
 *
 * @see sms_actions_edit_command_form()
 */
function sms_actions_edit_command_form_submit($form, &$form_state) {

  // $commands = sms_actions_get_commands();
  $command = new stdClass();
  $command->discriminator = $form_state['values']['discriminator'];
  $command->old_discriminator = isset($form_state['values']['old_discriminator']) ? $form_state['values']['old_discriminator'] : '';
  if ($form_state['clicked_button']['#value'] == t('Delete')) {
    if ($form_state['values']['delete'] === TRUE) {
      return sms_actions_command_confirm_delete_submit($form, $form_state);
    }
    $form_state['rebuild'] = TRUE;
    $form_state['confirm_delete'] = TRUE;
    return;
  }
  sms_actions_command_save($command);

  // $commands[$command->discriminator] = $command;
  // variable_set('sms_actions_commands', $commands);
  $form_state['redirect'] = 'admin/smsframework/actions';
}

/**
 * Form constructor for command delete confirmation form.
 *
 * @param string $discriminator
 *   Determines which command to confirm deletion.
 *
 * @return array
 *   Drupal form array.
 *
 * @see sms_actions_command_confirm_delete_submit()
 */
function sms_actions_command_confirm_delete($form_state, $discriminator) {
  $command = sms_actions_command_load($discriminator);
  $form['discriminator'] = array(
    '#type' => 'value',
    '#value' => $command->discriminator,
  );
  $form['delete'] = array(
    '#type' => 'value',
    '#value' => TRUE,
  );
  return confirm_form($form, t('Are you sure you want to delete the command %discriminator?', array(
    '%discriminator' => $command->discriminator,
  )), 'admin/smsframework/actions', t('Deleting a command will remove any action assignments. This action cannot be undone.'), t('Delete'), t('Cancel'));
}

/**
 * Submit handler for sms_actions_command_confirm_delete().
 *
 * @see sms_actions_command_confirm_delete()
 */
function sms_actions_command_confirm_delete_submit($form, &$form_state) {
  sms_actions_command_delete($form_state['values']['discriminator']);
  drupal_set_message(t('The command %command has been deleted.', array(
    '%command' => $form_state['values']['discriminator'],
  )));
  $form_state['redirect'] = 'admin/smsframework/actions';
}

Functions

Namesort descending Description
sms_actions_action_info Implementation of hook_action_info().
sms_actions_action_info_alter Implements hook_action_info_alter().
sms_actions_command_confirm_delete Form constructor for command delete confirmation form.
sms_actions_command_confirm_delete_submit Submit handler for sms_actions_command_confirm_delete().
sms_actions_command_delete Deletes a command.
sms_actions_command_list Menu callback: command listing.
sms_actions_command_load Loads a specific command.
sms_actions_command_save Saves a command.
sms_actions_do Executes actions defined by sms_actions module.
sms_actions_edit_command_form Form constructor for the sms_actions command editing form.
sms_actions_edit_command_form_submit Submit handler for sms_actions_edit_command_form().
sms_actions_edit_command_form_validate Validate handler for sms_actions_edit_command_form().
sms_actions_get_commands Returns an array of commands objects.
sms_actions_menu Implementation of hook_menu().
sms_actions_send_action Implements the action sms_action_send_action.
sms_actions_send_action_form Form constructor for sms_actions_send_action form
sms_actions_send_action_submit Submit handler for sms_actions_send_action form.
sms_actions_sms_incoming Implementation of hook_sms_incoming().
sms_actions_trigger_info Implementation of hook_trigger_info().