| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- <?php
- /*
- * This file is part of the MODX Revolution package.
- *
- * Copyright (c) MODX, LLC
- *
- * For complete copyright and license information, see the COPYRIGHT and LICENSE
- * files found in the top-level directory of this distribution.
- *
- */
- /**
- * REST Client service class with XML/JSON/QS support
- *
- * @package modx
- * @subpackage rest
- */
- class modRest {
- /** @var modX $modx */
- public $modx;
- /** @var array $config */
- public $config = array();
- /** @var mixed $handle The cURL resource handle. */
- public $handle;
- /** @var object $response Response body. */
- public $response;
- /** @var object $headers Parsed response header object */
- public $headers;
- /** @var object $info Response info object */
- public $info;
- /** @var string $error Response error string. */
- public $error;
- /** @var string $url The URL to query */
- public $url;
- /**
- * The modRest constructor
- *
- * @param modX $modx A reference to the modX instance
- * @param array $config An array of configuration options
- */
- public function __construct(modX &$modx,array $config = array()) {
- $this->modx =& $modx;
- $this->config = array_merge(array(
- 'addMethodParameter' => false,
- 'baseUrl' => null,
- 'curlOptions' => array(),
- 'defaultParameters' => array(),
- 'format' => null,
- 'headers' => array(),
- 'password' => null,
- 'suppressSuffix' => false,
- 'userAgent' => 'MODX RestClient/1.0.0',
- 'username' => null,
- ),$config);
- $this->modx->getService('lexicon','modLexicon');
- if ($this->modx->lexicon) {
- $this->modx->lexicon->load('rest');
- }
- }
- /**
- * @param string $key
- * @param mixed $value
- */
- public function setOption($key, $value){
- $this->config[$key] = $value;
- }
- /**
- * @param string $key
- * @param mixed $default
- * @return mixed
- */
- public function getOption($key,$default = null) {
- return array_key_exists($key,$this->config) ? $this->config[$key] : $default;
- }
- /**
- * @param string $url
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- public function get($url, $parameters=array(), $headers=array()){
- return $this->execute($url, 'GET', $parameters, $headers);
- }
- /**
- * @param string $url
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- public function post($url, $parameters=array(), $headers=array()){
- return $this->execute($url, 'POST', $parameters, $headers);
- }
- /**
- * @param string $url
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- public function put($url, $parameters=array(), $headers=array()){
- if (!empty($this->config['addMethodParameter'])) {
- $parameters['_method'] = "PUT";
- }
- return $this->execute($url,'PUT',$parameters, $headers);
- }
- /**
- * @param string $url
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- public function delete($url, $parameters=array(), $headers=array()){
- if (!empty($this->config['addMethodParameter'])) {
- $parameters['_method'] = "DELETE";
- }
- return $this->execute($url,'DELETE', $parameters, $headers);
- }
- /**
- * @param string $url
- * @param string $method
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- protected function execute($url, $method='GET', $parameters=array(), $headers=array()){
- $request = new RestClientRequest($this->modx,$this->config);
- if (!empty($headers['rootNode'])) {
- $request->setRootNode($headers['rootNode']);
- }
- return $request->execute($url,$method,$parameters,$headers);
- }
- }
- /**
- * Request class for handling REST requests
- *
- * @package modx
- * @subpackage rest
- */
- class RestClientRequest {
- /** @var modX $modx */
- public $modx;
- /** @var array $config */
- public $config = array();
- /** @var string $url */
- public $url;
- /** @var string $method */
- public $method = 'GET';
- /** @var mixed $handle */
- public $handle;
- /** @var array $requestParameters */
- public $requestParameters = array();
- /** @var array $requestOptions */
- public $requestOptions = array();
- /** @var array $headers */
- public $headers = array();
- /** @var array $defaultRequestParameters */
- public $defaultRequestParameters = array();
- /** @var string $rootNode */
- public $rootNode = 'request';
- /**
- * The RestClientRequest constructor
- *
- * @param modX $modx A reference to the modX instance
- * @param array $config An array of configuration options
- */
- function __construct(modX &$modx,array $config = array()) {
- $this->modx =& $modx;
- $this->config = array_merge($this->config,$config);
- if (!empty($this->config['headers'])) {
- $this->setHeaders($this->config['headers']);
- }
- if (!empty($this->config['defaultParameters'])) {
- $this->defaultRequestParameters = $this->config['defaultParameters'];
- }
- $this->_setDefaultRequestOptions();
- }
- /**
- * @param string $key
- * @param mixed $value
- */
- public function setOption($key, $value){
- $this->config[$key] = $value;
- }
- /**
- * @param string $key
- * @param mixed $default
- * @return mixed
- */
- public function getOption($key,$default = null) {
- return array_key_exists($key,$this->config) ? $this->config[$key] : $default;
- }
- /**
- * Set the root node of the request. Only used for XML requests.
- * @param string $node
- */
- public function setRootNode($node) {
- $this->rootNode = $node;
- }
- /**
- * Set the request parameters for the request.
- * @param array $parameters
- */
- public function setRequestParameters(array $parameters) {
- $this->requestParameters = array_merge($this->defaultRequestParameters,$parameters);
- }
- /**
- * Set the HTTP headers on the request
- *
- * @param array $headers
- * @param bool $merge
- */
- public function setHeaders($headers = array(),$merge = false) {
- $this->headers = $merge ? array_merge($this->headers,$headers) : $headers;
- }
- /**
- * Execute the request, properly preparing it, setting the URL and sending the request via cURL
- *
- * @param string $path
- * @param string $method
- * @param array $parameters
- * @param array $headers
- * @return RestClientResponse
- */
- public function execute($path,$method = 'GET', $parameters = array(), $headers = array()) {
- $this->url = $path;
- $this->method = strtoupper($method);
- $this->setRequestParameters($parameters);
- if (!empty($headers)) $this->setHeaders($headers,true);
- $this->prepare();
- return $this->send();
- }
- /**
- * Prepare the request for sending
- */
- protected function prepare() {
- $this->prepareHandle();
- $this->prepareAuthentication();
- $this->prepareUrl();
- $this->preparePayload();
- $this->prepareHeaders();
- $this->prepareRequestOptions();
- }
- /**
- * Send the request over the wire
- * @return RestClientResponse
- */
- protected function send() {
- $this->modx->log(modX::LOG_LEVEL_INFO,'[Rest] Sending request to '.$this->url.' with parameters: '.print_r($this->requestParameters,true));
- curl_setopt_array($this->handle,$this->requestOptions);
- $result = curl_exec($this->handle);
- $headerSize = curl_getinfo($this->handle,CURLINFO_HEADER_SIZE);
- $response = new RestClientResponse($this->modx,$result,$headerSize,$this->config);
- $info = (object) curl_getinfo($this->handle,CURLINFO_HTTP_CODE);
- $response->setResponseInfo($info);
- $error = curl_error($this->handle);
- $response->setResponseError($error);
- curl_close($this->handle);
- return $response;
- }
- /**
- * Load the request handle
- * @return mixed
- */
- protected function prepareHandle() {
- $this->handle = curl_init();
- return $this->handle;
- }
- /**
- * Set any authentication options for this request
- */
- protected function prepareAuthentication() {
- $username = $this->getOption('username','');
- $password = $this->getOption('password','');
- if (!empty($username)) {
- $this->requestOptions[CURLOPT_USERPWD] = $username.(!empty($password) ? ':'.$password : '');
- }
- }
- /**
- * Set any HTTP headers and load them into the request options
- */
- protected function prepareHeaders() {
- if (!empty($this->headers)) {
- if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) {
- $this->requestOptions[CURLOPT_HTTPHEADER] = array();
- }
- foreach ($this->headers as $key => $value) {
- $this->requestOptions[CURLOPT_HTTPHEADER][] = sprintf("%s: %s", $key, $value);
- }
- }
- }
- /**
- * Prepare the URL, prefixing the baseUrl if set, and setting the format suffix, if wanted
- * @return mixed
- */
- protected function prepareUrl() {
- $format = $this->getOption('format','json');
- $suppressSuffix = $this->getOption('suppressSuffix',false);
- if (!empty($format) && !$suppressSuffix) {
- $this->url .= '.'.$format;
- }
- if ($this->method != 'POST' && count($this->requestParameters)){
- $this->url .= strpos($this->url, '?') ? '&' : '?';
- $this->url .= $this->_formatQuery($this->requestParameters);
- }
- $baseUrl = $this->getOption('baseUrl',false);
- if (!empty($baseUrl)) {
- if ((!empty($this->url) && $this->url[0] != '/') || substr($baseUrl, -1) != '/') {
- $this->url = '/' . $this->url;
- }
- $this->url = $baseUrl . $this->url;
- }
- $this->requestOptions[CURLOPT_URL] = $this->url;
- return $this->url;
- }
- /**
- * Prepare the payload of parameters to be sent with the request
- */
- protected function preparePayload() {
- if ($this->method != 'GET') {
- $format = $this->getOption('format','json');
- switch ($format) {
- case 'json':
- if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) $this->requestOptions[CURLOPT_HTTPHEADER] = array();
- $this->requestOptions[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json; charset=utf-8';
- if (!empty($this->requestParameters)) {
- $params = $this->requestParameters;
- if (!empty($this->config['useRootNodeInJSON'])) {
- $params = array($this->rootNode => $params);
- }
- $json = json_encode($params);
- $this->requestOptions[CURLOPT_POSTFIELDS] = $json;
- }
- break;
- case 'xml':
- if (empty($this->requestOptions[CURLOPT_HTTPHEADER])) $this->requestOptions[CURLOPT_HTTPHEADER] = array();
- $this->requestOptions[CURLOPT_HTTPHEADER][] = 'Content-Type: application/xml; charset=utf-8';
- if (!empty($this->requestParameters)) {
- $xml = $this->toXml($this->requestParameters,$this->rootNode);
- $this->requestOptions[CURLOPT_POSTFIELDS] = $xml;
- }
- break;
- default:
- $this->requestOptions[CURLOPT_POSTFIELDS] = $this->_formatQuery($this->requestParameters);
- break;
- }
- }
- if ($this->method == 'POST') {
- $this->requestOptions[CURLOPT_POST] = true;
- } elseif ($this->method != 'GET') {
- $this->requestOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
- }
- }
- /**
- * Prepare the request options to be sent, setting them on the cURL handle
- */
- protected function prepareRequestOptions() {
- $curlOptions = $this->getOption('curlOptions');
- if (!empty($curlOptions) && is_array($curlOptions)) {
- foreach ($curlOptions as $key => $value) {
- $this->requestOptions[$key] = $value;
- }
- }
- }
- /**
- * Setup the default request options
- */
- private function _setDefaultRequestOptions() {
- $this->requestOptions = array(
- CURLOPT_HEADER => $this->getOption('header',true),
- CURLOPT_RETURNTRANSFER => $this->getOption('returnTransfer',true),
- CURLOPT_FOLLOWLOCATION => $this->getOption('followLocation',true),
- CURLOPT_TIMEOUT => $this->getOption('timeout',240),
- CURLOPT_USERAGENT => $this->getOption('userAgent'),
- CURLOPT_CONNECTTIMEOUT => $this->getOption('connectTimeout',0),
- CURLOPT_DNS_CACHE_TIMEOUT => $this->getOption('dnsCacheTimeout',120),
- CURLOPT_VERBOSE => $this->getOption('verbose',false),
- CURLOPT_SSL_VERIFYHOST => $this->getOption('sslVerifyhost',2),
- CURLOPT_SSL_VERIFYPEER => $this->getOption('sslVerifypeer',false),
- CURLOPT_COOKIE => $this->getOption('cookie',''),
- CURLOPT_COOKIEFILE => $this->getOption('cookieFile',''),
- CURLOPT_ENCODING => $this->getOption('encoding',''),
- CURLOPT_REFERER => $this->getOption('referer',''),
- CURLOPT_USERAGENT => $this->getOption('userAgent',''),
- CURLOPT_NETRC => $this->getOption('netrc',false),
- CURLOPT_HTTPPROXYTUNNEL => $this->getOption('httpProxyTunnel',false),
- CURLOPT_FRESH_CONNECT => $this->getOption('freshConnect',false),
- CURLOPT_FORBID_REUSE => $this->getOption('forbidReuse',false),
- CURLOPT_CRLF => $this->getOption('crlf',false),
- CURLOPT_AUTOREFERER => $this->getOption('autoreferer',false),
- CURLOPT_MAXREDIRS => $this->getOption('maxRedirs',3),
- );
- $proxy = $this->getOption('proxy',false);
- if (!empty($proxy)) {
- $this->requestOptions = array_merge($this->requestOptions,array(
- CURLOPT_PROXY => $proxy,
- CURLOPT_PROXYAUTH => $this->getOption('proxyAuth',CURLAUTH_BASIC),
- CURLOPT_PROXYPORT => $this->getOption('proxyPort',80),
- CURLOPT_PROXYTYPE => $this->getOption('proxyType',CURLPROXY_HTTP),
- ));
- $username = $this->getOption('proxyUsername');
- $password = $this->getOption('proxyPassword','');
- if (!empty($username)) {
- $this->requestOptions[CURLOPT_PROXYUSERPWD] = $username.':'.$password;
- }
- }
- }
- /**
- * Format an array of parameters into a query string
- * @param array $parameters
- * @return string
- */
- private function _formatQuery(array $parameters){
- $query = http_build_query($parameters);
- return rtrim($query);
- }
- /**
- * @param array $parameters
- * @param string $rootNode
- * @return string
- */
- public function toXml($parameters,$rootNode) {
- $doc = new DOMDocument("1.0",'UTF-8');
- $root = $doc->appendChild($doc->createElement($rootNode));
- $this->_populateXmlDoc($doc, $root, $parameters);
- return $doc->saveXML();
- }
- /**
- * @param DOMDocument $doc
- * @param DOMNode $node
- * @param array|DOMNode $parameters
- */
- protected function _populateXmlDoc(&$doc, &$node, &$parameters) {
- foreach ($parameters as $key => $val) {
- if (is_array($val)) {
- if (empty($val)) {
- continue;
- }
- $attribute_node = $node->appendChild($doc->createElement($key));
- foreach ($val as $child => $childValue) {
- if (is_null($child) || is_null($childValue)) {
- continue;
- } elseif (is_string($child) && !is_null($childValue)) {
- // e.g. "<items><property>1000</property></items>"
- $attribute_node->appendChild($doc->createElement($child, $childValue));
- } elseif (is_int($child) && !is_null($childValue)) {
- if (is_object($childValue)) {
- // e.g. "<items><item>...</item></items>"
- $this->_populateXmlDoc($doc, $attribute_node, $childValue);
- } elseif (substr($key, -1) == "s") {
- // e.g. "<items><item>gold</item><item>monthly</item></items>"
- $attribute_node->appendChild($doc->createElement(substr($key, 0, -1), $childValue));
- }
- }
- }
- } elseif (is_object($val)) {
- $this->_populateXmlDoc($doc,$node,$val);
- } else {
- $node->appendChild($doc->createElement($key, $val));
- }
- }
- }
- }
- /**
- * Response class for REST requests
- *
- * @package modx
- * @subpackage rest
- */
- class RestClientResponse {
- /** @var modX $modx */
- public $modx;
- /** @var array $config */
- public $config = array();
- /** @var string $response */
- public $response;
- /** @var int $headerSize */
- public $headerSize = 0;
- /** @var string $responseBody */
- public $responseBody;
- /** @var string $responseInfo */
- public $responseInfo;
- /** @var string $responseError */
- public $responseError;
- /** @var mixed $responseHeaders */
- public $responseHeaders;
- /**
- * Constructor for RestClientResponse class.
- *
- * @param modX $modx A reference to the modX instance
- * @param string $response The response data
- * @param int $headerSize The size of the response header, in bytes
- * @param array $config An array of configuration options
- */
- function __construct(modX &$modx,$response = '',$headerSize = 0,array $config = array()) {
- $this->modx =& $modx;
- $this->config = array_merge($this->config,$config);
- $this->response = $response;
- $this->headerSize = $headerSize;
- $this->setResponseBody($response);
- }
- /**
- * Set and parse the response body
- * @param string $result
- */
- public function setResponseBody($result) {
- $this->responseBody = $this->_parse($result);
- }
- /**
- * Set the response info
- * @param string $info
- */
- public function setResponseInfo($info) {
- $this->responseInfo = $info;
- }
- /**
- * Set the response error, if any
- * @param string $error
- */
- public function setResponseError($error) {
- $this->responseError = $error;
- }
- /**
- * Return the processed result based on the format the response was returned in
- * @return array
- */
- public function process() {
- switch ($this->config['format']) {
- case 'xml':
- $result = $this->fromXML($this->responseBody);
- break;
- case 'json':
- default:
- $result = $this->modx->fromJSON($this->responseBody);
- break;
- }
- return !empty($result) ? $result : array();
- }
- /**
- * Parse the result
- * @param string $result
- * @return string
- */
- public function _parse($result) {
- $headers = array();
- $httpVer = strtok($result, "\n");
- while($line = strtok("\n")){
- if(strlen(trim($line)) == 0) break;
- list($key, $value) = explode(':', $line, 2);
- $key = trim(strtolower(str_replace('-', '_', $key)));
- $value = trim($value);
- if(empty($headers[$key])){
- $headers[$key] = $value;
- }
- elseif(is_array($headers[$key])){
- $headers[$key][] = $value;
- }
- else {
- $headers[$key] = array($headers[$key], $value);
- }
- }
- $this->responseHeaders = (object) $headers;
- return substr($result,$this->headerSize);
- }
- /**
- * Convert JSON into an array
- *
- * @param string $data
- * @return array
- */
- protected function fromJSON($data) {
- return $this->modx->fromJSON($data);
- }
- /**
- * Convert XML into an array
- *
- * @param string|SimpleXMLElement $xml
- * @param mixed $attributesKey
- * @param mixed $childrenKey
- * @param mixed $valueKey
- * @return array
- */
- protected function fromXML($xml,$attributesKey=null,$childrenKey=null,$valueKey=null){
- if (is_string($xml)) {
- $xml = simplexml_load_string($xml);
- }
- if (empty($xml)) return '';
- if($childrenKey && !is_string($childrenKey)){$childrenKey = '@children';}
- if($attributesKey && !is_string($attributesKey)){$attributesKey = '@attributes';}
- if($valueKey && !is_string($valueKey)){$valueKey = '@values';}
- $return = array();
- $name = $xml->getName();
- $_value = trim((string)$xml);
- if(!strlen($_value)){$_value = null;};
- if($_value!==null){
- if($valueKey){$return[$valueKey] = $_value;}
- else{$return = $_value;}
- }
- $children = array();
- $first = true;
- foreach($xml->children() as $elementName => $child){
- $value = $this->fromXML($child,$attributesKey, $childrenKey,$valueKey);
- if(isset($children[$elementName])){
- if(is_array($children[$elementName])){
- if($first){
- $temp = $children[$elementName];
- unset($children[$elementName]);
- $children[$elementName][] = $temp;
- $first=false;
- }
- $children[$elementName][] = $value;
- }else{
- $children[$elementName] = array($children[$elementName],$value);
- }
- }
- else{
- $children[$elementName] = $value;
- }
- }
- if($children){
- if($childrenKey){$return[$childrenKey] = $children;}
- else{$return = array_merge($return,$children);}
- }
- $attributes = array();
- foreach($xml->attributes() as $name=>$value){
- $attributes[$name] = trim($value);
- }
- if($attributes){
- if($attributesKey){$return[$attributesKey] = $attributes;}
- else if(is_array($attributes) && is_array($return)) {
- $return = array_merge($return, $attributes);
- }
- }
- return $return;
- }
- }
|