<?php
class MdlProxy {
	private $setup = [
		'follow'     => true,
		'return'     => false,
		'timeout'    => 55,
		'headers'    => [],
		'env'        => null,	//auto
		'userAgent'  => 'Lumix Soft',
		'errorLevel' => E_USER_ERROR,
		'proxy'      => 'https://%MODULE%.modullus.com/proxy'
	];
	private $postData;
	private $lastError = '';
	private $postKeys;
	private $modules = [
		'base'      => [
			'abrev' => 'X',
		],
		'catalog'   => [
			'abrev' => 'C',
		],
		'form'      => [
			'abrev' => 'F',
		],
		'warehouse' => [
			'abrev' => 'W',
		],
		'billing'   => [
			'abrev' => 'B',
		],
		'report' => [
			'abrev' => 'R'
		],
		'data' => [
			'abrev' => 'D'
		]
	];

	public function __construct($setup = []) {
		if (isset($setup['setAccessHeaders'])) {
			$this->setAccessHeaders();
		}

		if (!$setup) {
			return;
		}

		foreach ($setup as $k => $v) {
			$this->setup[$k] = $v;
		}
	}

	public function decodeKey($key) {
		$keyInfo = ['module' => 'my', 'env' => '', 'srv' => '', 'dynKey' => null];
		$key     = explode('|', $key, 2)[0];                                      //might be a composed key, we consider only the first
		$aux     = explode('-', $key, 2);                                         //try to detect dynamic key

		if (count($aux) > 1) {
			$key               = $aux[0];
			$keyInfo['dynKey'] = $aux[1];
		}

		$key = substr($key, 33);

		if (!$key) {						//invalid key
			return $keyInfo;
		}

		//check if $key[0] is uppercase 65(A)-90(Z), 48(0)-57(9) if not we have a specific server defined first on one character
		while ($key && (ord($key[0]) > 90 || ord($key[0] < 58))) {	//at this position there is a server encoded, we loop until we find the module abrev
			$keyInfo['srv'] .= $key[0];
			$key            = substr($key, 1);
		}

		foreach ($this->modules as $mdl => $data) {
			if ($key[0] == $data['abrev']) {
				$keyInfo['module'] = $mdl;
				break;
			}
		}

		if (!is_null($this->setup['env'])) {	//enforced environment
			$keyInfo['env'] = $this->setup['env'];

			return $keyInfo;
		}

		$key = substr($key, 1);
		$n   = strlen($key);

		for ($i = $n - 1; $i >= 0; $i--) {
			if (!is_numeric($key[$i])) {
				$keyInfo['env'] = substr($key, 0, $i + 1);

				return $keyInfo;
			}
		}

		return $keyInfo;
	}

	/**
	 * This function is useful for doing api calls to base or a certain module
	 */
	public function call($returnResp = false) {
		$this->postData = '';

		if (!isset($_REQUEST['key'])) {
			trigger_error('No key is specified!');	//probably incorrect integration

			return;
		}

		if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
			return;
		}

