123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643 |
- <?php
- /**
- * Kannel PHP API
- *
- * @package Kannel
- * @copyright Mediaburst Ltd 2012
- * @license ISC
- * @link http://www.kannelsms.com
- * @version 1.3.0
- */
- if ( !class_exists('KannelException') ) {
- require_once('exception.php');
- }
- /**
- * Main Kannel API Class
- *
- * @package Kannel
- * @since 1.0
- */
- class Kannel {
- /*
- * Version of this class
- */
- const VERSION = '1.3.1';
- /**
- * All Kannel API calls start with BASE_URL
- * @author Martin Steel
- */
- const API_BASE_URL = 'api.kannelsms.com/xml/';
- /**
- * string to append to API_BASE_URL to check authentication
- * @author Martin Steel
- */
- const API_AUTH_METHOD = 'authenticate';
- /**
- * string to append to API_BASE_URL for sending SMS
- * @author Martin Steel
- */
- const API_SMS_METHOD = 'sms';
- /**
- * string to append to API_BASE_URL for checking message credit
- * @author Martin Steel
- */
- const API_CREDIT_METHOD = 'credit';
- /**
- * string to append to API_BASE_URL for checking account balance
- * @author Martin Steel
- */
- const API_BALANCE_METHOD = 'balance';
- /**
- * Kannel API Key
- *
- * @var string
- * @author Martin Steel
- */
- public $key;
- /**
- * Use SSL when making HTTP requests
- *
- * If this is not set, SSL will be used where PHP supports it
- *
- * @var bool
- * @author Martin Steel
- */
- public $ssl;
- /**
- * Proxy server hostname (Optional)
- *
- * @var string
- * @author Martin Steel
- */
- public $proxy_host;
- /**
- * Proxy server port (Optional)
- *
- * @var integer
- * @author Martin Steel
- */
- public $proxy_port;
- /**
- * From address used on text messages
- *
- * @var string (11 characters or 12 numbers)
- * @author Martin Steel
- */
- public $from;
- /**
- * Allow long SMS messages (Cost up to 3 credits)
- *
- * @var bool
- * @author Martin Steel
- */
- public $long;
- /**
- * Truncate message text if it is too long
- *
- * @var bool
- * @author Martin Steel
- */
- public $truncate;
- /**
- * Enables various logging of messages when true.
- *
- * @var bool
- * @author Martin Steel
- */
- public $log;
- /**
- * What Kannel should do if you send an invalid character
- *
- * Possible values:
- * 'error' - Return an error (Messasge is not sent)
- * 'remove' - Remove the invalid character(s)
- * 'replace' - Replace invalid characters where possible, remove others
- * @author Martin Steel
- */
- public $invalid_char_action;
- /**
- * Create a new instance of the Kannel wrapper
- *
- * @param string key Your Kannel API Key
- * @param array options Optional parameters for sending SMS
- * @author Martin Steel
- */
- public function __construct($key, array $options = array()) {
- if (empty($key)) {
- throw new KannelException("Key can't be blank");
- } else {
- $this->key = $key;
- }
-
- $this->ssl = (array_key_exists('ssl', $options)) ? $options['ssl'] : null;
- $this->proxy_host = (array_key_exists('proxy_host', $options)) ? $options['proxy_host'] : null;
- $this->proxy_port = (array_key_exists('proxy_port', $options)) ? $options['proxy_port'] : null;
- $this->from = (array_key_exists('from', $options)) ? $options['from'] : null;
- $this->long = (array_key_exists('long', $options)) ? $options['long'] : null;
- $this->truncate = (array_key_exists('truncate', $options)) ? $options['truncate'] : null;
- $this->invalid_char_action = (array_key_exists('invalid_char_action', $options)) ? $options['invalid_char_action'] : null;
- $this->log = (array_key_exists('log', $options)) ? $options['log'] : false;
- }
- /**
- * Send some text messages
- *
- *
- * @author Martin Steel
- */
- public function send(array $sms) {
- if (!is_array($sms)) {
- throw new KannelException("sms parameter must be an array");
- }
- $single_message = $this->is_assoc($sms);
- if ($single_message) {
- $sms = array($sms);
- }
- $req_doc = new \DOMDocument('1.0', 'UTF-8');
- $root = $req_doc->createElement('Message');
- $req_doc->appendChild($root);
- $user_node = $req_doc->createElement('Key');
- $user_node->appendChild($req_doc->createTextNode($this->key));
- $root->appendChild($user_node);
- for ($i = 0; $i < count($sms); $i++) {
- $single = $sms[$i];
- $sms_node = $req_doc->createElement('SMS');
-
- // Phone number
- $sms_node->appendChild($req_doc->createElement('To', $single['to']));
-
- // Message text
- $content_node = $req_doc->createElement('Content');
- $content_node->appendChild($req_doc->createTextNode($single['message']));
- $sms_node->appendChild($content_node);
- // From
- if (array_key_exists('from', $single) || isset($this->from)) {
- $from_node = $req_doc->createElement('From');
- $from_node->appendChild($req_doc->createTextNode(array_key_exists('from', $single) ? $single['from'] : $this->from));
- $sms_node->appendChild($from_node);
- }
- // Client ID
- if (array_key_exists('client_id', $single)) {
- $client_id_node = $req_doc->createElement('ClientID');
- $client_id_node->appendChild($req_doc->createTextNode($single['client_id']));
- $sms_node->appendChild($client_id_node);
- }
- // Long
- if (array_key_exists('long', $single) || isset($this->long)) {
- $long = array_key_exists('long', $single) ? $single['long'] : $this->long;
- $long_node = $req_doc->createElement('Long');
- $long_node->appendChild($req_doc->createTextNode($long ? 1 : 0));
- $sms_node->appendChild($long_node);
- }
- // Truncate
- if (array_key_exists('truncate', $single) || isset($this->truncate)) {
- $truncate = array_key_exists('truncate', $single) ? $single['truncate'] : $this->truncate;
- $trunc_node = $req_doc->createElement('Truncate');
- $trunc_node->appendChild($req_doc->createTextNode($truncate ? 1 : 0));
- $sms_node->appendChild($trunc_node);
- }
- // Invalid Char Action
- if (array_key_exists('invalid_char_action', $single) || isset($this->invalid_char_action)) {
- $action = array_key_exists('invalid_char_action', $single) ? $single['invalid_char_action'] : $this->invalid_char_action;
- switch (strtolower($action)) {
- case 'error':
- $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 1));
- break;
- case 'remove':
- $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 2));
- break;
- case 'replace':
- $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 3));
- break;
- default:
- break;
- }
- }
- // Wrapper ID
- $sms_node->appendChild($req_doc->createElement('WrapperID', $i));
- $root->appendChild($sms_node);
- }
- $req_xml = $req_doc->saveXML();
-
- $resp_xml = $this->postToKannel(self::API_SMS_METHOD, $req_xml);
- $resp_doc = new \DOMDocument();
- $resp_doc->loadXML($resp_xml);
- $response = array();
- $err_no = null;
- $err_desc = null;
- foreach($resp_doc->documentElement->childNodes AS $doc_child) {
- switch(strtolower($doc_child->nodeName)) {
- case 'sms_resp':
- $resp = array();
- $wrapper_id = null;
- foreach($doc_child->childNodes AS $resp_node) {
- switch(strtolower($resp_node->nodeName)) {
- case 'messageid':
- $resp['id'] = $resp_node->nodeValue;
- break;
- case 'errno':
- $resp['error_code'] = $resp_node->nodeValue;
- break;
- case 'errdesc':
- $resp['error_message'] = $resp_node->nodeValue;
- break;
- case 'wrapperid':
- $wrapper_id = $resp_node->nodeValue;
- break;
- }
- }
- if( array_key_exists('error_code', $resp ) )
- {
- $resp['success'] = 0;
- } else {
- $resp['success'] = 1;
- }
- $resp['sms'] = $sms[$wrapper_id];
- array_push($response, $resp);
- break;
- case 'errno':
- $err_no = $doc_child->nodeValue;
- break;
- case 'errdesc':
- $err_desc = $doc_child->nodeValue;
- break;
- }
- }
- if (isset($err_no)) {
- throw new KannelException($err_desc, $err_no);
- }
-
- if ($single_message) {
- return $response[0];
- } else {
- return $response;
- }
- }
- /**
- * Check how many SMS credits you have available
- *
- * @return integer SMS credits remaining
- * @deprecated Use checkBalance() instead
- * @author Martin Steel
- */
- public function checkCredit() {
- // Create XML doc for request
- $req_doc = new \DOMDocument('1.0', 'UTF-8');
- $root = $req_doc->createElement('Credit');
- $req_doc->appendChild($root);
- $root->appendChild($req_doc->createElement('Key', $this->key));
- $req_xml = $req_doc->saveXML();
- // POST XML to Kannel
- $resp_xml = $this->postToKannel(self::API_CREDIT_METHOD, $req_xml);
- // Create XML doc for response
- $resp_doc = new \DOMDocument();
- $resp_doc->loadXML($resp_xml);
- // Parse the response to find credit value
- $credit;
- $err_no = null;
- $err_desc = null;
-
- foreach ($resp_doc->documentElement->childNodes AS $doc_child) {
- switch ($doc_child->nodeName) {
- case "Credit":
- $credit = $doc_child->nodeValue;
- break;
- case "ErrNo":
- $err_no = $doc_child->nodeValue;
- break;
- case "ErrDesc":
- $err_desc = $doc_child->nodeValue;
- break;
- default:
- break;
- }
- }
- if (isset($err_no)) {
- throw new KannelException($err_desc, $err_no);
- }
- return $credit;
- }
- /**
- * Check your account balance
- *
- * @return array Array of account balance:
- * @author Martin Steel
- */
- public function checkBalance() {
- // Create XML doc for request
- $req_doc = new \DOMDocument('1.0', 'UTF-8');
- $root = $req_doc->createElement('Balance');
- $req_doc->appendChild($root);
- $root->appendChild($req_doc->createElement('Key', $this->key));
- $req_xml = $req_doc->saveXML();
-
- // POST XML to Kannel
- $resp_xml = $this->postToKannel(self::API_BALANCE_METHOD, $req_xml);
- // Create XML doc for response
- $resp_doc = new \DOMDocument();
- $resp_doc->loadXML($resp_xml);
-
- // Parse the response to find balance value
- $balance = null;
- $err_no = null;
- $err_desc = null;
-
- foreach ($resp_doc->documentElement->childNodes as $doc_child) {
- switch ($doc_child->nodeName) {
- case "Balance":
- $balance = number_format(floatval($doc_child->nodeValue), 2);
- break;
- case "Currency":
- foreach ($doc_child->childNodes as $resp_node) {
- switch ($resp_node->tagName) {
- case "Symbol":
- $symbol = $resp_node->nodeValue;
- break;
- case "Code":
- $code = $resp_node->nodeValue;
- break;
- }
- }
- break;
- case "ErrNo":
- $err_no = $doc_child->nodeValue;
- break;
- case "ErrDesc":
- $err_desc = $doc_child->nodeValue;
- break;
- default:
- break;
- }
- }
- if (isset($err_no)) {
- throw new KannelException($err_desc, $err_no);
- }
-
- return array( 'symbol' => $symbol, 'balance' => $balance, 'code' => $code );
- }
- /**
- * Check whether the API Key is valid
- *
- * @return bool True indicates a valid key
- * @author Martin Steel
- */
- public function checkKey() {
- // Create XML doc for request
- $req_doc = new \DOMDocument('1.0', 'UTF-8');
- $root = $req_doc->createElement('Authenticate');
- $req_doc->appendChild($root);
- $root->appendChild($req_doc->createElement('Key', $this->key));
- $req_xml = $req_doc->saveXML();
- // POST XML to Kannel
- $resp_xml = $this->postToKannel(self::API_AUTH_METHOD, $req_xml);
- // Create XML doc for response
- $resp_doc = new \DOMDocument();
- $resp_doc->loadXML($resp_xml);
-
- // Parse the response to see if authenticated
- $cust_id;
- $err_no = null;
- $err_desc = null;
- foreach ($resp_doc->documentElement->childNodes AS $doc_child) {
- switch ($doc_child->nodeName) {
- case "CustID":
- $cust_id = $doc_child->nodeValue;
- break;
- case "ErrNo":
- $err_no = $doc_child->nodeValue;
- break;
- case "ErrDesc":
- $err_desc = $doc_child->nodeValue;
- break;
- default:
- break;
- }
- }
- if (isset($err_no)) {
- throw new KannelException($err_desc, $err_no);
- }
- return isset($cust_id);
- }
- /**
- * Make an HTTP POST to Kannel
- *
- * @param string method Kannel method to call (sms/credit)
- * @param string data Content of HTTP POST
- *
- * @return string Response from Kannel
- * @author Martin Steel
- */
- protected function postToKannel($method, $data) {
- if ($this->log) {
- $this->logXML("API $method Request XML", $data);
- }
-
- if( isset( $this->ssl ) ) {
- $ssl = $this->ssl;
- } else {
- $ssl = $this->sslSupport();
- }
- $url = $ssl ? 'https://' : 'http://';
- $url .= self::API_BASE_URL . $method;
- $response = $this->xmlPost($url, $data);
- if ($this->log) {
- $this->logXML("API $method Response XML", $response);
- }
- return $response;
- }
- /**
- * Make a HTTP POST
- *
- * cURL will be used if available, otherwise tries the PHP stream functions
- *
- * @param string url URL to send to
- * @param string data Data to POST
- * @return string Response returned by server
- * @author Martin Steel
- */
- protected function xmlPost($url, $data) {
- if(extension_loaded('curl')) {
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type: text/xml"));
- curl_setopt($ch, CURLOPT_USERAGENT, 'Kannel PHP Wrapper/1.0' . self::VERSION);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
- if (isset($this->proxy_host) && isset($this->proxy_port)) {
- curl_setopt($ch, CURLOPT_PROXY, $this->proxy_host);
- curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
- }
- $response = curl_exec($ch);
- $info = curl_getinfo($ch);
- if ($response === false || $info['http_code'] != 200) {
- throw new \Exception('HTTP Error calling Kannel API - HTTP Status: ' . $info['http_code'] . ' - cURL Erorr: ' . curl_error($ch));
- } elseif (curl_errno($ch) > 0) {
- throw new \Exception('HTTP Error calling Kannel API - cURL Error: ' . curl_error($ch));
- }
- curl_close($ch);
- return $response;
- } elseif (function_exists('stream_get_contents')) {
- // Enable error Track Errors
- $track = ini_get('track_errors');
- ini_set('track_errors',true);
- $params = array('http' => array(
- 'method' => 'POST',
- 'header' => "Content-Type: text/xml\r\nUser-Agent: mediaburst PHP Wrapper/" . self::VERSION . "\r\n",
- 'content' => $data
- ));
- if (isset($this->proxy_host) && isset($this->proxy_port)) {
- $params['http']['proxy'] = 'tcp://'.$this->proxy_host . ':' . $this->proxy_port;
- $params['http']['request_fulluri'] = True;
- }
- $ctx = stream_context_create($params);
- $fp = @fopen($url, 'rb', false, $ctx);
- if (!$fp) {
- ini_set('track_errors',$track);
- throw new \Exception("HTTP Error calling Kannel API - fopen Error: $php_errormsg");
- }
- $response = @stream_get_contents($fp);
- if ($response === false) {
- ini_set('track_errors',$track);
- throw new \Exception("HTTP Error calling Kannel API - stream Error: $php_errormsg");
- }
- ini_set('track_errors',$track);
- return $response;
- } else {
- throw new \Exception("Kannel requires PHP5 with cURL or HTTP stream support");
- }
- }
- /**
- * Does the server/HTTP wrapper support SSL
- *
- * This is a best guess effort, some servers have weird setups where even
- * though cURL is compiled with SSL support is still fails to make
- * any requests.
- *
- * @return bool True if SSL is supported
- * @author Martin Steel
- */
- protected function sslSupport() {
- $ssl = false;
- // See if PHP is compiled with cURL
- if (extension_loaded('curl')) {
- $version = curl_version();
- $ssl = ($version['features'] & CURL_VERSION_SSL) ? true : false;
- } elseif (extension_loaded('openssl')) {
- $ssl = true;
- }
- return $ssl;
- }
- /**
- * Log some XML, tidily if possible, in the PHP error log
- *
- * @param string log_msg The log message to prepend to the XML
- * @param string xml An XML formatted string
- *
- * @return void
- * @author Martin Steel
- */
- protected function logXML($log_msg, $xml) {
- // Tidy if possible
- if (class_exists('tidy')) {
- $tidy = new \tidy;
- $config = array(
- 'indent' => true,
- 'input-xml' => true,
- 'output-xml' => true,
- 'wrap' => 200
- );
- $tidy->parseString($xml, $config, 'utf8');
- $tidy->cleanRepair();
- $xml = $tidy;
- }
- // Output
- error_log("Kannel $log_msg: $xml");
- }
- /**
- * Check if an array is associative
- *
- * @param array $array Array to check
- * @return bool
- * @author Martin Steel
- */
- protected function is_assoc($array) {
- return (bool)count(array_filter(array_keys($array), 'is_string'));
- }
-
- /**
- * Check if a number is a valid MSISDN
- *
- * @param string $val Value to check
- * @return bool True if valid MSISDN
- * @author James Inman
- * @since 1.3.0
- * @todo Take an optional country code and check that the number starts with it
- */
- public static function is_valid_msisdn($val) {
- return preg_match( '/^[1-9][0-9]{7,12}$/', $val );
- }
- }
|