<?php
/* version: 1.3.1 (28.11.2018)

last edit: 24.11.2018

verPHP: 7+

*** краткое содержание ***
	
	** VARS **
	
	SYS::$M[<name>]       // Массив модулей
	SYS::$_CONFIG[<name>]     // Базовые настройки движка
	
	** loading **
	
	SYS::lib(string $name )
	
	** other **
	
	SYS::Log( string $msg )
	SYS::isPOST( string $var,... ) : bool
	SYS::session( [bool $create] )
	SYS::getSessionID(): string
	SYS::guid(): string
	SYS::Loging( string $path, array $arMask [, string $dateF])
	SYS::isBot(): bool
	SYS::isAjax(): bool
	SYS::back_location(): string
	SYS::createMapSite(string $path, array $arUrl [, string $modify]): string
	SYS::toSAF(string $name): string
	SYS::getPath(string $name): string
	
	** automatic class function **
	
	SYY::DONE()	
	SYS::INIT()	
	
	** class events **
	
	static function Initialization();
	static function Finalization()

		
*** Абстрактные свойства подгружаемых модулей-классов ***
	
	static function Initialization();
		- функция вызывается при первой загрузки модуля
	
	static function Finalization()
		- функция вызывается перед уничтожением системы

Перед запуском SYS::INIT необходимо определить константу CONFIG_FILE, в которой будет указан путь загрузки файла-конфигурации

*/


spl_autoload_register(__NAMESPACE__ .'\SYS::lib');

/**
* Класс для самоуничтожения системы, по завершению работы скриптов
*/
class ObjectSysDestruction{
	function free(){
		if(SYS::$M){
			foreach(SYS::$M as $module)
				if(method_exists($module,'Finalization'))
					$module::Finalization();
			SYS::$M = null;
		}
	}
	
	function __destruct(){
		$this->free();
	}
		
	// function __wakeup() { SYS::reConnection(); }
}


class SYS {
	
	/**
	* @var array $M          - Массив загруженных модулей/классов
	* @var array $_CONFIG    - Массив настроек системы
	* @var array $SAFabc     - Массив-константа, хранящий в себе замену символов, для формирования правильного SAF-имени
	* @var string $regSAFdel - Регулярное выражение для удаления из строки всех символов, которые нарушают правила SAF-имени
	*/
	static public
		$M = [],                  // Массив модулей
		$_CONFIG;                 // Базовые настройки движка
	
	/**
	* @var string $id_user_session               - ID сессии пользователя
	* @var int $bot                              - бот ли (0 = неопределено, 1 = да, -1 = нет)
	* @var object(ObjectSysDestruction) $objDest - Объект самозакрытия системы
	* @var bool $ajax                            - пришел ли запрос через ajax
	*/
	static private 
		$id_user_session,         // ID сессии пользователя
		$bot = 0,                 // бот ли (0 = неопределено, 1 = да, -1 = нет)
		$objDest,                 // Объект самозакрытия системы
		$ajax = null;             // пришел ли запрос через ajax
		