		if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
			$this->setup['headers'][] = "Accept-Language: " . $_SERVER['HTTP_ACCEPT_LANGUAGE'];
		}

		foreach ($_SERVER as $key => $value) {
			if (substr($key, 0, 6) == "HTTP_X") {
				$this->setup['headers'][] = str_replace("_", "-", strtolower(explode("HTTP_", $key)[1])) . ':' . $value;
			}
		}

		$keyInfo            = $this->decodeKey($_REQUEST['key']);
		$this->setup['url'] = ($keyInfo['env'] ? $keyInfo['env'] . '-' : '') . $keyInfo['module'] . $keyInfo['srv'];
		$this->setup['url'] = str_replace('%MODULE%', $this->setup['url'], $this->setup['proxy']);

		foreach ($_REQUEST as $kParam => $vParam) {
			if (is_array($vParam)) {
				$kParam .= '[]';
			}

			$this->addPost($kParam, $vParam);
		}

		//response can be returned or echoed, in this case headers might needed to be set
		$this->setup['return'] = $returnResp;
		$resp = $this->request($this->setup);

		if ($returnResp) {
			return json_decode($resp, true);
		}

		echo $resp;
	}

	public function callMethod($fnct, $key, $data = []) {
		$this->postData = '';
		$keyInfo            = $this->decodeKey($key);
		$this->setup['url'] = ($keyInfo['env'] ? $keyInfo['env'] . '-' : '') . $keyInfo['module'] . $keyInfo['srv'];
		$this->setup['url'] = str_replace('%MODULE%', $this->setup['url'], $this->setup['proxy']) . "?ajax=$fnct&key=$key";

		if ($data && is_array($data)) {
			foreach ($data as $kParam => $vParam) {
				if (is_array($vParam)) {
					$kParam .= '[]';
				}

				$this->addPost($kParam, $vParam);
			}
		}

		//response will be returned to php, no need to set any headers
		$this->setup['return'] = true;
		$resp = $this->request();

		return json_decode($resp, true);
	}

	public function setAccessHeaders() {
		if (!isset($_SERVER['HTTP_REFERER'])) {
			return;
		}

		header('Access-Control-Allow-Origin: ' . rtrim($_SERVER['HTTP_REFERER'], '/'));
		header('Access-Control-Allow-Credentials: true');

		if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
			header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
		}
	}

	public function error($msg = '') {
		if (!$msg) {
			return $this->lastError;
		}

		$this->lastError = $msg;
		trigger_error($msg, E_USER_ERROR);

		return null;
	}

	public function request($setup = []) {
		if (!is_array($setup)) {
			$setup = ['url' => $setup];
		}

		//allow self signed sertificates or force http for development
		$this->setup['url'] = str_replace('https://', 'http://', $this->setup['url']);

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $this->setup['url']);
		curl_setopt($ch, CURLOPT_FAILONERROR, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->setup['timeout']);
		curl_setopt($ch, CURLOPT_ENCODING, '');
		curl_setopt($ch, CURLOPT_HEADER, true);
		//allow self signed sertificates or force http for development
		// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // not recommended to turn off
		// curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // not recommended to turn off

		if ($this->setup['headers']) {
			if (!is_array($this->setup['headers'])) {
				$this->setup['headers'] = [$this->setup['headers']];
			}

			curl_setopt($ch, CURLOPT_HTTPHEADER, $this->setup['headers']);
		}

		if ($this->postData) {
			curl_setopt($ch, CURLOPT_POST, true);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postData);
			$this->postData = '';
		}

		$request = curl_exec($ch);

		if (curl_errno($ch)) {
			trigger_error(preg_replace("/\\?.*\$/", '', curl_error($ch)), $this->setup['errorLevel']);
			curl_close($ch);

			return;
		}

		curl_close($ch);

		$aux = explode("\r\n\r\n", $request, 2);

		if ($aux[0] == 'HTTP/1.1 100 Continue') { //if big content response is sent in chunks and we have this header with newline after
			$aux    = explode("\r\n\r\n", $request, 3);
			$aux[0] = $aux[1];
			$aux[1] = $aux[2];
		}

		if (!$this->setup['return']) {
			foreach (explode("\r\n", $aux[0]) as $line) {
				//Ignore specific mdl headers
				if (preg_match('/^(content\-encoding|content\-length|transfer\-encoding|content\-transfer\-encoding|access-control-allow|server|strict-transport|expires)/i', $line)) {
					continue;
				}

				header($line);
			}
		}

		return $aux[1];
	}

	public function addPost($name, $val) {
		//if input supposed to be array and we have no data, we do not send it
		if (!$val && substr($name, -2) == '[]') {
			return;
		}

		if (!is_array($val)) {
			if (!$name) {
				$this->postData .= $val;
			} else {
				$this->postData .= "&$name=" . urlencode($val);
			}

			return;
		}

		//at this point value is array, double check name
		if ($name && substr($name, -2) != '[]') {
			$name .= '[]';
		}

		foreach ($val as $k => $v) {
			if ($name) { //if name is set this will be used, otherwise the key from the value array
				$k = $name;
			}

			$un = rtrim($k, '[]');

			if (!isset($this->postKeys[$un])) {
				$this->postKeys[$un] = 0;
			}

			$idx = $this->postKeys[$un];
			$this->postData .= $this->recFormatPostArray($idx, $k, $v);
			$this->postKeys[$un]++;
		}
	}

	private function recFormatPostArray($idx, $name, $val) {
		if (!is_array($val)) {
			return "&$name=" . urlencode($val);
		}

		//at this point $val is an array, so we index the array, this only happens on the first dept, as [] will be replaced after with [0]
		$name = str_replace('[]', "[$idx]", $name);
		$link = '';

		foreach ($val as $k => $v) {
			$k = urlencode($k);

			if (!is_array($v)) {
				$link .= "&{$name}[{$k}]=" . urlencode($v);

				continue;
			}

			if (!isset($name[$k])) {
				$link .= "&{$name}[{$k}]=" . json_encode($v);

				continue;
			}

			$link .= $this->recFormatPostArray($idx, "$name[$k]", $v);
		}

		return $link;
	}
}

global $_SKIP_MDLPROXY;

if (!isset($_SKIP_MDLPROXY) || (isset($_SKIP_MDLPROXY) && !$_SKIP_MDLPROXY)) {
	$mdlProxy = new MdlProxy(['setAccessHeaders' => true]);
	$mdlProxy->call();
}
