3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
35 class org_civicrm_sms_clickatell
extends CRM_SMS_Provider
{
38 * api type to use to send a message
41 protected $_apiType = 'http';
47 protected $_providerInfo = array();
50 * Clickatell API Server Session ID
54 protected $_sessionID = NULL;
57 * Curl handle resource id
63 * Temporary file resource id
68 public $_apiURL = "https://api.clickatell.com";
70 protected $_messageType = array(
83 protected $_messageStatus = array(
84 '001' => 'Message unknown',
85 '002' => 'Message queued',
87 '004' => 'Received by recipient',
88 '005' => 'Error with message',
89 '006' => 'User cancelled message delivery',
90 '007' => 'Error delivering message',
92 '009' => 'Routing error',
93 '010' => 'Message expired',
94 '011' => 'Message queued for later delivery',
95 '012' => 'Out of credit',
99 * We only need one instance of this object. So we use the singleton
100 * pattern and cache the instance in this variable
105 static private $_singleton = array();
110 * Create and auth a Clickatell session.
112 * @param array $provider
113 * @param bool $skipAuth
115 * @return \org_civicrm_sms_clickatell
117 function __construct($provider = array( ), $skipAuth = FALSE) {
119 $this->_apiType
= CRM_Utils_Array
::value('api_type', $provider, 'http');
120 $this->_providerInfo
= $provider;
126 // first create the curl handle
129 * Reuse the curl handle
131 $this->_ch
= curl_init();
132 if (!$this->_ch ||
!is_resource($this->_ch
)) {
133 return PEAR
::raiseError('Cannot initialise a new curl handle.');
136 curl_setopt($this->_ch
, CURLOPT_TIMEOUT
, 20);
137 curl_setopt($this->_ch
, CURLOPT_VERBOSE
, 1);
138 curl_setopt($this->_ch
, CURLOPT_FAILONERROR
, 1);
139 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
140 curl_setopt($this->_ch
, CURLOPT_FOLLOWLOCATION
, 1);
142 curl_setopt($this->_ch
, CURLOPT_COOKIEJAR
, "/dev/null");
143 curl_setopt($this->_ch
, CURLOPT_SSL_VERIFYHOST
, 2);
144 curl_setopt($this->_ch
, CURLOPT_USERAGENT
, 'CiviCRM - http://civicrm.org/');
146 $this->authenticate();
150 * singleton function used to manage this object
152 * @param array $providerParams
157 static function &singleton($providerParams = array(
159 $providerID = CRM_Utils_Array
::value('provider_id', $providerParams);
160 $skipAuth = $providerID ?
FALSE : TRUE;
161 $cacheKey = (int) $providerID;
163 if (!isset(self
::$_singleton[$cacheKey]) ||
$force) {
166 $provider = CRM_SMS_BAO_Provider
::getProviderInfo($providerID);
168 self
::$_singleton[$cacheKey] = new org_civicrm_sms_clickatell($provider, $skipAuth);
170 return self
::$_singleton[$cacheKey];
174 * Authenticate to the Clickatell API Server.
176 * @return mixed true on sucess or PEAR_Error object
180 function authenticate() {
181 $url = $this->_providerInfo
['api_url'] . "/http/auth";
183 $postDataArray = array(
184 'user' => $this->_providerInfo
['username'],
185 'password' => $this->_providerInfo
['password'],
186 'api_id' => $this->_providerInfo
['api_params']['api_id']
189 if (array_key_exists('is_test', $this->_providerInfo
['api_params']) &&
190 $this->_providerInfo
['api_params']['is_test'] == 1 ) {
191 $response = array('data' => 'OK:' . rand());
193 $postData = $this->urlEncode($postDataArray);
194 $response = $this->curl($url, $postData);
196 if (PEAR
::isError($response)) {
199 $sess = explode(":", $response['data']);
201 $this->_sessionID
= trim($sess[1]);
203 if ($sess[0] == "OK") {
207 return PEAR
::raiseError($response['data']);
213 * @param $postDataArray
216 * @return object|string
218 function formURLPostData($url, &$postDataArray, $id = NULL) {
219 $url = $this->_providerInfo
['api_url'] . $url;
220 $postDataArray['session_id'] = $this->_sessionID
;
222 if (strlen($id) < 32 ||
strlen($id) > 32) {
223 return PEAR
::raiseError('Invalid API Message Id');
225 $postDataArray['apimsgid'] = $id;
231 * Send an SMS Message via the Clickatell API Server
237 * @param null $userID
238 * @internal param \the $array message with a to/from/text
240 * @return mixed true on sucess or PEAR_Error object
243 function send($recipients, $header, $message, $jobID = NULL, $userID = NULL) {
244 if ($this->_apiType
== 'http') {
245 $postDataArray = array( );
246 $url = $this->formURLPostData("/http/sendmsg", $postDataArray);
248 if (array_key_exists('from', $this->_providerInfo
['api_params'])) {
249 $postDataArray['from'] = $this->_providerInfo
['api_params']['from'];
251 if (array_key_exists('concat', $this->_providerInfo
['api_params'])) {
252 $postDataArray['concat'] = $this->_providerInfo
['api_params']['concat'];
255 $postDataArray['to'] = $header['To'];
256 $postDataArray['text'] = utf8_decode(substr($message, 0, 460)); // max of 460 characters, is probably not multi-lingual
257 if (array_key_exists('mo', $this->_providerInfo
['api_params'])) {
258 $postDataArray['mo'] = $this->_providerInfo
['api_params']['mo'];
260 // sendmsg with callback request:
261 $postDataArray['callback'] = 3;
264 if (array_key_exists('is_test', $this->_providerInfo
['api_params']) &&
265 $this->_providerInfo
['api_params']['is_test'] == 1
271 * Check if we are using a queue when sending as each account
272 * with Clickatell is assigned three queues namely 1, 2 and 3.
274 if (isset($header['queue']) && is_numeric($header['queue'])) {
275 if (in_array($header['queue'], range(1, 3))) {
276 $postDataArray['queue'] = $header['queue'];
281 * Must we escalate message delivery if message is stuck in
282 * the queue at Clickatell?
284 if (isset($header['escalate']) && !empty($header['escalate'])) {
285 if (is_numeric($header['escalate'])) {
286 if (in_array($header['escalate'], range(1, 2))) {
287 $postDataArray['escalate'] = $header['escalate'];
293 $response = array('data' => 'ID:' . rand());
296 $postData = $this->urlEncode($postDataArray);
297 $response = $this->curl($url, $postData);
299 if (PEAR
::isError($response)) {
302 $send = explode(":", $response['data']);
304 if ($send[0] == "ID") {
305 $this->createActivity($send[1], $message, $header, $jobID, $userID);
309 // TODO: Should add a failed activity instead.
310 CRM_Core_Error
::debug_log_message($response['data'] . " - for phone: {$postDataArray['to']}");
311 return PEAR
::raiseError($response['data'], null, PEAR_ERROR_RETURN
);
319 function callback() {
320 $apiMsgID = $this->retrieve('apiMsgId', 'String');
322 $activity = new CRM_Activity_DAO_Activity();
323 $activity->result
= $apiMsgID;
325 if ($activity->find(TRUE)) {
326 $actStatusIDs = array_flip(CRM_Core_OptionGroup
::values('activity_status'));
328 $status = $this->retrieve('status', 'String');
331 $statusID = $actStatusIDs['Cancelled'];
332 $clickStat = $this->_messageStatus
[$status] . " - Message Unknown";
336 $statusID = $actStatusIDs['Scheduled'];
337 $clickStat = $this->_messageStatus
[$status] . " - Message Queued";
341 $statusID = $actStatusIDs['Completed'];
342 $clickStat = $this->_messageStatus
[$status] . " - Delivered to Gateway";
346 $statusID = $actStatusIDs['Completed'];
347 $clickStat = $this->_messageStatus
[$status] . " - Received by Recipient";
351 $statusID = $actStatusIDs['Cancelled'];
352 $clickStat = $this->_messageStatus
[$status] . " - Error with Message";
356 $statusID = $actStatusIDs['Cancelled'];
357 $clickStat = $this->_messageStatus
[$status] . " - User cancelled message";
361 $statusID = $actStatusIDs['Cancelled'];
362 $clickStat = $this->_messageStatus
[$status] . " - Error delivering message";
366 $statusID = $actStatusIDs['Completed'];
367 $clickStat = $this->_messageStatus
[$status] . " - Ok, Message Received by Gateway";
371 $statusID = $actStatusIDs['Cancelled'];
372 $clickStat = $this->_messageStatus
[$status] . " - Routing Error";
376 $statusID = $actStatusIDs['Cancelled'];
377 $clickStat = $this->_messageStatus
[$status] . " - Message Expired";
381 $statusID = $actStatusIDs['Scheduled'];
382 $clickStat = $this->_messageStatus
[$status] . " - Message Queued for Later";
386 $statusID = $actStatusIDs['Cancelled'];
387 $clickStat = $this->_messageStatus
[$status] . " - Out of Credit";
392 // update activity with status + msg in location
393 $activity->status_id
= $statusID;
394 $activity->location
= $clickStat;
395 $activity->activity_date_time
= CRM_Utils_Date
::isoToMysql($activity->activity_date_time
);
397 CRM_Core_Error
::debug_log_message("SMS Response updated for apiMsgId={$apiMsgID}.");
402 // if no update is done
403 CRM_Core_Error
::debug_log_message("Could not update SMS Response for apiMsgId={$apiMsgID}.");
408 * @return $this|null|object
412 $fromPhone = $this->retrieve('from', 'String');
413 $fromPhone = $this->formatPhone($this->stripPhone($fromPhone), $like, "like");
415 return parent
::processInbound($fromPhone, $this->retrieve('text', 'String'), NULL, $this->retrieve('moMsgId', 'String'));
421 * @param string URL to call
422 * @param string HTTP Post Data
424 * @return mixed HTTP response body or PEAR Error Object
427 function curl($url, $postData) {
428 $this->_fp
= tmpfile();
430 curl_setopt($this->_ch
, CURLOPT_URL
, $url);
431 curl_setopt($this->_ch
, CURLOPT_POST
, 1);
432 curl_setopt($this->_ch
, CURLOPT_POSTFIELDS
, $postData);
433 curl_setopt($this->_ch
, CURLOPT_FILE
, $this->_fp
);
435 $status = curl_exec($this->_ch
);
436 $response['http_code'] = curl_getinfo($this->_ch
, CURLINFO_HTTP_CODE
);
438 if (empty($response['http_code'])) {
439 return PEAR
::raiseError('No HTTP Status Code was returned.');
441 elseif ($response['http_code'] === 0) {
442 return PEAR
::raiseError('Cannot connect to the Clickatell API Server.');
446 $response['error'] = curl_error($this->_ch
);
447 $response['errno'] = curl_errno($this->_ch
);
453 while ($str = fgets($this->_fp
, 4096)) {
458 $response['data'] = $pairs;