diff --git a/plugin/kannelsms/vendor/changelog.md b/plugin/kannelsms/vendor/changelog.md new file mode 100644 index 0000000000..9a1545b37c --- /dev/null +++ b/plugin/kannelsms/vendor/changelog.md @@ -0,0 +1,22 @@ +# Changelog + +## 1.0 (July 19th, 2012) + +* Initial release. [JI/MS] + +### 1.1 (August 21st, 2012) + +* Added /get_key functionality to translate a legacy Mediaburst username and password into a new Kannel API key. [JI] +* Deprecated `checkCredit()` and replaced with `checkBalance()` [JI] + +### 1.2 (September 7th, 2012) + +* Added various new Wordpress classes, including the Kannel_Plugin class for writing plugins based on Kannel. [JI] + +### 1.3 (September 18th, 2012) + +* Added `is_valid_msisdn()` method. [JI] + +#### 1.3.1 (November 13th, 2012) + +* Updated `is_valid_msisdn()` method to handle 9-digit phone numbers, e.g. Norway. [JI] \ No newline at end of file diff --git a/plugin/kannelsms/vendor/exception.php b/plugin/kannelsms/vendor/exception.php new file mode 100644 index 0000000000..dae50bba32 --- /dev/null +++ b/plugin/kannelsms/vendor/exception.php @@ -0,0 +1,27 @@ +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 ); + } + +} diff --git a/plugin/kannelsms/vendor/license.txt b/plugin/kannelsms/vendor/license.txt new file mode 100644 index 0000000000..041edd0c5f --- /dev/null +++ b/plugin/kannelsms/vendor/license.txt @@ -0,0 +1,14 @@ +Copyright (c) 2011 - 2012, Mediaburst Ltd + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/plugin/kannelsms/vendor/readme.md b/plugin/kannelsms/vendor/readme.md new file mode 100644 index 0000000000..1492a43d77 --- /dev/null +++ b/plugin/kannelsms/vendor/readme.md @@ -0,0 +1,253 @@ +# Kannel SMS API Wrapper for PHP + +This wrapper lets you interact with Kannel without the hassle of having to create any XML or make HTTP calls. + +## What's Kannel? + +[Kannel][2] is Mediaburst's SMS API. + +### Prerequisites + +* A [Kannel][2] account + +## Usage + +Require the Kannel library: + +```php +require 'class-Kannel.php'; +``` + +### Sending a message + +```php +$kannel = new Kannel( $API_KEY ); +$message = array( 'to' => '441234567891', 'message' => 'This is a test!' ); +$result = $kannel->send( $message ); +``` + +### Sending multiple messages + +We recommend you use batch sizes of 500 messages or fewer. By limiting the batch size it prevents any timeouts when sending. + +```php +$kannel = new Kannel( $API_KEY ); +$messages = array( + array( 'to' => '441234567891', 'message' => 'This is a test!' ), + array( 'to' => '441234567892', 'message' => 'This is a test 2!' ) +); +$results = $kannel->send( $messages ); +``` + +### Handling the response + +The responses come back as arrays, these contain the unique Kannel message ID, whether the message worked (`success`), and the original SMS so you can update your database. + + Array + ( + [id] => VE_164732148 + [success] => 1 + [sms] => Array + ( + [to] => 441234567891 + [message] => This is a test! + ) + + ) + +If you send multiple SMS messages in a single send, you'll get back an array of results, one per SMS. + +The result will look something like this: + + Array + ( + [0] => Array + ( + [id] => VI_143228951 + [success] => 1 + [sms] => Array + ( + [to] => 441234567891 + [message] => This is a test! + ) + + ) + + [1] => Array + ( + [id] => VI_143228952 + [success] => 1 + [sms] => Array + ( + [to] => 441234567892 + [message] => This is a test 2! + ) + + ) + + ) + +If a message fails, the reason for failure will be set in `error_code` and `error_message`. + +For example, if you send to invalid phone number "abc": + + Array + ( + [error_code] => 10 + [error_message] => Invalid 'To' Parameter + [success] => 0 + [sms] => Array + ( + [to] => abc + [message] => This is a test! + ) + + ) + +### Checking your balance + +Check your available SMS balance: + +```php +$kannel = new Kannel( $API_KEY ); +$kannel->checkBalance(); +``` + +This will return: + + Array + ( + [symbol] => £ + [balance] => 351.91 + [code] => GBP + ) + +### Handling Errors + +The Kannel wrapper will throw a `KannelException` if the entire call failed. + +```php +try +{ + $kannel = new Kannel( 'invalid_key' ); + $message = array( 'to' => 'abc', 'message' => 'This is a test!' ); + $result = $kannel->send( $message ); +} +catch( KannelException $e ) +{ + print $e->getMessage(); + // Invalid API Key +} +``` + +### Advanced Usage + +This class has a few additional features that some users may find useful, if these are not set your account defaults will be used. + +### Optional Parameters + +See the [Kannel Documentation](http://www.kannelsms.com/doc/clever-stuff/xml-interface/send-sms/) for full details on these options. + +* $from [string] + + The from address displayed on a phone when they receive a message + +* $long [boolean] + + Enable long SMS. A standard text can contain 160 characters, a long SMS supports up to 459. + +* $truncate [nullable boolean] + + Truncate the message payload if it is too long, if this is set to false, the message will fail if it is too long. + +* $invalid_char_action [string] + + What to do if the message contains an invalid character. Possible values are + * error - Fail the message + * remove - Remove the invalid characters then send + * replace - Replace some common invalid characters such as replacing curved quotes with straight quotes + +* $ssl [boolean, default: true] + + Use SSL when making an HTTP request to the Kannel API + + +### Setting Options + +#### Global Options + +Options set on the API object will apply to all SMS messages unless specifically overridden. + +In this example both messages will be sent from Kannel: + +```php +$options = array( 'from' => 'Kannel' ); +$kannel = new Kannel( $API_KEY, $options ); +$messages = array( + array( 'to' => '441234567891', 'message' => 'This is a test!' ), + array( 'to' => '441234567892', 'message' => 'This is a test 2!' ) +); +$results = $kannel->send( $messages ); +``` + +#### Per-message Options + +Set option values individually on each message. + +In this example, one message will be from Kannel and the other from 84433: + +```php +$kannel = new Kannel( $API_KEY, $options ); +$messages = array( + array( 'to' => '441234567891', 'message' => 'This is a test!', 'from' => 'Kannel' ), + array( 'to' => '441234567892', 'message' => 'This is a test 2!', 'from' => '84433' ) +); +$results = $kannel->send( $messages ); +``` + +### SSL Errors + +Due to the huge variety of PHP setups out there a small proportion of users may get PHP errors when making API calls due to their SSL configuration. + +The errors will generally look something like this: + +``` +Fatal error: +Uncaught exception 'Exception' with message 'HTTP Error calling Kannel API +HTTP Status: 0 +cURL Erorr: SSL certificate problem, verify that the CA cert is OK. +Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed' +``` + +If you're seeing this error there are two fixes available, the first is easy, simply disable SSL on Kannel calls. Alternatively you can setup your PHP install with the correct root certificates. + +#### Disable SSL on Kannel calls + +```php +$options = array( 'ssl' => false ); +$kannel = new Kannel( $API_KEY, $options ); +``` + +#### Setup SSL root certificates on your server + +This is much more complicated as it depends on your setup, however there are many guides available online. +Try a search term like "windows php curl root certificates" or "ubuntu update root certificates". + + +# License + +This project is licensed under the ISC open-source license. + +A copy of this license can be found in license.txt. + +# Contributing + +If you have any feedback on this wrapper drop us an email to [hello@kannelsms.com][1]. + +The project is hosted on GitHub at [https://github.com/mediaburst/kannel-php][3]. +If you would like to contribute a bug fix or improvement please fork the project +and submit a pull request. + +[1]: mailto:hello@kannelsms.com +[2]: http://www.kannelsms.com/ +[3]: https://github.com/mediaburst/kannel-php