<?php 
/**
* @version		$Id: EpiOAuth.php 1404 2013-03-25 10:09:12Z datahell $
* @package		Elxis
* @subpackage	OAuth / Authentication
* @copyright	Copyright (c) 2006-2013 Elxis CMS (http://www.elxis.org). All rights reserved.
* @license		Elxis Public License ( http://www.elxis.org/elxis-public-license.html )
* @author		Elxis Team ( http://www.elxis.org )
* @description 	Elxis CMS is free software. Read the license for copyright notices and details
*/

defined('_ELXIS_') or die ('Direct access to this location is not allowed');


class EpiOAuth {

	public $version = '1.0';
	protected $requestTokenUrl;
	protected $accessTokenUrl;
	protected $authorizeUrl;
	protected $consumerKey;
	protected $consumerSecret;
	protected $token;
	protected $tokenSecret;
	protected $signatureMethod;


	public function __construct($consumerKey, $consumerSecret, $signatureMethod='HMAC-SHA1') {
		$this->consumerKey = $consumerKey;
		$this->consumerSecret = $consumerSecret;
		$this->signatureMethod = $signatureMethod;
		$this->curl = EpiCurl::getInstance();
	}


	public function getAccessToken() {
		$resp = $this->httpRequest('GET', $this->accessTokenUrl);
		return new EpiOAuthResponse($resp);
	}


	public function getAuthorizationUrl() { 
		$retval = "{$this->authorizeUrl}?";
		$token = $this->getRequestToken();
		return $this->authorizeUrl.'?oauth_token='.$token->oauth_token;
	}


	public function getRequestToken() {
		$resp = $this->httpRequest('GET', $this->requestTokenUrl);
		return new EpiOAuthResponse($resp);
	}


	public function httpRequest($method = null, $url = null, $params = null) {
		if (empty($method) || empty($url)) { return false; }
		if (empty($params['oauth_signature'])) { $params = $this->prepareParameters($method, $url, $params); }
		switch($method) {
			case 'GET':
				return $this->httpGet($url, $params);
			break;
			case 'POST':
				return $this->httpPost($url, $params);
			break;
		}
	}


	public function setToken($token = null, $secret = null) {
		$params = func_get_args();
		$this->token = $token;
		$this->tokenSecret = $secret;
	} 


	public function encode($string) {
		return rawurlencode(utf8_encode($string));
	}


	protected function addOAuthHeaders(&$ch, $url, $oauthHeaders) {
		$_h = array('Expect:');
		$urlParts = parse_url($url);
		$oauth = 'Authorization: OAuth realm="'.$urlParts['path'].'",';
		foreach($oauthHeaders as $name => $value) {
			$oauth .= "{$name}=\"{$value}\",";
		}
		$_h[] = substr($oauth, 0, -1);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $_h); 
	}


	protected function generateNonce() {
		if (isset($this->nonce)) { // for unit testing
			return $this->nonce;
		}
		return md5(uniqid(rand(), true));
	}


	protected function generateSignature($method = null, $url = null, $params = null) {
		if(empty($method) || empty($url)) { return false; }
		//concatenating
		$concatenatedParams = '';
		foreach($params as $k => $v) {
			$v = $this->encode($v);
			$concatenatedParams .= "{$k}={$v}&";
		}
		$concatenatedParams = $this->encode(substr($concatenatedParams, 0, -1));
		//normalize url
		$normalizedUrl = $this->encode($this->normalizeUrl($url));
		$method = $this->encode($method); // don't need this but why not?
		$signatureBaseString = "{$method}&{$normalizedUrl}&{$concatenatedParams}";
		return $this->signString($signatureBaseString);
	}


	protected function httpGet($url, $params = null) {
		if (count($params['request']) > 0) {
			$url .= '?';
			foreach ($params['request'] as $k => $v) { $url .= "{$k}={$v}&"; }
			$url = substr($url, 0, -1);
		}
		$ch = curl_init($url);
		$this->addOAuthHeaders($ch, $url, $params['oauth']);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		$resp = $this->curl->addCurl($ch);
		return $resp;
	}


	protected function httpPost($url, $params = null) {
		$ch = curl_init($url);
		$this->addOAuthHeaders($ch, $url, $params['oauth']);
		curl_setopt($ch, CURLOPT_POST, 1);
		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params['request']));
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		$resp = $this->curl->addCurl($ch);
		return $resp;
	}


	protected function normalizeUrl($url = null) {
		$urlParts = parse_url($url);
		$scheme = strtolower($urlParts['scheme']);
		$host = strtolower($urlParts['host']);
		$port = (isset($urlParts['port'])) ? (int)$urlParts['port'] : ($scheme == 'https') ? 443 : 80;

		$retval = "{$scheme}://{$host}";
		if ($port > 0 && ($scheme === 'http' && $port !== 80) || ($scheme === 'https' && $port !== 443)) {
			$retval .= ":{$port}";
		}
		$retval .= $urlParts['path'];
		if (!empty($urlParts['query'])) {
			$retval .= "?{$urlParts['query']}";
		}
		return $retval;
	}


	protected function prepareParameters($method = null, $url = null, $params = null) {
		if(empty($method) || empty($url)) { return false; }
		$oauth = array();
		$oauth['oauth_consumer_key'] = $this->consumerKey;
		$oauth['oauth_token'] = $this->token;
		$oauth['oauth_nonce'] = $this->generateNonce();
		$oauth['oauth_timestamp'] = !isset($this->timestamp) ? time() : $this->timestamp; // for unit test
		$oauth['oauth_signature_method'] = $this->signatureMethod;
		$oauth['oauth_version'] = $this->version;
		//encoding
		array_walk($oauth, array($this, 'encode'));
		if (is_array($params)) {
			array_walk($params, array($this, 'encode'));
		}
		$encodedParams = array_merge($oauth, (array)$params);
		//sorting
		ksort($encodedParams);
		//signing
		$oauth['oauth_signature'] = $this->encode($this->generateSignature($method, $url, $encodedParams));
		return array('request' => $params, 'oauth' => $oauth);
	}


	protected function signString($string = null) {
		$retval = false;
		switch($this->signatureMethod) {
			case 'HMAC-SHA1':
				$key = $this->encode($this->consumerSecret) . '&' . $this->encode($this->tokenSecret);
				$retval = base64_encode(hash_hmac('sha1', $string, $key, true));
			break;
		}

		return $retval;
	}

}


