<?php

/**
 * Dbfield for retrieving user login from blackbox methods
 * Internal blackbox (blackbox.yandex-team.ru) doesn't support other dbfields
 * For more information see https://doc.yandex-team.ru/projects/blackbox/reference/DBFields.xml
 */
define('BLACKBOX_LOGIN', 'accounts.login.uid');

/**
 * Exception for all Blackbox's errors
 */
class BlackboxError extends Exception {}

/**
 * PHP interface to Blackbox API
 */
class Blackbox {
	protected $blackboxUrl;
	protected $cookieRefreshUrl;

	/**
	 * Check if Session_id cookie value is valid
	 *
	 * @param SimpleXMLElement $response
	 * @return bool
	 */
	public function __construct($blackbox_url='http://blackbox.yandex-team.ru/blackbox/', $cookie_refresh_url='http://pass.yandex-team.ru/?retpath=') {
		$this->blackboxUrl = $blackbox_url;
		$this->cookieRefreshUrl = $cookie_refresh_url;
	}

	/**
	 * Check if Session_id cookie value is valid
	 *
	 * @param SimpleXMLElement $response
	 * @return bool
	 */
	public function isValid($response) {
		return $response->status && $response->status=='VALID';
	}

	/**
	 * Check if Session_id cookie value is valid
	 *
	 * @param SimpleXMLElement $response
	 * @return bool
	 */
	public function isNeedRenew($response) {
		if ($response->status=='NEED_RESET' || ($response->status=='NOAUTH' && $response->age<2*60*60)) {
			return $this->cookieRefreshUrl;
		}
		return false;
	}

	/**
	 * Call Blackbox's method sessionid (check Session_id cookie)
	 *
	 * @param string $sessionid	Session_id cookie value
	 * @param string $userip	User ip address (e.g. $_SERVER['REMOTE_ADDR'])
	 * @param string $host		Your service's host name
	 * @param array $dbfields	Set of dbfields to retrieve (e.g. array(BLACKBOX_LOGIN))
	 *
	 * @return SimpleXMLElement	Structure for Blackbox's answer (status, error, age, uid, dbfields if it was requested)
	 */
	public function sessionid($sessionid, $userip, $host, $dbfields=null) {
		return $this->request('sessionid', array(
			'sessionid' => $sessionid,
			'userip' => $userip,
			'host' => $host,
		), $dbfields);
	}

	/**
	 * Process request to Blackbox
	 *
	 * @param string $method
	 * @param array $params
	 * @param array $dbfields
	 * @throw BlackboxError
	 * @return SimpleXMLElement
	 */
	protected function request($method, $params=array(), $dbfields=null) {
		$params['method'] = $method;
		if ($dbfields && is_array($dbfields)) {
			$params['dbfields'] = implode(',', $dbfields);
		}
		$r = new SimpleXMLElement(self::httpRequest($this->blackboxUrl .'?'. http_build_query($params)));

		if ($r->exception) {
			throw new BlackboxError($r->exception .' ('.$r->exception['id'].'), '. $r->error);
		}

		if ($dbfields) {
			$df = $r->addChild('dbfields');
			foreach ($r->dbfield as $k=>$v) {
				$df->addAttribute($v['id'], strval($v));
			}
		}

		return $r;
	}

	/**
	 * Process HTTP request
	 * Detect CURL extension is installed or file_get_contents is allowed else uses raw socket
	 *
	 * @param string $url
	 * @return string
	 */
	protected static function httpRequest($url) {
		/* PHP CURL extension */
		if (function_exists('curl_init')) {
			$ch = curl_init();
			curl_setopt_array($ch, array(
				CURLOPT_URL => $url,
				CURLOPT_HEADER => false,
				CURLOPT_RETURNTRANSFER => true,
				CURLOPT_FOLLOWLOCATION => true,
			));
			$r = curl_exec($ch);
			curl_close($ch);

		/* PHP file_get_contents function */
		} elseif (ini_get('allow_url_fopen')) {
			$r = file_get_contents($url);

		/* Raw socket is a rocket ;) */
		} else {
			$t = parse_url($url);
			$r = '';
			if (($f = fsockopen($t['host'], 80))) {
				$req = 'GET '. $t['path'] .'?'. $t['query'] ." HTTP/1.1\r\n";
				$req .= 'Host: '. $t['host'] ."\r\n";
				$req .= "Connection: Close\r\n\r\n";
				fwrite($f, $req);
				while (!feof($f)) {
					$r .= fgets($f, 128);
				}
				/* decode chuncked response hack */
				$rl = explode("\r\n", $r);
				for ($i=0, $ci=sizeof($rl), $r=''; $i<$ci; $i++) {
					if ($i != $ci-1 && hexdec($rl[$i]) == strlen($rl[$i+1])) {
						$r .= $rl[++$i];
					}
				}
				fclose($f);
			}
		}

		return $r;
	}
}
