<?php
    use lfkeitel\phptotp\{Base32,Totp};

	//ini_set('display_errors','Off'); //Чтоб ошибки не отправлялись клиентам
	//ini_set("error_log", "php_error.log"); //Чтоб ошибки сохранялись в локальную папку

	$host = $_SERVER['HTTP_HOST'];
	$host = '.'.cutAfterLast($host,'.',2);
	ini_set('session.cookie_domain', $host);

	//if(isset($_GET[session_name()]) && $_GET[session_name()]!='') //Чтоб сессия переданная гетом была главней
	//	session_id($_GET[session_name()]);

    startSession(true); //Для ручного определения времени жизни сесии

	if(isset($_SESSION['REMOTE_ADDR']) && $_SESSION['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR']) unset($_SESSION['USER_ID']); //Делаемся не авторизованным если зашли с другого ip адреса
	if(!isset($_SESSION['USER_ID'])) { $_SESSION['USER_ID']=null; }
	if(!isset($_COOKIE['GUID'])) { $_COOKIE['GUID']=null; }

	//include("../include/xmltools.php");
	//include("../include/toExcell.php");
	//require_once("config.xyz");

	function getCurrentDirectory() {
		$path = dirname($_SERVER['PHP_SELF']);
		$position = strrpos($path,'/') + 1;
		return substr($path,$position);
	}

	//Полный путь к скрипту включая протокол без названия файла на конце "/"
	function getFullPath()
	{
		$pro="http";
		if (isset( $_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') $pro="https";
		$path = $pro.'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
		$position = strrpos($path,'/') + 1;
		return substr($path,0,$position);
	}

    function sendError($code, $error)
    {
        $obj = new StdClass();
        $obj->error_code=$code;
        $obj->error_message=$error;
        header('Content-Type: application/json');
        header("Cache-Control: no-cache, must-revalidate");
        echo json_encode($obj);
        exit();
    }

	function getSQLValue($t,$v)
	{
		global $db_connection;

		if($t=='object' && gettype($v)=='string'){
		    $t='string'; //Если id шники uuid
		}
		
		if($t=='object'){
		    if ($v=='-1'||$v=='' || $v==null) $v='NULL';
		}else
		if($t=='i1' || $t=='i2' || $t=='i4' || $t=='integer'){
		    if($v=='') $v='NULL';
		}else
		if($t=='f8' || $t=='f4' || $t=='real' || $t=='double'){
		    if($v=='')$v='NULL';
			$v=str_replace(',','.',$v); //Разделитель целой и дробной части точка
		}else
		if($t=='b'){
		    if($v=='') $v='NULL'; else
			if($v=='1') $v='true'; else
			if($v=='0') $v='false';
		}else
		if($t=='string' || $t=='html' || $t=='text' || $t=='dateTime' || $t=='time' || $t=='date' || $t=='file') {
            if ($v == '') {
                $v = 'NULL';
            } else {
                if (strpos($db_connection, 'pgsql') !== false)
                    $v = str_replace("'", "''", $v); //так как в SQL строку вставляется
                else
                    $v = str_replace('\'', '\\\'', $v); //так как в SQL строку вставляется
                $v = '\'' . $v . '\'';
            }
        }else if($t=="NULL" || $t==null){
            if ($v == '') {
                $v = 'NULL';
            } else {
                $v='\''.$v.'\'';
            }
		}else{
			$v='\''.$v.'\'';
		}
		return $v;
	}

	function getValue($t,$v)
	{
	    if($t=='object' && gettype($v)=='string'){
		    $t='string'; //Если id шники uuid
	    }
		
		if($t=='object'){
		    if (($v=='-1')||($v=='')) $v=null;
		}else
		if($t=='i4' || $t=='integer'){
		    if($v=='') $v=null;
		}else
		if($t=='f8'){
		    if($v=='')$v=null;
			$v=str_replace(',','.',$v); //Разделитель целой и дробной части точка
		}else
		if($t=='b'){
		    if($v=='') $v=null; else
			if($v=='1') $v=true; else
			if($v=='0') $v=false;
		}else
		if($t=='string' || $t=='dateTime' || $t=='date'){
		    if($v=='') $v=null;
		}
		return $v;
	}

	function getPDOTypeParam($t)
	{
		//if($t=='object' && gettype($v)=='string') $t='string'; //Если id шники uuid

		if($t=='object')
		{	//return PDO::PARAM_INT;
			return PDO::PARAM_STR;
		}else
		if($t=='i4' || $t=='integer')
		{	return PDO::PARAM_INT;
		}else
		if($t=='f8')
		{	return PDO::PARAM_STR;
		}else
		if($t=='b')
		{	return PDO::PARAM_BOOL;
		}else
		if($t=='string' || $t=='dateTime' || $t=='date')
		{	return PDO::PARAM_STR;
		}else
		if($t=='blob')
		{	return PDO::PARAM_LOB;
		}else
			return PDO::PARAM_STR;
	}

	//Заменить все значения первого фильтра значениями из второго
	function setFilter($n1,$n2)
	{
		if($n1==null || $n2==null) return;
		$nc1=$n1->firstChild;
		while ($nc1)
		{
			if ($nc1->nodeName=='column')
			{
				$nc2=findNodeOnAttribute($n2, 'column', 'n', $nc1->getAttribute("n"));
				if($nc2) getCdata($nc1)->nodeValue=getCdata($nc2)->nodeValue;
			}
			$nc1 = $nc1->nextSibling;
		}
	}

	//Получить узел метаданных  из базы данных
	function getMetadataNode($name)
    {
        global $db,$Schema;

        $xmls='';
        $sql='select xml from '.$Schema.'_metadata where del=false and name=\''.$name.'\';';
        $resX = $db->query($sql);
        while ($rowX = $resX->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
        {
            $xmls='<?xml version="1.0" encoding="UTF-8"?><metadata>'.trts($rowX['xml']).'</metadata>';
        }
        if($xmls!='')
        {
            //sendError(1,"Metadata node \"".$name."\" not find in database!");
            $objXMLDocument = new DOMDocument();
            try
            {
                $objXMLDocument->loadXML($xmls);
            } catch (Exception $e)
            {	sendError(1,$e->getMessage());
            }
            $currNode=findNodeOnAttribute($objXMLDocument->documentElement, "type","n",$name);
            return $currNode;
        }else{
            return null;
        }
    }

	function special_handler($exception)
	{
        sendError(1,$exception->getMessage());
	}
	set_exception_handler('special_handler'); //чтоб не пойманные исключения посылались в виде XML

	//Схема базы по умолчанияю
	if(!isset($Schema)) {
	   $Schema="";
	}
	//Тип идентификаторов
	$idType="object";
	if(!isset($idType)) {
	    $idType="object";
	}

    $db = connectToDB();
	if($db==null)
        sendError(1,trt('Error_connection_to_DB'));

	//Пытаемся автоматически залогинется по GUID из COOKIE (TODO авторизация должна быть в отдельном файле! Смотри директорию password )
	/*if($_SESSION['USER_ID']==null && $_COOKIE['GUID']!=null)
	{
		$res = $db->query("select * from ".$Schema."p__Login(null,null,null,'".$_COOKIE['GUID']."');");
		//$res = $db->query("select * from p__Login_1(null,null,null,'".$_COOKIE['GUID']."');");
		if($res->rowCount()>0)
		{
			$result = $res->fetch(PDO::FETCH_ASSOC);
			$_SESSION['USER_ID']=$result['id'];
		}
	}*/

	$fn=filter_input(INPUT_GET, 'fn', FILTER_VALIDATE_INT, array('options'=>array('default'=>-1)));

	$HTTP_INPUT=file_get_contents("php://input");
	if($HTTP_INPUT)
	{
		$doc = new DOMDocument();
		try
		{
		    $doc->loadXML($HTTP_INPUT);
		} catch (Exception $e)
		{
            sendError(1,$e->getMessage());
		}
		$reqNode = $doc->documentElement;

		if ($reqNode)
		{
			$fn = $reqNode->getAttribute("fn"); //Номер функции
		}
	}

//описание
//(fn==0) - отправить метаданные клиенту по запрошенному узлу
//(fn==1) - вставить одну запись в базу данных (результат id записи)
//(fn==2) - обновить запись
//(fn==3) - удалить запись
//(fn==4 || fn==11) - отправить данные клиенту соответствии с значением фильтра
//(fn==5) - взять данные для редактирования 1й записи по id
//(fn==6) - вернуть клиенту данные для заполнения обьекта SELECT выделенно в отдельную функцию для экономии трафика здесь могут также использоваться фильтры
//(fn==7) - залогинеться
//(fn==8) - отчёты почти тоже самое что и функция 4
//(fn==9) - Сохранить двоичные данные в базу
//(fn==10) - Получить двоичные данные из базы

	if ($fn==0) //отправить метаданные клиенту по запрошенному узлу
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

        $typename=findFirstNode($reqNode, "type")->getAttribute("n");
        $currNode=getMetadataNode($typename);

		if ($currNode!=null)
		{
            $objXMLDocument=$currNode->ownerDocument;

			//Вернём значение прав доступа для запрошенного объекта
			$allow_ins=false;
            $allow_upd=false;
            $allow_del=false;
			$sql_query='select '.$Schema.'p_getaccess(:user_id1,:action_insert) as ins,'.$Schema.'p_getaccess(:user_id2,:action_update) as upd,'.$Schema.'p_getaccess(:user_id3,:action_delete) as del;';
            $stmt = $db->prepare($sql_query);
            $stmt->bindValue(':user_id1', $_SESSION['USER_ID'], PDO::PARAM_INT); //getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID'])
            $stmt->bindValue(':user_id2', $_SESSION['USER_ID'], PDO::PARAM_INT); //getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID'])
            $stmt->bindValue(':user_id3', $_SESSION['USER_ID'], PDO::PARAM_INT); //getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID'])
            $stmt->bindValue(':action_insert', 'Insert_'.$typename, PDO::PARAM_STR);
            $stmt->bindValue(':action_update', 'Update_'.$typename, PDO::PARAM_STR);
            $stmt->bindValue(':action_delete', 'Delete_'.$typename, PDO::PARAM_STR);
            $res=$stmt->execute();
			while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
			{
                $allow_ins=$row['ins'] == 't';
                $allow_upd=$row['upd'] == 't';
                $allow_del=$row['del'] == 't';
			}
			$xmlAttr = $objXMLDocument->createAttribute("ins"); //insert
			$xmlAttr->nodeValue = $allow_ins ? "1" : "0";
			$currNode->setAttributeNode($xmlAttr);
			$xmlAttr = $objXMLDocument->createAttribute("upd"); //update
			$xmlAttr->nodeValue = $allow_upd ? "1" : "0";
			$currNode->setAttributeNode($xmlAttr);
			$xmlAttr = $objXMLDocument->createAttribute("del"); //delete
			$xmlAttr->nodeValue = $allow_del ? "1" : "0";
			$currNode->setAttributeNode($xmlAttr);

			$allow=true;
			$xmlAttr = $objXMLDocument->createAttribute("sel"); //select
			$xmlAttr->nodeValue = $allow ? "1" : "0";
			$currNode->setAttributeNode($xmlAttr);

			//Удаляем все запросы из узла
			for($i=0;$i<5;$i++)
			{	$nsql=findFirstNode($currNode, "sql-query");
				if($nsql!=NULL) $nsql->parentNode->removeChild($nsql);
			}

			$xmlstring='<?xml version="1.0" encoding="utf-8"?><metadata fn="0">'.$objXMLDocument->saveXML($currNode).'</metadata>';
			header('Content-type: text/xml');
			header("Cache-Control: no-cache, must-revalidate");
			echo $xmlstring;
			Exit();
		}else
		{
            sendError(1,'Не найден запрошенный узел: "'.$typename.'"!');
		}
	}else
	if ($fn==1) //вставка записи (результат id записи)
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$typename=findFirstNode($reqNode,"type")->getAttribute("n");
        $currNode=getMetadataNode($typename);

        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

			$sql_query=getCdataValue(findFirstNodeOnAttribute($currNode, "sql-query", "t", "i"));

			$nodeProp=findFirstNode($reqNode,"properties");
			//Представляем SQL в виде параметров
			$nodePropData=$nodeProp->firstChild;
			while($nodePropData != null)
			{
				if (($nodePropData->nodeName=="prop"))
				{
					//$vt=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("vt");
					$nPropR = findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"));
					if($nPropR!=NULL) { $vt=$nPropR->getAttribute("vt"); } else { $vt=''; }
					
					if($vt=='blob')
					{	$sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',':'.$nodePropData->getAttribute("n"),$sql_query);
						//Название файла запишем в указаное поле
                        $val=getCdataValue($nodePropData);
						$cd=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("cd");
						$nd=findFirstNodeOnAttribute($nodeProp,'prop','n',$cd);
						if($nd!=null) { getCdata($nd)->nodeValue=$val; }
					}else if($vt=='file')
					{
						$val=getCdataValue($nodePropData);
						$valSql=getSQLValue($vt, $val);
						$sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',$valSql,$sql_query);
						//Копируем файл из './temp/' в указанную папку относительно корня сайта
						$flnm = afterLast($val,'_');
						$dir = "./temp/";
						if($val!='' && file_exists($dir.$flnm))
						{
							$path= $_SERVER['DOCUMENT_ROOT'].'/'.findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("path");
							@mkdir($path); //Создаём папку если её нет
							if(!rename($dir.$flnm, $path.$flnm))
                                sendError(1,'Can\'t rename to "'.$path.$v.'"!');
						}
					}else
					{	$v=getSQLValue($vt, getCdataValue($nodePropData));
						$sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',$v,$sql_query);
					}
				}
				$nodePropData=$nodePropData->nextSibling;
			}
			$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query); //Потому что PostgreSQL не может хранить id пользователя привязаного к сесии

			$stmt = $db->prepare($sql_query);
			if($stmt === false) sendError(1,'Error preparing Statement');

			//присваеваем параметрам значения (В записи может быть только 1 двоичное поля см bindParam или сделать несколько переменных)
			$nodePropData=$nodeProp->firstChild;
			while($nodePropData != null)
			{
				if (($nodePropData->nodeName=="prop"))
				{
					//$vt=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("vt");
					$nPropR = findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"));
					if($nPropR!=NULL) { $vt=$nPropR->getAttribute("vt"); } else { $vt=''; }
					
					//Если тип blob то в качастве параметра приходит название файла переданого на сервер заранее
					if($vt=='blob')
					{	$v=getCdataValue($nodePropData);
						if($v!='' && file_exists('./temp/'.afterLast($v,'_')))
						{	$v = fopen('./temp/'.afterLast($v,'_'), 'rb');
						} else $v=null;
						$stmt->bindParam(':'.$nodePropData->getAttribute("n"), $v, PDO::PARAM_LOB);
					}
				}
				$nodePropData=$nodePropData->nextSibling;
			}
			try
			{
				$res = $stmt->execute();
			} catch (Exception $e)
			{
                if(str_contains($e->getMessage(), ']]'))
                    sendError(1, $e->getMessage());
                else
                    sendError(1, '[['.trt("SQL_query_error").']]'.$e->getMessage());
			}
			$result = $stmt->fetch(PDO::FETCH_NUM);
			if($result[0]=='')
			{
			    if(strpos($db_connection, 'sqlite')!==false) {
                    $result[0] = $db->lastInsertId(); //Для SQLite
                }else{
                    sendError(1,trt('Failed_to_insert_record').'!');
                }
			}
			
			$xmlstring='<?xml version="1.0" encoding="utf-8"?><metadata fn="1"><type n="'.$typename.'" id="'.$result[0].'"></type></metadata>';
            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xmlstring;
			Exit();
		}else
		{
            sendError(1,'Не найден запрошенный узел: "'.$typename.'"!');
		}
	}else
	if ($fn==2) //редактирование (результат id записи)
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$nodeType=findFirstNode($reqNode,"type");
		$typename=$nodeType->getAttribute("n");
		$obj_id=$nodeType->getAttribute("id");
		$node_properties=findFirstNode($nodeType,"properties");

        $currNode=getMetadataNode($typename);

        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

			$sql_query=getCdataValue(findFirstNodeOnAttribute($currNode, "sql-query", "t", "u"));
			//Представляем blob поля в SQL запросе в виде параметров
			$nodeProps=findFirstNode($reqNode,"properties");
			$nodePropData=$nodeProps->firstChild;
			while($nodePropData != null)
			{
				if (($nodePropData->nodeName=="prop"))
				{
					//$vt=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("vt");
					$nPropR = findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"));
					if($nPropR!=NULL) { $vt=$nPropR->getAttribute("vt"); } else { $vt=''; }
					
					if($vt=='blob')
					{	$sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',':'.$nodePropData->getAttribute("n"),$sql_query);
						//Название файла запишем в указаное поле
						$val=getCdataValue($nodePropData);
						$cd=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("cd"); //Имя поля с которого нужно брать название файла
						$nd=findFirstNodeOnAttribute($nodeProps,'prop','n',$cd);
						if($nd!=null) getCdata($nd)->nodeValue=$val;
					}else if($vt=='file') //Файл который не загружается в базу а храниться в файловой системе
					{
					    $val=getCdataValue($nodePropData);
					    $valSql=getSQLValue($vt, $val);
					    $sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',$valSql,$sql_query);				    
						//Копируем файл в указанную папку относительно корня сайта
					    $flnm = afterLast($val,'_');
						$dir = "./temp/";
						if($val!='' && file_exists($dir.$flnm))
						{
							$path= $_SERVER['DOCUMENT_ROOT'].'/'.findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("path");
							@mkdir($path);//Создаём папку если её нет
							if(!rename($dir.$flnm, $path.$flnm))
                                sendError(1,'Can\'t rename to "'.$path.$v.'"!');
						}
					}else
					{
						$val=getSQLValue($vt, getCdataValue($nodePropData));
						$sql_query=str_replace('${'.$nodePropData->getAttribute("n").'}',$val,$sql_query);
					}
				}
				$nodePropData=$nodePropData->nextSibling;
			}
			$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query); //Потому что PostgreSQL не может хранить id пользователя привязаного к сесии
			$sql_query=str_replace('${'.$currNode->getAttribute("ObjectID").'}',getSQLValue(gettype($obj_id),$obj_id),$sql_query); //Так как пока идентификатор базы отдельно передаётся