	static $SAFabc = [
		'а'=> 'a',   'А'=> 'a',   'A'=> 'a',
		'б'=> 'b',   'Б'=> 'b',   'B'=> 'b',
		'в'=> 'v',   'В'=> 'v',   'V'=> 'v',
		'г'=> 'g',   'Г'=> 'g',   'G'=> 'g',
		'д'=> 'd',   'Д'=> 'd',   'D'=> 'd',
		'е'=> 'e',   'Е'=> 'e',   'E'=> 'e',
		'ё'=> 'e',   'Ё'=> 'e',   'э'=> 'e',  'Э'=> 'e',
		'ж'=> 'g',   'Ж'=> 'g',   
		'з'=> 'z',   'З'=> 'z',   'Z'=> 'z',
		'и'=> 'i',   'И'=> 'i',   'I'=> 'i',
		'й'=> 'y',   'Й'=> 'y',   'Y'=> 'y',
		'к'=> 'k',   'К'=> 'k',   'K'=> 'k',
		'л'=> 'l',   'Л'=> 'l',   'L'=> 'l',
		'м'=> 'm',   'М'=> 'm',   'M'=> 'm',
		'н'=> 'n',   'Н'=> 'n',   'N'=> 'n',
		'о'=> 'o',   'О'=> 'o',   'O'=> 'o',
		'п'=> 'p',   'П'=> 'p',   'P'=> 'p',
		'р'=> 'r',   'Р'=> 'r',   'R'=> 'r',
		'с'=> 's',   'С'=> 's',   'S'=> 's',
		'т'=> 't',   'Т'=> 't',   'T'=> 't',
		'у'=> 'u',   'У'=> 'u',   'U'=> 'u',
		'ф'=> 'f',   'Ф'=> 'f',   'F'=> 'f',
		'х'=> 'h',   'Х'=> 'h',   'H'=> 'h',
		'ц'=> 'c',   'Ц'=> 'c',   'C'=> 'c',
		'ч'=> 'ch',  'Ч'=> 'ch',
		'ш'=> 'sh',  'Ш'=> 'sh',  'щ'=> 'sh', 'Щ'=> 'sh',
		'ы'=> 'i',   'Ы'=> 'i',  
		'ю'=> 'yu',  'Ю'=> 'yu',
		'я'=> 'ya',  'Я'=> 'ya',
		' '=> '_',   '&'=> '_and_', '|'=>'_or_', '!'=>'_not_',
		'J'=> 'j',   'Q'=> 'q',   'W'=> 'w',  'X'=> 'x'
	];
	static $regSAFdel = "/[^0-9_\-a-z]/";
	
	
	/**
	* загрузка библиотек классов/функций, находящиеся в каталогах сайта указанных в SYS::$_CONFIG['path']['loads']
	*
	* @param string $name - имя библиотеки без указания расшинения (т.е. вместо "module.php" или "module.class.php" следует указывать "module")
	* @return bool - возвращает успешность загрузки
	*/
	static function lib(string $name){
		$name = str_replace('\\', '/', $name);

		foreach(self::$_CONFIG['path']['loads'] as $pathLib){
			$path = "${_SERVER['DOCUMENT_ROOT']}/$pathLib/$name.class.php";
			if(file_exists($path)){
				try{
					include_once $path;
					
					if(class_exists($name)){
						self::$M[$name] = $name;
						if(method_exists($name,'Initialization'))
							$name::Initialization();
					}
					return true;
				} catch(Throwable $ex){
					self::Log('[ERROR] Libs "'.$_SERVER['DOCUMENT_ROOT'].'/'.$pathLib.'/'.$name.'.class.php": '.$ex->getMessage().' Line:'.$ex->getLine()."\n".$ex->getTraceAsString());
					return false;
				};
			}
		}

		foreach(self::$_CONFIG['path']['loads'] as $pathLib){
			$path = "${_SERVER['DOCUMENT_ROOT']}/$pathLib/$name.php";
			if(file_exists($path)){
				try{
					include_once $path;
					return true;
				} catch(Error $ex){
					self::Log('[ERROR] Libs "'.$_SERVER['DOCUMENT_ROOT'].'/'.$pathLib.'/'.$name.'.php": '.$ex->getMessage().' Line:'.$ex->getLine()."\n".$ex->getTraceAsString());
					return false;
				};
			}
		}

		self::Log('[ERROR] Libs "'.$name.'": the library file was not found');
		return false;
	}
	
	
	/**
	* Записывает в лок-файл (./system.log) переданное сообщение 
	*
	* @param string $msg  - сообщение, которое необходимо записать в лог-файл
	*/
	static function Log(string $msg){
		$fp = fopen($_SERVER['DOCUMENT_ROOT'].'/system.log', 'a');
		fwrite($fp, date('Y-m-d H-i-s').' '.$msg.chr(13).chr(10));
		fclose($fp);
	}
	
	/**
	* проверяет существование переменной(-ых) переданной(-ых) в POST, а так же ее/их не пустое значение
	* # метод остался с первых версий, вместо него рекомендуется использовать класс Request
	*
	* @param string $var  - имя переменной
	* @return bool
	*/
	static function isPOST(...$var): bool {
		$res = true;
		foreach($arVar as $var)
			$res = $res && isset($_POST[$var]) && !empty($_POST[$var]);
		return $res;
	}	
	
