<?php
/**
 * DooRestClient class file.
 *
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @link http://www.doophp.com/
 * @copyright Copyright &copy; 2009 Leng Sheng Hong
 * @license http://www.doophp.com/license
 */

/**
 * A REST client to make requests to 3rd party RESTful web services such as Twitter.
 *
 * <p>You can send GET, POST, PUT and DELETE requests with DooRestClient.
 * Method chaining is also supported by this class.</p>
 * Example usage:
 * <code>
 * //Example usage in a Doo Controller
 * //import the class and create the REST client object
 * $client = $this->load()->helper('DooRestClient', true);
 * $client->connect_to("http://twitter.com/direct_messages.xml")
 *        ->auth('twituser', 'password', true)
 *        ->get()
 *
 * if($client->isSuccess()){
 *      echo "<pre>Received content-type: {$client->resultContentType()}<br/>";
 *      print_r( $client->result() );
 * }else{
 *      echo "<pre>Request unsuccessful, error {$client->resultCode()} return";
 * }
 * </code>
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @version $Id: DooRestClient.php 1000 2009-07-7 18:27:22
 * @package doo.helper
 * @since 1.0
 */
class DooRestClient {
    protected $server_url;
    protected $curl_opt;
    protected $auth_user;
    protected $auth_pwd;
    protected $args;
    protected $result;
    protected $header_code_received;
    protected $content_type_received;

    //for sending Accept header, this is to be used with requests to REST server which is built with Doo Framework
    const HTML = 'text/html';
    const XML = 'application/xml';
    const JSON = 'application/json';
    const JS = 'application/javascript';
    const CSS = 'text/css';
    const RSS = 'application/rss+xml';
    const YAML = 'text/yaml';
    const ATOM = 'application/atom+xml';
    const PDF = 'application/pdf';
    const TEXT = 'text/plain';
    const PNG = 'image/png';
    const JPG = 'image/jpeg';
    const GIF = 'image/gif';
    const CSV = 'text/csv';

    function  __construct($server_url=NULL) {
        if($server_url!=NULL)
            $this->server_url = $server_url;
        $this->curl_opt = array();
        $this->curl_opt['RETURNTRANSFER'] = true;
        $this->curl_opt['HEADER'] = false;
        $this->curl_opt['FRESH_CONNECT'] = true;
    }

    /**
     * Get/set the REST server URL
     * @param string $server_url
     * @return mixed
     */
    public function connect_to($server_url=NULL){
        if($server_url==NULL)
            return $this->server_url;
        $this->server_url = $server_url;
        return $this;
    }