//sendError(1,$sql_query);
			$stmt = $db->prepare($sql_query);
			if($stmt === false) sendError(1,'Error preparing Statement');

			//Присваеваем параметру двоичную информацию (Внимание! Только 1 параметр может быть в 1 записи (почему?))
			$pos_v = 0;
			$mas_v[$pos_v]=null; //Чтобы данные не перекрывали друг друга при вставке
			$nodePropData=$nodeProps->firstChild;
			while($nodePropData != null)
			{
				if (($nodePropData->nodeName=="prop"))
				{
					//$vt=findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"))->getAttribute("vt");
					$nPropR = findFirstNodeOnAttribute($currNode,"prop","n",$nodePropData->getAttribute("n"));
					if($nPropR!=NULL) { $vt=$nPropR->getAttribute("vt"); } else { $vt=''; }
					
					if($vt=='blob')
					{
						$path_v=getCdataValue($nodePropData);
						if($path_v!='' && file_exists('./temp/'.afterLast($path_v,'_')))
						{	$v = fopen('./temp/'.afterLast($path_v,'_'), 'rb');
						}else 
						{	$v=null;
						}
						$mas_v[$pos_v]=$v;
						$stmt->bindParam(':'.$nodePropData->getAttribute("n"), $mas_v[$pos_v], PDO::PARAM_LOB);
						$pos_v++;
					}
				}
				$nodePropData=$nodePropData->nextSibling;
			}

			try
			{	$res = $stmt->execute();
			} catch (Exception $e)
			{
                if(str_contains($e->getMessage(), ']]')) //If already sending short error text.
                    sendError(1, $e->getMessage());
                else
                    sendError(1, '[['.trt("SQL_query_error").']]'.$e->getMessage()."\n".$sql_query);
			}
			$result = $stmt->fetch(PDO::FETCH_NUM); //$obj_id
			if($result[0]==''){ $result[0]=$obj_id; }
			$xmlstring='<?xml version="1.0" encoding="utf-8"?><metadata fn="2"><type n="'.$typename.'" id="'.$result[0].'"></type></metadata>';
            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xmlstring;
			Exit();
		}else
		{
            sendError(1,'Не найден запрошенный узел: "'.$typename.'"!');
		}
	}else
	if ($fn==3) //удаление (результат id записи)
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$nodeType=findFirstNode($reqNode,"type");
		$typename=$nodeType->getAttribute("n");
		$obj_id=$nodeType->getAttribute("id");

        $currNode=getMetadataNode($typename);
        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

			$sql_query=getCdataValue(findFirstNodeOnAttribute($currNode, "sql-query", "t", "d"));
			$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query); //Потому что PostgreSQL не может хранить id пользователя привязаного к сесии
			$sql_query=str_replace('${'.$currNode->getAttribute("ObjectID").'}',getSQLValue(gettype($obj_id),$obj_id),$sql_query);

			//Выполняем
			try
			{	$res = $db->query($sql_query);
			}catch (Exception $e)
			{
                if(str_contains($e->getMessage(), ']]')) //If already sending short error text.
                    sendError(1, $e->getMessage());
                else
                    sendError(1, '[['.trt("SQL_query_error").']]'.$e->getMessage()."\n".$sql_query);
			}
			//записываем id удалённой записи для удаления без перезагрузки страницы через javascript
			$xmlstring="";
			$xmlstring.="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
			$xmlstring.='<metadata fn="3">';
			$xmlstring.="    <type n=\"".$typename."\" id=\"".$obj_id."\"></type>\n";
			$xmlstring.="</metadata>\n";
            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xmlstring;
			Exit();
		}else
		{
            sendError(1,'Не найден запрошенный узел: "'.$typename.'"!');
		}
	}else
	if ($fn==4 || $fn==11) //взять данные из базы по переданным значениям фильтра ($fn==11 для обновления записи у клиента после вставки или редактировании)
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$rowspagecount = 100; //записей на страницу
		$nTypeR=findFirstNode($reqNode,'type');
		$typename=$nTypeR->getAttribute("n");
		$pagepos=$nTypeR->getAttribute("pp"); //текущяя страница page pos

        $currNode=getMetadataNode($typename);
        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

			$objListR = findFirstNode($nTypeR,'objects-list');
			//В переданном запросе может быть не полный фильтр заполняем серверный значениями из переданного
			$f1=findNodeOnPath($currNode,'objects-list/filter');
			$f2=findNodeOnPath($nTypeR,'objects-list/filter');
			setFilter($f1,$f2);//заменить все значения первого фильтра значениями из второго

			$sql_query=getCdataValue(findNodeOnPath($currNode, "objects-list/sql-query"));

			if($f1!=null)
			{
				$nextnode=$f1->firstChild;
				while ($nextnode)
				{	if ($nextnode->nodeName=='column')
					{	$vt=$nextnode->getAttribute("vt");
						$val=getCdataValue($nextnode);
						$val=getSQLValue($vt,$val);
						$sql_query=str_replace('${'.$nextnode->getAttribute("n").'}',$val,$sql_query);
					}
					$nextnode = $nextnode->nextSibling;
				}
			}
			$vType=gettype($_SESSION['USER_ID']);
			$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);
			if($objListR!=null && $objListR->getAttribute('order')!='' && $objListR->getAttribute('order')!=null && $objListR->getAttribute('order')!='null')
			{
				$sql_query=str_replace('${_order}',getSQLValue('i4',findNodeOnAttribute(findFirstNode($currNode,'objects-list'), "column","n",$objListR->getAttribute("order"))->getAttribute("order")),$sql_query);
			}else  $sql_query=str_replace('${_order}','1',$sql_query); 

			//sendError(1,$sql_query);
			//Выполняем запрос
			try
			{	$res = $db->query($sql_query);
			} catch (Exception $e)
			{	sendError(1,$e->getMessage().' '.$sql_query);
		  	}
			//Формируем ответ
			$pagecount=ceil($res->rowCount()/$rowspagecount); //Кол-во страниц

			//В месте с фильтром может прити и название полей которые нужно выбрать если есть хоть 1 поле то выберать только его
			$columns = array();
			$nextnode=findNode($nTypeR,'objects-list');
			if($nextnode)
			{	$nextnode=$nextnode->firstChild;//Пытаемся заполнить из запроса
				$i=0;
				while ($nextnode)
				{	if ($nextnode->nodeName=='column')
					{	$columns[$i]=$nextnode->getAttribute("n");
						$i++;
					}
					$nextnode = $nextnode->nextSibling;
				}
			}
			if(count($columns)==0)//Если нет ни одного столбца заполняем массив из серверного XML
			{
				$nextnode=findNode($currNode,'objects-list');
				if($nextnode)
				{	$nextnode=$nextnode->firstChild;//Пытаемся заполнить из запроса
					$i=0;
					while ($nextnode)
					{	if ($nextnode->nodeName=='column')
						{	$columns[$i]=$nextnode->getAttribute("n");
							$i++;
						}
						$nextnode = $nextnode->nextSibling;
					}
				}
			}

			//перебираем RS и строим XML только из тех столбцов которые записанны в секци objects-list поля column в не зависимости от их видимости
            $obj = new StdClass();
            $obj->error_code=0;
            $obj->error_message = '';
            $obj->fn=$fn;
            $obj->n=$typename;
            $obj->pc=$pagecount;
            $obj->pp=$pagepos;
            //Перечисляю название выбираемых столбцов через запятую (почему в JAVA версии этого куска кода нет?)
            $obj->objects_list=[];
            $nextnode=findNode($currNode,'objects-list')->firstChild;
            while ($nextnode)
            {
                if ($nextnode->nodeName=='column')
                {
                    array_push($obj->objects_list,$nextnode->getAttribute("n"));
                }
                $nextnode = $nextnode->nextSibling;
            }
            $obj->data=[];

            $node=findFirstNode($reqNode,'objects-list');
            $pos=-1;
            while ($row = $res->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
            {
                $pos++;
                if (($pagepos!=-1)&&(($pos<($pagepos*$rowspagecount))||($pos>=$pagepos*$rowspagecount+$rowspagecount))) { continue; }

                array_push($obj->data,new StdClass());

                //разрешать или запрещять редактировать запись надо проверять в хранимке а также запрещять либо разрешать редактировать колонку
                //для каждой записи формируеться строка настроек со значениями что нужно запретить в таком виде "iuds"
                $access=''; //u = enable update field, d = enable delete field
                if(!array_key_exists("_u",$row)) { $access.="u"; } else { $access.=$row["_u"]; }
                if(!array_key_exists("_d",$row)) { $access.="d"; } else { $access.=$row["_d"]; }

                if(array_key_exists($currNode->getAttribute("ObjectID"),$row)) {
                    end($obj->data)->id=$row[$currNode->getAttribute("ObjectID")];
                    end($obj->data)->a=$access;
                }else {
                    end($obj->data)->id="";
                    end($obj->data)->a=$access;
                }

                end($obj->data)->row=[];

                $nextnode=findNode($currNode,'objects-list')->firstChild;
                while ($nextnode)
                {
                    if ($nextnode->nodeName=='column')
                    {
                        if(array_key_exists($nextnode->getAttribute("n"),$row))
                        {
                            $field = $nextnode->getAttribute("n");
                            array_push(end($obj->data)->row,$row[$field]);
                        }else
                        {
                            sendError(1,"Column \"".$nextnode->getAttribute("n")."\" not exists in \"$typename\" for select!");
                        }
                    }
                    $nextnode = $nextnode->nextSibling;
                }
            }
            $res->closeCursor();

            header('Content-Type: application/json; charset=utf-8');
            header("Cache-Control: no-cache, must-revalidate");
            echo json_encode($obj);
            exit;
		}else
		{
            sendError(1,"Не найден запрошеный узел!");
		}
	}else
	if ($fn==5) //вернуть клиенту данные по id для редактирования одной записи
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$sql_query='';
		$node=findFirstNode($reqNode,'type');
		$typename=$node->getAttribute("n");
		$idval=$node->getAttribute("id"); //Значение идентификатора

        $currNode=getMetadataNode($typename);
        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

			//Тип поля с ObjectID
			$nField=findFirstNodeOnAttribute(findNode($currNode, 'properties'), "prop", "n", $node->getAttribute('ObjectID'));
			$csql=findNode(findFirstNodeOnAttribute($currNode, "sql-query", "t", "s"), "#cdata-section");
			if($csql!=NULL && $csql->nodeValue!="") //Есть ли SQL запрос
			{
				$sql_query=$csql->nodeValue;
				$sql_query=str_replace('${'.$node->getAttribute("ObjectID").'}',getSQLValue($nField->getAttribute('vt'),$idval),$sql_query);
				$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);
			}
		}
		//sendError(1,$sql_query);
		try
		{
			$res = $db->query($sql_query);
		} catch (Exception $e)
		{	sendError(1,$e->getMessage());
		}
		if(strpos($db_connection, 'sqlite')===false) //Для SQLite не работает rowCount()
		{
			if($res->rowCount()!=1) sendError(1,"Количество записей не равно одному!");
		}

		$xmls='';
		$resX = $db->query('select xml from '.$Schema.'_metadata where del=false and name=\''.$typename.'\';');
		while ($rowX = $resX->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
		{
		    $xmls='<?xml version="1.0" encoding="UTF-8"?><metadata>'.trts($rowX['xml']).'</metadata>';
		}
		if($xmls=='')
		{
            sendError(1,"Metadata node \"".$name."\" is empty!");
		}
		
		//загружаем мета данные и смотрим какие поля должны передать клиенту
		$mdoc = new DOMDocument();
		try
		{
		    $mdoc->loadXML($xmls);
		} catch (Exception $e)
		{	sendError(1,$e->getMessage());
		}
		//находим нужный узел
		$node=findNodeOnAttribute($mdoc->documentElement, "type","n",$typename);
		$node=findFirstNode($node,'properties');

		$xmlstring='<?xml version="1.0" encoding="utf-8"?>'."\n";
		$xmlstring.='<metadata fn="5">'."\n";
		$xmlstring.='<type n="'.$typename.'" id="'.$idval.'">'."\n";
		while ($row = $res->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
		{	$xmlstring.='<properties>';
			$nextnode=$node->firstChild;
			while($nextnode)
			{
				if($nextnode->nodeName=='prop')
				{
					try
					{
					    if(array_key_exists ( $nextnode->getAttribute("n"), $row) || ($nextnode->getAttribute("vt")=="file" && array_key_exists ( $nextnode->getAttribute("cd"), $row)))
					    {
    						if($nextnode->getAttribute("vt")=="b")
    						{
    						    if($row[$nextnode->getAttribute("n")]===false)
    						    {
    						        $row[$nextnode->getAttribute("n")]="0";
    						    } else if($row[$nextnode->getAttribute("n")]===true)
    						    {
    						        $row[$nextnode->getAttribute("n")]="1";
    						    }
    						}
    
    						if($nextnode->getAttribute("vt")=="blob") { //Только blob не file так как file как обычная текстовая строка (100 символов)
    						    if(array_key_exists($nextnode->getAttribute("cd"), $row)) {
                                    $xmlstring .= '<prop n="' . $nextnode->getAttribute("n") . '"><![CDATA[' . $row[$nextnode->getAttribute("cd")] . ']]></prop>' . "\n";
                                }else{
                                    sendError(1,'Поле "'.$nextnode->getAttribute("cd").'" не найдено в результирующем наборе!');
                                }
    						}
    						else {
    						    $xmlstring.='<prop n="'.$nextnode->getAttribute("n").'"><![CDATA['.$row[$nextnode->getAttribute("n")].']]></prop>'."\n";
    						}
					    }else
					    {
                            sendError(1,'Поле "'.$nextnode->getAttribute("n").'" не найдено в результирующем наборе!');
					    }
						
					} catch (Exception $e) { sendError(1,$e->getMessage()); }
				}
				$nextnode = $nextnode->nextSibling;
			}
			$xmlstring.='</properties>';
		}
		$xmlstring.='</type>';
		$xmlstring.='</metadata>';

		header('Content-type: text/xml');
		echo $xmlstring;
	}else
	if ($fn==6) //вернуть клиенту данные колонки таблицы для заполнения выпадающего списка SELECT либо выборка названия поля типа object
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		$tNodeR=findFirstNode($reqNode,'type');
		$typename=$tNodeR->getAttribute("n"); //Название принятого узла
		$columnname=$tNodeR->getAttribute("c"); //Столбец для выбора (через , могут перечислятся)
		$columns=explode(",",$columnname);
		$propName=$tNodeR->getAttribute("pn"); //Название поля <prop> нигде не используется передаётся обратно в результат
		//Теперь а нутри фильтра передаётся поэтому ненужно $propid=$tNodeR->getAttribute("id"); //-1 или '' то много записей иначе 1 должна вернуться
		//if($propid==-1 || $propid=='') $propid='NULL';
		

        $currNode=getMetadataNode($typename);
        if($currNode==null) sendError(1,"Not find \"".$typename."\"!");
        $objXMLDocument=$currNode->ownerDocument;

		$objListR = findFirstNode($tNodeR,'objects-list'); //Из запроса
		$f1=findNodeOnPath($currNode, 'objects-list/filter');
		$f2=findNodeOnPath($tNodeR,'objects-list/filter');

		setFilter($f1,$f2);//заменить все значения первого фильтра значениями из второго

		$sql_query=getCdataValue(findNodeOnPath($currNode, "objects-list/sql-query"));
		if($f1!=NULL)
		{
			$nextnode=$f1->firstChild;
			while ($nextnode)
			{	if ($nextnode->nodeName=='column')
				{	$val=getSQLValue($nextnode->getAttribute("vt"),getCdata($nextnode)->nodeValue);
					$sql_query=str_replace('${'.$nextnode->getAttribute("n").'}',$val,$sql_query);
				}
				$nextnode = $nextnode->nextSibling;
			}
		}
		//$sql_query=str_replace('${'.$currNode->getAttribute("ObjectID").'}',getSQLValue(gettype($propid),$propid),$sql_query); //Чтоб вернулась 1 запись если это не выпадающий
		$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);
		if($objListR!=null && $objListR->getAttribute('order')!='' && $objListR->getAttribute('order')!=null && $objListR->getAttribute('order')!='null')
		{
			$sql_query=str_replace('${_order}',findNodeOnAttribute(findFirstNode($currNode,'objects-list'), "column","n",$objListR->getAttribute("order"))->getAttribute("order"),$sql_query);
		}else  $sql_query=str_replace('${_order}','1',$sql_query); 

		try
		{	$res = $db->query($sql_query);
		} catch (Exception $e)
		{	sendError(1,$e->getMessage());
  		}

	  	//выбираем данные из базы и отправляем клиенту
		$xmlstring='<?xml version="1.0" encoding="utf-8"?>'."\n";
		$xmlstring.='<metadata fn="6">'."\n";
		$xmlstring.='<type n="'.$typename.'" pn="'.$propName.'">'."\n";
		while ($row = $res->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
		{
			$xmlstring.='<record id="'.$row[$currNode->getAttribute("ObjectID")].'">';
			$val="";
			for($i=0;$i<count($columns);$i++)
			{
				if ($val!="") $val.=" ";
				if(array_key_exists($columns[$i],$row))
				{
				    $val.=$row[$columns[$i]];
				}else
				{
                    sendError(1,"Column \"$columns[$i]\" not exists in \"$typename\" for select to drop down list!");
				}
			}
			$xmlstring.='<![CDATA['.$val.']]>';
			$xmlstring.='</record>'."\n";
		}
		$xmlstring.='</type>'."\n";;
		$xmlstring.='</metadata>'."\n";;
		header('Content-type: text/xml');
		echo $xmlstring;
	}else
	if ($fn==7)//Залогинеться
	{
		$cmd=getCdataValue(findFirstNode($reqNode,"cmd"));
		$login=getCdataValue(findFirstNode($reqNode,"login"));
		$password=getCdataValue(findFirstNode($reqNode,"password"));
        $key=getCdataValue(findFirstNode($reqNode,"key"));
        $time=getCdataValue(findFirstNode($reqNode,"time"));
		$guid=getCdataValue(findFirstNode($reqNode,"guid")); //Зачем коментил?

		if($cmd==0) //Restore password by email
		{
            $recovery=false;
			$res = $db->query("select email from ".$Schema."_Users where del=false and (email = '$login' or login = '$login');");
			if($res->rowCount()>0)
			{
                while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
                    /*$pos++;
                    if (($pagepos!=-1)&&(($pos<($pagepos*$rowspagecount))||($pos>=$pagepos*$rowspagecount+$rowspagecount))) { continue; }

                    //разрешать или запрещять редактировать запись надо проверять в хранимке а также запрещять либо разрешать редактировать колонку
                    //для каждой записи формируеться строка настроек со значениями что нужно запретить в таком виде "iuds"
                    //$access=$row["access"];
                    $access=''; //u = enable update field, d = enable delete field
                    if(!array_key_exists("_u",$row)) { $access.="u"; } else { $access.=$row["email"]; }*/

                    $password = getPassword(6);
                    $sql = 'update ' . $Schema . '_users set password=md5(:password) where email=:email';
                    $stmt = $db->prepare($sql);
                    $stmt->bindValue(':password', $password, PDO::PARAM_STR);
                    $stmt->bindValue(':email', $row["email"], PDO::PARAM_STR);
                    try {
                        $stmt->execute();
                        if ($stmt->rowCount() > 0) {
                            //$result = $stmt->fetch(PDO::FETCH_NUM);
                            $recovery = true;
                        }
                    } catch (Exception $e) {
                        $db->rollBack();
                        sendError(1, $e->getMessage());
                    }

                    $html = '<html><head><title>Message</title></head><body>';
                    $html .= '<h1>New password:</h1>';
                    $html .= '<b>' . $password . '</b>';
                    $html .= '</body></html>';
                    //mail($login,'rigor.kz','Not implement',"Content-type: text/html; charset=utf-8\r\nFrom: rigor Site <info@rigor.kz>");
                    if (!mail($login, 'Password for monitoring', $html, "Content-type: text/html; charset=utf-8\r\nFrom: Transit Site <no-reply@istt.kz>")) {
                        sendError(1,"Failed to send mail to: " . $row["email"]);
                    }
                }
			}
			if($recovery) {
                $xs = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
                $xs .= '<metadata fn="7"><![CDATA[OK]]></metadata>';
                header('Content-type: text/xml');
                header("Cache-Control: no-cache, must-revalidate");
                echo $xs;
                exit();
            }else{
                $xs = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
                $xs .= '<metadata fn="7"><![CDATA[ERROR]]></metadata>';
                header('Content-type: text/xml');
                header("Cache-Control: no-cache, must-revalidate");
                echo $xs;
                exit();
            }

		}elseif($cmd==1) //Logout
		{
			$sql='delete from '.$Schema.'_Logins where sessionid='.getSQLValue('string',$_COOKIE['GUID']).' and user_id='.getSQLValue('object',$_SESSION['USER_ID']).';';
			try
			{	$db->exec($sql);
			} catch (Exception $e)
			{	sendError(1,$e->getMessage());
			}
			unset($_SESSION['USER_ID']);

			$xs='<?xml version="1.0" encoding="utf-8"?>'."\n";
			$xs.='<metadata fn="7">'."\n";
			$xs.='	<cmd><![CDATA['.$cmd.']]></cmd><login><![CDATA[0]]></login><sesid><![CDATA['.session_id().']]></sesid><sesname><![CDATA['.session_name().']]></sesname>'."\n";
			$xs.='</metadata>';
            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xs;
			exit();
			
		}elseif($cmd==2) //Проверить залогинен ли пользователь	
		{
			$xs='<?xml version="1.0" encoding="utf-8"?>'."\n";
			$xs.='<metadata fn="7">'."\n";			
			if(!isset($_SESSION['USER_ID']) || $_SESSION['USER_ID']=='')
			{
				$xs.='	<![CDATA[0]]><cmd><![CDATA['.$cmd.']]></cmd>'."\n";
			}else
			{
				$xs.='	<![CDATA[1]]><cmd><![CDATA['.$cmd.']]></cmd>'."\n";
				$res = $db->query('select * from '.$Schema.'p__Login('.getSQLValue($idType,$_SESSION['USER_ID']).',null,null,null,null,null);');
				if($row = $res->fetch(PDO::FETCH_ASSOC))
				{
					$xs.='	<name><![CDATA['.$row['name'].']]></name>'."\n";
					$xs.='	<surname><![CDATA['.$row['surname'].']]></surname>'."\n";
					$xs.='	<patronymic><![CDATA['.$row['patronymic'].']]></patronymic>'."\n";
					$xs.='	<company_id><![CDATA['.$row['company_id'].']]></company_id>'."\n";
                    $xs.='	<expiration><![CDATA['.$row['expiration'].']]></expiration>'."\n";
                    $xs.='	<overdue><![CDATA['.$row['overdue'].']]></overdue>'."\n";
				}
			}
			$xs.='</metadata>';

            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xs;
			exit();

		}elseif ($cmd==3) //Авторизация по логину и паролю
		{
			//По идентификатору выбираем информацию о пользователе
			$ans='0';
			$name='';
			$surname='';
			$patronymic='';
            $expiration=false;
            $overdue=false;
			$sql="select * from ".$Schema."p__Login(".getSQLValue($idType,$_SESSION['USER_ID']).",'$login','$password',null,null,null);";
            try
            {
                $res = $db->query($sql);
            } catch (Exception $e)
            {
                if(str_contains($e->getMessage(), ']]'))
                    sendError(1, $e->getMessage());
                else
                    sendError(1, '[['.trt("SQL_query_error").']]'.$e->getMessage());
            }
			if($res->rowCount()>0)
			{
			    $result = $res->fetch(PDO::FETCH_ASSOC);
				$ans='1';
				$_SESSION['USER_ID']=$result['id'];
				$name=$result['name'];
				$surname=$result['surname'];
				$patronymic=$result['patronymic'];
                $expiration=$result['expiration']; //Дата смены пароля
                $overdue=$result['overdue']; //Просрочен ли пароль
                //Проверяю на соответствие токену TOPT если секретный ключ задан
                if($result['secret']){
                    $secret = Base32::decode($result['secret']);
                    $genkey = (new Totp('sha1',0,60))->GenerateToken($secret,$time);
                    if($key != $genkey) {
                        $ans = '0';
                        $_SESSION['USER_ID'] = '';
                    }
                }
			}
			$xs='<?xml version="1.0" encoding="utf-8"?>'."\n";
			$xs.='<metadata fn="7">'."\n";
			$xs.='<![CDATA['.$ans.']]>';
			$xs.='	<cmd><![CDATA['.$cmd.']]></cmd>'."\n";
			$xs.='	<login><![CDATA['.$ans.']]></login>'."\n";
			$xs.='	<name><![CDATA['.$name.' '.$surname.' '.$patronymic.']]></name>'."\n";
			$xs.='	<sesid><![CDATA['.session_id().']]></sesid>'."\n";
			$xs.='	<sesname><![CDATA['.session_name().']]></sesname>'."\n";
            $xs.='	<expiration><![CDATA['.$expiration.']]></expiration>'."\n";
            $xs.='	<overdue><![CDATA['.$overdue.']]></overdue>'."\n";
			$xs.='</metadata>';
            header('Content-type: text/xml');
            header("Cache-Control: no-cache, must-revalidate");
			echo $xs;
			exit();
		}else{
            sendError(1,'Command "'.$cmd.'" not find!');
		}
	
	}else
	if ($fn==8)//Получить отчёт как Excel.xls файл (почти тоже самое что и функция 4)
	{
        $dir='./temp/';
        // отправка файла в отдельном файле download.php для совместимости с java версией
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими

		//Выбираем информацию о текущем  пользователе
		$name='';
		$sql="select Coalesce(surname,'') || ' ' || Coalesce(name,'') || ' ' || Coalesce(patronymic,'') as name from ".$Schema."_users where id=:user_id;";
        $stmt = $db->prepare($sql);
        $stmt->bindValue(':user_id', $_SESSION['USER_ID'], PDO::PARAM_INT);
        $res=$stmt->execute();
        if($res && $stmt->rowCount()>0)
		{	$result = $stmt->fetch(PDO::FETCH_ASSOC);
			$name='<i>'.$result['name'].'</i><br>';
		}
		$res=null; //Чтоб сработал сборщик мусора, а то вываливается ошибка: "Cannot execute queries while other unbuffered queries are active".

		$nTypeR=findFirstNode($reqNode,'type');
		$typename=$nTypeR->getAttribute("n");
		$ext=$nTypeR->getAttribute("ext");

        $currNode=getMetadataNode($typename);
        if ($currNode!=null)
        {
            $objXMLDocument=$currNode->ownerDocument;

            $objListR = findFirstNode($nTypeR,'objects-list');
			//В переданном запросе может быть не полный фильтр заполняем серверный значениями из переданного
			$f1=findNodeOnPath($currNode,'objects-list/filter');
			$f2=findNodeOnPath($nTypeR,'objects-list/filter');
			setFilter($f1,$f2);//заменить все значения первого фильтра значениями из второго

            //Текстовые данные заполняются в поле caption и они вставляются в поля для информации что было заполнено
			//Выбираем параметры фильтра (Для информирования что было заполнено)
			$filter='<b>'.trt('Filter_options').'</b><br>';
			$nextnode=$f1->firstChild;
			while ($nextnode)
			{	if ($nextnode->nodeName=='column')
				{
					if($nextnode->getAttribute('visible')!='0')
					{
						$filter.='<i>'.$nextnode->getAttribute("d").': </i>';
						if($nextnode->getAttribute("vt")=='object')
						{
							if(getCdataValue($nextnode)=='')
							{	$filter.='';
							}else
							{
								//Ищем нужный узел и выполняем запрос
                                $nTypeO=getMetadataNode($nextnode->getAttribute("object"));
								//$nTypeO=findNodeOnAttribute($objXMLDocument->documentElement,"type","n",$nextnode->getAttribute("object"));
								$sql_query=getCdataValue(findNodeOnPath($nTypeO, "objects-list/sql-query"));

								$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);

								$nOID=findNodeOnAttribute(findNodeOnPath($nTypeO, "objects-list/filter"), 'column', 'n', $nTypeO->getAttribute('ObjectID'));
								$sql_query=str_replace('${'.$nOID->getAttribute('n').'}',getSQLValue($nOID->getAttribute('vt'),  getCdataValue($nextnode)),$sql_query);

								//Обнуляем остальные значения фильтра
								$nCol=findNodeOnPath($nTypeO, "objects-list/filter")->firstChild;
								while ($nCol)
								{	if ($nCol->nodeName=='column')
									{	$sql_query=str_replace('${'.$nCol->getAttribute("n").'}','NULL',$sql_query);
									}
									$nCol = $nCol->nextSibling;
								}

								$res=null;
								try
								{	$res = $db->query($sql_query);
								} catch (Exception $e)
								{	sendError(1,$e->getMessage());
								}
								if($res->rowCount()!=1) sendError(1,trt('The number of records is not equal to one!').' '.$sql_query);


								$columns=explode(",",$nextnode->getAttribute('FieldCaption'));
								if($res->rowCount()>0)
								{
									$row = $res->fetch(PDO::FETCH_ASSOC);								
									for($i=0;$i<count($columns);$i++)
									{
										$filter.=$row[$columns[$i]].' ';
									}
								}

								$res=null; //Чтоб сработал сборщик мусора, а то вываливается ошибка: "Cannot execute queries while other unbuffered queries are active".
							}
						}else if($nextnode->getAttribute("vt")=='b')
						{
							if(getCdataValue($nextnode)=='1') $filter.='Да';
							if(getCdataValue($nextnode)=='0') $filter.='Нет';
						}else
						{
							$filter.=getCdataValue($nextnode);
						}
						$filter.='<br>';
					}
				}
				$nextnode = $nextnode->nextSibling;
			}

			$sql_query=getCdataValue(findNodeOnPath($currNode, "objects-list/sql-query"));
			$nextnode=$f1->firstChild;
			while ($nextnode)
			{	if ($nextnode->nodeName=='column')
				{	$vt=$nextnode->getAttribute("vt");
					$val=getCdataValue($nextnode);
					$val=getSQLValue($vt,$val);
					$sql_query=str_replace('${'.$nextnode->getAttribute("n").'}',$val,$sql_query);
				}
				$nextnode = $nextnode->nextSibling;
			}
			$sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);
            if($objListR!=null && $objListR->getAttribute('order')!='' && $objListR->getAttribute('order')!=null && $objListR->getAttribute('order')!='null')
            {
                $sql_query=str_replace('${_order}',findNodeOnAttribute(findFirstNode($currNode,'objects-list'), "column","n",$objListR->getAttribute("order"))->getAttribute("order"),$sql_query);
            }else  $sql_query=str_replace('${_order}','1',$sql_query);

			//Выполняем запрос
			try
			{	$res = $db->query($sql_query);
			} catch (Exception $e)
			{	sendError(1,$e->getMessage().$sql_query);
		  	}

			//Сохраняем результсет в файл в виде HTML с расширением XLS
            $num=rand(0,1000);
			$file='file_'.$num.'.xls';
			$myFile = $dir.$file;

            if(($fh = @fopen($myFile, 'w')) !== FALSE){
				fwrite($fh, '<html>'."\n");
				fwrite($fh, '	<head>'."\n");
				fwrite($fh, '		<title>'.$currNode->getAttribute("d").'</title>'."\n");
				fwrite($fh, '		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'."\n");
				fwrite($fh, '<style>td {mso-number-format: "\@";}</style>');
				fwrite($fh, '	</head>'."\n");
				fwrite($fh, '	<body>'."\n");

				fwrite($fh, '<b>'.trt('Time_and_date_of_generation').': </b><i>'.date('H:i:s m.d.Y').'</i><br>');
				fwrite($fh, '<b>'.trt('Creator').': </b>'.$name);
				fwrite($fh, $filter);
				
				fwrite($fh, '		<table border="1" cellspacing="0">'."\n");
				fwrite($fh, '			<caption><b>'.findNode($currNode,'objects-list')->getAttribute("d").'</b></caption>'."\n");
				fwrite($fh, '			<thead>'."\n");
				fwrite($fh, '				<tr>');
                fwrite($fh, '<td bgcolor="#d1d1d1">№</td>');
				$nextnode=findNode($currNode,'objects-list')->firstChild;
				$col=0;
				while ($nextnode)
				{	if ($nextnode->nodeName=='column')
					{	fwrite($fh, '<td bgcolor="#d1d1d1" width="'.$nextnode->getAttribute("width").'px"><b>'.$nextnode->getAttribute("d")."</b></td>");
					}
					$nextnode = $nextnode->nextSibling;
				}
				fwrite($fh, '				</tr>'."\n");
				fwrite($fh, '			</thead>'."\n");
				fwrite($fh, '			<tbody>'."\n");
                $pos=0;
				while ($row = $res->fetch(PDO::FETCH_ASSOC))
				{
					fwrite($fh, '				<tr>');
                    fwrite($fh, '<td>'.(++$pos).'</td>');
					$nextnode=findNode($currNode,'objects-list')->firstChild;
					while ($nextnode)
					{	if ($nextnode->nodeName=='column')
						{
							fwrite($fh, '<td>'.$row[$nextnode->getAttribute("n")].'</td>');
						}
						$nextnode = $nextnode->nextSibling;
					}
					fwrite($fh, '</tr>'."\n");
				}
				$res->closeCursor(); //Мож поможет избавиться от ошибки: "Cannot execute queries while other unbuffered queries are active."
				
  				fwrite($fh, '			</tbody>'."\n");
				fwrite($fh, '		</table>'."\n");
				
				fwrite($fh, '	</body>'."\n");
				fwrite($fh, '</html>'."\n");

				fclose($fh);
			}else{
                sendError(1,trt('Failed_to_generate_report'));
            }

			/*if($ext=="pdf")
			{
				//Если в виде PDF
				$mpdf = new mPDF('utf-8', 'A4', '8', '', 10, 10, 7, 7, 10, 10); // задаем формат, отступы и.т.д.
				$mpdf->list_indent_first_level = 0;
				$mpdf->WriteHTML(file_get_contents($myFile)); // формируем pdf

				$dir='./temp/';
				$file='file_'.$num.'.pdf';
				$myFile = $dir.$file;

				$mpdf->Output($myFile, 'F');
			}*/
			//$myFile=getFullPath().'temp/'.$file;
			
			//отправляем ссылку на файл TODO переделать в JSON
			header('Content-type: text/xml');
			echo '<?xml version="1.0" encoding="utf-8"?><metadata fn="8"><file><![CDATA['.$num.']]></file></metadata>';

			//deleteTempFiles($dir);
		}else
		{
            sendError(1,trt('Not found the requested node:').' "'.$typename.'"!');
		}

	}else
	if ($fn==9) //Сохранить файл во временную папку
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими
		$dir = "./temp/";
		if (!file_exists($dir)) {
		    mkdir($dir, 0777);
		}

		//Так как у файлов могут быть одинаковые имена считаем CRC и переименовываем файл отправля пользователю новое название файла
		//В базе данных название файла будет преобразовываться так: "файл.txt" -> "файл_crc32.txt" 
		if(isset($_FILES['file']))
		{
			if(file_exists($_FILES['file']['tmp_name']))
			{
				$hash = hash_file( 'crc32', $_FILES['file']['tmp_name'] );
				if(move_uploaded_file($_FILES['file']['tmp_name'],delPHPExt($dir.$hash.'.'.strtolower(getExtension($_FILES['file']['name'])))))
				{
				    //Отправляем новое название файла клиенту
				    print "ok=".beforeLast($_FILES['file']['name'],'.').'_'.$hash.'.'.strtolower(getExtension($_FILES['file']['name']))."\n";
				}
			}else { print "ok=\n File \"".$_FILES['file']['tmp_name']."\" not find"; }
		}

		//Данный код загружается в iframe
		print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">';
		print '<html>';
		print '	<head>';
		print '		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
		print '	</head>';
		print '	<body>';
		print '		<form name="form" enctype="multipart/form-data" action="records.php?fn=9" method="post">';
		print '			<input type="hidden" name="state" value=""/>';
		print '			<input type="file" name="file"><br/>';
		print '			<input type="submit" value="Send File">';
		print '			<input type="reset"  value="Reset">';
		print '		</form>';
		print '	</body>';
		print '</html>';

		//Отчищяем временные файлы которые больше суток на сервере
		deleteTempFiles($dir);

	}else
	if ($fn==10) //Отправить двоичные данные клиенту в виде файла (пока без докачки)
	{
		session_write_close(); //Разблокируем сессионный файл так как запросы могут быть достаточно долгими
		//Клиент передаёт название таблицы, название поля, id поля
		$typename = $_REQUEST['t']; //Тип
		$field = $_REQUEST['f']; //Название поля с двоичными данными
		$name = $_REQUEST['n']; //поле с названием файла
		$idval = $_REQUEST['i']; //Идентификатор поля в базе

		$xmls='';
		$resX = $db->query('select xml from '.$Schema.'_metadata where del=false and name=\''.$typename.'\';');
		while ($rowX = $resX->fetch(PDO::FETCH_ASSOC))// $row - ассоциативный массив значений, ключи - названия столбцов
		{
		    $xmls='<?xml version="1.0" encoding="UTF-8"?><metadata>'.trts($rowX['xml']).'</metadata>';
		}
		if($xmls=='')
		{
            sendError(1,"Metadata node \"".$name."\" is empty!");
		}
		
		//Ищем поле в метаданных 
		$objXMLDocument = new DOMDocument();
		try
		{
		    $objXMLDocument->loadXML($xmls);
		} catch (Exception $e)
		{	echo $e->getMessage();
		}

        $currNode=findNodeOnAttribute($objXMLDocument->documentElement,"type","n",$typename);
		if ($currNode!=null)
		{
			$pnode=findFirstNode($currNode,'properties');
			$fnode=findFirstNodeOnAttribute($pnode,"prop","n",$field); //Поле в metedata.xml файла
			$nnode=findFirstNodeOnAttribute($pnode,"prop","n",$fnode->getAttribute("cd")); //Поле в metedata.xml с именем файла

			if($fnode->getAttribute("vt")=="blob") //Отправить файл клиенту из базы данных
			{
				$stmt = $db->prepare("SELECT \"$field\",length(\"$field\"),substring(\"$name\", position('_' IN \"$name\")+1) as name FROM $Schema\"$typename\" WHERE id='$idval'");
				$stmt->execute();
				$stmt->bindColumn(1, $blob, PDO::PARAM_LOB);
				$stmt->bindColumn(2, $size, PDO::PARAM_INT);
				$stmt->bindColumn(3, $name, PDO::PARAM_STR);
				$stmt->fetch(PDO::FETCH_BOUND);
				$data = stream_get_contents($blob);

				header("Content-type: application/octet-stream");
				header('Content-Disposition: attachment; filename="'.$name.'"');
				header('Content-Length: '.$size);
				echo $data;				
			}else if($fnode->getAttribute("vt")=="file") //Отправить файл клиенту из файловой системы
			{
    	        //Тип поля с ObjectID
			    $nField=findFirstNodeOnAttribute(findNode($currNode, 'properties'), "prop", "n", $currNode->getAttribute('ObjectID'));
    	        $csql=findNode(findFirstNodeOnAttribute($currNode, "sql-query", "t", "s"), "#cdata-section");
    	        if($csql!=NULL && $csql->nodeValue!="") //Есть ли SQL запрос
    	        {
    	            $sql_query=$csql->nodeValue;
    	            $sql_query=str_replace('${'.$currNode->getAttribute("ObjectID").'}',getSQLValue($nField->getAttribute('vt'),$idval),$sql_query);
    	            $sql_query=str_replace('${_user_id}',getSQLValue(gettype($_SESSION['USER_ID']),$_SESSION['USER_ID']),$sql_query);
    	        }

    	        $res = $db->query($sql_query);
				while ($row = $res->fetch(PDO::FETCH_ASSOC))
				{
				    $fname = $row[$field];
				}
				$dataFName = afterLast($fname,'_');
				$rezFName = beforeLast($fname,'_');
				$rezFName .= '.'.afterLast($fname,'.');

				if(file_exists($_SERVER['DOCUMENT_ROOT'].'/'.$fnode->getAttribute('path').$dataFName))
				{
    				header("Content-type: application/octet-stream");
    				header('Content-Disposition: attachment; filename="'.$rezFName.'"');
    				header('Content-Length: '.filesize($_SERVER['DOCUMENT_ROOT'].'/'.$fnode->getAttribute('path').$dataFName));
    				readfile($_SERVER['DOCUMENT_ROOT'].'/'.$fnode->getAttribute('path').$dataFName);
    				exit();
				}else
				{
				    header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found", true, 404);
				    echo 'File "'.$_SERVER['DOCUMENT_ROOT'].'/'.$fnode->getAttribute('path').$dataFName.'" not found!';
				    exit;
				}
			}
		}
	}else
	{
        sendError(1,"Неизвестная функция \"$fn\"!");
	}