	/**
	* Создает уникальный идентификатор по системе GUID
	*
	* @param bool $trim   - если true, то будет убраны фигурные скобки
	* @return string
	*/
	static function guid($trim = true){
		// Windows
		if (function_exists('com_create_guid') === true) {
			if ($trim === true)
				return trim(com_create_guid(), '{}');
			else
				return com_create_guid();
		}

		$lbrace = $trim ? "" : chr(123);    // "{"
		$rbrace = $trim ? "" : chr(125);    // "}"

		// OSX/Linux
		if (function_exists('openssl_random_pseudo_bytes') === true) {
			$data = openssl_random_pseudo_bytes(16);
			$data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
			$data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
			return vsprintf($lbrace.'%s%s-%s-%s-%s-%s%s%s'.$rbrace, str_split(bin2hex($data), 4));
		}

		// Fallback (PHP 4.2+)
		mt_srand((double)microtime() * 10000);
		$charid = strtolower(md5(uniqid(rand(), true)));
		$hyphen = chr(45);                  // "-"
		$guidv4 = $lbrace.
				  substr($charid,  0,  8).$hyphen.
				  substr($charid,  8,  4).$hyphen.
				  substr($charid, 12,  4).$hyphen.
				  substr($charid, 16,  4).$hyphen.
				  substr($charid, 20, 12).$rbrace;
		return $guidv4;
	}
	
	/**
	* создает новую или возобновляет текущую сессию зашедшего пользоввателя
	*
	* @param bool $create - если true, то принудительно создает сессию заново, если же false (по умолчанию), то пытается возобновить текущую.
	*/
	static function session(bool $create = false){
			
		session_name(self::$_CONFIG['session']['name']);
		if(self::$_CONFIG['session']['timeout'] > 0)
			session_set_cookie_params (time()+self::$_CONFIG['session']['timeout']);
		
		if($create)
			session_regenerate_id(true);
		else session_start();
		
		self::$id_user_session = session_id();
	}
	
	/**
	* возвращает имя сессии
	*
	* @return string
	*/
	static function getSessionID(): string {
		return self::$id_user_session;
	}
	
	/**
	* Журналирование запроса, по указанным параметрам, в указанный файл
	*
	* @param string $path  - путь к файл-журналу, в который вести запись
	* @param array $arMask - маска и данные, которые необходимо записать в журнал (указанный порядок будет соблюден)
	*		ua     - запись User-Agent
	*		page   - запись страницы запроса
	*		ip     - запись IP, с которого пришел запрос
	*		script - запись запускаемого скрипта
	*		get    - запись переданных GET-переменных в запросе
	*		post   - запись переданных POST-переменных в запросе
	*		cookie - запись переданных COOKIE-переменных в запросе
	*		*      - все остальное запишиться как переданно в массиве.
	* @param string $dateF - формат записи даты/времени в журнале
	*/
	static function Loging(string $path, array $arMask, string $dateF = 'H:i:s'){
		$fLog = fopen($path, 'a');
		fwrite($fLog,"_________________________\n\r=  ".date($dateF)."\n\r----------\n\r");
		foreach($arMask as $mask)
			switch($mask){
				case 'ua':
					fwrite($fLog,'User-Agent = "'.$_SERVER['HTTP_USER_AGENT']."\"\n\r"); 
					break;
				
				case 'page':
					fwrite($fLog,"page = ".Route::getUserURL()."\n\r"); 
					break;
				
				case 'ip': 
					fwrite($fLog,"IP = ".Route::getUserIP()."\n\r"); 
					break;
				
				case 'script': 
					fwrite($fLog,"SCRIPT = ".$_SERVER['SCRIPT_NAME']."\n\r"); 
					break;
				
				case 'get':
					fwrite($fLog,"GET:\n\r");
					foreach($_GET as $name => $val)
						fwrite($fLog,"\t<li>$name = $val\n\r");
					fwrite($fLog,"\n\r");
					break;
					
				case 'post':
					fwrite($fLog,"POST:\n\r");
					foreach($_POST as $name => $val)
						fwrite($fLog,"\t<li>$name = $val\n\r");
					fwrite($fLog,"\n\r");
					break;
					
				case 'cookie':
					fwrite($fLog,"COOKIE:\n\r");
					foreach($_COOKIE as $name => $val)
						fwrite($fLog,"\t<li>$name = $val\n\r");
					fwrite($fLog,"\n\r");
					break;
					
				default:
					fwrite($fLog,"Other param: $mask\n\r");
			}
		fwrite($fLog,"\n\r");
		fclose($fLog);
	}
	

