Version: current

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

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -