1<?php 2/** 3 * @package Net_SMS 4 */ 5 6/** 7 * HTTP_Request class. 8 */ 9require_once 'HTTP/Request2.php'; 10require_once 'Net/SMS.php'; 11 12/** 13 * Net_SMS_clickatell_http Class implements the HTTP API for accessing the 14 * Clickatell (www.clickatell.com) SMS gateway. 15 * 16 * Copyright 2003-2009 The Horde Project (http://www.horde.org/) 17 * 18 * See the enclosed file COPYING for license information (LGPL). If you 19 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. 20 * 21 * $Horde: framework/Net_SMS/SMS/clickatell_http.php,v 1.29 2008/01/02 11:12:11 jan Exp $ 22 * 23 * @see http://www.clickatell.com/developers/api_http.php 24 * @author Marko Djukic <marko@oblo.com> 25 * @package Net_SMS 26 */ 27class Net_SMS_clickatell_http extends Net_SMS { 28 29 var $_session_id = null; 30 var $_base_url = 'http://api.clickatell.com/http/'; 31 32 protected $request; 33 34 protected $username; 35 protected $password; 36 protected $api_id; 37 38 public function __construct($params = null, HTTP_Request2 $request) 39 { 40 parent::__construct($params); 41 $this->setRequest($request); 42 $this->setUsername(isset($params['user']) ? $params['user'] : null); 43 $this->setPassword(isset($params['password']) ? $params['password'] : null); 44 $this->setAPIId(isset($params['api_id']) ? $params['api_id'] : null); 45 } 46 47 public function setRequest(HTTP_Request2 $request) { 48 $this->request = $request; 49 } 50 51 /** 52 * An array of capabilities, so that the driver can report which operations 53 * it supports and which it doesn't. Possible values are:<pre> 54 * auth - The gateway require authentication before sending; 55 * batch - Batch sending is supported; 56 * multi - Sending of messages to multiple recipients is supported; 57 * receive - Whether this driver is capable of receiving SMS; 58 * credit - Is use of the gateway based on credits; 59 * addressbook - Are gateway addressbooks supported; 60 * lists - Gateway support for distribution lists. 61 * </pre> 62 * 63 * @var array 64 */ 65 var $capabilities = array('auth' => true, 66 'batch' => 100, 67 'multi' => true, 68 'receive' => false, 69 'credit' => true, 70 'addressbook' => false, 71 'lists' => false); 72 73 /** 74 * Authenticate at the gateway and set a session id if successful. Caching 75 * is used to minimise the http calls for subsequent messages. 76 * 77 * @access private 78 * 79 * @return mixed True on success or PEAR Error on failure. 80 */ 81 function _authenticate() 82 { 83 /* We have already authenticated so return true. */ 84 if (!empty($this->_session_id)) { 85 return true; 86 } 87 88 89 /* Set up the http authentication url. */ 90 $url = sprintf('auth?user=%s&password=%s&api_id=%s', 91 urlencode($this->getUsername()), 92 urlencode($this->getPassword()), 93 $this->getAPIId()); 94 95 /* Do the HTTP authentication and get the response. */ 96 $response = Net_SMS_clickatell_http::_callURL($url); 97 if (is_a($response, 'PEAR_Error')) { 98 return PEAR::raiseError(sprintf(_("Authentication failed. %s"), $response->getMessage())); 99 } 100 101 /* Split up the response. */ 102 $response = explode(':', $response); 103 if ($response[0] == 'OK') { 104 $this->_session_id = trim($response[1]); 105 return true; 106 } else { 107 return $this->getError($response[1], _("Authentication failed. %s")); 108 } 109 } 110 111 /** 112 * This function does the actual sending of the message. 113 * 114 * @access private 115 * 116 * @param array $message The array containing the message and its send 117 * parameters. 118 * @param array $to The recipients. 119 * 120 * @return array An array with the success status and additional 121 * information. 122 */ 123 function _send($message, $to) 124 { 125 /* Set up the http sending url. */ 126 $url = sprintf('sendmsg?session_id=%s&text=%s', 127 $this->_session_id, 128 urlencode($message['text'])); 129 130 $req_feat = 0; 131 if (!empty($message['send_params']['from'])) { 132 /* If source from is set, require it for transit gateways and append 133 to url. */ 134 $req_feat =+ 16; 135 $url .= '&from=' . urlencode($message['send_params']['from']); 136 } 137 if (!empty($message['send_params']['msg_type']) && 138 $message['send_params']['msg_type'] == 'SMS_FLASH') { 139 /* If message type is flash, require it for transit gateways. */ 140 $req_feat =+ 512; 141 $url .= '&msg_type=' . $message['send_params']['msg_type']; 142 } 143 if (!empty($req_feat)) { 144 /* If features have been required, add to url. */ 145 $url .= '&req_feat=' . $req_feat; 146 } 147 148 /* Append the recipients of this message and call the url. */ 149 foreach ($to as $key => $val) { 150 if (preg_match('/^.*?<?\+?(\d{7,})(>|$)/', $val, $matches)) { 151 $to[$key] = $matches[1]; 152 } else { 153 /* FIXME: Silently drop bad recipients. This should be logged 154 * and/or reported. */ 155 unset($to[$key]); 156 } 157 } 158 $to = implode(',', $to); 159 $url .= '&to=' . $to; 160 $response = trim($this->_callURL($url)); 161 162 /* Ugly parsing of the response, but that's how it comes back. */ 163 $lines = explode("\n", $response); 164 $response = array(); 165 166 if (count($lines) > 1) { 167 foreach ($lines as $line) { 168 $parts = explode('To:', $line); 169 $recipient = trim($parts[1]); 170 $outcome = explode(':', $parts[0]); 171 $response[$recipient] = array(($outcome[0] == 'ID' ? 1 : 0), $outcome[1]); 172 } 173 } else { 174 /* Single recipient. */ 175 $outcome = explode(':', $lines[0]); 176 $response[$to] = array(($outcome[0] == 'ID' ? 1 : 0), $outcome[1]); 177 } 178 179 return $response; 180 } 181 182 /** 183 * Returns the current credit balance on the gateway. 184 * 185 * @access private 186 * 187 * @return integer The credit balance available on the gateway. 188 */ 189 function _getBalance() 190 { 191 /* Set up the url and call it. */ 192 $url = sprintf('getbalance?session_id=%s', 193 $this->_session_id); 194 $response = trim($this->_callURL($url)); 195 196 /* Try splitting up the response. */ 197 $lines = explode('=', $response); 198 199 /* Split up the response. */ 200 $response = explode(':', $response); 201 if ($response[0] == 'Credit') { 202 return trim($response[1]); 203 } else { 204 return $this->getError($response[1], _("Could not check balance. %s")); 205 } 206 } 207 208 /** 209 * Identifies this gateway driver and returns a brief description. 210 * 211 * @return array Array of driver info. 212 */ 213 function getInfo() 214 { 215 return array( 216 'name' => _("Clickatell via HTTP"), 217 'desc' => _("This driver allows sending of messages through the Clickatell (http://clickatell.com) gateway, using the HTTP API"), 218 ); 219 } 220 221 /** 222 * Returns the required parameters for this gateway driver. 223 * 224 * @return array Array of required parameters. 225 */ 226 function getParams() 227 { 228 return array( 229 'user' => array('label' => _("Username"), 'type' => 'text'), 230 'password' => array('label' => _("Password"), 'type' => 'text'), 231 'api_id' => array('label' => _("API ID"), 'type' => 'text'), 232 ); 233 } 234 235 /** 236 * Returns the parameters that can be set as default for sending messages 237 * using this gateway driver and displayed when sending messages. 238 * 239 * @return array Array of parameters that can be set as default. 240 * @todo Set up batch fields/params, would be nice to have ringtone/logo 241 * support too, queue choice, unicode choice. 242 */ 243 function getDefaultSendParams() 244 { 245 $params = array(); 246 $params['from'] = array( 247 'label' => _("Source address"), 248 'type' => 'text'); 249 250 $params['deliv_time'] = array( 251 'label' => _("Delivery time"), 252 'type' => 'enum', 253 'params' => array(array('now' => _("immediate"), 'user' => _("user select")))); 254 255 $types = array('SMS_TEXT' => _("Standard"), 'SMS_FLASH' => _("Flash")); 256 $params['msg_type'] = array( 257 'label' => _("Message type"), 258 'type' => 'keyval_multienum', 259 'params' => array($types)); 260 261 return $params; 262 } 263 264 /** 265 * Returns the parameters for sending messages using this gateway driver, 266 * displayed when sending messages. These are filtered out using the 267 * default values set for the gateway. 268 * 269 * @return array Array of required parameters. 270 * @todo Would be nice to use a time/date setup rather than minutes from 271 * now for the delivery time. Upload field for ringtones/logos? 272 */ 273 function getSendParams($params) 274 { 275 if (empty($params['from'])) { 276 $params['from'] = array( 277 'label' => _("Source address"), 278 'type' => 'text'); 279 } 280 281 if ($params['deliv_time'] == 'user') { 282 $params['deliv_time'] = array( 283 'label' => _("Delivery time"), 284 'type' => 'int', 285 'desc' => _("Value in minutes from now.")); 286 } 287 288 if (count($params['msg_type']) > 1) { 289 $params['msg_type'] = array( 290 'label' => _("Message type"), 291 'type' => 'enum', 292 'params' => array($params['msg_type'])); 293 } else { 294 $params['msg_type'] = $params['msg_type'][0]; 295 } 296 297 return $params; 298 } 299 300 /** 301 * Returns a string representation of an error code. 302 * 303 * @param integer $error The error code to look up. 304 * @param string $text An existing error text to use to raise a 305 * PEAR Error. 306 * 307 * @return mixed A textual message corresponding to the error code or a 308 * PEAR Error if passed an existing error text. 309 * 310 * @todo Check which of these are actually required and trim down the 311 * list. 312 */ 313 function getError($error, $error_text = '') 314 { 315 /* Make sure we get only the number at the start of an error. */ 316 list($error) = explode(',', $error); 317 $error = trim($error); 318 319 /* An array of error codes returned by the gateway. */ 320 $errors = array('001' => _("Authentication failed"), 321 '002' => _("Unknown username or password."), 322 '003' => _("Session ID expired."), 323 '004' => _("Account frozen."), 324 '005' => _("Missing session ID."), 325 '007' => _("IP lockdown violation."), 326 '101' => _("Invalid or missing parameters."), 327 '102' => _("Invalid UDH. (User Data Header)."), 328 '103' => _("Unknown apimsgid (API Message ID)."), 329 '104' => _("Unknown climsgid (Client Message ID)."), 330 '105' => _("Invalid destination address."), 331 '106' => _("Invalid source address."), 332 '107' => _("Empty message."), 333 '108' => _("Invalid or missing api_id."), 334 '109' => _("Missing message ID."), 335 '110' => _("Error with email message."), 336 '111' => _("Invalid protocol."), 337 '112' => _("Invalid msg_type."), 338 '113' => _("Max message parts exceeded."), 339 '114' => _("Cannot route message to specified number."), 340 '115' => _("Message expired."), 341 '116' => _("Invalid unicode data."), 342 '201' => _("Invalid batch ID."), 343 '202' => _("No batch template."), 344 '301' => _("No credit left."), 345 '302' => _("Max allowed credit.")); 346 347 if (empty($error_text)) { 348 return $errors[$error]; 349 } else { 350 return PEAR::raiseError(sprintf($error_text, $errors[$error])); 351 } 352 } 353 354 /** 355 * Do the http call using a url passed to the function. 356 * 357 * @access private 358 * 359 * @param string $url The url to call. 360 * 361 * @return mixed The response on success or PEAR Error on failure. 362 */ 363 function _callURL($url) 364 { 365 /** @todo Shift to factory */ 366 $this->request->setMethod('POST'); 367 $this->request->setConfig('timeout', 5); 368 $this->request->setConfig('follow_redirects', true); 369 370 371 $this->request->setURL($this->_base_url . $url); 372 373 $response = $this->request->send(); 374 if ($response->getStatus() != 200) { 375 throw new Net_SMS_Exception(sprintf(_("Could not open %s."), $url)); 376 } 377 378 return $response->getBody(); 379 } 380 381 public function getUsername() { 382 return $this->username; 383 } 384 385 public function getPassword() { 386 return $this->password; 387 } 388 389 public function getAPIId() { 390 return $this->api_id; 391 } 392 393 public function setAPIId($id) { 394 $this->api_id = $id; 395 } 396 397 public function setUsername($username) { 398 $this->username = $username; 399 } 400 401 public function setPassword($password) { 402 $this->password = $password; 403 } 404} 405