PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/sapphire/api/RestfulService.php

https://github.com/benbruscella/vpcounselling.com
PHP | 407 lines | 233 code | 63 blank | 111 comment | 36 complexity | 57ef59619ce604735a963f8c9d44ce19 MD5 | raw file
  1. <?php
  2. /**
  3. * RestfulService class allows you to consume various RESTful APIs.
  4. * Through this you could connect and aggregate data of various web services.
  5. * For more info visit wiki documentation - http://doc.silverstripe.org/doku.php?id=restfulservice
  6. *
  7. * @package sapphire
  8. * @subpackage integration
  9. */
  10. class RestfulService extends ViewableData {
  11. protected $baseURL;
  12. protected $queryString;
  13. protected $errorTag;
  14. protected $checkErrors;
  15. protected $cache_expire;
  16. protected $authUsername, $authPassword;
  17. protected $customHeaders = array();
  18. protected $proxy;
  19. protected static $default_proxy;
  20. /**
  21. * Sets default proxy settings for outbound RestfulService connections
  22. *
  23. * @param string $proxy The URL of the proxy to use.
  24. * @param int $port Proxy port
  25. * @param string $user The proxy auth user name
  26. * @param string $password The proxy auth password
  27. * @param boolean $socks Set true to use socks5 proxy instead of http
  28. */
  29. static function set_default_proxy($proxy, $port = 80, $user = "", $password = "", $socks = false) {
  30. self::$default_proxy = array(
  31. CURLOPT_PROXY => $proxy,
  32. CURLOPT_PROXYUSERPWD => "{$user}:{$password}",
  33. CURLOPT_PROXYPORT => $port,
  34. CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP)
  35. );
  36. }
  37. /**
  38. * Creates a new restful service.
  39. * @param string $base Base URL of the web service eg: api.example.com
  40. * @param int $expiry Set the cache expiry interva. Defaults to 1 hour (3600 seconds)
  41. */
  42. function __construct($base, $expiry=3600){
  43. $this->baseURL = $base;
  44. $this->cache_expire = $expiry;
  45. $this->proxy = self::$default_proxy;
  46. parent::__construct();
  47. }
  48. /**
  49. * Sets the Query string parameters to send a request.
  50. * @param array $params An array passed with necessary parameters.
  51. */
  52. function setQueryString($params=NULL){
  53. $this->queryString = http_build_query($params,'','&');
  54. }
  55. /**
  56. * Set proxy settings for this RestfulService instance
  57. *
  58. * @param string $proxy The URL of the proxy to use.
  59. * @param int $port Proxy port
  60. * @param string $user The proxy auth user name
  61. * @param string $password The proxy auth password
  62. * @param boolean $socks Set true to use socks5 proxy instead of http
  63. */
  64. function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) {
  65. $this->proxy = array(
  66. CURLOPT_PROXY => $proxy,
  67. CURLOPT_PROXYUSERPWD => "{$user}:{$password}",
  68. CURLOPT_PROXYPORT => $port,
  69. CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP)
  70. );
  71. }
  72. /**
  73. * Set basic authentication
  74. */
  75. function basicAuth($username, $password) {
  76. $this->authUsername = $username;
  77. $this->authPassword = $password;
  78. }
  79. /**
  80. * Set a custom HTTP header
  81. */
  82. function httpHeader($header) {
  83. $this->customHeaders[] = $header;
  84. }
  85. protected function constructURL(){
  86. return "$this->baseURL" . ($this->queryString ? "?$this->queryString" : "");
  87. }
  88. /**
  89. * @deprecated Use RestfulService::request()
  90. */
  91. public function connect($subURL = '') {
  92. user_error("RestfulService::connect is deprecated; use RestfulService::request", E_USER_NOTICE);
  93. return $this->request($subURL)->getBody();
  94. }
  95. /**
  96. * Makes a request to the RESTful server, and return a {@link RestfulService_Response} object for parsing of the result.
  97. * @todo Better POST, PUT, DELETE, and HEAD support
  98. * @todo Caching of requests - probably only GET and HEAD requestst
  99. * @todo JSON support in RestfulService_Response
  100. * @todo Pass the response headers to RestfulService_Response
  101. *
  102. * This is a replacement of {@link connect()}.
  103. */
  104. public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) {
  105. $url = $this->baseURL . $subURL; // Url for the request
  106. if($this->queryString) {
  107. if(strpos($url, '?') !== false) {
  108. $url .= '&' . $this->queryString;
  109. } else {
  110. $url .= '?' . $this->queryString;
  111. }
  112. }
  113. $url = str_replace(' ', '%20', $url); // Encode spaces
  114. $method = strtoupper($method);
  115. assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS')));
  116. $cachedir = TEMP_FOLDER; // Default silverstripe cache
  117. $cache_file = md5($url); // Encoded name of cache file
  118. $cache_path = $cachedir."/xmlresponse_$cache_file";
  119. // Check for unexpired cached feed (unless flush is set)
  120. if(!isset($_GET['flush']) && @file_exists($cache_path) && @filemtime($cache_path) + $this->cache_expire > time()) {
  121. $store = file_get_contents($cache_path);
  122. $response = unserialize($store);
  123. } else {
  124. $ch = curl_init();
  125. $timeout = 5;
  126. $useragent = "SilverStripe/" . SapphireInfo::Version();
  127. curl_setopt($ch, CURLOPT_URL, $url);
  128. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  129. curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
  130. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  131. curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
  132. curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  133. // Add headers
  134. if($this->customHeaders) {
  135. $headers = array_merge((array)$this->customHeaders, (array)$headers);
  136. }
  137. if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  138. // Add authentication
  139. if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, "$this->authUsername:$this->authPassword");
  140. // Add fields to POST requests
  141. if($method == 'POST') {
  142. curl_setopt($ch, CURLOPT_POST, 1);
  143. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  144. }
  145. // Apply proxy settings
  146. if(is_array($this->proxy)) {
  147. curl_setopt_array($ch, $this->proxy);
  148. }
  149. // Set any custom options passed to the request() function
  150. curl_setopt_array($ch, $curlOptions);
  151. // Run request
  152. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  153. $responseBody = curl_exec($ch);
  154. $curlError = curl_error($ch);
  155. // Problem verifying the server SSL certificate; just ignore it as it's not mandatory
  156. if(strpos($curlError,'14090086') !== false) {
  157. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  158. $responseBody = curl_exec($ch);
  159. $curlError = curl_error($ch);
  160. }
  161. if($responseBody === false) {
  162. user_error("Curl Error:" . $curlError, E_USER_WARNING);
  163. return;
  164. }
  165. $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  166. $response = new RestfulService_Response($responseBody, curl_getinfo($ch, CURLINFO_HTTP_CODE));
  167. curl_close($ch);
  168. // Serialise response object and write to cache
  169. $store = serialize($response);
  170. file_put_contents($cache_path,$store);
  171. }
  172. return $response;
  173. }
  174. /**
  175. * Gets attributes as an array, of a particular type of element.
  176. * Example : <photo id="2636" owner="123" secret="ab128" server="2">
  177. * returns id, owner,secret and sever attribute values of all such photo elements.
  178. * @param string $xml The source xml to parse, this could be the original response received.
  179. * @param string $collection The name of parent node which wraps the elements, if available
  180. * @param string $element The element we need to extract the attributes.
  181. */
  182. public function getAttributes($xml, $collection=NULL, $element=NULL){
  183. $xml = new SimpleXMLElement($xml);
  184. $output = new DataObjectSet();
  185. if($collection)
  186. $childElements = $xml->{$collection};
  187. if($element)
  188. $childElements = $xml->{$collection}->{$element};
  189. if($childElements){
  190. foreach($childElements as $child){
  191. $data = array();
  192. foreach($child->attributes() as $key => $value){
  193. $data["$key"] = Convert::raw2xml($value);
  194. }
  195. $output->push(new ArrayData($data));
  196. }
  197. }
  198. return $output;
  199. }
  200. /**
  201. * Gets an attribute of a particular element.
  202. * @param string $xml The source xml to parse, this could be the original response received.
  203. * @param string $collection The name of the parent node which wraps the element, if available
  204. * @param string $element The element we need to extract the attribute
  205. * @param string $attr The name of the attribute
  206. */
  207. public function getAttribute($xml, $collection=NULL, $element=NULL, $attr){
  208. $xml = new SimpleXMLElement($xml);
  209. $attr_value = "";
  210. if($collection)
  211. $childElements = $xml->{$collection};
  212. if($element)
  213. $childElements = $xml->{$collection}->{$element};
  214. if($childElements)
  215. $attr_value = (string) $childElements[$attr];
  216. return Convert::raw2xml($attr_value);
  217. }
  218. /**
  219. * Gets set of node values as an array.
  220. * When you get to the depth in the hierachchy use node_child_subchild syntax to get the value.
  221. * @param string $xml The the source xml to parse, this could be the original response received.
  222. * @param string $collection The name of parent node which wraps the elements, if available
  223. * @param string $element The element we need to extract the node values.
  224. */
  225. public function getValues($xml, $collection=NULL, $element=NULL){
  226. $xml = new SimpleXMLElement($xml);
  227. $output = new DataObjectSet();
  228. $childElements = $xml;
  229. if($collection)
  230. $childElements = $xml->{$collection};
  231. if($element)
  232. $childElements = $xml->{$collection}->{$element};
  233. if($childElements){
  234. foreach($childElements as $child){
  235. $data = array();
  236. $this->getRecurseValues($child,$data);
  237. $output->push(new ArrayData($data));
  238. }
  239. }
  240. return $output;
  241. }
  242. protected function getRecurseValues($xml,&$data,$parent=""){
  243. $conv_value = "";
  244. $child_count = 0;
  245. foreach($xml as $key=>$value)
  246. {
  247. $child_count++;
  248. $k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key;
  249. if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node"
  250. $conv_value = Convert::raw2xml($value);
  251. }
  252. //Review the fix for similar node names overriding it's predecessor
  253. if(array_key_exists($k, $data) == true) {
  254. $data[$k] = $data[$k] . ",". $conv_value;
  255. }
  256. else {
  257. $data[$k] = $conv_value;
  258. }
  259. }
  260. return $child_count;
  261. }
  262. /**
  263. * Gets a single node value.
  264. * @param string $xml The source xml to parse, this could be the original response received.
  265. * @param string $collection The name of parent node which wraps the elements, if available
  266. * @param string $element The element we need to extract the node value.
  267. */
  268. function getValue($xml, $collection=NULL, $element=NULL){
  269. $xml = new SimpleXMLElement($xml);
  270. if($collection)
  271. $childElements = $xml->{$collection};
  272. if($element)
  273. $childElements = $xml->{$collection}->{$element};
  274. if($childElements)
  275. return Convert::raw2xml($childElements);
  276. }
  277. /**
  278. * Searches for a node in document tree and returns it value.
  279. * @param string $xml source xml to parse, this could be the original response received.
  280. * @param string $node Node to search for
  281. */
  282. function searchValue($xml, $node=NULL){
  283. $xml = new SimpleXMLElement($xml);
  284. $childElements = $xml->xpath($node);
  285. if($childElements)
  286. return Convert::raw2xml($childElements[0]);
  287. }
  288. /**
  289. * Searches for a node in document tree and returns its attributes.
  290. * @param string $xml the source xml to parse, this could be the original response received.
  291. * @param string $node Node to search for
  292. */
  293. function searchAttributes($xml, $node=NULL){
  294. $xml = new SimpleXMLElement($xml);
  295. $output = new DataObjectSet();
  296. $childElements = $xml->xpath($node);
  297. if($childElements)
  298. foreach($childElements as $child){
  299. $data = array();
  300. foreach($child->attributes() as $key => $value){
  301. $data["$key"] = Convert::raw2xml($value);
  302. }
  303. $output->push(new ArrayData($data));
  304. }
  305. return $output;
  306. }
  307. }
  308. /**
  309. * @package sapphire
  310. * @subpackage integration
  311. */
  312. class RestfulService_Response extends SS_HTTPResponse {
  313. protected $simpleXML;
  314. function __construct($body, $statusCode = 200, $headers = null) {
  315. $this->setbody($body);
  316. $this->setStatusCode($statusCode);
  317. $this->headers = $headers;
  318. }
  319. function simpleXML() {
  320. if(!$this->simpleXML) {
  321. try {
  322. $this->simpleXML = new SimpleXMLElement($this->body);
  323. }
  324. catch(Exception $e) {
  325. user_error("String could not be parsed as XML. " . $e, E_USER_WARNING);
  326. }
  327. }
  328. return $this->simpleXML;
  329. }
  330. /**
  331. * Return an array of xpath matches
  332. */
  333. function xpath($xpath) {
  334. return $this->simpleXML()->xpath($xpath);
  335. }
  336. /**
  337. * Return the first xpath match
  338. */
  339. function xpath_one($xpath) {
  340. $items = $this->xpath($xpath);
  341. return $items[0];
  342. }
  343. }
  344. ?>