diff --git a/README.md b/README.md index 715cd27..7325bdf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ premium-api =========== -Client API for Sales.lv **[Premium](http://www.sales.lv/lv/risinajumi/premium/)** service. In its essence **Premium** is a platform for +Client API for Sales.lv **[Premium](https://sales.lv/sms/sms-premium/)** service. In its essence **Premium** is a platform for implementing services based on mobile-originated (incoming from users) SMS message processing. In practice there are a number of applications built upon this platform, for example, user message processing, SMS micropayment handling, lotteries, etc. (Lotteries are a special case as there is a lot of lottery-specific functionality implemented in Premium that goes above and beyond SMS processing). @@ -14,7 +14,7 @@ as well as examples of using our libraries A quick start guide ------------ -- Sign up for the [Premium service with Sales.lv](http://www.sales.lv/lv/risinajumi/premium/). Once you have done that, you will be provided with an API key and a campaign code to use for API requests. +- Sign up for the [Premium service with Sales.lv](https://sales.lv/sms/sms-premium/). Once you have done that, you will be provided with an API key and a campaign code to use for API requests. - Take a look at the [API documentation](https://github.com/Sales-LV/premium-api/wiki) and the client libraries. PHP client library @@ -22,14 +22,24 @@ PHP client library PHP client library is located in `lib/php/premium-api.php`. An usage example is provided in `lib/php/example.php`. Requirements: -* [PHP 5.2 or newer](http://www.php.net/) +* [PHP 5.5 or newer](http://www.php.net/) * One of these: * [pecl_http](http://pecl.php.net/package/pecl_http) extension is recommended but not mandatory. * enabled [cURL library](http://www.php.net/manual/en/book.curl.php). - * [allow_url_fopen](http://php.net/manual/en/filesystem.configuration.php) set to true. + * [allow_url_fopen](http://php.net/manual/en/filesystem.configuration.php) set to true (attachment file upload is not currently supported for this method.) Library usage is [described in the wiki](https://github.com/Sales-LV/premium-api/wiki/PHP-API-library). +Changelog +------------ +1.1.0: +- compatibility moved up to 5.5; +- some error messages indicate data unavailability due to GDPR limitations; +- files can be attached to submitted messages for upload and storage in Premium (when required by the campaign) + +1.0.*: +- initial releases + Feedback, support & questions ------------ Please write to support@sales.lv with any feedback, questions or suggestions that might arise. \ No newline at end of file diff --git a/lib/php/example.php b/lib/php/example.php index c825a95..80a9d7e 100644 --- a/lib/php/example.php +++ b/lib/php/example.php @@ -41,11 +41,11 @@ function debug_output(PremiumAPI $APIObject) // Messages echo '

Message list retrieval

'; - $Messages = $PremiumAPI -> Messages_List(array( + $Messages = $PremiumAPI -> Messages_List([ 'Time' => date('c', strtotime('2010-02-07')) - ), array( + ], [ 'Time' => date('c', strtotime('2010-02-09')) - )); + ]); debug_output($PremiumAPI); error_output($PremiumAPI); results_output($Messages); @@ -57,14 +57,17 @@ function debug_output(PremiumAPI $APIObject) results_output($Message); echo '

Create a new message

'; - $Message = $PremiumAPI -> Messages_Create(array( + $Message = $PremiumAPI -> Messages_Create([ 'Phone' => 21234567, 'FirstName' => 'George', 'LastName' => 'Brown', 'ReceiptUnique' => '123/456', 'IP' => $_SERVER['REMOTE_ADDR'] - )); + ], + [ // Attachments + ['tmp_name' => getcwd().'/example.php', 'type' => 'text/php', 'name' => 'example.php'], + ['tmp_name' => getcwd().'/premium-api.php', 'type' => 'text/php', 'name' => 'premium-api.php'], + ]); debug_output($PremiumAPI); error_output($PremiumAPI); results_output($Message); -?> \ No newline at end of file diff --git a/lib/php/premium-api.php b/lib/php/premium-api.php index fddfce6..c77b13f 100644 --- a/lib/php/premium-api.php +++ b/lib/php/premium-api.php @@ -2,12 +2,14 @@ /** * Utility class for Premium API connection * - * @version 1.0.4 + * @version 1.1.0 */ class PremiumAPI { - private static $Version = '1.0.4'; + private static $Version = '1.1.0'; private static $UserAgent = 'SalesLV/Premium-API'; + private static $UAString = ''; + private static $VerifySSL = false; // Error constants @@ -48,6 +50,23 @@ class PremiumAPI const ERROR_INVALID_API_VERSION = 16; // Invalid data format requested. Same as above. const ERROR_INVALID_DATA_FORMAT = 17; + // 1.1.0: Attachment support + const ERROR_ATTACHMENTS_NOT_ALLOWED = 18; + // File type of the attachment is not permitted (e.g. only images are allowed and you're trying to upload a zip file.) + const ERROR_ATTACHMENT_TYPE_NOT_PERMITTED = 19; + // File upload failed, you chould notify the user and rectify the situation + const ERROR_ATTACHMENT_UPLOAD_FAILED = 20; + // Indicates that message data has been processed successfully but there was a problem with handling the attached file(s). + // User should be notified to recitfy the sitation or inform them that their file was not saved. + const ERROR_MESSAGE_SUCCESSFUL_BUT_ATTACHMENT_FAILED = 21; + // The list of attachments passed to some method doesn't conform to the specification + const ERROR_MALFORMED_ATTACHMENT_ARRAY = 22; + // The file that should be added as an attachment was not accessible + const ERROR_ATTACHMENT_FILE_NOT_READABLE = 23; + // PHP version incompatible with this library + const ERROR_PHP_VERSION_INCOMPATIBLE = 24; + // Attachments upload not supported with the current configuration + const ERROR_ATTACHMENTS_NOT_SUPPORTED_WITH_THIS_METHOD = 25; /** * @var string API endpoint URL @@ -75,13 +94,13 @@ class PremiumAPI */ private $Error = ''; - public $Debug = array( - 'LastHTTPRequest' => array( + public $Debug = [ + 'LastHTTPRequest' => [ 'URL' => '', - 'Request' => array(), - 'Response' => array() - ) - ); + 'Request' => [], + 'Response' => [] + ] + ]; // !Public utility methods @@ -92,6 +111,20 @@ class PremiumAPI */ public function __construct($Key, $CampaignCode) { + self::$UAString = self::$UserAgent.'/'.self::$Version; + if (extension_loaded('http')) + { + self::$UAString .= '-http'; + } + elseif (extension_loaded('curl')) + { + self::$UAString .= '-curl'; + } + elseif (ini_get('allow_url_fopen')) + { + self::$UAString .= '-stream'; + } + $this -> APIKey = $Key; $this -> CampaignCode = $CampaignCode; @@ -162,11 +195,11 @@ public function Messages_List(array $Parameters, array $Parameters2 = null, $Off { $Data = $this -> HTTPRequest( $this -> APIURL.'Messages:List', - array( + [ 'Filter1' => json_encode($Parameters), 'Filter2' => $Parameters2 ? json_encode($Parameters2) : false, 'Offset' => (int)$Offset - ) + ] ); return $this -> ParseResponse($Data); @@ -176,19 +209,42 @@ public function Messages_List(array $Parameters, array $Parameters2 = null, $Off * Method for submitting a new message * * @param array Message parameters + * @param array Optional file attachments (array of file paths to upload). Should contain an array for each file with the following parameters + * (same as in the $_FILES array): + * - string name: Original file name + * - string type: File type + * - string tmp_name: Current path to file from where it can be read. * * @return array Operation result */ - public function Messages_Create(array $Parameters) + public function Messages_Create(array $Parameters, array $Attachments = null) { if (empty($Parameters['IP'])) { $Parameters['IP'] = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; } + if ($Attachments) + { + foreach ($Attachments as $Attachment) + { + if (!isset($Attachment['tmp_name']) || !isset($Attachment['type']) || !isset($Attachment['name'])) + { + return $this -> SetError(self::ERROR_MALFORMED_ATTACHMENT_ARRAY, 'Attachment list does not conform to specification'); + } + + if (!is_readable($Attachment['tmp_name'])) + { + return $this -> SetError(self::ERROR_ATTACHMENT_FILE_NOT_READABLE, 'Attachment file "'.$Attachment['tmp_name'].'" was not readable'); + } + } + } + $Result = $this -> HTTPRequest( $this -> APIURL.'Messages:Create', - $Parameters + $Parameters, + null, // Additional headers + $Attachments // Attachment files ); return $this -> ParseResponse($Result); @@ -259,28 +315,32 @@ private function SetError($ErrorCode, $ErrorMessage) * 'Content' => string Response body * ) */ - private function HTTPRequest($URL, array $POSTData = null, array $Headers = null) + private function HTTPRequest($URL, array $POSTData = null, array $Headers = null, array $Files = null) { $this -> Debug['LastHTTPRequest']['URL'] = $URL; $this -> Debug['LastHTTPRequest']['Method'] = $POSTData ? 'POST' : 'GET'; $this -> Debug['LastHTTPRequest']['Request'] = $POSTData; $this -> Debug['LastHTTPRequest']['Response'] = ''; - $Result = array(); + $Result = []; try { if (extension_loaded('http')) { - $Result = self::HTTPRequest_http($URL, $POSTData, $Headers); + $Result = self::HTTPRequest_http($URL, $POSTData, $Headers, $Files); } elseif (extension_loaded('curl')) { - $Result = self::HTTPRequest_curl($URL, $POSTData, $Headers); + $Result = self::HTTPRequest_curl($URL, $POSTData, $Headers, $Files); } elseif (ini_get('allow_url_fopen')) { - $Result = self::HTTPRequest_fopen($URL, $POSTData, $Headers); + if ($Files) + { + return $this -> SetError(self::ERROR_ATTACHMENTS_NOT_SUPPORTED_WITH_THIS_METHOD, 'Attachment upload not supported for this HTTP connection method (stream context,) please install curl or pecl_http'); + } + $Result = self::HTTPRequest_fopen($URL, $POSTData, $Headers, $Files); } else { @@ -302,7 +362,7 @@ private function HTTPRequest($URL, array $POSTData = null, array $Headers = null /** * Utility method for making HTTP requests with the pecl_http extension, see HTTPRequest for more information */ - private static function HTTPRequest_http($URL, array $POSTData = null, array $Headers = null) + private static function HTTPRequest_http($URL, array $POSTData = null, array $Headers = null, array $Files = null) { $Method = $POSTData ? HttpRequest::METH_POST : HttpRequest::METH_GET; @@ -313,78 +373,99 @@ private static function HTTPRequest_http($URL, array $POSTData = null, array $He } $Request -> setPostFields($POSTData); + if ($Files) + { + foreach ($Files as $File) + { + $Request -> addPostFile($File['name'], $File['tmp_name'], $File['type']); + } + } + $Request -> send(); - return array( + return [ 'Headers' => array_merge( - array( + [ 'Response Code' => $Request -> getResponseCode(), 'Response Status' => $Request -> getResponseStatus() - ), + ], $Request -> getResponseHeader() ), 'Body' => $Request -> getResponseBody() - ); + ]; } /** * Utility method for making HTTP requests with CURL. See PremiumAPI::HTTPRequest for more information */ - private static function HTTPRequest_curl($URL, array $POSTData = null, array $Headers = null) + private static function HTTPRequest_curl($URL, array $POSTData = null, array $Headers = null, array $Files = null) { - // Preparing request content - $POSTBody = $POSTData ? self::PrepareBody($POSTData) : ''; + if ($Files) + { + if (!$POSTData) + { + $POSTData = []; + } + + $Index = 0; + foreach ($Files as $File) + { + $POSTData['Attachment['.$Index.']'] = curl_file_create($File['tmp_name'], $File['type'], $File['name']); + $Index++; + } + } // Preparing request headers - $Headers = self::PrepareHeaders($Headers, $URL, strlen($POSTBody)); + $Headers = ['Expect' => '']; + $Headers = self::PrepareHeaders($Headers, $URL); - // Making the request - $cURLRequest = curl_init(); - curl_setopt_array($cURLRequest, array( + $cURLParams = [ CURLOPT_URL => $URL, CURLOPT_HEADER => 1, CURL_HTTP_VERSION_1_0 => true, - CURLOPT_POST => $POSTBody ? 1 : 0, + CURLOPT_POST => $POSTData ? 1 : 0, CURLOPT_CONNECTTIMEOUT => 60, CURLOPT_TIMEOUT => 120, CURLOPT_MAXREDIRS => 5, - CURLOPT_USERAGENT => self::$UserAgent.'/'.self::$Version, - CURLOPT_POSTFIELDS => $POSTBody, + CURLOPT_FAILONERROR => 1, + CURLOPT_USERAGENT => self::$UAString, + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_POSTFIELDS => $POSTData, CURLOPT_ENCODING => 'gzip', CURLOPT_RETURNTRANSFER => 1, CURLOPT_HTTPHEADER => $Headers, CURLOPT_SSL_VERIFYPEER => self::$VerifySSL - )); + ]; + + // Making the request + $cURLRequest = curl_init(); + curl_setopt_array($cURLRequest, $cURLParams); $ResponseBody = curl_exec($cURLRequest); curl_close($cURLRequest); - $ResponseBody = str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $ResponseBody); + $ResponseBody = str_replace(["\r\n", "\n\r", "\r"], ["\n", "\n", "\n"], $ResponseBody); $ResponseParts = explode("\n\n", $ResponseBody); - $ResponseHeaders = array(); + $ResponseHeaders = []; if (count($ResponseParts) > 1) { $ResponseHeaders = self::ParseHeadersFromString($ResponseParts[0]); } $ResponseBody = isset($ResponseParts[1]) ? $ResponseParts[1] : $ResponseBody; - //if (isset($ResponseHeaders['Content-Encoding']) && $ResponseHeaders['Content-Encoding'] == 'gzip') - //{ - //$ResponseBody = gzinflate($ResponseBody); - //} - return array( + return [ 'Headers' => $ResponseHeaders, 'Body' => $ResponseBody - ); + ]; } /** * Utility method for making the HTTP request with file_get_contents. See PremiumAPI::HTTPRequest for more information */ - private static function HTTPRequest_fopen($URL, array $POSTData = null, array $Headers = null) + private static function HTTPRequest_fopen($URL, array $POSTData = null, array $Headers = null, array $Files = null) { - // Preparing reqiest body + // Preparing request body $POSTBody = $POSTData ? self::PrepareBody($POSTData) : ''; // Preparing headers @@ -392,24 +473,24 @@ private static function HTTPRequest_fopen($URL, array $POSTData = null, array $H $Headers = implode("\r\n", $Headers)."\r\n"; // Making the request - $Context = stream_context_create(array( - 'http' => array( + $Context = stream_context_create([ + 'http' => [ 'method' => $POSTBody ? 'POST' : 'GET', 'header' => $Headers, 'content' => $POSTBody, 'protocol_version' => 1.0 - ) - )); + ] + ]); $Content = file_get_contents($URL, false, $Context); $ResponseHeaders = $http_response_header; $ResponseHeaders = self::ParseHeadersFromArray($ResponseHeaders); - return array( + return [ 'Headers' => $ResponseHeaders, 'Body' => $Content - ); + ]; } /** @@ -417,22 +498,25 @@ private static function HTTPRequest_fopen($URL, array $POSTData = null, array $H * * @param array Headers to send in addition to the default set (keys are names, values are content) * @param string URL that will be used for the request (for the "Host" header) - * @param int Content length for the Content-Length header + * @param int Optional content length for the Content-Length header * * return array Headers in a numeric array. Each item in the array is a separate header string containing both name and content */ - private static function PrepareHeaders(array $Headers = null, $URL, $ContentLength) + private static function PrepareHeaders(array $Headers = null, $URL, $ContentLength = null) { $URLInfo = parse_url($URL); $Host = $URLInfo['host']; - $DefaultHeaders = array( + $DefaultHeaders = [ 'Host' => $Host, 'Connection' => 'close', - 'Content-Type' => 'application/x-www-form-urlencoded', - 'Content-Length' => $ContentLength, - 'User-Agent' => self::$UserAgent.'/'.self::$Version - ); + 'User-Agent' => self::$UAString + ]; + + if (!is_null($ContentLength)) + { + $DefaultHeaders['Content-Length'] = $ContentLength; + } if ($Headers) { @@ -443,7 +527,7 @@ private static function PrepareHeaders(array $Headers = null, $URL, $ContentLeng $Headers = $DefaultHeaders; } - $Result = array(); + $Result = []; foreach ($Headers as $Name => $Content) { $Result[] = $Name.': '.$Content; @@ -460,7 +544,7 @@ private static function PrepareHeaders(array $Headers = null, $URL, $ContentLeng */ private static function PrepareBody(array $Data) { - $POSTBody = array(); + $POSTBody = []; foreach ($Data as $Key => $Value) { $POSTBody[] = $Key.'='.urlencode($Value); @@ -504,7 +588,7 @@ private static function ParseHeadersFromString($HeaderString) */ private static function ParseHeadersFromArray(array $Headers) { - $Result = array(); + $Result = []; $CurrentHeader = 0;