Commit | Line | Data |
---|---|---|
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 | */ | |
35 | class 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 |