class EpiOAuthResponse {

	private $__resp;


	public function __construct($resp) {
		$this->__resp = $resp;
	}


	public function __get($name) {
		if ($this->__resp->code < 200 || $this->__resp->code > 299) {
			return false;
		}
		parse_str($this->__resp->data, $result);
		foreach($result as $k => $v) {
			$this->$k = $v;
		}
		return $result[$name];
	}

}


class EpiCurl {

	const timeout = 3;
	static $inst = null;
	static $singleton = 0;
	private $mc;
	private $msgs;
	private $running;
	private $requests = array();
	private $responses = array();
	private $properties = array();


	public function __construct() {
    	if (self::$singleton == 0) {
    		throw new Exception('This class cannot be instantiated by the new keyword.  You must instantiate it using: $obj = EpiCurl::getInstance();');
		}

		$this->mc = curl_multi_init();
		$this->properties = array(
			'code'  => CURLINFO_HTTP_CODE,
			'time'  => CURLINFO_TOTAL_TIME,
			'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD,
			'type'  => CURLINFO_CONTENT_TYPE
		);
	}


	public function addCurl($ch) {
    	$key = (string)$ch;
    	$this->requests[$key] = $ch;
    	$res = curl_multi_add_handle($this->mc, $ch);

		if ($res === CURLM_OK || $res === CURLM_CALL_MULTI_PERFORM) {
			do {
				$mrc = curl_multi_exec($this->mc, $active);
			} while ($mrc === CURLM_CALL_MULTI_PERFORM);
			return new EpiCurlManager($key);
		} else {
			return $res;
		}
	}


	public function getResult($key = null) {
		if ($key != null) {
			if (isset($this->responses[$key])) {
				return $this->responses[$key];
			}

			$running = null;
			do {
				$resp = curl_multi_exec($this->mc, $runningCurrent);
				if ($running !== null && $runningCurrent != $running) {
					$this->storeResponses($key);
					if (isset($this->responses[$key])) {
						return $this->responses[$key];
					}
				}
				$running = $runningCurrent;
			} while($runningCurrent > 0);
		}
		return false;
	}


	private function storeResponses() {
		while($done = curl_multi_info_read($this->mc)) {
			$key = (string)$done['handle'];
			$this->responses[$key]['data'] = curl_multi_getcontent($done['handle']);
			foreach($this->properties as $name => $const) {
				$this->responses[$key][$name] = curl_getinfo($done['handle'], $const);
				curl_multi_remove_handle($this->mc, $done['handle']);
			}
		}
	}


	static function getInstance() {
		if (self::$inst == null) {
			self::$singleton = 1;
			self::$inst = new EpiCurl();
		}
		return self::$inst;
	}

}


class EpiCurlManager {

	private $key;
	private $epiCurl;


  	public function __construct($key) {
		$this->key = $key;
		$this->epiCurl = EpiCurl::getInstance();
	}


	public function __get($name) {
		$responses = $this->epiCurl->getResult($this->key);
		return (isset($responses[$name])) ? $responses[$name] : null;
	}

}

?>