<?php
if(!function_exists('curl_init')){exit('Pour utiliser l\'API myAppliz, veuillez installer l\'extension cURL sur votre serveur.');}
if(!function_exists('json_decode')){exit('Pour utiliser l\'API myAppliz, veuillez installer l\'extension JSON sur votre serveur.');}
if(!in_array('sha256', hash_algos())){exit('Pour utiliser l\'API myAppliz, veuillez installer SHA256 sur votre serveur. Vous devrez peut-être mettre PHP à jour.');}

class myAppliz
{
	const URL = 'https://myappliz.com/';
	const URL_API = 'https://myappliz.com/api/';
	
	private $public_key = null;
	private $private_key = null;
	private $version = null;
	
	private $options = [
	
		'ids_in_keys' => true,
		'datetime_format' => 'c',
		'type' => 'array'
	
	];

	public function __construct(array $config = [])
	{
		if(!session_id())
		{
			session_start();
		}
		
		if(empty($config))
		{
			throw new Exception('Erreur de connexion à l\'API.');
		}
		
		if(!isset($config['public_key']))
		{
			throw new Exception('Vous devez fournir la clé publique de votre Appliz.');
		}
		
		$this->public_key = $config['public_key'];
		
		if(!isset($config['private_key']))
		{
			throw new Exception('Vous devez fournir la clé privée de votre Appliz.');
		}
		
		$this->private_key = $config['private_key'];
		
		if(!isset($config['version']))
		{
			throw new Exception('Vous devez préciser la version de l\'API que vous souhaiter utiliser.');
		}
		
		$this->version = $config['version'];
		
		if(isset($config['options']['type']) && ($config['options']['type'] == 'object'))
		{
			$this->options['type'] = $config['options']['type'];
		}
		
		if(isset($config['options']['ids_in_keys']))
		{
			$this->options['ids_in_keys'] = (bool)$config['options']['ids_in_keys'];
		}
		
		if(isset($config['options']['datetime_format']))
		{
			$this->options['datetime_format'] = $config['options']['datetime_format'];
		}
	}
	
	private function make_signed_request($content)
	{
		return hash_hmac('sha256', $content, $this->private_key, false);
	}
	