    /**
     * Check if a given URL exist.
     *
     * The url exists if the return HTTP code is 200
     * @param string $url Url of the page
     * @return boolean True if exists (200)
     */
    public static function checkUrlExist($url){
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_NOBODY, true); // set to HEAD request
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // don't output the response
        curl_exec($ch);
        $valid = curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;
        curl_close($ch);
        return $valid;
    }

    /**
     * Send request to a URL and returns the HEAD request HTTP code.
     *
     * @param string $url Url of the page
     * @return int returns the HTTP code
     */
    public static function retrieveHeaderCode($url){
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_NOBODY, true); // set to HEAD request
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // don't output the response
        curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return $code;
    }

    /**
     * Get/set the connection timeout duration (seconds)
     * @param int $sec Timeout duration in seconds
     * @return mixed
     */
    public function timeout($sec=NULL){
        if($sec===NULL)
            return $this->curl_opt['CONNECTTIMEOUT'];
        else
            $this->curl_opt['CONNECTTIMEOUT'] = $sec;
        return $this;
    }

    /**
     * Get/set data for the REST request.
     *
     * The data can either be a string of params <b>id=19&year=2009&filter=true</b>
     * or an assoc array <b>array('id'=>19, 'year'=>2009, 'filter'=>true)</b>
     *
     * <p>The data is returned when no data value is passed into the method.</p>
     *
     * @param string|array $data
     * @return mixed
     */
    public function data($data=NULL){
        if($data==NULL)
            return $this->args;

        if(is_string($data)){
            $this->args = $data;
        }
        else{
            $datastr = '';
            foreach($data as $k=>$v){
                $datastr .= "$k=$v&";
            }
            $this->args = $datastr;
        }
        return $this;
    }

    /**
     * Get/Set options for executing the REST connection.
     *
     * This method prepares options for Curl to work.
     * Instead of setting CURLOPT_URL = value, you should use URL = value while setting various options
     *
     * The options are returned when no option array is passed into the method.
     *
     * <p>See http://www.php.net/manual/en/function.curl-setopt.php for the list of Curl options.</p>
     * Option keys are case insensitive. Example option input:
     * <code>
     * $client = new DooRestClient('http://somewebsite.com/api/rest');
     * $client->options(array(
     *                      'returnTransfer'=>true,
     *                      'header'=>true,
     *                      'SSL_VERIFYPEER'=>false,
     *                      'timeout'=>10
     *                  ));
     *
     * $client->execute('get');
     * //or $client->get();
     * </code>
     * @param array $optArr
     * @return mixed
     */
    public function options($optArr=NULL){
        if($optArr==NULL)
            return $this->curl_opt;
        $this->curl_opt = array_merge($this->curl_opt, $optArr);
        return $this;
    }

    /**
     * Get/set authentication details for the RESTful call
     *
     * Authentication can be done with HTTP Basic or Digest. HTTP Auth Digest will be used by default.
     * If no values are passed into the method,
     * the auth details with be returned in an array consist of Username and Password.
     *
     * <p>If you are implementing your own RESTful api, you can handle authentication with DooDigestAuth::http_auth()
     * or setup authentication in your routes.</p>
     *
     * @param string $username Username
     * @param string $password Password
     * @param bool $basic to switch between HTTP Basic or Digest authentication
     * @return mixed
     */
    public function auth($username=NULL, $password=NULL, $basic=FALSE){
        if($username===NULL && $password===NULL)
            return array($this->auth_user, $this->auth_pwd);

        $this->auth_user = $username;
        $this->auth_pwd = $password;

        $this->curl_opt['HTTPAUTH'] = ($basic)?CURLAUTH_BASIC : CURLAUTH_DIGEST;

        return $this;
    }

    /**
     * Get/set desired accept type.
     * <p>This should be used if the REST server analyze Accept header to parse what format
     * you're seeking for the result content. eg. json, xml, rss, atom.
     * DooRestClient provides a list of common used format to be used in your code.</p>
     * Example to retrieve result in JSON:
     * <code>
     * $client = new DooRestClient;
     * $client->connect_to("http://twitter.com/direct_messages")
     *        ->auth('username', 'password', true)
     *        ->accept(DooRestClient::JSON)
     *        ->get();
     * </code>
     * @param string $type
     * @return mixed
     */
    public function accept($type=NULL){
        if($type==NULL)
            if(isset($this->curl_opt['HTTPHEADER']) && $this->curl_opt['HTTPHEADER'][0])
                return str_replace('Accept: ', '', $this->curl_opt['HTTPHEADER'][0]);
            else
                return;

        $this->curl_opt['HTTPHEADER'] = array("Accept: $type");
        return $this;
    }

    /**
     * Get/set desired content type to be post to the server
     * <p>This should be used if the REST server analyze Content-Type header to parse what format
     * you're posting to the API. eg. json, xml, rss, atom.
     * DooRestClient provides a list of common used format to be used in your code.</p>
     * Example to retrieve result in JSON:
     * <code>
     * $client = new DooRestClient;
     * $client->connect_to("http://twitter.com/post_status")
     *        ->auth('username', 'password', true)
     *        ->setContentType(DooRestClient::JSON)
     *        ->post();
     * </code>
     * @param string $type
     * @return mixed
     */
    public function setContentType($type=NULL){
        if($type==NULL)
            if(isset($this->curl_opt['HTTPHEADER']) && $this->curl_opt['HTTPHEADER'][0])
                return str_replace('Content-Type: ', '', $this->curl_opt['HTTPHEADER'][0]);
            else
                return;

        $this->curl_opt['HTTPHEADER'] = array("Content-Type: $type");
        return $this;
    }

    /**
     * Execute the RESTful request through either GET, POST, PUT or DELETE request method
     * @param string $method Method string is case insensitive.
     */
    public function execute($method){
        $method = strtolower($method);
        if($method=='get')
            $this->get();
        elseif($method=='post')
            $this->post();
        elseif($method=='put')
            $this->post();
        elseif($method=='delete')
            $this->post();
    }

    /**
     * Execute the request with HTTP GET request method
     * @return DooRestClient
     */
    public function get(){
        if($this->args!=NULL)
            $serverurl = $this->server_url .'?'. $this->args ;
        else
            $serverurl = $this->server_url;

        $ch = curl_init( $serverurl );

        $arr = array();
        foreach($this->curl_opt as $k=>$v){
            $arr[ constant('CURLOPT_'.strtoupper($k))] = $v;
        }

        //set HTTP auth username and password is found
        if(isset($this->auth_user) || isset($this->auth_pwd))
            $arr[CURLOPT_USERPWD] = $this->auth_user .':'. $this->auth_pwd;

        //set GET method
        $arr[CURLOPT_HTTPGET] = true;

        curl_setopt_array($ch, $arr);

        $this->result = curl_exec($ch);
        $this->header_code_received = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $this->content_type_received = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

        curl_close($ch);
        return $this;
    }

    /**
     * Execute the request with HTTP POST request method
     * @return DooRestClient
     */
    public function post(){
        $ch = curl_init( $this->server_url );

        $arr = array();
        foreach($this->curl_opt as $k=>$v){
            $arr[ constant('CURLOPT_'.strtoupper($k))] = $v;
        }

        //set HTTP auth username and password is found
        if(isset($this->auth_user) || isset($this->auth_pwd))
            $arr[CURLOPT_USERPWD] = $this->auth_user .':'. $this->auth_pwd;

        //set POST method and fields
        $arr[CURLOPT_POST] = true;
        $arr[CURLOPT_POSTFIELDS] = $this->args;

        curl_setopt_array($ch, $arr);

        $this->result = curl_exec($ch);
        $this->header_code_received = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $this->content_type_received = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

        curl_close($ch);
        return $this;
    }

    /**
     * Execute the request with HTTP PUT request method
     * @return DooRestClient
     */
    public function put(){
        $ch = curl_init( $this->server_url );

        $arr = array();
        foreach($this->curl_opt as $k=>$v){
            $arr[ constant('CURLOPT_'.strtoupper($k))] = $v;
        }

        //set HTTP auth username and password is found
        if(isset($this->auth_user) || isset($this->auth_pwd))
            $arr[CURLOPT_USERPWD] = $this->auth_user .':'. $this->auth_pwd;

        //set PUT method and fields
        $arr[CURLOPT_CUSTOMREQUEST] = 'PUT';
        $arr[CURLOPT_POSTFIELDS] = $this->args;

        curl_setopt_array($ch, $arr);

        $this->result = curl_exec($ch);
        $this->header_code_received = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $this->content_type_received = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

        curl_close($ch);
        return $this;
    }

    /**
     * Execute the request with HTTP DELETE request method
     * @return DooRestClient
     */
    public function delete(){
        $ch = curl_init( $this->server_url );

        $arr = array();
        foreach($this->curl_opt as $k=>$v){
            $arr[ constant('CURLOPT_'.strtoupper($k))] = $v;
        }

        //set HTTP auth username and password is found
        if(isset($this->auth_user) || isset($this->auth_pwd))
            $arr[CURLOPT_USERPWD] = $this->auth_user .':'. $this->auth_pwd;

        //set DELETE method, delete methods don't have fields,ids should be set in server url
        $arr[CURLOPT_CUSTOMREQUEST] = 'DELETE';

        curl_setopt_array($ch, $arr);

        $this->result = curl_exec($ch);
        $this->header_code_received = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $this->content_type_received = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

        curl_close($ch);
        return $this;
    }

    //-------------- Result handlers --------------

    /**
     * Get result of the executed request
     * @return string
     */
    public function result(){
        return $this->result;
    }

    /**
     * Determined if it's a successful request
     * @return bool
     */
    public function isSuccess(){
        return ($this->header_code_received>=200 && $this->header_code_received<300);
    }

    /**
     * Get result's HTTP status code of the executed request
     * @return int
     */
    public function resultCode(){
        return $this->header_code_received;
    }


    /**
     * Get result's content type of the executed request
     * @return int
     */
    public function resultContentType(){
        return $this->content_type_received;
    }

    /**
     * Convert the REST result to XML object
     *
     * Returns a SimpleXMLElement object by default which consumed less memory than DOMDocument.
     * However if you need the result to be DOMDocument which is more flexible and powerful in modifying XML,
     * just passed in True to the function.
     *
     * @param bool $domObject convert result in to DOMDOcument if True
     * @return SimpleXMLElement|DOMDocument
     */
    public function xml_result($domObject=FALSE){
        if($domObject){
            $d = new DOMDocument('1.0','UTF-8');
            $d->loadXML($this->result);
            return $d;
        }else
            return simplexml_load_string($this->result);
    }

    /**
     * Convert the REST result to JSON object
     * @param bool $toArray convert result into assoc array if True.
     * @return object
     */
    public function json_result($toArray=FALSE){
        return json_decode($this->result,$toArray);
    }

}