	/**
	* Проверка, не запрашивает ли страницу поисковый бот, или другая программа сканирования
	*
	* @return bool
	*/
	static function isBot(): bool{
		if(self::$bot == 0){
			$uaLow = strtolower($_SERVER['HTTP_USER_AGENT']);
			$bot =  strpos($uaLow,'bot')!== false || 
					strpos($uaLow,'megaindex')!== false ||
					strpos($uaLow,'search')!== false ||
					strpos($uaLow,'http') !== false ||
					strpos($uaLow,'library')!== false ||
					strpos($uaLow,'php')!== false ||
					strpos($uaLow,'python')!== false ||
					strpos($uaLow,'wordpress')!== false ||           // DDOS
					strpos($uaLow,'feedfetcher-google')!== false ||  // DDOS
					strpos($uaLow,'dataprovider') !== false;
			
			self::$bot = $bot ? 1 : -1;
		}
		return self::$bot == 1;
	}

	/**
	* Проверяет, не был ли запрос сделан через AJAX
	*
	* @return bool - влзвращает закешированный результат проверки переменной окружения $_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest'
	*/
	static function isAjax(){
		return self::$ajax;
	}
	
	/**
	* Возвращает адрес с которого был совершен переход на страницу
	*
	* @return string
	*/
	static function back_location(): string{
		return $_SERVER['HTTP_REFERER'] ?? '';
	}
	
	/**
	* Создает карту сайта, по переданному массиву ссылок
	*
	* @param string $path   - куда и под каким именем создавать файл-карту сайта
	* @param array $arUrl   - массив ссылок сайта
	* @param string $modify - параметр, который сообщает, как часто меняются данные по ссылкам
	* @return string
	*/
	static function createMapSite(string $path, array $arUrl, string $modify='weekly'): string {
		/*
			always  - всегда
			hourly  - каждый час
			daily   - каждый день
			weekly  - каждую неделю
			monthly - каждый месяц
			yearly  - каждый год
			never   - никогда
		*/
		$xml = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"];
		foreach($arUrl as $url){
			$url = str_replace(
				['&',"'",'"','>','<'],
				['&amp;','&apos;','&quot;','&gt;','&lt;'],
				$url
			);
			$xml[] = "<url><loc>$url</loc><changefreq>$modify</changefreq></url>\n";
		}
		$xml[] = '</urlset>';
		$xml = implode($xml);
		file_put_contents('./'.$path,$xml);
		return $xml;
	}

	/**
	* Преобразовывает переданную строку в SAF-имя
	*
	* @param string $name - строка, которую необходимо преобразовать в SAF-имя
	* @return string
	*/
	static function toSAF($name){
		return preg_replace(static::$regSAFdel, '',strtr($name, static::$SAFabc));
	}

	/**
	* Метод для более удобного получения путей из конфига (SYS::getPath('loads') == SYS::$_GONFIG['path']['loads'])
	*
	* @param string $name - наименование раздела
	* @return string
	*/
	static function getPath($name){
		return static::$_CONFIG['path'][$name];
	}
	
	/*
	**  инициализация
	*****/
	
	/**
	* Принудительное завершение работы системы
	*/
	static function DONE(){
		self::$objDest->free();
		exit;
	}
	
	/**
	* Старт системы
	*/
	static function INIT(){
		include_once CONFIG_FILE;

		if(self::$_CONFIG['debug']){
			ini_set('display_errors','On');
			ini_set('display_startup_errors','On');
			error_reporting(-1);
		}
		self::$objDest = new ObjectSysDestruction;
		
		$dbConfig = self::$_CONFIG['DB'][self::$_CONFIG['DB']['use']];
		switch( self::$_CONFIG['DB']['use'] ){
			case 'postgresql':
				self::lib('DB/postgresql.db');
				DB::connect($dbConfig['host'], $dbConfig['basename'], $dbConfig['user'], $dbConfig['password'], $dbConfig['port']);
				break;

			case 'mysql':
				self::lib('DB/mysql.db');
				DB::connect($dbConfig['host'], $dbConfig['basename'], $dbConfig['user'], $dbConfig['password'], $dbConfig['port']);
				break;
		}

		self::$ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';


		DB::$debug = self::$_CONFIG['debug'];
		Route::$basePath = self::$_CONFIG['path']['fileControllers'];
		
		View::$_DATA['BreadCrumbs'] = BreadCrumbs::get();
		View::$_DATA['head'] = ResourseForHeader::get();
			
		self::session();
		
		if(isset(self::$_CONFIG['modules']))
			foreach(self::$_CONFIG['modules'] as $name)
				self::lib($name);

		include_once self::$_CONFIG['path']['init'].'/init.php';
		include_once self::$_CONFIG['path']['init'].'/route.php';
	}

}

// небольша защита от скрытых запросов
if($_SERVER['HTTP_USER_AGENT'] == '')exit;
SYS::INIT();
