2007-08-30 02:12:23 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* RestfulService class allows you to consume various RESTful APIs.
|
|
|
|
* Through this you could connect and aggregate data of various web services.
|
2008-02-25 03:10:37 +01:00
|
|
|
* For more info visit wiki documentation - http://doc.silverstripe.com/doku.php?id=restfulservice
|
|
|
|
* @package sapphire
|
|
|
|
* @subpackage integration
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
class RestfulService extends ViewableData {
|
|
|
|
protected $baseURL;
|
|
|
|
protected $queryString;
|
|
|
|
protected $errorTag;
|
2007-11-15 23:40:36 +01:00
|
|
|
protected $checkErrors;
|
|
|
|
protected $cache_expire;
|
2008-03-17 09:25:07 +01:00
|
|
|
protected $authUsername, $authPassword;
|
|
|
|
protected $customHeaders = array();
|
|
|
|
|
2008-02-25 03:10:37 +01:00
|
|
|
/**
|
|
|
|
* Creates a new restful service.
|
|
|
|
* @param string $base Base URL of the web service eg: api.example.com
|
|
|
|
* @param int $expiry Set the cache expiry interva. Defaults to 1 hour (3600 seconds)
|
|
|
|
*/
|
2007-11-15 23:40:36 +01:00
|
|
|
function __construct($base, $expiry=3600){
|
2007-08-30 02:12:23 +02:00
|
|
|
$this->baseURL = $base;
|
2007-11-15 23:40:36 +01:00
|
|
|
$this->cache_expire = $expiry;
|
2009-09-18 05:02:19 +02:00
|
|
|
parent::__construct();
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the Query string parameters to send a request.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @param array $params An array passed with necessary parameters.
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
function setQueryString($params=NULL){
|
|
|
|
$this->queryString = http_build_query($params,'','&');
|
|
|
|
}
|
|
|
|
|
2008-03-17 09:25:07 +01:00
|
|
|
/**
|
|
|
|
* Set basic authentication
|
|
|
|
*/
|
|
|
|
function basicAuth($username, $password) {
|
|
|
|
$this->authUsername = $username;
|
|
|
|
$this->authPassword = $password;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a custom HTTP header
|
|
|
|
*/
|
|
|
|
function httpHeader($header) {
|
|
|
|
$this->customHeaders[] = $header;
|
|
|
|
}
|
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
protected function constructURL(){
|
2008-03-17 09:25:07 +01:00
|
|
|
return "$this->baseURL" . ($this->queryString ? "?$this->queryString" : "");
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
|
|
|
|
2008-11-18 02:48:37 +01:00
|
|
|
/**
|
|
|
|
* @deprecated Use RestfulService::request()
|
|
|
|
*/
|
|
|
|
public function connect($subURL = '') {
|
|
|
|
user_error("RestfulService::connect is deprecated; use RestfulService::request", E_USER_NOTICE);
|
2009-01-05 07:19:48 +01:00
|
|
|
return $this->request($subURL)->getBody();
|
2008-11-18 02:48:37 +01:00
|
|
|
}
|
|
|
|
|
2008-03-31 01:17:36 +02:00
|
|
|
/**
|
|
|
|
* Makes a request to the RESTful server, and return a {@link RestfulService_Response} object for parsing of the result.
|
|
|
|
* @todo Better POST, PUT, DELETE, and HEAD support
|
|
|
|
* @todo Caching of requests - probably only GET and HEAD requestst
|
|
|
|
* @todo JSON support in RestfulService_Response
|
|
|
|
* @todo Pass the response headers to RestfulService_Response
|
|
|
|
*
|
|
|
|
* This is a replacement of {@link connect()}.
|
|
|
|
*/
|
2008-10-16 10:34:45 +02:00
|
|
|
public function request($subURL = '', $method = "GET", $data = null, $headers = null) {
|
2009-01-05 07:19:48 +01:00
|
|
|
$url = $this->baseURL . $subURL; // Url for the request
|
2008-10-21 05:45:44 +02:00
|
|
|
if($this->queryString) {
|
|
|
|
if(strpos($url, '?') !== false) {
|
|
|
|
$url .= '&' . $this->queryString;
|
|
|
|
} else {
|
|
|
|
$url .= '?' . $this->queryString;
|
|
|
|
}
|
|
|
|
}
|
2009-01-05 07:19:48 +01:00
|
|
|
$url = str_replace(' ', '%20', $url); // Encode spaces
|
2008-03-31 01:17:36 +02:00
|
|
|
$method = strtoupper($method);
|
|
|
|
|
|
|
|
assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS')));
|
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
$cachedir = TEMP_FOLDER; // Default silverstripe cache
|
|
|
|
$cache_file = md5($url); // Encoded name of cache file
|
|
|
|
$cache_path = $cachedir."/xmlresponse_$cache_file";
|
2008-03-31 01:17:36 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
// Check for unexpired cached feed (unless flush is set)
|
|
|
|
if(!isset($_GET['flush']) && @file_exists($cache_path) && @filemtime($cache_path) + $this->cache_expire > time()) {
|
|
|
|
$store = file_get_contents($cache_path);
|
|
|
|
$response = unserialize($store);
|
2008-03-31 01:17:36 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
} else {
|
|
|
|
$ch = curl_init();
|
|
|
|
$timeout = 5;
|
2009-07-14 22:55:42 +02:00
|
|
|
$useragent = "SilverStripe/" . SapphireInfo::Version();
|
2009-01-05 07:19:48 +01:00
|
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
|
|
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
|
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
|
|
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
|
|
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
2008-03-31 01:17:36 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
// Add headers
|
|
|
|
if($this->customHeaders) {
|
|
|
|
$headers = array_merge((array)$this->customHeaders, (array)$headers);
|
|
|
|
}
|
2008-10-21 05:45:44 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
2008-10-21 05:45:44 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
// Add authentication
|
|
|
|
if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, "$this->authUsername:$this->authPassword");
|
2008-10-21 05:45:44 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
// Add fields to POST requests
|
|
|
|
if($method == 'POST') {
|
|
|
|
curl_setopt($ch, CURLOPT_POST, 1);
|
|
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
|
|
|
}
|
|
|
|
|
2009-07-14 22:51:58 +02:00
|
|
|
// Run request
|
2009-01-05 07:19:48 +01:00
|
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
|
$responseBody = curl_exec($ch);
|
2008-03-31 01:17:36 +02:00
|
|
|
$curlError = curl_error($ch);
|
2009-02-02 00:49:53 +01:00
|
|
|
|
|
|
|
// Problem verifying the server SSL certificate; just ignore it as it's not mandatory
|
|
|
|
if(strpos($curlError,'14090086') !== false) {
|
|
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
|
|
$responseBody = curl_exec($ch);
|
|
|
|
$curlError = curl_error($ch);
|
|
|
|
}
|
|
|
|
|
|
|
|
if($responseBody === false) {
|
2008-03-31 01:17:36 +02:00
|
|
|
user_error("Curl Error:" . $curlError, E_USER_WARNING);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
$response = new RestfulService_Response($responseBody, curl_getinfo($ch, CURLINFO_HTTP_CODE));
|
2008-03-31 01:17:36 +02:00
|
|
|
|
2009-01-05 07:19:48 +01:00
|
|
|
curl_close($ch);
|
|
|
|
|
|
|
|
// Serialise response object and write to cache
|
|
|
|
$store = serialize($response);
|
|
|
|
file_put_contents($cache_path,$store);
|
|
|
|
}
|
|
|
|
|
2008-03-31 01:17:36 +02:00
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
/**
|
|
|
|
* Gets attributes as an array, of a particular type of element.
|
|
|
|
* Example : <photo id="2636" owner="123" secret="ab128" server="2">
|
|
|
|
* returns id, owner,secret and sever attribute values of all such photo elements.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @param string $xml The source xml to parse, this could be the original response received.
|
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available
|
|
|
|
* @param string $element The element we need to extract the attributes.
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
|
2008-08-11 04:57:59 +02:00
|
|
|
public function getAttributes($xml, $collection=NULL, $element=NULL){
|
2007-08-30 02:12:23 +02:00
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
$output = new DataObjectSet();
|
|
|
|
|
|
|
|
if($collection)
|
|
|
|
$childElements = $xml->{$collection};
|
|
|
|
if($element)
|
|
|
|
$childElements = $xml->{$collection}->{$element};
|
|
|
|
|
|
|
|
if($childElements){
|
2009-01-05 07:19:48 +01:00
|
|
|
foreach($childElements as $child){
|
|
|
|
$data = array();
|
|
|
|
foreach($child->attributes() as $key => $value){
|
|
|
|
$data["$key"] = Convert::raw2xml($value);
|
|
|
|
}
|
|
|
|
$output->push(new ArrayData($data));
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets an attribute of a particular element.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @param string $xml The source xml to parse, this could be the original response received.
|
|
|
|
* @param string $collection The name of the parent node which wraps the element, if available
|
|
|
|
* @param string $element The element we need to extract the attribute
|
|
|
|
* @param string $attr The name of the attribute
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
|
2008-08-11 04:57:59 +02:00
|
|
|
public function getAttribute($xml, $collection=NULL, $element=NULL, $attr){
|
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
$attr_value = "";
|
2007-08-30 02:12:23 +02:00
|
|
|
|
2008-08-11 04:57:59 +02:00
|
|
|
if($collection)
|
2007-08-30 02:12:23 +02:00
|
|
|
$childElements = $xml->{$collection};
|
|
|
|
if($element)
|
|
|
|
$childElements = $xml->{$collection}->{$element};
|
2008-08-11 04:57:59 +02:00
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
if($childElements)
|
|
|
|
$attr_value = (string) $childElements[$attr];
|
2008-08-11 04:57:59 +02:00
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
return Convert::raw2xml($attr_value);
|
|
|
|
|
|
|
|
}
|
2008-08-11 04:57:59 +02:00
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets set of node values as an array.
|
|
|
|
* When you get to the depth in the hierachchy use node_child_subchild syntax to get the value.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @param string $xml The the source xml to parse, this could be the original response received.
|
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available
|
|
|
|
* @param string $element The element we need to extract the node values.
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
|
2008-08-11 04:57:59 +02:00
|
|
|
public function getValues($xml, $collection=NULL, $element=NULL){
|
2007-08-30 02:12:23 +02:00
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
$output = new DataObjectSet();
|
|
|
|
|
|
|
|
$childElements = $xml;
|
|
|
|
if($collection)
|
|
|
|
$childElements = $xml->{$collection};
|
|
|
|
if($element)
|
|
|
|
$childElements = $xml->{$collection}->{$element};
|
|
|
|
|
|
|
|
if($childElements){
|
2008-08-09 05:29:30 +02:00
|
|
|
foreach($childElements as $child){
|
|
|
|
$data = array();
|
|
|
|
$this->getRecurseValues($child,$data);
|
|
|
|
$output->push(new ArrayData($data));
|
|
|
|
}
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getRecurseValues($xml,&$data,$parent=""){
|
2008-02-25 03:10:37 +01:00
|
|
|
$conv_value = "";
|
2007-08-30 02:12:23 +02:00
|
|
|
$child_count = 0;
|
|
|
|
foreach($xml as $key=>$value)
|
|
|
|
{
|
|
|
|
$child_count++;
|
|
|
|
$k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key;
|
2008-02-25 03:10:37 +01:00
|
|
|
if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node"
|
|
|
|
$conv_value = Convert::raw2xml($value);
|
|
|
|
}
|
|
|
|
//Review the fix for similar node names overriding it's predecessor
|
|
|
|
if(array_key_exists($k, $data) == true) {
|
|
|
|
$data[$k] = $data[$k] . ",". $conv_value;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$data[$k] = $conv_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
|
|
|
return $child_count;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a single node value.
|
2008-02-25 03:10:37 +01:00
|
|
|
* @param string $xml The source xml to parse, this could be the original response received.
|
|
|
|
* @param string $collection The name of parent node which wraps the elements, if available
|
|
|
|
* @param string $element The element we need to extract the node value.
|
2007-08-30 02:12:23 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
function getValue($xml, $collection=NULL, $element=NULL){
|
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
|
|
|
|
if($collection)
|
|
|
|
$childElements = $xml->{$collection};
|
|
|
|
if($element)
|
|
|
|
$childElements = $xml->{$collection}->{$element};
|
|
|
|
|
|
|
|
if($childElements)
|
|
|
|
return Convert::raw2xml($childElements);
|
|
|
|
}
|
|
|
|
|
2008-02-25 03:10:37 +01:00
|
|
|
/**
|
|
|
|
* Searches for a node in document tree and returns it value.
|
|
|
|
* @param string $xml source xml to parse, this could be the original response received.
|
|
|
|
* @param string $node Node to search for
|
|
|
|
*/
|
2007-08-30 02:12:23 +02:00
|
|
|
function searchValue($xml, $node=NULL){
|
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
$childElements = $xml->xpath($node);
|
|
|
|
|
|
|
|
if($childElements)
|
|
|
|
return Convert::raw2xml($childElements[0]);
|
|
|
|
}
|
|
|
|
|
2008-02-25 03:10:37 +01:00
|
|
|
/**
|
|
|
|
* Searches for a node in document tree and returns its attributes.
|
|
|
|
* @param string $xml the source xml to parse, this could be the original response received.
|
|
|
|
* @param string $node Node to search for
|
|
|
|
*/
|
2007-08-30 02:12:23 +02:00
|
|
|
function searchAttributes($xml, $node=NULL){
|
|
|
|
$xml = new SimpleXMLElement($xml);
|
|
|
|
$output = new DataObjectSet();
|
|
|
|
|
|
|
|
$childElements = $xml->xpath($node);
|
|
|
|
|
|
|
|
if($childElements)
|
|
|
|
foreach($childElements as $child){
|
|
|
|
$data = array();
|
|
|
|
foreach($child->attributes() as $key => $value){
|
|
|
|
$data["$key"] = Convert::raw2xml($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
$output->push(new ArrayData($data));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
2008-03-31 01:17:36 +02:00
|
|
|
}
|
|
|
|
|
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
|
|
|
class RestfulService_Response extends SS_HTTPResponse {
|
2008-03-31 01:17:36 +02:00
|
|
|
protected $simpleXML;
|
|
|
|
|
|
|
|
function __construct($body, $statusCode = 200, $headers = null) {
|
|
|
|
$this->setbody($body);
|
|
|
|
$this->setStatusCode($statusCode);
|
|
|
|
$this->headers = $headers;
|
|
|
|
}
|
2007-08-30 02:12:23 +02:00
|
|
|
|
2008-03-31 01:17:36 +02:00
|
|
|
function simpleXML() {
|
2009-07-02 09:18:18 +02:00
|
|
|
if(!$this->simpleXML) {
|
|
|
|
try {
|
|
|
|
$this->simpleXML = new SimpleXMLElement($this->body);
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
user_error("String could not be parsed as XML. " . $e, E_USER_WARNING);
|
|
|
|
}
|
|
|
|
}
|
2008-03-31 01:17:36 +02:00
|
|
|
return $this->simpleXML;
|
|
|
|
}
|
2007-08-30 02:12:23 +02:00
|
|
|
|
2008-03-31 01:17:36 +02:00
|
|
|
/**
|
|
|
|
* Return an array of xpath matches
|
|
|
|
*/
|
|
|
|
function xpath($xpath) {
|
|
|
|
return $this->simpleXML()->xpath($xpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the first xpath match
|
|
|
|
*/
|
|
|
|
function xpath_one($xpath) {
|
|
|
|
$items = $this->xpath($xpath);
|
|
|
|
return $items[0];
|
|
|
|
}
|
2007-08-30 02:12:23 +02:00
|
|
|
}
|
2008-03-31 01:17:36 +02:00
|
|
|
|
2009-02-02 00:49:53 +01:00
|
|
|
?>
|