chocolatdemariage/www/modules/tntofficiel/libraries/TNTOfficiel_JsonRPCClient.php
Preprod mutu dc0edebeb0 tnt
2017-09-12 10:48:51 +02:00

410 lines
12 KiB
PHP

<?php
/**
* TNT OFFICIAL MODULE FOR PRESTASHOP
*
* @author GFI Informatique <www.gfi.fr>
* @copyright 2016-2017 GFI Informatique, 2016-2017 TNT
* @license https://opensource.org/licenses/MIT MIT License
*/
require_once _PS_MODULE_DIR_.'tntofficiel/libraries/TNTOfficiel_Debug.php';
class TNTOfficiel_AccessDeniedException extends Exception {}
class TNTOfficiel_ConnectionFailureException extends Exception {}
class TNTOfficiel_ServerErrorException extends Exception {}
/**
* JsonRPC client class
*
* @package JsonRPC
* @author Frederic Guillot
*/
class TNTOfficiel_JsonRPCClient
{
/**
* URL of the server
*
* @access private
* @var string
*/
private $url;
/**
* If the only argument passed to a function is an array
* assume it contains named arguments
*
* @access public
* @var boolean
*/
public $named_arguments = true;
/**
* HTTP client timeout
*
* @access private
* @var integer
*/
private $timeout;
/**
* Username for authentication
*
* @access private
* @var string
*/
private $username;
/**
* Password for authentication
*
* @access private
* @var string
*/
private $password;
/**
* True for a batch request
*
* @access public
* @var boolean
*/
public $is_batch = false;
/**
* Batch payload
*
* @access public
* @var array
*/
public $batch = array();
/**
* Enable debug output to the php error log
*
* @access public
* @var boolean
*/
public $debug = false;
/**
* Default HTTP headers to send to the server
*
* @access private
* @var array
*/
private $headers = array(
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
'Content-Type: application/json',
'Accept: application/json',
'Connection: close',
);
/**
* SSL certificates verification
*
* @access public
* @var boolean
*/
public $ssl_verify_peer = true;
/**
* Constructor
*
* @access public
* @param string $url Server URL
* @param integer $timeout HTTP timeout
* @param array $headers Custom HTTP headers
*/
public function __construct($url, $timeout = 3, $headers = array())
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$this->url = $url;
$this->timeout = $timeout;
$this->headers = array_merge($this->headers, $headers);
}
/**
* Automatic mapping of procedures
*
* @access public
* @param string $method Procedure name
* @param array $params Procedure arguments
* @return mixed
*/
public function __call($method, array $params)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
// Allow to pass an array and use named arguments
if ($this->named_arguments && count($params) === 1 && is_array($params[0])) {
$params = $params[0];
}
return $this->execute($method, $params);
}
/**
* Set authentication parameters
*
* @access public
* @param string $username Username
* @param string $password Password
* @return Client
*/
public function authentication($username, $password)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$this->username = $username;
$this->password = $password;
return $this;
}
/**
* Start a batch request
*
* @access public
* @return Client
*/
public function batch()
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$this->is_batch = true;
$this->batch = array();
return $this;
}
/**
* Send a batch request
*
* @access public
* @return array
*/
public function send()
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$this->is_batch = false;
return $this->parseResponse(
$this->doRequest($this->batch)
);
}
/**
* Execute a procedure
*
* @access public
* @param string $procedure Procedure name
* @param array $params Procedure arguments
* @return mixed
*/
public function execute($procedure, array $params = array())
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$params['date'] = time();
if ($this->is_batch) {
$this->batch[] = $this->prepareRequest($procedure, $params);
return $this;
}
return $this->parseResponse(
$this->_doRequest($this->prepareRequest($procedure, $params))
);
}
/**
* Prepare the payload
*
* @access public
* @param string $procedure Procedure name
* @param array $params Procedure arguments
* @return array
*/
public function prepareRequest($procedure, array $params = array())
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$payload = array(
'jsonrpc' => '2.0',
'method' => $procedure,
'id' => mt_rand()
);
if (! empty($params)) {
$payload['params'] = $params;
}
return $payload;
}
/**
* Parse the response and return the procedure result
*
* @access public
* @param array $payload
* @return mixed
*/
public function parseResponse(array $payload)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
if ($this->isBatchResponse($payload)) {
$results = array();
foreach ($payload as $response) {
$results[] = $this->getResult($response);
}
return $results;
}
return $this->getResult($payload);
}
/**
* Throw an exception according the RPC error
*
* @access public
* @param array $error
* @throws BadFunctionCallException
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function handleRpcErrors(array $error)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
switch ($error['code']) {
case -32601:
throw new BadFunctionCallException('Procedure not found: '. $error['message']);
case -32602:
throw new InvalidArgumentException('Invalid arguments: '. $error['message']);
default:
throw new RuntimeException('Invalid request/response: '. $error['message'], $error['code']);
}
}
/**
* Throw an exception according the HTTP response
*
* @access public
* @param array $headers
* @throws TNTOfficiel_AccessDeniedException
* @throws TNTOfficiel_ServerErrorException
*/
public function handleHttpErrors(array $headers)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$exceptions = array(
'401' => 'TNTOfficiel_AccessDeniedException',
'403' => 'TNTOfficiel_AccessDeniedException',
'404' => 'TNTOfficiel_ConnectionFailureException',
'500' => 'TNTOfficiel_ServerErrorException',
);
foreach ($headers as $header) {
foreach ($exceptions as $code => $exception) {
if (strpos($header, 'HTTP/1.0 '.$code) !== false || strpos($header, 'HTTP/1.1 '.$code) !== false) {
throw new $exception('Response: '.$header);
}
}
}
}
/**
* Do the HTTP request
*
* @access private
* @param array $payload
* @return array
* @throws TNTOfficiel_ConnectionFailureException
*/
private function _doRequest(array $payload)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
if (extension_loaded('curl')) {
$ch = curl_init(trim($this->url));
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $this->headers,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => Tools::jsonEncode($payload),
CURLOPT_RETURNTRANSFER => true,
));
$response = Tools::jsonDecode(curl_exec($ch), true);
curl_close($ch);
if (!$response) {
throw new TNTOfficiel_ConnectionFailureException('Unable to establish a connection');
}
} else {
$stream = @fopen(trim($this->url), 'r', false, $this->_getContext($payload));
if (!is_resource($stream)) {
throw new TNTOfficiel_ConnectionFailureException('Unable to establish a connection');
}
$metadata = stream_get_meta_data($stream);
$this->handleHttpErrors($metadata['wrapper_data']);
$response = Tools::jsonDecode(stream_get_contents($stream), true);
}
if ($this->debug) {
error_log('==> Request: ' . PHP_EOL . Tools::jsonEncode($payload, JSON_PRETTY_PRINT));
error_log('==> Response: ' . PHP_EOL . Tools::jsonEncode($response, JSON_PRETTY_PRINT));
}
TNTOfficiel_Debug::log(array('msg' => '<<', 'file' => __FILE__, 'line' => __LINE__, 'dump' => array('request' => $payload, 'response' => $response)));
return is_array($response) ? $response : array();
}
/**
* Prepare stream context
*
* @access private
* @param array $payload
* @return resource
*/
private function getContext(array $payload)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
$headers = $this->headers;
if (! empty($this->username) && ! empty($this->password)) {
$headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password);
}
return stream_context_create(array(
'http' => array(
'method' => 'POST',
'protocol_version' => 1.1,
'timeout' => $this->timeout,
'max_redirects' => 2,
'header' => implode("\r\n", $headers),
'content' => Tools::jsonEncode($payload),
'ignore_errors' => true,
),
"ssl" => array(
"verify_peer" => $this->ssl_verify_peer,
"verify_peer_name" => $this->ssl_verify_peer,
)
));
}
/**
* Return true if we have a batch response
*
* @access public
* @param array $payload
* @return boolean
*/
private function isBatchResponse(array $payload)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
return array_keys($payload) === range(0, count($payload) - 1);
}
/**
* Get a RPC call result
*
* @access private
* @param array $payload
* @return mixed
*/
private function getResult(array $payload)
{
TNTOfficiel_Debug::log(array('msg' => '>>', 'file' => __FILE__, 'line' => __LINE__));
if (isset($payload['error']['code'])) {
$this->handleRpcErrors($payload['error']);
}
return isset($payload['result']) ? $payload['result'] : null;
}
}