	private function getCurrentURL()
	{
		return (isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http').'://'.$_SERVER['HTTP_HOST'].(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '');
	}
	
	private function isAjaxRequest()
	{
		return (isset($_GET['use_ajax']) || isset($_POST['use_ajax']) || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'));
	}
	
	private function build_post_fields($data, $existingKeys = '', &$returnArray = [])
	{
		if(($data instanceof CURLFile) || !(is_array($data) || is_object($data)))
		{
			$returnArray[$existingKeys] = $data;
			
			return $returnArray;
		}
		else
		{
			foreach($data as $key => $item)
			{
				$this->build_post_fields($item, $existingKeys?$existingKeys."[$key]":$key, $returnArray);
			}
			
			return $returnArray;
		}
	}
	
	private function executeCurlSimpleRequest(array $curlOptions = [])
	{
		$c = curl_init();
		curl_setopt_array($c, $curlOptions);
		$output = curl_exec($c);
		curl_close($c);
		
		return $output;
	}
	
	private function processCurl(array $content = [], array $post = [], array $curlOptions = [], $multiChunk = false)
	{
		if($multiChunk === false)
		{
			$post['data'] = json_encode($content);
			
			$curlOptions += [
			
				CURLOPT_HTTPHEADER => [
				
					'Content-Type: multipart/form-data',
					'X-Public: '.$this->public_key,
					'X-Hash: '.$this->make_signed_request($post['data'])
										
				],
				CURLOPT_POSTFIELDS => $this->build_post_fields($post),
			
			];
			
			$output = $this->executeCurlSimpleRequest($curlOptions);
			
			return $output !== false ? json_decode($output, true) : false;
		}
		
		$curlOptions[CURLOPT_NOPROGRESS] = false;
		
		$chunk_args_1 = $content['args'][1];
		
		if($multiChunk > 1)
		{
			$chunk_args_1 = array_chunk($chunk_args_1, $multiChunk);
		}
		
		$curl_array = [];
		$mh = curl_multi_init();
		
		foreach($chunk_args_1 as $key => $val)
		{
			if(isset($val[0]['tmp_name']))
			{
				$val[0]['type'] = isset($val[0]['type']) ? $val[0]['type'] : null;
				$val[0]['name'] = isset($val[0]['name']) ? $val[0]['name'] : null;
				
				$post['file'] = new CURLFile($val[0]['tmp_name'], $val[0]['type'], $val[0]['name']);
			}
			
			$content['args'][1] = $val;
			
			$post['data'] = json_encode($content);
			
			$curlOptions_add = [
			
				CURLOPT_HTTPHEADER => [
				
					'Content-Type: multipart/form-data',
					'X-Public: '.$this->public_key,
					'X-Hash: '.$this->make_signed_request($post['data'])
				
				],
				CURLOPT_POSTFIELDS => $this->build_post_fields($post),
			
			];
			
			$c = curl_init();
			curl_setopt_array($c, $curlOptions + $curlOptions_add);
			curl_multi_add_handle($mh, $c);
			$curl_array[$key] = ['c' => $c, 'req' => $val];
		}
		
		$running = null;
		do {
		usleep(10000);
		curl_multi_exec($mh, $running);
		} while($running > 0);
		
		$res = []; 
		
		foreach($chunk_args_1 as $key => $val) 
		{
			$output = curl_multi_getcontent($curl_array[$key]['c']);
			curl_multi_remove_handle($mh, $curl_array[$key]['c']);
			curl_close($curl_array[$key]['c']);
			
			if($output !== false)
			{
				$output = json_decode($output, true);
			}
			
			if($output === false)
			{
				$output['exception'] = ['Une erreur inattendue s\'est produite.', 1707639570];
			}
			
			if(isset($output['process']))
			{
				$res = $output;
				break;
			}
			
			if(isset($output['exception']))
			{
				$res[]['exception'] = ['data' => $output['exception'], 'req' => $curl_array[$key]['req']];
			}
			elseif(isset($output['data']))
			{
				$res[]['result'] = ['data' => (array)$output['data'], 'req' => $curl_array[$key]['req']];
			}
		}
		
		curl_multi_close($mh);
		
		return $res;
	}
	
	private function curl(array $content = [], array $options = [])
	{
		$options += [
		
			'multi_chunk' => false,
		
		];
		
		$_GET['for_no_data'] = isset($_GET['for_no_data']) ? $_GET['for_no_data'] : 'ok';
		
		$post = [
		
			'url' => $this->getCurrentURL(),
			'v' => $this->version,
			'ajax' => $this->isAjaxRequest(),
			'var_get' => $_GET,
			'var_sess' => isset($_SESSION['myappliz']) ? $_SESSION['myappliz'] : ['no_data' => true]
		
		];
		
		$curlOptions = [
		
			CURLOPT_URL => self::URL_API,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_HEADER => false,
			CURLOPT_POST => true,
			CURLOPT_ENCODING => 'gzip',
			CURLOPT_FOLLOWLOCATION => true,
		
		];
		
		return $this->processCurl($content, $post, $curlOptions, $options['multi_chunk']);
	}
	
	public function load_js_api()
	{
		return '<script src="'.self::URL.'api/js/'.$this->public_key.'/" async></script>';
	}
	
	public function verif_signature()
	{
		if(
			!isset($_POST['data']) ||
			!isset($_SERVER['HTTP_X_PUBLIC']) ||
			!isset($_SERVER['HTTP_X_HASH']) ||
			($_SERVER['HTTP_X_PUBLIC'] !== $this->public_key)
		)
		{
			return false;
		}
		
		$hash = $this->make_signed_request($_POST['data']);
		
		if(($_SERVER['HTTP_X_HASH'] !== $hash) && (!isset($_SERVER['HTTP_X_HASH_OLD']) || ($_SERVER['HTTP_X_HASH_OLD'] !== $hash)))
		{
			return false;
		}
		
		return json_decode($_POST['data'], $this->options['type'] !== 'object');
	}
	
	public function __call(string $meth, array $args = [])
    {
		$use_curl = true;
		
		if($meth == 'login')
		{
			if(isset($_SESSION['myappliz']['access_id']))
			{
				$use_curl = false;
				
				$output = [
				
					'data' => ['is_connected' => true]
					
				];
			}
			elseif(!isset($_GET['myappliz_access_id']))
			{
				$use_curl = false;
				
				$redirect_url = self::URL.'?login=required&appliz='.$this->public_key.(isset($args[0]['redirect_url']) ? '&next='.rawurlencode($args[0]['redirect_url']) : '');
				
				$output = [
				
					'data' => [
					
						'is_connected' => false, 'redirect_url' => $redirect_url
					
					]
				];
				
				if(!isset($args[0]['redirect_auto']) || ($args[0]['redirect_auto']))
				{
					$output['process'] = [
					
						'header' => [
						
							['exit' => true, 'args' => ['Location: '.$redirect_url, true, 302]]
							
						]
						
					];
				}
			}
		}
		elseif($meth == 'url')
		{
			$use_curl = false;
			
			$perms = ['perms', 'uninstall'];
			
			if(!isset($_SESSION['myappliz']) || !isset($_SESSION['myappliz']['urls']))
			{
				throw new Exception('Méthode '.$meth.' : nécessite que l\'utilisateur soit connecté via la méthode login.');
			}
			
			if(!isset($args[0]) || !in_array($args[0], $perms))
			{
				throw new Exception('Méthode '.$meth.' nécessite le paramètre perms ou uninstall.');
			}
			
			$output = ['data' => $_SESSION['myappliz']['urls'][$args[0]]];
			
			if(isset($args[1]) && ($args[1] === true))
			{
				$output = [
				
					'header' => [
					
						['exit' => true, 'args' => ['Location: '.$_SESSION['myappliz']['urls'][$args[0]], true, 302]]
					
					]
				
				];
			}
		}
		
		if($meth == 'files')
		{
			if(!isset($args[0]) || !isset($args[1]))
			{
				throw new Exception('Méthode '.$meth.' : nécessite plus de paramètres.');
			}
			
			if(!in_array($args[0], ['upload', 'get', 'remove']))
			{
				throw new Exception('Méthode '.$meth.' : les paramètres sont incorrects.');
			}
			
			$args[1] = (array)$args[1];
			
			if($args[0] == 'upload')
			{
				$use_curl = false;
				
				$options = ['multi_chunk' => 1];
				
				$output = $this->curl(['meth' => $meth, 'args' => $args, 'options' => $this->options, 'via' => '__call'], $options);
				
				if(!isset($output['process']))
				{
					$datas = [];
					
					foreach($output as $key => $val)
					{
						if(isset($val['exception']))
						{
							if(isset($val['exception']['req'][1]['ref']))
							{
								$datas['data']['fail'][(string)$val['exception']['req'][1]['ref']] = $val['exception']['data'];
							}
							else
							{
								$datas['data']['fail'][$key] = $val['exception']['data'];
							}
						}
						elseif(isset($val['result']))
						{
							foreach($val['result']['data'] as $key1 => $val1)
							{
								if(isset($val1['ref']))
								{
									$datas['data']['success'][$val1['ref']] = $val1;
								}
								else
								{
									$datas['data']['success'][$key] = $val1;
								}
							}
						}
						
						$output = $datas;
					}
				}
			}
		}
		
		if($use_curl)
		{
			$output = $this->curl(['meth' => $meth, 'args' => $args, 'options' => $this->options, 'via' => '__call']);
		}
		
		if($output === false)
		{
			throw new Exception('Une erreur inattendue s\'est produite.', 1707639568);
		}
		elseif(isset($output['exception']))
		{
			if(is_array($output['exception']))
			{
				throw new Exception(...$output['exception']);
			}
			else
			{
				throw new Exception($output['exception'], 1707639569);
			}
		}
		elseif(isset($output['process']))
		{
			if(isset($output['process']['logout']))
			{
				$_SESSION = [];
				$params = session_get_cookie_params();
				setcookie(session_name(), '', (time()-42000), $params['path'], $params['domain'], $params['secure'], $params['httponly']);
				session_destroy();
			}
			
			if(isset($output['process']['session']))
			{
				foreach($output['process']['session'] as $key => $val)
				{
					if(is_array($val) && isset($val['date'], $val['timezone'], $val['timezone_type']))
					{
						$dateTime = DateTime::__set_state($val);
						
						if(isset($dateTime) && ($dateTime instanceof DateTime))
						{
							$val = $dateTime;
						}
					}
					
					$_SESSION['myappliz'][$key] = $val;
				}
			}
			
			if(isset($output['process']['exit']))
			{
				exit();
			}
			
			if(isset($output['process']['header']))
			{
				foreach($output['process']['header'] as $header)
				{
					header(...$header['args']);
					
					if(isset($header['exit']))
					{
						exit();
					}
				}
			}
		}
		
		if(isset($output['data']))
		{
			if($use_curl)
			{
				if($meth == 'users')
				{
					$ids_in_keys = $this->options['ids_in_keys'];
					
					if(isset($args[3]['ids_in_keys']))
					{
						$ids_in_keys = $args[3]['ids_in_keys'];
					}
					
					if($ids_in_keys === false)
					{
						$paging = null;
						if(isset($output['data']['paging']))
						{
							$paging = $output['data']['paging'];
							
							unset($output['data']['paging']);
						}
						
						$output['data'] = array_values($output['data']);
						
						if($paging)
						{
							$output['data']['paging'] = $paging;
						}
					}
				}
				elseif($meth == 'files')
				{
					if(isset($args[0], $args[1]))
					{
						$args[1] = array_unique($args[1], SORT_REGULAR);
						
						$ids_in_keys = $this->options['ids_in_keys'];
						
						if(isset($args[2]['ids_in_keys']))
						{
							$ids_in_keys = $args[2]['ids_in_keys'];
						}
						
						if($ids_in_keys === false)
						{
							$paging = null;
							if(isset($output['data']['paging']))
							{
								$paging = $output['data']['paging'];
								
								unset($output['data']['paging']);
							}
							
							$output['data'] = array_values($output['data']);
							
							if($paging)
							{
								$output['data']['paging'] = $paging;
							}
						}
					}
				}
			}
			
			return ($this->options['type'] == 'object') ? json_decode(json_encode($output), false)->data : $output['data'];
		}
		
		return false;
    }
}