sms_clickatell.module in SMS Framework 6
Same filename and directory in other branches
Clickatell gateway module for Drupal SMS Framework. Outbound+Inbound+Receipts
Thanks to diggersf for his great work on the original module.
Applying a sender will only work with Approved Sender IDs in your Clickatell account | My Settings | Manage Sender IDs.
It is recommended that Clean URLs are on if you are using WAP PUSH, receipt functions or anything else that will handle a URL.
For inbound messaging you must configure clickatell to send messages to:
- http(s)://yourhost.example.com/sms/clickatell/receiver
For receipts to work you must configure clickatell to send the receipt callbacks to:
- http(s)://yourhost.example.com/sms/clickatell/receipt
The send function in this module supports several options, including message sender. Please see sms_clickatell_command()
Relevant Clickatell documentation:
- https://www.clickatell.com/developers/api_http.php
- https://www.clickatell.com/downloads/http/Clickatell_HTTP.pdf
- http://www.clickatell.com/downloads/Clickatell_two-way_technical_guide.pdf
@todo WAP Push functionality.
@package sms @subpackage sms_clickatell
File
modules/sms_clickatell/sms_clickatell.moduleView source
<?php
/**
* @file
* Clickatell gateway module for Drupal SMS Framework. Outbound+Inbound+Receipts
*
* Thanks to diggersf for his great work on the original module.
*
* Applying a sender will only work with Approved Sender IDs in your Clickatell
* account | My Settings | Manage Sender IDs.
*
* It is recommended that Clean URLs are on if you are using WAP PUSH, receipt
* functions or anything else that will handle a URL.
*
* For inbound messaging you must configure clickatell to send messages to:
* - http(s)://yourhost.example.com/sms/clickatell/receiver
*
* For receipts to work you must configure clickatell to send the receipt callbacks to:
* - http(s)://yourhost.example.com/sms/clickatell/receipt
*
* The send function in this module supports several options, including message
* sender. Please see sms_clickatell_command()
*
* Relevant Clickatell documentation:
* - https://www.clickatell.com/developers/api_http.php
* - https://www.clickatell.com/downloads/http/Clickatell_HTTP.pdf
* - http://www.clickatell.com/downloads/Clickatell_two-way_technical_guide.pdf
*
* @todo WAP Push functionality.
*
* @package sms
* @subpackage sms_clickatell
*/
/**
* Implement hook_gateway_info()
*
* @return
* SMS Framework gateway info array
*
* @ingroup hooks
*/
function sms_clickatell_gateway_info() {
return array(
'clickatell' => array(
'name' => 'Clickatell',
'send' => 'sms_clickatell_send',
'configure form' => 'sms_clickatell_admin_form',
'message_status_codes' => 'sms_clickatell_message_status_codes',
'response_codes' => 'sms_clickatell_response_codes',
),
);
}
/**
* Implement hook_menu()
*
* @return
* Drupal menu items array
*
* @ingroup hooks
*/
function sms_clickatell_menu() {
$items = array();
$items['sms/clickatell/receiver'] = array(
'title' => 'Clickatell SMS message receiver',
'page callback' => 'sms_clickatell_receive_message',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['sms/clickatell/receipt'] = array(
'title' => 'Clickatell SMS receipt receiver',
'page callback' => 'sms_clickatell_receive_receipt',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Configuration form for gateway module
*
* @param $configuration
*
* @return
* Drupal form array
*/
function sms_clickatell_admin_form($configuration) {
$form['sms_clickatell_balance'] = array(
'#type' => 'item',
'#title' => t('Current balance'),
'#value' => sms_clickatell_balance(),
);
$form['sms_clickatell_ssl'] = array(
'#type' => 'checkbox',
'#title' => t('Use SSL Encyption'),
'#description' => t('Drupal\'s built-in HTTP client only supports SSL on PHP 4.3 compiled with OpenSSL.'),
'#default_value' => $configuration['sms_clickatell_ssl'],
);
$form['sms_clickatell_api_id'] = array(
'#type' => 'textfield',
'#title' => t('API ID'),
'#description' => t('Clickatell issues this number upon addition of an HTTP sub-product to your account.'),
'#size' => 40,
'#maxlength' => 255,
'#default_value' => $configuration['sms_clickatell_api_id'],
);
$form['sms_clickatell_user'] = array(
'#type' => 'textfield',
'#title' => t('User'),
'#description' => t('The username of your Clickatell account.'),
'#size' => 40,
'#maxlength' => 255,
'#default_value' => $configuration['sms_clickatell_user'],
);
$form['sms_clickatell_password'] = array(
'#type' => 'textfield',
'#title' => t('Password'),
'#description' => t('The current password on your Clickatell account.'),
'#size' => 30,
'#maxlength' => 64,
'#default_value' => $configuration['sms_clickatell_password'],
);
$form['sms_clickatell_from'] = array(
'#type' => 'textfield',
'#title' => t('Default Sender (from)'),
'#description' => t('Name/number of the default sender. Will be used if a sender is not specified on message send.<br /><em>This will only work with <strong>Approved</strong> Sender IDs in your Clickatell account | My Settings | Manage Sender IDs.</em><br />A valid international format number up to 16 characters (eg: 447911222333) or alphanumeric string of 11 characters (eg: My Service).<br />If empty / sender not specified on message send / not Approved Seender ID, then Clickatell will apply a gateway number.'),
'#size' => 16,
'#maxlength' => 16,
'#default_value' => $configuration['sms_clickatell_from'],
);
$form['sms_clickatell_callback'] = array(
'#type' => 'select',
'#title' => t('Status Callback (delivery receipts)'),
'#description' => t('Enable delivery receipts when message status changes in the gateway.'),
'#multiple' => FALSE,
'#options' => array(
0 => t('No message status returned'),
1 => t('Return only intermediate statuses'),
2 => t('Return only final statuses of a message'),
3 => t('Return both intermediate and final stauses of a message'),
),
'#default_value' => $configuration['sms_clickatell_callback'],
);
return $form;
}
function sms_clickatell_admin_form_validate($form, &$form_state) {
// Check default sender
if (!empty($form_state['values']['sms_clickatell_from'])) {
if (!is_numeric($form_state['values']['sms_clickatell_from'])) {
if (strlen($form_state['values']['sms_clickatell_from']) > 11) {
form_set_error('sms_clickatell_from', t('An alphanumeric sender is limited to a maximum of 11 characters.'));
}
}
}
// Attempt a connection with the given credentials
$result = sms_clickatell_command('auth', array(), $form_state['values']);
if (!$result['status']) {
form_set_error('', t('A Clickatell gateway error occured: @code: @text', array(
'@code' => $result['gateway_status_code'],
'@text' => $result['gateway_status_text'],
)));
}
variable_set('sms_clickatell_session_id_timestamp', 0);
}
/**
* Callback for sending messages.
*
* Options for this send function: see sms_txtlocal_command()
*
* @param $number
* MSISDN of message recipient. Expected to include the country code prefix.
* @param $message
* Message body text.
* @param $options
* Options from SMS Framework.
*
* @return
* Response from sms_clickatell_command()
*/
function sms_clickatell_send($number, $message, $options) {
// Attach the country code to the number, if required
if (array_key_exists('country', $options)) {
$number = $options['country'] . $number;
}
return sms_clickatell_command('sendmsg', array(
'number' => $number,
'message' => $message,
'options' => $options,
));
}
/**
* Get account balance
*
* @return
* Balance text
*/
function sms_clickatell_balance() {
$result = sms_clickatell_command('getbalance');
// This part of the array will either contain the balance or a useful error message
return $result['gateway_status_text'];
}
/**
* Executes a command using the Clickatell API
*
* data array fields:
* number - MSISDN of message recipient. Purely numeric and must begin with intl prefix, eg. 4477121231234.
* message - Message text. Max 459 chars (3x SMS). Use %n for newline.
* options - Array of additional options, as below.
*
* data['options'] array fields:
* sender - Optional: Sender ID may be an MSISDN (max 16 chars) or an alphanumeric string (max 11 chars). See note about Approved Sender IDs in the header of this file. Clickatell param: 'from'
* reference - Optional: Reference tag to apply to message. Will appear on any receipt. No spaces. Clickatell param: 'cliMsgId'
* delaymins - Optional: Delay message sending by N minutes. Clickatell param: 'deliv_time'
* expiremins - Optional: The message send will abort if not sent within N minutes. Clickatell param: 'validity'
* priority - Optional: Queue priority to apply to the message. Can be 1, 2 or 3, where 1 is high priority. Clickatell param: 'queue'
* expectreply - Optional: Route the message properly so that the user can reply. Clickatell param: 'mo'
*
* @param $command
* One of 'auth', 'sendmsg' or 'getbalance'.
* @param $data
* All data required to perform the command.
* @param $config
* Gateway configuration parameters.
*
* @return
* Response from command.
*/
function sms_clickatell_command($command = 'auth', $data = array(), $config = NULL) {
$gateway = sms_gateways('gateway', 'clickatell');
if ($config == NULL) {
$config = $gateway['configuration'];
}
if ($config['sms_clickatell_ssl']) {
$scheme = 'https';
}
else {
$scheme = 'http';
}
switch ($command) {
case 'auth':
$query = 'api_id=' . $config['sms_clickatell_api_id'] . '&user=' . $config['sms_clickatell_user'] . '&password=' . $config['sms_clickatell_password'];
break;
case 'sendmsg':
// Check if the message requires unicode handling
if ($unicode_message = sms_clickatell_unicode($data['message'])) {
$message = $unicode_message;
}
else {
$message = drupal_urlencode($data['message']);
}
$query = 'session_id=' . sms_clickatell_get_session_id() . '&to=' . $data['number'] . '&text=' . $message;
// Check if the message requires concatenation (long messages)
// Note: concatenation over multiple messages reduces each SMS message length by 7 chars.
$concat = 1;
if (strlen($message) > 160) {
$concat = 2;
if (strlen($message) > 306) {
$concat = 3;
}
}
$query .= '&concat=' . $concat;
// Add any optional arguments
if (isset($data) && array_key_exists('options', $data)) {
// sender (Clickatell: from)
if (array_key_exists('sender', $data['options'])) {
$query .= '&from=' . $data['options']['sender'];
$sender_set = TRUE;
}
// delaymins (Clickatell: deliv_time)
if (array_key_exists('delaymins', $data['options']) && $data['options']['delaymins'] >= 10 && $data['options']['delaymins'] <= 10080) {
$query .= '&deliv_time=' . $data['options']['delaymins'];
}
// priority (Clickatell: queue)
if (array_key_exists('priority', $data['options']) && $data['options']['priority'] >= 1 && $data['options']['priority'] <= 3) {
$query .= '&queue=' . $data['options']['priority'];
}
// expiremins (Clickatell: validity)
if (array_key_exists('expiremins', $data['options']) && $data['options']['expiremins'] >= 1 && $data['options']['expiremins'] <= 1440) {
$query .= '&validity=' . $data['options']['expiremins'];
}
// reference (Clickatell: cliMsgId)
if (array_key_exists('reference', $data['options']) && strlen($data['options']['reference']) <= 32) {
$query .= '&cliMsgId=' . $data['options']['reference'];
}
// expectreply (Clickatell: mo)
if (array_key_exists('expectreply', $data['options'])) {
$query .= '&mo=' . $data['options']['expectreply'];
}
}
// If sender is not set and default sender exists, then apply default sender
if (!isset($sender_set) && $config['sms_clickatell_from']) {
$query .= '&from=' . $config['sms_clickatell_from'];
}
// Apply callback parameter if set
if ($config['sms_clickatell_callback']) {
$query .= '&callback=' . $config['sms_clickatell_callback'];
}
break;
case 'getbalance':
$query = 'session_id=' . sms_clickatell_get_session_id();
break;
}
// Run the command
$http_result = drupal_http_request($scheme . '://api.clickatell.com/http/' . $command . '?' . $query);
// Check for HTTP errors
if ($http_result->error) {
return array(
'status' => FALSE,
'message' => t('An error occured during the HTTP request: @error', array(
'@error' => $http_result->error,
)),
);
}
if ($http_result->data) {
// Check for Clickatell errors
if (preg_match('/^ERR: ([\\d]+), (([\\w]|[\\s])+)$/', $http_result->data, $matches)) {
$errorcode = $matches[1];
$errortext = $matches[2];
$result = array(
'status' => FALSE,
'status_code' => sms_clickatell_map_response_code($errorcode),
'gateway_status_code' => $errorcode,
'gateway_status_text' => $errortext,
);
}
elseif ($command == 'auth') {
// Add Clickatell session ID to result array.
list($status, $sid) = explode(': ', $http_result->data);
$result = array(
'status' => TRUE,
'status_code' => SMS_GW_OK,
'sid' => $sid,
);
}
elseif ($command == 'getbalance') {
// Add Clickatell credit balance to result array.
preg_match('/Credit:[\\s]+([\\d]+\\.*[\\d]+)$/', $http_result->data, $matches);
$result = array(
'status' => TRUE,
'status_code' => SMS_GW_OK,
'gateway_status_text' => $matches[1],
);
}
else {
// Return a good response array
$result = array(
'status' => TRUE,
'status_code' => SMS_GW_OK,
);
}
}
return $result;
}
/**
* Get a new or existing Clickatell session ID
*
* @return
* Clickatell session ID
*/
function sms_clickatell_get_session_id() {
if (variable_get('sms_clickatell_session_id_timestamp', 0) < strtotime('-10 mins')) {
if ($result = sms_clickatell_command()) {
if ($result['status']) {
variable_set('sms_clickatell_session_id', $result['sid']);
variable_set('sms_clickatell_session_id_timestamp', time());
watchdog('sms', 'Clickatell session ID refreshed: %sid', array(
'%sid' => $result['sid'],
));
}
}
}
return variable_get('sms_clickatell_session_id', 0);
}
/**
* Receive an SMS message and pass it into the SMS Framework
*
* Will generate an $options array with the following variables:
* receiver - The destination MSISDN number. Clickatell param: 'to'
* reference - The message ID code that refers to a message originally sent with the 'expectreply' or 'mo' parameter. Clickatell param: 'moMsgId'
* For raw gateway params see Clickatell_two-way_technical_guide.pdf page 9.
*
* I have neglected the Clickatell 'timestamp' param because it is passed in non-UTC
* timezone and is in MySQL format. It is more useful to capture a timestamp in your
* hook_sms_incoming() function.
*/
function sms_clickatell_receive_message() {
$number = $_REQUEST['from'];
$message = $_REQUEST['text'];
$options = array();
// Define raw gateway response parameters
$options['gateway_params'] = array();
if (array_key_exists('to', $_REQUEST) && !empty($_REQUEST['to'])) {
$options['gateway_params']['to'] = $_REQUEST['to'];
}
if (array_key_exists('api_id', $_REQUEST) && !empty($_REQUEST['api_id'])) {
$options['gateway_params']['api_id'] = $_REQUEST['api_id'];
}
if (array_key_exists('moMsgId', $_REQUEST) && !empty($_REQUEST['moMsgId'])) {
$options['gateway_params']['moMsgId'] = $_REQUEST['moMsgId'];
}
// Define message receiver and reference in options array
$options['receiver'] = array_key_exists('to', $_REQUEST) ? $_REQUEST['to'] : '';
$options['reference'] = array_key_exists('moMsgId', $_REQUEST) ? $_REQUEST['moMsgId'] : '';
sms_incoming($number, $message, $options);
}
/**
* Receive a message receipt from Clickatell
*
* Will generate an $options array with the following variables:
* reference - A message reference code, if set on message send. Clickatell param: 'cliMsgId'
* receiver - The destination MSISDN number. Clickatell param: 'to'
* gateway_message_status - A Clickatell message status code.
* gateway_message_status_text - A text string associated with the gateway_status.
* For raw gateway params see Clickatell_HTTP.pdf page 10.
*
* Note that there may be >1 receipt for a message that takes time to be delivered.
*
* I have neglected the Clickatell 'timestamp' param because it is passed in non-UTC
* timezone and is in MySQL format. It is more useful to capture a timestamp in your
* hook_sms_receipt() function.
*/
function sms_clickatell_receive_receipt() {
$number = array_key_exists('from', $_REQUEST) ? $_REQUEST['from'] : NULL;
$reference = array_key_exists('cliMsgId', $_REQUEST) ? $_REQUEST['cliMsgId'] : NULL;
$gw_msg_status_code = array_key_exists('status', $_REQUEST) ? $_REQUEST['status'] : SMS_MSG_STATUS_UNKNOWN;
$options = array();
// Define raw gateway receipt call parameters
$options['gateway_params'] = array();
if (array_key_exists('to', $_REQUEST) && !empty($_REQUEST['to'])) {
$options['gateway_params']['to'] = $_REQUEST['to'];
}
if (array_key_exists('api_id', $_REQUEST) && !empty($_REQUEST['api_id'])) {
$options['gateway_params']['api_id'] = $_REQUEST['api_id'];
}
if (array_key_exists('moMsgId', $_REQUEST) && !empty($_REQUEST['moMsgId'])) {
$options['gateway_params']['moMsgId'] = $_REQUEST['moMsgId'];
}
if (array_key_exists('charge', $_REQUEST) && !empty($_REQUEST['charge'])) {
$options['gateway_params']['charge'] = $_REQUEST['charge'];
}
if (array_key_exists('cliMsgId', $_REQUEST) && !empty($_REQUEST['cliMsgId'])) {
$options['gateway_params']['cliMsgId'] = $_REQUEST['cliMsgId'];
}
// Define message receiver and reference in options array
$options['receiver'] = array_key_exists('to', $_REQUEST) ? $_REQUEST['to'] : '';
$options['reference'] = $reference;
// Get framework message status code and Clickatell status text
$status = sms_clickatell_map_message_status_code($gw_msg_status_code);
$gw_msg_status_codes = sms_clickatell_message_status_codes();
$gw_msg_status_text = $gw_msg_status_codes[$gw_msg_status_code];
// Define gateway-specific status (code) and text (success/error message)
$options['gateway_message_status'] = $gw_msg_status_code;
$options['gateway_message_status_text'] = $gw_msg_status_text;
// Invoke the SMS Framework receipt handler
sms_receipt($number, $reference, $status, $options);
}
/**
* Map a Clickatell message status code to an SMS Framework message status code
*
* @return
* SMS Framework message status code. See sms constants.
*/
function sms_clickatell_map_message_status_code($code) {
switch ($code) {
case '003':
case '004':
return SMS_MSG_STATUS_DELIVERED;
case '002':
case '011':
return SMS_MSG_STATUS_QUEUED;
case '008':
return SMS_MSG_STATUS_OK;
case '001':
case '005':
case '006':
case '007':
case '009':
return SMS_MSG_STATUS_ERROR;
case '010':
return SMS_MSG_STATUS_EXPIRED;
case '012':
return SMS_MSG_STATUS_NOCREDIT;
default:
return SMS_MSG_STATUS_UNKNOWN;
}
}
/**
* Map a Clickatell gateway response status code to an SMS Framework gateway status code
*
* @return
* SMS Framework gateway status code. See sms constants.
*/
function sms_clickatell_map_response_code($code) {
switch ($code) {
case '001':
case '002':
case '003':
case '004':
case '005':
case '007':
return SMS_GW_ERR_AUTH;
case '101':
case '102':
case '105':
case '106':
case '107':
case '108':
case '109':
case '111':
case '112':
case '116':
case '120':
case '123':
case '201':
case '202':
return SMS_GW_ERR_INVALID_CALL;
case '103':
case '104':
return SMS_GW_ERR_NOT_FOUND;
case '113':
return SMS_GW_ERR_MSG_LIMITS;
case '114':
return SMS_GW_ERR_MSG_ROUTING;
case '110':
return SMS_GW_ERR_MSG_OTHER;
case '115':
return SMS_GW_ERR_MSG_QUEUING;
case '121':
case '122':
case '128':
return SMS_GW_ERR_DEST_NUMBER;
case '301':
case '302':
return SMS_GW_ERR_CREDIT;
default:
return SMS_GW_ERR_OTHER;
}
}
/**
* Returns an array of message status codes and strings that are generated by the Clickatell gateway
*
* Clickatell always uses leading zeros, so its important to enclose the code
* keys in quotes.
*
* @return
* Associative array of message status codes and text strings.
*/
function sms_clickatell_message_status_codes() {
return array(
'001' => 'Message unknown',
'002' => 'Message queued',
'003' => 'Delivered to gateway',
'004' => 'Received by recipient',
'005' => 'Error with message',
'006' => 'User cancelled message delivery',
'007' => 'Error delivering message',
'008' => 'OK',
'009' => 'Routing error',
'010' => 'Message expired',
'011' => 'Message queued for later delivery',
'012' => 'Out of credit',
);
}
/**
* Returns an array of response codes and messages that are generated by the Clickatell gateway
*
* @return
* Associative array of response codes and text.
*/
function sms_clickatell_response_codes() {
return array(
'001' => 'Authentication failed',
'002' => 'Unknown username or password',
'003' => 'Session ID expired',
'004' => 'Account frozen',
'005' => 'Missing session ID',
'007' => 'IP Lockdown violation',
'101' => 'Invalid or missing parameters',
'102' => 'Invalid user data header',
'103' => 'Unknown API message ID',
'104' => 'Unknown client message ID',
'105' => 'Invalid destination address',
'106' => 'Invalid source address',
'107' => 'Empty message',
'108' => 'Invalid or missing API ID',
'109' => 'Missing message ID',
'110' => 'Error with email message',
'111' => 'Invalid protocol',
'112' => 'Invalid message type',
'113' => 'Maximum message parts exceeded',
'114' => 'Cannot route message',
'115' => 'Message expired',
'116' => 'Invalid Unicode data',
'120' => 'Invalid delivery time',
'121' => 'Destination mobile number blocked',
'122' => 'Destination mobile opted out',
'123' => 'Invalid Sender ID',
'128' => 'Number delisted',
'201' => 'Invalid batch ID',
'202' => 'No batch template',
'301' => 'No credit left',
'302' => 'Max allowed credit',
);
}
/**
* Converts a string to USC-2 encoding if neccessary.
*
* @param $message
* Message string.
*
* @return
* Converted message string or FALSE.
*/
function sms_clickatell_unicode($message) {
if (function_exists('iconv')) {
$latin = @iconv('UTF-8', 'ISO-8859-1', $message);
if (strcmp($latin, $message)) {
$arr = unpack('H*hex', @iconv('UTF-8', 'UCS-2BE', $message));
return strtoupper($arr['hex']) . '&unicode=1';
}
}
return FALSE;
}
Functions
Name | Description |
---|---|
sms_clickatell_admin_form | Configuration form for gateway module |
sms_clickatell_admin_form_validate | |
sms_clickatell_balance | Get account balance |
sms_clickatell_command | Executes a command using the Clickatell API |
sms_clickatell_gateway_info | Implement hook_gateway_info() |
sms_clickatell_get_session_id | Get a new or existing Clickatell session ID |
sms_clickatell_map_message_status_code | Map a Clickatell message status code to an SMS Framework message status code |
sms_clickatell_map_response_code | Map a Clickatell gateway response status code to an SMS Framework gateway status code |
sms_clickatell_menu | Implement hook_menu() |
sms_clickatell_message_status_codes | Returns an array of message status codes and strings that are generated by the Clickatell gateway |
sms_clickatell_receive_message | Receive an SMS message and pass it into the SMS Framework |
sms_clickatell_receive_receipt | Receive a message receipt from Clickatell |
sms_clickatell_response_codes | Returns an array of response codes and messages that are generated by the Clickatell gateway |
sms_clickatell_send | Callback for sending messages. |
sms_clickatell_unicode | Converts a string to USC-2 encoding if neccessary. |