Fix for CRM-12391
[civicrm-core.git] / tools / extensions / org.civicrm.sms.clickatell / org_civicrm_sms_clickatell.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.2 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2012 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2012
32 * $Id$
33 *
34 */
35class org_civicrm_sms_clickatell extends CRM_SMS_Provider {
36
37 /**
38 * api type to use to send a message
39 * @var string
40 */
41 protected $_apiType = 'http';
42
43 /**
44 * provider details
45 * @var string
46 */
47 protected $_providerInfo = array();
48
49 /**
50 * Clickatell API Server Session ID
51 *
52 * @var string
53 */
54 protected $_sessionID = NULL;
55
56 /**
57 * Curl handle resource id
58 *
59 */
60 protected $_ch;
61
62 /**
63 * Temporary file resource id
64 * @var resource
65 */
66 protected $_fp;
67
68 public $_apiURL = "https://api.clickatell.com";
69
70 protected $_messageType = array(
71 'SMS_TEXT',
72 'SMS_FLASH',
73 'SMS_NOKIA_OLOGO',
74 'SMS_NOKIA_GLOGO',
75 'SMS_NOKIA_PICTURE',
76 'SMS_NOKIA_RINGTONE',
77 'SMS_NOKIA_RTTL',
78 'SMS_NOKIA_CLEAN',
79 'SMS_NOKIA_VCARD',
80 'SMS_NOKIA_VCAL',
81 );
82
83 protected $_messageStatus = array(
84 '001' => 'Message unknown',
85 '002' => 'Message queued',
86 '003' => 'Delivered',
87 '004' => 'Received by recipient',
88 '005' => 'Error with message',
89 '006' => 'User cancelled message delivery',
90 '007' => 'Error delivering message',
91 '008' => 'OK',
92 '009' => 'Routing error',
93 '010' => 'Message expired',
94 '011' => 'Message queued for later delivery',
95 '012' => 'Out of credit',
96 );
97
98 /**
99 * We only need one instance of this object. So we use the singleton
100 * pattern and cache the instance in this variable
101 *
102 * @var object
103 * @static
104 */
105 static private $_singleton = array();
106
107 /**
108 * Constructor
109 *
110 * Create and auth a Clickatell session.
111 *
112 * @return void
113 */
114 function __construct($provider = array( ), $skipAuth = FALSE) {
115 // initialize vars
116 $this->_apiType = CRM_Utils_Array::value('api_type', $provider, 'http');
117 $this->_providerInfo = $provider;
118
119 if ($skipAuth) {
120 return TRUE;
121 }
122
123 // first create the curl handle
124
125 /**
126 * Reuse the curl handle
127 */
128 $this->_ch = curl_init();
129 if (!$this->_ch || !is_resource($this->_ch)) {
130 return PEAR::raiseError('Cannot initialise a new curl handle.');
131 }
132
133 curl_setopt($this->_ch, CURLOPT_TIMEOUT, 20);
134 curl_setopt($this->_ch, CURLOPT_VERBOSE, 1);
135 curl_setopt($this->_ch, CURLOPT_FAILONERROR, 1);
136 curl_setopt($this->_ch, CURLOPT_FOLLOWLOCATION, 1);
137 curl_setopt($this->_ch, CURLOPT_COOKIEJAR, "/dev/null");
138 curl_setopt($this->_ch, CURLOPT_SSL_VERIFYHOST, 2);
139 curl_setopt($this->_ch, CURLOPT_USERAGENT, 'CiviCRM - http://civicrm.org/');
140
141 $this->authenticate();
142 }
143
144 /**
145 * singleton function used to manage this object
146 *
147 * @return object
148 * @static
149 *
150 */
151 static function &singleton($providerParams = array(
152 ), $force = FALSE) {
153 $providerID = CRM_Utils_Array::value('provider_id', $providerParams);
154 $skipAuth = $providerID ? FALSE : TRUE;
155 $cacheKey = (int) $providerID;
156
157 if (!isset(self::$_singleton[$cacheKey]) || $force) {
158 $provider = array();
159 if ($providerID) {
160 $provider = CRM_SMS_BAO_Provider::getProviderInfo($providerID);
161 }
162 self::$_singleton[$cacheKey] = new org_civicrm_sms_clickatell($provider, $skipAuth);
163 }
164 return self::$_singleton[$cacheKey];
165 }
166
167 /**
168 * Authenticate to the Clickatell API Server.
169 *
170 * @return mixed true on sucess or PEAR_Error object
171 * @access public
172 * @since 1.1
173 */
174 function authenticate() {
175 $url = $this->_providerInfo['api_url'] . "/http/auth";
176
177 $postDataArray = array(
178 'user' => $this->_providerInfo['username'],
179 'password' => $this->_providerInfo['password'],
180 'api_id' => $this->_providerInfo['api_params']['api_id']
181 );
182
183 if (array_key_exists('is_test', $this->_providerInfo['api_params']) &&
184 $this->_providerInfo['api_params']['is_test'] == 1 ) {
185 $response = array('data' => 'OK:' . rand());
186 } else {
1e2f5f25
DS
187 $postData = $this->urlEncode($postDataArray);
188 $response = $this->curl($url, $postData);
6a488035
TO
189 }
190 if (PEAR::isError($response)) {
191 return $response;
192 }
193 $sess = explode(":", $response['data']);
194
195 $this->_sessionID = trim($sess[1]);
196
197 if ($sess[0] == "OK") {
198 return TRUE;
199 }
200 else {
201 return PEAR::raiseError($response['data']);
202 }
203 }
204
205 function formURLPostData($url, &$postDataArray, $id = NULL) {
206 $url = $this->_providerInfo['api_url'] . $url;
207 $postDataArray['session_id'] = $this->_sessionID;
208 if ($id) {
209 if (strlen($id) < 32 || strlen($id) > 32) {
210 return PEAR::raiseError('Invalid API Message Id');
211 }
212 $postDataArray['apimsgid'] = $id;
213 }
214 return $url;
215 }
216
217 /**
218 * Send an SMS Message via the Clickatell API Server
219 *
220 * @param array the message with a to/from/text
221 *
222 * @return mixed true on sucess or PEAR_Error object
223 * @access public
224 */
225 function send($recipients, $header, $message, $jobID = NULL) {
226 if ($this->_apiType = 'http') {
227 $postDataArray = array( );
228 $url = $this->formURLPostData("/http/sendmsg", $postDataArray);
229
230 if (array_key_exists('from', $this->_providerInfo['api_params'])) {
231 $postDataArray['from'] = $this->_providerInfo['api_params']['from'];
232 }
233 $postDataArray['to'] = $header['To'];
234 $postDataArray['text'] = substr($message, 0, 160); // max of 160 characters, is probably not multi-lingual
235 if (array_key_exists('mo', $this->_providerInfo['api_params'])) {
236 $postDataArray['mo'] = $this->_providerInfo['api_params']['mo'];
237 }
238 // sendmsg with callback request:
239 $postDataArray['callback'] = 3;
240
241 $isTest = 0;
242 if (array_key_exists('is_test', $this->_providerInfo['api_params']) &&
243 $this->_providerInfo['api_params']['is_test'] == 1
244 ) {
245 $isTest = 1;
246 }
247
248 /**
249 * Check if we are using a queue when sending as each account
250 * with Clickatell is assigned three queues namely 1, 2 and 3.
251 */
252 if (isset($header['queue']) && is_numeric($header['queue'])) {
253 if (in_array($header['queue'], range(1, 3))) {
254 $postDataArray['queue'] = $header['queue'];
255 }
256 }
257
258 /**
259 * Must we escalate message delivery if message is stuck in
260 * the queue at Clickatell?
261 */
262 if (isset($header['escalate']) && !empty($header['escalate'])) {
263 if (is_numeric($header['escalate'])) {
264 if (in_array($header['escalate'], range(1, 2))) {
265 $postDataArray['escalate'] = $header['escalate'];
266 }
267 }
268 }
269
270 if ($isTest == 1) {
271 $response = array('data' => 'ID:' . rand());
272 }
273 else {
1e2f5f25 274 $postData = $this->urlEncode($postDataArray);
6a488035
TO
275 $response = $this->curl($url, $postData);
276 }
277 if (PEAR::isError($response)) {
278 return $response;
279 }
280 $send = explode(":", $response['data']);
281
282 if ($send[0] == "ID") {
283 $this->createActivity($send[1], $message, $header, $jobID);
284 return $send[1];
285 }
286 else {
d65e1a68
TW
287 // TODO: Should add a failed activity instead.
288
f53ea1ce 289 CRM_Core_Error::debug_log_message($response['data'] . " - for phone: {$postDataArray['to']}");
d65e1a68 290 return;
6a488035
TO
291 }
292 }
293 }
294
295 function callback() {
296 $apiMsgID = $this->retrieve('apiMsgId', 'String');
297
298 $activity = new CRM_Activity_DAO_Activity();
299 $activity->result = $apiMsgID;
300
301 if ($activity->find(TRUE)) {
302 $actStatusIDs = array_flip(CRM_Core_OptionGroup::values('activity_status'));
303
304 $status = $this->retrieve('status', 'String');
305 switch ($status) {
306 case "001":
307 $statusID = $actStatusIDs['Cancelled'];
308 $clickStat = $this->_messageStatus[$status] . " - Message Unknown";
309 break;
310
311 case "002":
312 $statusID = $actStatusIDs['Scheduled'];
313 $clickStat = $this->_messageStatus[$status] . " - Message Queued";
314 break;
315
316 case "003":
317 $statusID = $actStatusIDs['Completed'];
318 $clickStat = $this->_messageStatus[$status] . " - Delivered to Gateway";
319 break;
320
321 case "004":
322 $statusID = $actStatusIDs['Completed'];
323 $clickStat = $this->_messageStatus[$status] . " - Received by Recipient";
324 break;
325
326 case "005":
327 $statusID = $actStatusIDs['Cancelled'];
328 $clickStat = $this->_messageStatus[$status] . " - Error with Message";
329 break;
330
331 case "006":
332 $statusID = $actStatusIDs['Cancelled'];
333 $clickStat = $this->_messageStatus[$status] . " - User cancelled message";
334 break;
335
336 case "007":
337 $statusID = $actStatusIDs['Cancelled'];
338 $clickStat = $this->_messageStatus[$status] . " - Error delivering message";
339 break;
340
341 case "008":
342 $statusID = $actStatusIDs['Completed'];
343 $clickStat = $this->_messageStatus[$status] . " - Ok, Message Received by Gateway";
344 break;
345
346 case "009":
347 $statusID = $actStatusIDs['Cancelled'];
348 $clickStat = $this->_messageStatus[$status] . " - Routing Error";
349 break;
350
351 case "010":
352 $statusID = $actStatusIDs['Cancelled'];
353 $clickStat = $this->_messageStatus[$status] . " - Message Expired";
354 break;
355
356 case "011":
357 $statusID = $actStatusIDs['Scheduled'];
358 $clickStat = $this->_messageStatus[$status] . " - Message Queued for Later";
359 break;
360
361 case "012":
362 $statusID = $actStatusIDs['Cancelled'];
363 $clickStat = $this->_messageStatus[$status] . " - Out of Credit";
364 break;
365 }
366
367 if ($statusID) {
368 // update activity with status + msg in location
369 $activity->status_id = $statusID;
370 $activity->location = $clickStat;
371 $activity->activity_date_time = CRM_Utils_Date::isoToMysql($activity->activity_date_time);
372 $activity->save();
373 CRM_Core_Error::debug_log_message("SMS Response updated for apiMsgId={$apiMsgID}.");
374 return TRUE;
375 }
376 }
377
378 // if no update is done
379 CRM_Core_Error::debug_log_message("Could not update SMS Response for apiMsgId={$apiMsgID}.");
380 return FALSE;
381 }
382
383 function inbound() {
384 $like = "";
385 $fromPhone = $this->retrieve('from', 'String');
386 $fromPhone = $this->formatPhone($this->stripPhone($fromPhone), $like, "like");
387
f53ea1ce 388 return parent::processInbound($fromPhone, $this->retrieve('text', 'String'), NULL, $this->retrieve('moMsgId', 'String'));
6a488035
TO
389 }
390
391 /**
392 * Perform curl stuff
393 *
394 * @param string URL to call
395 * @param string HTTP Post Data
396 *
397 * @return mixed HTTP response body or PEAR Error Object
398 * @access private
399 */
400 function curl($url, $postData) {
401 $this->_fp = tmpfile();
402
403 curl_setopt($this->_ch, CURLOPT_URL, $url);
404 curl_setopt($this->_ch, CURLOPT_POST, 1);
405 curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $postData);
406 curl_setopt($this->_ch, CURLOPT_FILE, $this->_fp);
407
408 $status = curl_exec($this->_ch);
409 $response['http_code'] = curl_getinfo($this->_ch, CURLINFO_HTTP_CODE);
410
411 if (empty($response['http_code'])) {
412 return PEAR::raiseError('No HTTP Status Code was returned.');
413 }
414 elseif ($response['http_code'] === 0) {
415 return PEAR::raiseError('Cannot connect to the Clickatell API Server.');
416 }
417
418 if ($status) {
419 $response['error'] = curl_error($this->_ch);
420 $response['errno'] = curl_errno($this->_ch);
421 }
422
423 rewind($this->_fp);
424
425 $pairs = "";
426 while ($str = fgets($this->_fp, 4096)) {
427 $pairs .= $str;
428 }
429 fclose($this->_fp);
430
431 $response['data'] = $pairs;
432 unset($pairs);
433 asort($response);
434
435 return ($response);
436 }
437}
438