<?php
/*
 * 调用远程RESTful的客户端类
 * 要求最低的PHP版本是5.2.0，并且还要支持以下库：cURL, Libxml 2.6.0
 * This class for invoke remote RESTful Webservice
 * The requirement of PHP version is 5.2.0 or above, and support as below:
 * cURL, Libxml 2.6.0
 *
 * @Version: 0.0.1 alpha
 * @Created: 11:06:48 2010/11/23
 * @Author:	Edison tsai<dnsing@gmail.com>
 * @Blog:	http://www.timescode.com
 * @Link:	http://www.dianboom.com
 */

 class HttpRequestService{
  
  #cURL Object
	private $ch;
  #Contains the last HTTP status code returned.
	public $http_code;
  #Contains the last API call.
	private $http_url;
  #Set up the API root URL.
	public $api_url;
  #Set timeout default.
	public $timeout = 10;
  #Set connect timeout.
	public $connecttimeout = 30; 
  #Verify SSL Cert.
	public $ssl_verifypeer = false;
  #Response format.
	public $format = ''; // Only support json & xml for extension
	public $decodeFormat = 'json'; //default is json
	public $_encode         ='utf-8';
  #Decode returned json data.
	//public $decode_json = true;
  #Contains the last HTTP headers returned.
	public $http_info = array();
	public $http_header = array();
	private $contentType;
	private $postFields;
	private static $paramsOnUrlMethod = array('GET','DELETE');
	private static $supportExtension  = array('json','xml');
  #For tmpFile
	private $file = null;
  #Set the useragnet.
	private static $userAgent = 'Timescode_RESTClient v0.0.1-alpha';


	public function __construct(){

		$this->ch = curl_init();
		/* cURL settings */
		curl_setopt($this->ch, CURLOPT_USERAGENT, self::$userAgent);
		curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
		curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, TRUE);
		curl_setopt($this->ch, CURLOPT_AUTOREFERER, TRUE);
        curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, TRUE);
		curl_setopt($this->ch, CURLOPT_HTTPHEADER, array('Expect:'));
		curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
		curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
		curl_setopt($this->ch, CURLOPT_HEADER, FALSE);

	}

     /**
      * Execute calls
      * @param $url String
      * @param $method String
      * @param $postFields String 
      * @param $username String
      * @param $password String
      * @param $contentType String 
      * @return RESTClient
      */
	public function call($url,$method,$postFields=null,$username=null,$password=null,$contentType=null){
		
		if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0 && !empty($this->format)) {
				$url = "{$this->api_url}{$url}.{$this->format}";
			}

		$this->http_url		= $url;
		$this->contentType	= $contentType;
		$this->postFields	= $postFields;

		$url				= in_array($method, self::$paramsOnUrlMethod) ? $this->to_url() : $this->get_http_url();

		is_object($this->ch) or $this->__construct();

		switch ($method) {
		  case 'POST':
			curl_setopt($this->ch, CURLOPT_POST, TRUE);
			if ($this->postFields != null) {
			  curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->postFields);
			}
			break;
		  case 'DELETE':
			curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
			break;
		  case 'PUT':
			curl_setopt($this->ch, CURLOPT_PUT, TRUE);
			if ($this->postFields != null) {
				$this->file = tmpFile();
				fwrite($this->file, $this->postFields);
				fseek($this->file, 0);
			  curl_setopt($this->ch, CURLOPT_INFILE,$this->file);
			  curl_setopt($this->ch, CURLOPT_INFILESIZE,strlen($this->postFields));
			}
			break;
		}

		$this->setAuthorizeInfo($username, $password);
		$this->contentType != null && curl_setopt($this->ch, CURLOPT_HTTPHEADER, array('Content-type:'.$this->contentType));

		curl_setopt($this->ch, CURLOPT_URL, $url);

		$response = curl_exec($this->ch);
		$this->http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
		$this->http_info = array_merge($this->http_info, curl_getinfo($this->ch));

		$this->close();

		return $response;
	}
	public function call_fopen($url,$method,$postFields=null,$username=null,$password=null,$contentType=null){
		
		if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0 && !empty($this->format)) {
				$url = "{$this->api_url}{$url}.{$this->format}";
			}

		$this->http_url		= $url;
		$this->contentType	= $contentType;
		$this->postFields	= $postFields;

		$url				= in_array($method, self::$paramsOnUrlMethod) ? $this->to_url() : $this->get_http_url();
		$params = array('http' => array(
			   'method' => 'POST',
			   'header' => 'Content-type: application/x-www-form-urlencoded'."\r\n",
			   'content' => $this->create_post_body($this->postFields)
			));
	   $ctx = stream_context_create($params);
	   $fp = fopen($url, 'rb', false, $ctx);

	   if (!$fp) {
		  die ("can not open server!");
	   }

	   $response = @stream_get_contents($fp);
	   if ($response === false) {
		   die ("can not get message form server!");
		  //throw new Exception("Problem reading data from {$url}, {$php_errormsg}");
	   }
	   return $response;
	}
	
	 public function setEncode($encode){
		!empty($encode) and $this->_encode = $encode;
		return $this;
	}
	 public static function convertEncoding($source, $in, $out){
		$in	= strtoupper($in);
		$out = strtoupper($out);
		if ($in == "UTF8"){
			$in = "UTF-8";
		}
		if ($out == "UTF8"){
			$out = "UTF-8";
		}
		if( $in==$out ){
			return $source;
		}
	
		if(function_exists('mb_convert_encoding')) {
			return mb_convert_encoding($source, $out, $in );
		}elseif (function_exists('iconv'))  {
			return iconv($in,$out."//IGNORE", $source);
		}
		return $source;
	}
	/**
      * POST wrapper，不基于curl函数，环境可以不支持curl函数
      * @param method String
      * @param parameters Array
      * @return mixed
      */
	public function do_post_request($url, $postdata, $files)
	{
		$data = "";
		$boundary = "---------------------".substr(md5(rand(0,32000)), 0, 10);

		//Collect Postdata
		foreach($postdata as $key => $val)
		{
			$data .= "--$boundary\r\n";
			$data .= "Content-Disposition: form-data; name=\"".$key."\"\r\n\r\n".$val."\r\n";
		}
		$data .= "--$boundary\r\n";

		//Collect Filedata
		foreach($files as $key => $file)
		{
			$fileContents = file_get_contents($file['tmp_name']);
			$data .= "Content-Disposition: form-data; name=\"{$key}\"; filename=\"{$file['name']}\"\r\n";
			$data .= "Content-Type: ".$file['type']."\r\n";
			$data .= "Content-Transfer-Encoding: binary\r\n\r\n";
			$data .= $fileContents."\r\n";
			$data .= "--$boundary--\r\n";
		}

		$params = array('http' => array(
			   'method' => 'POST',
			   'header' => 'Content-Type: multipart/form-data; boundary='.$boundary,
			   'content' => $data
			));


	   $ctx = stream_context_create($params);
	   $fp = fopen($url, 'rb', false, $ctx);

	   if (!$fp) {
		  die ("can not open server!");
	   }

	   $response = @stream_get_contents($fp);
	   if ($response === false) {
		   die ("can not get message form server!");
		  //throw new Exception("Problem reading data from {$url}, {$php_errormsg}");
	   }
	   return $response;
	}
     /**
      * POST wrapper for insert data
      * @param $url String
      * @param $params mixed 
      * @param $username String
      * @param $password String
      * @param $contentType String
      * @return RESTClient
      */
     public function _POST($url,$params=null,$username=null,$password=null,$contentType=null) {
         $response = $this->call($url,'POST',$params,$username,$password,$contentType);
		 //$this->convertEncoding($response,'utf-8',$this->_encode)
		 return $this->json_foreach($this->parseResponse($response));
     }
	
	public function _POST_FOPEN($url,$params=null,$username=null,$password=null,$contentType=null) {
         $response = $this->call_fopen($url,'POST',$params,$username,$password,$contentType);
		 return $this->json_foreach($this->parseResponse($response));
     }
	 public function _photoUpload($url, $postdata, $files) {
         $response = $this->do_post_request($url, $postdata, $files);
		 return $this->json_foreach($this->parseResponse($response));
     }
	 //将stdclass object转换成数组，并转换编码
	 public function json_foreach($jsonArr)
	 {
		 if(is_object($jsonArr))
		 {
			 $jsonArr=get_object_vars($jsonArr);
		 }
			foreach($jsonArr AS $k=>$v){
			if(is_array($v))
			{
				$jsonArr[$k]=$this->json_foreach($v);
			}
			elseif(is_object($v))
			{
				$v=get_object_vars($v);
				$jsonArr[$k]=$this->json_foreach($v);
			}
			else
			{
				$v=$this->convertEncoding($v,"utf-8",$this->_encode);
				$jsonArr[$k]=$v;
			}
			
		}
		return $jsonArr;
	 }
     /**
      * PUT wrapper for update data
      * @param $url String
      * @param $params mixed 
      * @param $username String
      * @param $password String
      * @param $contentType String
      * @return RESTClient
      */
     public function _PUT($url,$params=null,$username=null,$password=null,$contentType=null) {
         $response = $this->call($url,'PUT',$params,$username,$password,$contentType);
		 return $this->parseResponse($this->convertEncoding($response,'utf-8',$this->_encode));
     }
	 public function create_post_body($post_params) {
		$params = array();
		foreach ($post_params as $key => &$val) {
			if(is_array($val)) 
			{
				$val = implode(',', $val);
			}
			$params[] = $key.'='.urlencode($val);
		}
		return implode('&', $params);
	}
     /**
      * GET wrapper for get data
      * @param $url String
      * @param $params mixed
      * @param $username String
      * @param $password String
      * @return RESTClient
      */
     public function _GET($url,$params=null,$username=null,$password=null) {
         $response = $this->call($url,'GET',$params,$username,$password);
		 return $this->parseResponse($response);
     }

     /**
      * DELETE wrapper for delete data
      * @param $url String
      * @param $params mixed
      * @param $username String
      * @param $password String
      * @return RESTClient
      */
     public function _DELETE($url,$params=null,$username=null,$password=null) {
		 #Modified by Edison tsai on 09:50 2010/11/26 for missing part
		 $response = $this->call($url,'DELETE',$params,$username,$password);
		 return $this->parseResponse($response);
     }

	 /*
	 * Parse response, including json, xml, plain text
	 * @param $resp String
	 * @param $ext	String, including json/xml
	 * @return String
	 */
	 public function parseResponse($resp,$ext=''){
		
		$ext = !in_array($ext, self::$supportExtension) ? $this->decodeFormat : $ext;
		
		switch($ext){
				case 'json':
					$resp = json_decode($resp);break;
				case 'xml':
					$resp = self::xml_decode($resp);break;
		}
			return $resp;
	 }

	 /*
	 * XML decode
	 * @param $data String
	 * @param $toArray boolean, true for make it be array
	 * @return String
	 */
	  public static function xml_decode($data,$toArray=false){
		  /* TODO: What to do with 'toArray'? Just write it as you need. */
			$data = simplexml_load_string($data);
			return $data;
	  }

	  public static function objectToArray($obj){
			
	  }

	   /**
	   * parses the url and rebuilds it to be
	   * scheme://host/path
	   */
	  public function get_http_url() {
		$parts = parse_url($this->http_url);

		$port = @$parts['port'];
		$scheme = $parts['scheme'];
		$host = $parts['host'];
		$path = @$parts['path'];

		$port or $port = ($scheme == 'https') ? '443' : '80';

		if (($scheme == 'https' && $port != '443')
			|| ($scheme == 'http' && $port != '80')) {
		  $host = "$host:$port";
		}
		return "$scheme://$host$path";
	  }

	  /**
	   * builds a url usable for a GET request
	   */
	  public function to_url() {
		$post_data = $this->to_postdata();
		$out = $this->get_http_url();
		if ($post_data) {
		  $out .= '?'.$post_data;
		}
		return $out;
	  }

	  /**
	   * builds the data one would send in a POST request
	   */
	  public function to_postdata() {
		return http_build_query($this->postFields);
	  }

     /**
      * Settings that won't follow redirects
      * @return RESTClient
      */
     public function setNotFollow() {
         curl_setopt($this->ch,CURLOPT_AUTOREFERER,FALSE);
         curl_setopt($this->ch,CURLOPT_FOLLOWLOCATION,FALSE);
         return $this;
     }

     /**
      * Closes the connection and release resources
      * @return void
      */
     public function close() {
         curl_close($this->ch);
         if($this->file !=null) {
             fclose($this->file);
         }
     }

     /**
      * Sets the URL to be Called
	  * @param $url String
      * @return void
      */
     public function setURL($url) {
         $this->url = $url; 
     }

     /**
      * Sets the format type to be extension
	  * @param $format String
      * @return boolean
      */
	 public function setFormat($format=null){
		if($format==null)return false;
		$this->format = $format;
		return true;
	 }

     /**
      * Sets the format type to be decoded
	  * @param $format String
      * @return boolean
      */
	 public function setDecodeFormat($format=null){
		if($format==null)return false;
		$this->decodeFormat = $format;
		return true;
	 }

     /**
      * Set the Content-Type of the request to be send
      * Format like "application/json" or "application/xml" or "text/plain" or other
      * @param string $contentType
      * @return void
      */
     public function setContentType($contentType) {
         $this->contentType = $contentType;
     }

     /**
      * Set the authorize info for Basic Authentication
      * @param $username String
      * @param $password String
      * @return void
      */
     public function setAuthorizeInfo($username,$password) {
         if($username != null) { #The password might be blank
             curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
             curl_setopt($this->ch, CURLOPT_USERPWD, "{$username}:{$password}");
         }
     }

     /**
      * Set the Request HTTP Method
      * @param $method String
      * @return void
      */
     public function setMethod($method) {
         $this->method=$method;
     }

     /**
      * Set Parameters to be send on the request
      * It can be both a key/value par array (as in array("key"=>"value"))
      * or a string containing the body of the request, like a XML, JSON or other
      * Proper content-type should be set for the body if not a array
      * @param $params mixed
      * @return void
      */
     public function setParameters($params) {
         $this->postFields=$params;
     }

	  /**
	   * Get the header info to store.
	   */
	  public function getHeader($ch, $header) {
		$i = strpos($header, ':');
		if (!empty($i)) {
		  $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
		  $value = trim(substr($header, $i + 2));
		  $this->http_header[$key] = $value;
		}
		return strlen($header);
	  }
	  
 }
?>
