ソースを参照

fixed reinstall ref tables by appinfo

Piotr Labudda 8 年 前
コミット
6b6c4a722e

+ 8 - 146
SE/se-lib/ACL.php

@@ -2,6 +2,7 @@
 
 Lib::loadClass('Core_AclHelper');
 Lib::loadClass('AntAclBase');
+Lib::loadClass('RefConfig');
 
 class ACL {
 
@@ -286,40 +287,6 @@ class ACL {
 		if (null == $appInfo && 0 !== json_last_error()) throw new Exception("Parsing Json failed: " . json_last_error());
 		return $appInfo;
 	}
-	public static function generateRefSelectSqlByBackRef($rootObjectNamespace, $childName) { // TODO: mv to generateRefSelectSqlByFlatRelationCache
-		// TODO: generate view which is select from {replaced(pk, remote pk) on ref table from backRef}
-		// {
-		// 	DBG::nicePrint($refInfo, "\$refInfo");
-		// 	DBG::nicePrint($rootObjectNamespace, "\$rootObjectNamespace");
-		// 	DBG::nicePrint($childName, "\$childName");
-		// 	DBG::nicePrint($childNamespace, "\$childNamespace");
-		// 	$replacedObjNs = Api_WfsNs::namespaceFromTypeName($childName);
-		// 	$replacedChildName = Api_WfsNs::typeName($rootObjectNamespace);
-		// 	DBG::nicePrint($replacedObjNs, "\$replacedObjNs");
-		// 	DBG::nicePrint($replacedChildName, "\$replacedChildName");
-		// 	return ACL::getRefTable($replacedObjNs, $replacedChildName, 1);
-		// 	throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
-		// }
-		$childNs = Api_WfsNs::namespaceFromTypeName($childName);
-		$rootTypeName = Api_WfsNs::typeName($rootObjectNamespace);
-		$backRefTable = ACL::getRefTable($childNs, $rootTypeName);
-		DBG::nicePrint($backRefTable, "ACL::getRefTable({$childNs}, {$rootTypeName})");
-
-		// TODO: check if ref_config is not backRef to avoid loop // $refInfo = self::getRefConfig($fieldNs, $item['typeName'], $item['typeName']);
-
-		$lastActionDateField = "NULL"; // , IF(l.A_RECORD_UPDATE_DATE > r.A_RECORD_UPDATE_DATE, l.A_RECORD_UPDATE_DATE, r.A_RECORD_UPDATE_DATE) as A_LAST_ACTION_DATE
-		$sql = "
-			select backRef.REMOTE_PRIMARY_KEY as PRIMARY_KEY
-					, backRef.PRIMARY_KEY as REMOTE_PRIMARY_KEY
-					, backRef.REMOTE_TYPENAME as REMOTE_TYPENAME
-					, backRef.A_STATUS as A_STATUS
-					, 0 as TRANSACTION_ID
-					, {$lastActionDateField} as A_LAST_ACTION_DATE
-			from `{$backRefTable}` backRef
-		";
-		DBG::log($sql, 'sql', "generateRefSelectSqlByBackRef");
-		return $sql;
-	}
 	public static function generateRefSelectSqlByFlatRelationCache($rootObjectNamespace, $childName) { // CRM_REF_CONFIG
 		$appInfo = DB::getPDO()->fetchValue("
 			select f.appInfo
@@ -456,97 +423,14 @@ class ACL {
 		DBG::log($sql, 'sql', "generateRefSelectSqlByFlatRelationCache");
 		return $sql;
 	}
-	public static function setRefSource($rootObjectNamespace, $childName, $source, $viewSelectSql = null) { // CRM_REF_CONFIG
-		if (!in_array($source, ['view', 'table', 'backRef'])) throw new Exception("Wrong param source - expected 'table', 'view' or 'backRef'");
-		if ('view' === $source && !$viewSelectSql) throw new Exception("Missing create view sql");
-		$refInfo = self::getRefConfig($rootObjectNamespace, $childName);
-		if (true) { // if ($source != $refInfo['SOURCE']) {
-			if ('view' === $source) {
-				$refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW";
-				DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
-			}
-			if ('backRef' === $source) {
-				$refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW";
-				DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
-			}
-			$affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refInfo['ID'], [
-				'SOURCE' => $source,
-			]);
-		}
-	}
 	public static function getRefConfig($rootObjectNamespace, $childName, $childNamespace = null) { // CRM_REF_CONFIG
-		$rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
-		if (!$childNamespace) {
-			$rootAcl = self::getAclByNamespace($rootObjectNamespace);
-			$childXsdType = $rootAcl->getXsdFieldType($childName);
-			list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
-			DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
-			switch ($typePrefix) {
-				case 'ref_uri': $childAcl = self::getAclByNamespace($childNamespace); break;
-				case 'ref': $childAcl = self::getAclByTypeName($childNamespace); break;
-				default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
-			}
-		}
-		$refInfo = [];// define $refInfo = [ ID, A_STATUS, VERSION ]
-		try {// check that ref config table exists
-			$sqlRootTableNs = DB::getPDO()->quote($rootObjectNamespace, PDO::PARAM_STR);
-			$sqlChildName = DB::getPDO()->quote($childName, PDO::PARAM_STR);
-			$sqlChildNamespace = DB::getPDO()->quote($childNamespace, PDO::PARAM_STR);
-			$refInfo = DB::getPDO()->fetchFirst("
-				select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
-				from `CRM_REF_CONFIG` c
-				where c.ROOT_OBJECT_NS = {$sqlRootTableNs}
-					and c.CHILD_NAME = {$sqlChildName}
-					and c.CHILD_NS = {$sqlChildNamespace}
-			");
-		} catch (Exception $e) {
-			DB::getPDO()->execSql("
-				CREATE TABLE IF NOT EXISTS `CRM_REF_CONFIG` (
-					`ID` INT NOT NULL AUTO_INCREMENT
-					, `ROOT_OBJECT_NS` VARCHAR(255) NOT NULL
-					, `CHILD_NAME` VARCHAR(255) NOT NULL
-					, `CHILD_NS` VARCHAR(255) NOT NULL
-					, `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
-					, `VERSION` int(11) NOT NULL DEFAULT 0
-					, `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-					, PRIMARY KEY (`ID`)
-				) ENGINE = MyISAM DEFAULT CHARSET=latin2;
-			");
-			try {
-				DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` ADD `SOURCE` enum('table', 'view') not null default 'table' ");
-			} catch (Exception $e) {
-				DBG::log($e);
-			}
-		}
-		try {
-			DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` CHANGE `SOURCE` `SOURCE` enum('table', 'view', 'backRef') not null default 'table' ");
-		} catch (Exception $e) {
-			DBG::log($e);
-		}
-		if (empty($refInfo)) {
-			$refInfo = [ 'ID' => 0, 'A_STATUS' => 'WAITING', 'VERSION' => 0, 'SOURCE' => 'table' ];
-			$refInfo['ID'] = DB::getPDO()->insert("CRM_REF_CONFIG", [
-				'ROOT_OBJECT_NS' => $rootObjectNamespace,
-				'CHILD_NAME' => $childName,
-				'CHILD_NS' => $childNamespace
-			]);
-		}
-
-		// { // TODO: fix source if ref for `SystemObjects__x3A__*` and defined flat_relation_cache - move to AclReinstall?
-		// 	if ('SystemObjects__x3A__' === substr($childNamespace, 0, strlen('SystemObjects__x3A__'))) {
-		// 		if ('table' === $refInfo['SOURCE']) {
-		// 			$toUpdate = [
-		// 				'SOURCE' => 'view',
-		// 				'A_STATUS' => 'WAITING',
-		// 			];
-		// 			DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refInfo['ID'], $toUpdate);
-		// 			$refInfo = array_merge($refInfo, $toUpdate);
-		// 		}
-		// 	}
-		// }
-
-		if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table for field '{$childName}' in object '{$rootObjectNamespace}'");
-		return $refInfo;
+		$refConfig = RefConfig::fetch($rootObjectNamespace, $childName, $childNamespace)->toArray();
+		return [
+			'ID' => $refConfig['id'],
+			'A_STATUS' => $refConfig['status'],
+			'VERSION' => $refConfig['version'],
+			'SOURCE' => $refConfig['source']
+		];
 	}
 	public static function upgradeRefConfigFrom1to2($refInfo) {
 		if (1 == $refInfo['VERSION']) {
@@ -583,28 +467,6 @@ class ACL {
 			':namespace' => $namespace,
 		]);
 	}
-	public static function getChildRefFullList($namespace) {
-		$namespace = ACL::getBaseNamespace($namespace);
-		if (!$namespace) throw new Exception("Missing namespace");
-		return DB::getPDO()->fetchAll("
-			select c.CHILD_NAME as namespace
-				, c.A_STATUS
-				, c.ID
-				, c.SOURCE
-			from CRM_REF_CONFIG c
-			where c.ROOT_OBJECT_NS = :namespace
-			--	and c.A_STATUS = 'NORMAL'
-		", [
-			':namespace' => $namespace,
-		]);
-	}
-	public static function updateChildRefSource($id, $source) {
-		if (!$id) throw new Exception("Missing id");
-		if (!in_array($source, ['view', 'table', 'backRef'])) throw new Exception("Wrong source");
-		return DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $id, [
-			'SOURCE' => $source
-		]);
-	}
 
 	public static function fetchRefs($namespace, $childNamespace, $primaryKey, $params = []) { // TODO: $params: limit, total
 		if (!$namespace) throw new Exception("Missing namespace");

+ 383 - 0
SE/se-lib/RefConfig.php

@@ -0,0 +1,383 @@
+<?php
+
+Lib::loadClass('Type_Field');
+Lib::loadClass('Type_RefConfig');
+Lib::loadClass('Api_WfsNs');
+Lib::loadClass('ACL');
+
+/*
+	RefStorage - for ref data manipulation
+	RefConfig - for ref config
+
+	`CRM_REF_CONFIG`:
+	- `ID`: primaryKey (used to generate **Storage** name for data)
+	- `ROOT_OBJECT_NS`: root object namespace
+	- `CHILD_NAME`: child typeName (or alias name - not supported yet)
+	- `CHILD_NS`: child namespace (required when use `CHILD_NAME` as alias)
+	- `A_STATUS`: status
+		- 'WAITING': not Active, 'NORMAL': Active, 'DELETED': removed
+	- `VERSION`: **Storage** version
+	- `A_LAST_ACTION_DATE`: last action timestamp
+	- `SOURCE`: place where data is stored
+		- 'table': table with data
+		- 'view': view by flat relation cache
+		- 'backRef': view on ref table with replaced fields
+
+	**Storage** - table or view - place where data is stored
+
+	RefConfig::isActive($objectNamespace, $childTypeName) // Ref is active when: CRM_REF_CONFIG.A_STATUS = NORMAL
+	RefConfig::getRefTable($objectNamespace, $childTypeName) // ref table - TODO not needed?
+ */
+
+class RefConfig {
+
+	static function isActive($objectNamespace, $childTypeName) {
+		$refInfo = self::fetch($objectNamespace, $childTypeName);
+		return ('NORMAL' === $refInfo->status);
+	}
+
+	static function fetch($rootObjectNamespace, $childName, $childNamespace = null) {
+		if (!$childTypeName) $childTypeName = Api_WfsNs::namespaceFromTypeName($fieldName);
+		$rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
+		if (!$childNamespace) {
+			$rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
+			$childXsdType = $rootAcl->getXsdFieldType($childName);
+			list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
+			DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
+			switch ($typePrefix) {
+				case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
+				case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
+				default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
+			}
+		}
+		$refInfo = [];// define $refInfo = [ ID, A_STATUS, VERSION ]
+		try {// check that ref config table exists
+			$sqlRootTableNs = DB::getPDO()->quote($rootObjectNamespace, PDO::PARAM_STR);
+			$sqlChildName = DB::getPDO()->quote($childName, PDO::PARAM_STR);
+			$sqlChildNamespace = DB::getPDO()->quote($childNamespace, PDO::PARAM_STR);
+			$refInfo = DB::getPDO()->fetchFirst("
+				select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
+				from `CRM_REF_CONFIG` c
+				where c.ROOT_OBJECT_NS = {$sqlRootTableNs}
+					and c.CHILD_NAME = {$sqlChildName}
+					and c.CHILD_NS = {$sqlChildNamespace}
+			");
+		} catch (Exception $e) {
+			DB::getPDO()->execSql("
+				CREATE TABLE IF NOT EXISTS `CRM_REF_CONFIG` (
+					`ID` INT NOT NULL AUTO_INCREMENT
+					, `ROOT_OBJECT_NS` VARCHAR(255) NOT NULL
+					, `CHILD_NAME` VARCHAR(255) NOT NULL
+					, `CHILD_NS` VARCHAR(255) NOT NULL
+					, `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
+					, `VERSION` int(11) NOT NULL DEFAULT 0
+					, `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+					, PRIMARY KEY (`ID`)
+				) ENGINE = MyISAM DEFAULT CHARSET=latin2;
+			");
+			try {
+				DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` ADD `SOURCE` enum('table', 'view') not null default 'table' ");
+			} catch (Exception $e) {
+				DBG::log($e);
+			}
+		}
+		try {
+			DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` CHANGE `SOURCE` `SOURCE` enum('table', 'view', 'backRef') not null default 'table' ");
+		} catch (Exception $e) {
+			DBG::log($e);
+		}
+		if (empty($refInfo)) {
+			$refInfo = [ 'ID' => 0, 'A_STATUS' => 'WAITING', 'VERSION' => 0, 'SOURCE' => 'table' ];
+			$refInfo['ID'] = DB::getPDO()->insert("CRM_REF_CONFIG", [
+				'ROOT_OBJECT_NS' => $rootObjectNamespace,
+				'CHILD_NAME' => $childName,
+				'CHILD_NS' => $childNamespace
+			]);
+		}
+
+		// { // TODO: fix source if ref for `SystemObjects__x3A__*` and defined flat_relation_cache - move to AclReinstall?
+		// 	if ('SystemObjects__x3A__' === substr($childNamespace, 0, strlen('SystemObjects__x3A__'))) {
+		// 		if ('table' === $refInfo['SOURCE']) {
+		// 			$toUpdate = [
+		// 				'SOURCE' => 'view',
+		// 				'A_STATUS' => 'WAITING',
+		// 			];
+		// 			DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refInfo['ID'], $toUpdate);
+		// 			$refInfo = array_merge($refInfo, $toUpdate);
+		// 		}
+		// 	}
+		// }
+
+		if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table for field '{$childName}' in object '{$rootObjectNamespace}'");
+		return Type_RefConfig::build($refInfo);
+	}
+
+	static function getChildRefFullList($namespace) {
+		$namespace = ACL::getBaseNamespace($namespace);
+		if (!$namespace) throw new Exception("Missing namespace");
+		return array_map('Type_RefConfig::build', DB::getPDO()->fetchAll("
+			select c.ID
+				, c.A_STATUS
+				, c.SOURCE
+				, c.CHILD_NAME
+			from CRM_REF_CONFIG c
+			where c.ROOT_OBJECT_NS = :namespace
+			--	and c.A_STATUS = 'NORMAL'
+		", [
+			':namespace' => $namespace,
+		]));
+	}
+
+	static function isRefField($objectNamespace, $fieldName) {
+		return (false !== strpos($fieldName, ':'));
+	}
+
+	/*
+		@param $appInfo = [
+			[type] => ref:default_db__x3A__BI_audit_CEIDG:BI_audit_CEIDG
+			[minOccurs] => 0
+			[maxOccurs] => unbounded
+			[restrictions] => []
+			[appInfo] => []
+		]
+	*/
+	static function needUpdate($objectNamespace, $childTypeName, Type_Field $newField, Type_Field $oldField = null) {
+		DBG::log(['objectNamespace' => $objectNamespace, 'childTypeName' => $childTypeName, 'newField' => $newField, 'oldField' => $oldField], 'array', "RefConfig::needUpdate...");
+		if (!$oldField) throw new Exception("Missig oldField in RefConfig::needUpdate({$objectNamespace}, {$childTypeName}, ...) - TODO: fetch from #acl cache");
+
+		if (!($newField instanceof Type_Field_Ref)) return false;
+		if (!($oldField instanceof Type_Field_Ref)) return false;
+
+		$oldRefActive = self::isActive($objectNamespace, $childTypeName);
+		if (!$oldRefActive) return false; // if old not installed / adtivated then just fix struct and install
+
+		$newRefSource = $newField->source;
+		$oldRefConf = self::fetch($objectNamespace, $childTypeName);
+		$oldRefSource = $oldRefConf->source;
+		if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::needUpdate Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
+		if ($newRefSource !== $oldRefSource) return true;
+
+		return false;
+	}
+
+	static function update($objectNamespace, $childTypeName, Type_Field $newField) {
+		if (!($newField instanceof Type_Field_Ref)) return;
+
+		// $oldRefActive = self::isActive($objectNamespace, $childTypeName);
+		// if (!$oldRefActive) return; // if old not installed / adtivated then just fix struct and install
+
+		$newRefSource = $newField->source;
+		$oldRefConf = self::fetch($objectNamespace, $childTypeName);
+		$oldRefSource = $oldRefConf->source;
+		if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::update Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
+		{ // always update ref config at reinstall - drop / create ref tables (table or view)
+			switch ($newRefSource) {
+				case 'table': return self::installRefTable($objectNamespace, $childTypeName, $newField, $oldRefConf);
+				case 'view': return self::installRefView($objectNamespace, $childTypeName, $newField, $oldRefConf);
+				case 'backRef': return self::installBackRef($objectNamespace, $childTypeName, $newField, $oldRefConf);
+			}
+		}
+		if ($newRefSource !== $oldRefSource) {
+			if ('table' === $oldRefSource) {
+				// TODO: check if table has data
+				// TODO: if no data in table then DROP ?
+			}
+			throw new Exception("TODO: RefConfig::update Change ref source from '{$oldRefSource}' to '{$newRefSource}' in '{$objectNamespace}'-&gt;'{$childTypeName}'");
+		}
+	}
+
+	static function installRefTable($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
+		if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
+		$affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
+			'SOURCE' => 'table',
+		]);
+	}
+
+	static function installRefView($objectNamespace, $childTypeName, Type_Field $typeField, Type_RefConfig $refConfig = null) {
+		if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
+		$viewSelectSql = RefConfig::generateRefSelectSqlByFlatRelationCache($objectNamespace, $childTypeName, $typeField);
+		$refTableName = "CRM__#REF_TABLE__{$refConfig->id}_VIEW";
+		DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
+
+		$affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
+			'SOURCE' => 'view',
+		]);
+	}
+
+	static function installBackRef($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
+		$viewSelectSql = self::generateRefSelectSqlByBackRef($objectNamespace, $childTypeName);
+		if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
+		$refTableName = "CRM__#REF_TABLE__{$refConfig->id}_VIEW";
+		DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
+
+		$affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
+			'SOURCE' => 'backRef',
+		]);
+	}
+
+	static function generateRefSelectSqlByBackRef($rootObjectNamespace, $childName) {
+		// generate view which is select from {replaced(pk, remote pk) on ref table from backRef}
+		// {
+		// 	DBG::nicePrint($refInfo, "\$refInfo");
+		// 	DBG::nicePrint($rootObjectNamespace, "\$rootObjectNamespace");
+		// 	DBG::nicePrint($childName, "\$childName");
+		// 	DBG::nicePrint($childNamespace, "\$childNamespace");
+		// 	$replacedObjNs = Api_WfsNs::namespaceFromTypeName($childName);
+		// 	$replacedChildName = Api_WfsNs::typeName($rootObjectNamespace);
+		// 	DBG::nicePrint($replacedObjNs, "\$replacedObjNs");
+		// 	DBG::nicePrint($replacedChildName, "\$replacedChildName");
+		// 	return ACL::getRefTable($replacedObjNs, $replacedChildName, 1);
+		// 	throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
+		// }
+		$childNs = Api_WfsNs::namespaceFromTypeName($childName);
+		$rootTypeName = Api_WfsNs::typeName($rootObjectNamespace);
+		$backRefTable = ACL::getRefTable($childNs, $rootTypeName);
+		DBG::nicePrint($backRefTable, "ACL::getRefTable({$childNs}, {$rootTypeName})");
+
+		// TODO: check if ref_config is not backRef to avoid loop // $refInfo = self::getRefConfig($fieldNs, $item['typeName'], $item['typeName']);
+
+		$lastActionDateField = "NULL"; // , IF(l.A_RECORD_UPDATE_DATE > r.A_RECORD_UPDATE_DATE, l.A_RECORD_UPDATE_DATE, r.A_RECORD_UPDATE_DATE) as A_LAST_ACTION_DATE
+		$sql = "
+			select backRef.REMOTE_PRIMARY_KEY as PRIMARY_KEY
+					, backRef.PRIMARY_KEY as REMOTE_PRIMARY_KEY
+					, backRef.REMOTE_TYPENAME as REMOTE_TYPENAME
+					, backRef.A_STATUS as A_STATUS
+					, 0 as TRANSACTION_ID
+					, {$lastActionDateField} as A_LAST_ACTION_DATE
+			from `{$backRefTable}` backRef
+		";
+		DBG::log($sql, 'sql', "generateRefSelectSqlByBackRef");
+		return $sql;
+	}
+
+	static function generateRefSelectSqlByFlatRelationCache($rootObjectNamespace, $childName, Type_Field $typeField) { // CRM_REF_CONFIG
+		$appInfo = $typeField->appInfo;
+		if (empty($appInfo)) throw new Exception("Empty app:info for field '{$rootObjectNamespace}/{$childName}'");
+
+		DBG::log(['$appInfo'=>$appInfo, '$rootObjectNamespace'=>$rootObjectNamespace, '$childName'=>$childName], 'array', "\$appInfo");
+		$rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
+		$childXsdType = $rootAcl->getXsdFieldType($childName);
+		list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
+		switch ($typePrefix) {
+			case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
+			case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
+			default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
+		}
+
+		$lastActionDateField = "NULL"; // , IF(l.A_RECORD_UPDATE_DATE > r.A_RECORD_UPDATE_DATE, l.A_RECORD_UPDATE_DATE, r.A_RECORD_UPDATE_DATE) as A_LAST_ACTION_DATE
+		$rootPrimaryKeyField = $rootAcl->getPrimaryKeyField();
+		$childPrimaryKeyField = $childAcl->getPrimaryKeyField();
+		$rootTableName = $rootAcl->getRootTableName();
+		$childTableName = $childAcl->getRootTableName();
+		//   '$appInfo' => [
+		//     'flat_relation_cache' => [
+		//       'source' => [
+		//         '@name' => 'ID',
+		//         '@xpath' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK/ID_PROCES',
+		//       ),
+		//     ),
+		//   ),
+		//   '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
+		//   '$childName' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK',
+
+		//   '$appInfo' => [
+		//   'flat_relation_cache' => [
+		//     'source' => [
+		//       '@name' => 'ID',
+		//       '@xpath' => 'default_db__x3A__CRM_PROCES:PROCES/PARENT_ID',
+		//     ),
+		//   ),
+		// ),
+		// '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
+		// '$childName' => 'default_db__x3A__CRM_PROCES:PROCES',
+		$appInfoRootFieldName = null;
+		$appInfoChildFieldName = null;
+		{
+			if (empty($appInfo['flat_relation_cache']['source']['@name'])) throw new Exception("Missing flat_relation_cache/source/@name");
+			if (empty($appInfo['flat_relation_cache']['source']['@xpath'])) throw new Exception("Missing flat_relation_cache/source/@xpath");
+			$appInfoName = $appInfo['flat_relation_cache']['source']['@name'];
+			$appInfoXpath = $appInfo['flat_relation_cache']['source']['@xpath'];
+			// $rootNs = $rootAcl->getNamespace()
+			if ("{$childName}/" === substr($appInfoXpath, 0, strlen("{$childName}/"))) {
+				$appInfoRootFieldName = substr($appInfoXpath, strlen("{$childName}/"));
+				$appInfoChildFieldName = $appInfoName;
+			} else {
+				throw new Exception("TODO parse flat_relation_cache");
+			}
+		}
+		if (!$appInfoRootFieldName || !$appInfoChildFieldName) throw new Exception("Error Processing flat_relation_cache");
+		$sqlWhereFromRestrictions = [];
+		DBG::log(['root'=>$rootAcl->getFields(), 'child'=>$childAcl->getFields()], 'array', "rootAcl and childAcl fields - xsdRestrictions");
+		if ($rootAcl instanceof AntAclBase && $childAcl instanceof AntAclBase) {
+			$rootLocalFieldsWithRestrictions = array_filter($rootAcl->getFields(), function ($field) {
+				if (!$field['isLocal']) return false;
+				if (empty($field['xsdRestrictions'])) return false;
+				if ('[]' == $field['xsdRestrictions']) return false;
+				return true;
+			});
+			$childLocalFieldsWithRestrictions = array_filter($childAcl->getFields(), function ($field) {
+				if (!$field['isLocal']) return false;
+				if (empty($field['xsdRestrictions'])) return false;
+				if ('[]' == $field['xsdRestrictions']) return false;
+				return true;
+			});
+			DBG::log(['root'=>$rootLocalFieldsWithRestrictions, 'child'=>$childLocalFieldsWithRestrictions], 'array', "root and child fields with xsdRestrictions");
+			if (!empty($rootLocalFieldsWithRestrictions)) {
+				$sqlTablePrefix = 'root';
+				$sqlWhereFromRestrictions = array_reduce(
+					array_map(function ($field) use ($sqlTablePrefix) {
+						$sqlRestrictions = [];
+						// 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
+						$restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
+						if (!empty($restrictions)) {
+							if (!empty($restrictions['enumeration'])) {
+								$sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
+							}
+						}
+						return $sqlRestrictions;
+					}, $rootLocalFieldsWithRestrictions),
+					function ($ret, $cur) {
+						return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
+					},
+					$sqlWhereFromRestrictions
+				);
+			}
+			if (!empty($childLocalFieldsWithRestrictions)) {
+				$sqlTablePrefix = 'child';
+				$sqlWhereFromRestrictions = array_reduce(
+					array_map(function ($field) use ($sqlTablePrefix) {
+						$sqlRestrictions = [];
+						// 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
+						$restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
+						if (!empty($restrictions)) {
+							if (!empty($restrictions['enumeration'])) {
+								$sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
+							}
+						}
+						return $sqlRestrictions;
+					}, $childLocalFieldsWithRestrictions),
+					function ($ret, $cur) {
+						return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
+					},
+					$sqlWhereFromRestrictions
+				);
+			}
+		}
+		$sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
+		$sqlChildFieldName = $childAcl->getSqlFieldName($appInfoRootFieldName);
+		$sql = "
+			select root.{$rootPrimaryKeyField} as PRIMARY_KEY
+					, child.{$childPrimaryKeyField} as REMOTE_PRIMARY_KEY
+					, '' as REMOTE_TYPENAME
+					, 'WAITING' as A_STATUS
+					, 0 as TRANSACTION_ID
+					, {$lastActionDateField} as A_LAST_ACTION_DATE
+			from `{$rootTableName}` root
+				join `{$childTableName}` child on(child.{$sqlChildFieldName} = root.{$appInfoChildFieldName})
+			where {$sqlWhereFromRestrictions}
+		";
+		DBG::log($sql, 'sql', "generateRefSelectSqlByFlatRelationCache");
+		return $sql;
+	}
+
+}

+ 111 - 145
SE/se-lib/Route/Storage/AclReinstall.php

@@ -5,6 +5,8 @@ Lib::loadClass('Router');
 Lib::loadClass('Response');
 Lib::loadClass('UI');
 Lib::loadClass('SchemaFactory');
+Lib::loadClass('RefConfig');
+Lib::loadClass('Type_Field');
 
 class Route_Storage_AclReinstall extends RouteBase {
 
@@ -35,151 +37,7 @@ class Route_Storage_AclReinstall extends RouteBase {
 			]);
 
 			if ('reinstall' == V::get('_postTask', '', $_POST)) {
-				Lib::loadClass('Schema_SystemObjectFieldStorageAcl');
-				$objFieldAcl = new Schema_SystemObjectFieldStorageAcl();
-				$objFieldAcl->updateCache($namespace);
-
-				try {
-					$dbgInfo = [
-						'idInstance' => ACL::getInstanceId($namespace),
-						'rootInstance' => ACL::getRootNamespace($namespace),
-						'conf' => ACL::fetchInstanceConfig($namespace),
-						// 'table' => ACL::getInstanceTable($namespace), // Object structure not installed 'default_db/{tableName}'
-					];
-					DBG::nicePrint($dbgInfo, "dbg");
-				} catch (Exception $e) {
-					UI::alert('warning', $e->getMessage());
-				}
-
-				$item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
-
-				$childRefList = ACL::getChildRefFullList($namespace);
-				DBG::nicePrint($childRefList, '$childRefList');
-				DBG::nicePrint($item, '$item');
-				$activeFields = array_filter($item['field'], function ($field) {
-					return ($field['isActive'] > 0);
-				});
-				$fieldNsList = array_map(function ($field) {
-					return $field['fieldNamespace'];
-				}, $activeFields);
-				DBG::nicePrint($fieldNsList, '$fieldNsList');
-				if ('AntAcl' === $item['_type']) {
-					foreach ($childRefList as $childRef) { // [ namespace, A_STATUS ]
-						if ($childRef['A_STATUS'] !== 'DELETED' && !in_array($childRef['namespace'], $fieldNsList)) {
-							UI::alert('danger', "remove ref config for '{$childRef['namespace']}' ...");
-							if (!$childRef['ID']) throw new Exception("Missing ref config ID");
-							DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $childRef['ID'], [
-								'A_STATUS' => 'DELETED',
-								'A_LAST_ACTION_DATE' => 'NOW()',
-							]);
-						}
-						else if ($childRef['A_STATUS'] !== 'DELETED' && in_array($childRef['namespace'], $fieldNsList)) {
-							UI::alert('info', "ref config for '{$childRef['namespace']}' active - OK");
-						}
-						else if ($childRef['A_STATUS'] === 'DELETED' && in_array($childRef['namespace'], $fieldNsList)) {
-							UI::alert('warning', "activate ref config for '{$childRef['namespace']}' ...");
-							if (!$childRef['ID']) throw new Exception("Missing ref config ID");
-							DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $childRef['ID'], [
-								'A_STATUS' => 'NORMAL',
-								'A_LAST_ACTION_DATE' => 'NOW()',
-							]);
-						}
-						else if ($childRef['A_STATUS'] === 'DELETED' && !in_array($childRef['namespace'], $fieldNsList)) {
-							UI::alert('info', "ref config for '{$childRef['namespace']}' removed - OK");
-						}
-						else {
-							UI::alert('danger', "Not implemented action for '{$childRef['namespace']}'");
-						}
-					}
-					// TODO: create missing refConfig - field is not in $childRefList
-				}
-
-				if ('AntAcl' === $item['_type']) { // fix ref config by appInfo
-					$refFields = array_filter($item['field'], function ($field) {
-						return ('ref:' === substr($field['xsdType'], 0, 4));
-					});
-					DBG::log($refFields, 'array', "DBG \$refFields");
-					// analyze
-					$toMakeRefEvaluateByBackRef = false;
-					$toMakeRefAsView = false;
-					foreach ($refFields as $field) {
-						$fieldName = $field['fieldNamespace'];
-						$appInfo = ACL::decodeAppInfoJson($field['appInfo']);
-						DBG::log($appInfo, 'array', "DBG field({$fieldName}) \$appInfo");
-						// $appInfo['flat_relation_cache'] = [ ... ]
-						// $appInfo['flat_relation_cache'] = [ ... ]
-						// $appInfo['flat_relation_cache']['@backref_evaluate'] = 'true'
-						// $appInfo['flat_relation_cache']['source'] = [ ... ]
-						// $appInfo['flat_relation_cache']['source']['@name'] => 'krs'
-						// $appInfo['flat_relation_cache']['source']['@xpath'] => 'default_db__x3A__BI_audit_KRS:BI_audit_KRS/krs'
-						// $appInfo['flat_relation_cache']['source']['@ref_engine'] => 'view'
-						if (!empty($appInfo['flat_relation_cache'])) {
-							$flatCache = $appInfo['flat_relation_cache'];
-							if ('true' === V::get('@backref_evaluate', '', $flatCache)) {
-								$toMakeRefEvaluateByBackRef = true;
-							}
-							if (!empty($appInfo['flat_relation_cache']['source'])) {
-								$sourceInfo = $appInfo['flat_relation_cache']['source'];
-								if ('view' === V::get('@ref_engine', '', $sourceInfo)) {
-									$toMakeRefAsView = true;
-								}
-							}
-						}
-					}
-					// update ref config
-					if ($toMakeRefAsView && !$toMakeRefEvaluateByBackRef) {
-						UI::alert('danger', "TODO: set ref config to 'view' - field({$fieldName})");
-					}
-					if ($toMakeRefEvaluateByBackRef) { // $toMakeRefAsView nie ma znaczenia - czyta wg configa dla backRef
-						try {
-							$fieldRefConfRow = array_filter($childRefList, function ($childRef) use ($fieldName) {
-								return ($fieldName === $childRef['namespace']);
-							});
-							if (!$fieldRefConfRow) throw new Exception("Nie znaleziono konfiguracji dla powiązania {$fieldName}");
-							if (count($fieldRefConfRow) > 1) throw new Exception("BUG Znaleziono za dużo konfiguracji dla powiązania {$fieldName}");
-							$fieldRefConfRow = $fieldRefConfRow[0];
-							DBG::nicePrint($fieldRefConfRow, "\$fieldRefConfRow - TODO field({$fieldName})");
-							// [namespace] => default_db__x3A__BI_audit_KRS:BI_audit_KRS
-							// [A_STATUS] => NORMAL
-							// [ID] => 118
-							// [SOURCE] => view
-							$refTable = ACL::getRefTable($item['namespace'], $fieldName); // updates ref_config_table if needed
-							try {
-								$refSelect = ACL::generateRefSelectSqlByBackRef($item['namespace'], $fieldName);
-								ACL::setRefSource($item['namespace'], $fieldName, 'backRef', $refSelect);
-							} catch (Exception $e) {
-								DBG::log($e);
-								UI::alert('danger', $e->getMessage());
-							}
-							if ('backRef' !== $fieldRefConfRow['SOURCE']) {
-								ACL::updateChildRefSource($fieldRefConfRow['ID'], 'backRef');
-							}
-						} catch (Exception $e) {
-							UI::alert('danger', $e->getMessage());
-						}
-					}
-				}
-
-				{
-					if ('AntAcl' === $item['_type']) {
-						$dbName = DB::getPDO()->getDatabaseName();
-						$sqlFunBody = ACL::generateIsInstanceFunctionBody($namespace, $item);
-						DBG::nicePrint($sqlFunBody, "\$sqlFunBody");
-						$idInstance = ACL::getInstanceId($namespace);
-						DB::getPDO()->execSql(" DROP FUNCTION IF EXISTS `{$dbName}`.`isInstance_{$idInstance}` ");
-						// CREATE
-						//     [DEFINER = { user | CURRENT_USER }]
-						//     FUNCTION sp_name ([func_parameter[,...]])
-						//     RETURNS type
-						//     [characteristic ...] routine_body
-						DB::getPDO()->execSql("
-							CREATE DEFINER=`root`@`localhost`
-							FUNCTION `{$dbName}`.`isInstance_{$idInstance}` ( pk INT(11) )
-							RETURNS TINYINT(1)
-							{$sqlFunBody}
-						");
-					}
-				}
+				$this->reinstallAcl($namespace);
 				return;
 			}
 
@@ -286,6 +144,13 @@ class Route_Storage_AclReinstall extends RouteBase {
 			if ($newField['maxOccurs'] != $oldField['maxOccurs']) $fieldDiff[] = 'maxOccurs';
 			if (json_encode($newField['restrictions']) !== $oldField['xsdRestrictions']) $fieldDiff[] = 'xsdRestrictions';
 			if (json_encode($newField['appInfo']) !== $oldField['appInfo']) $fieldDiff[] = 'appInfo';
+			if (RefConfig::isRefField($item['namespace'], $fieldName)) {
+				$typeNewField = Type_Field::build($newField);
+				$typeOldField = Type_Field::build($oldField);
+				if (RefConfig::needUpdate($item['namespace'], $oldField['fieldNamespace'], $typeNewField, $typeOldField)) {
+					$fieldDiff[] = 'RefConfig';
+				}
+			}
 			echo (!empty($fieldDiff))
 			?	UI::h('p', [ 'style' => "" ], "Pole '{$fieldName}' - zmiany: " . implode(", ", $fieldDiff))
 			:	UI::h('p', [ 'style' => "font-style:italic; color:silver" ], "Pole '{$fieldName}' - bez zmian");
@@ -404,4 +269,105 @@ class Route_Storage_AclReinstall extends RouteBase {
 		exit;
 	}
 
+	function reinstallAcl($namespace) {
+		Lib::loadClass('Schema_SystemObjectFieldStorageAcl');
+		$objFieldAcl = new Schema_SystemObjectFieldStorageAcl();
+		$objFieldAcl->updateCache($namespace);
+
+		try {
+			$dbgInfo = [
+				'idInstance' => ACL::getInstanceId($namespace),
+				'rootInstance' => ACL::getRootNamespace($namespace),
+				'conf' => ACL::fetchInstanceConfig($namespace),
+				// 'table' => ACL::getInstanceTable($namespace), // Object structure not installed 'default_db/{tableName}'
+			];
+			DBG::nicePrint($dbgInfo, "dbg");
+		} catch (Exception $e) {
+			UI::alert('warning', $e->getMessage());
+		}
+
+		$item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
+
+		$childRefList = RefConfig::getChildRefFullList($namespace);
+		DBG::nicePrint($childRefList, '$childRefList');
+		DBG::nicePrint($item, '$item');
+		$activeFields = array_filter($item['field'], function ($field) {
+			return ($field['isActive'] > 0);
+		});
+		$fieldNsList = array_map(function ($field) {
+			return $field['fieldNamespace'];
+		}, $activeFields);
+		DBG::nicePrint($fieldNsList, '$fieldNsList');
+
+		if ('AntAcl' === $item['_type']) { // fix ref config status (turn on/off)
+			foreach ($childRefList as $childRef) { // [ namespace, A_STATUS ]
+				if ($childRef->status !== 'DELETED' && !in_array($childRef->childName, $fieldNsList)) {
+					UI::alert('danger', "remove ref config for '{$childRef->childName}' ...");
+					if (!$childRef->id) throw new Exception("Missing ref config ID");
+					DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $childRef->id, [
+						'A_STATUS' => 'DELETED',
+						'A_LAST_ACTION_DATE' => 'NOW()',
+					]);
+				}
+				else if ($childRef->status !== 'DELETED' && in_array($childRef->childName, $fieldNsList)) {
+					UI::alert('info', "ref config for '{$childRef->childName}' active - OK");
+				}
+				else if ($childRef->status === 'DELETED' && in_array($childRef->childName, $fieldNsList)) {
+					UI::alert('warning', "activate ref config for '{$childRef->childName}' ...");
+					if (!$childRef->id) throw new Exception("Missing ref config ID");
+					DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $childRef->id, [ // TODO: update ref table, update source -- fixed below
+						'A_STATUS' => 'NORMAL',
+						'A_LAST_ACTION_DATE' => 'NOW()',
+					]);
+				}
+				else if ($childRef->status === 'DELETED' && !in_array($childRef->childName, $fieldNsList)) {
+					UI::alert('info', "ref config for '{$childRef->childName}' removed - OK");
+				}
+				else {
+					UI::alert('danger', "Not implemented action for '{$childRef->childName}'");
+				}
+			}
+			// TODO: create missing refConfig - field is not in $childRefList
+		}
+
+		if ('AntAcl' === $item['_type']) { // fix ref tables by appInfo
+			$refFields = array_filter($item['field'], function ($field) {
+				return ('ref:' === substr($field['xsdType'], 0, 4));
+			});
+			DBG::log($refFields, 'array', "DBG \$refFields");
+			foreach ($refFields as $field) {
+				$typeNewField = Type_Field::build($field);
+				// if (RefConfig::needUpdate($item['namespace'], $oldField['fieldNamespace'], $typeNewField, $typeOldField))
+				try {
+					RefConfig::update($item['namespace'], $field['fieldNamespace'], $typeNewField);
+				} catch (Exception $e) {
+					// TODO: deactivate RefConfig if error
+					DBG::log($e);
+					UI::alert('danger', $e->getMessage());
+				}
+			}
+		}
+
+		{
+			if ('AntAcl' === $item['_type']) {
+				$dbName = DB::getPDO()->getDatabaseName();
+				$sqlFunBody = ACL::generateIsInstanceFunctionBody($namespace, $item);
+				DBG::nicePrint($sqlFunBody, "\$sqlFunBody");
+				$idInstance = ACL::getInstanceId($namespace);
+				DB::getPDO()->execSql(" DROP FUNCTION IF EXISTS `{$dbName}`.`isInstance_{$idInstance}` ");
+				// CREATE
+				//     [DEFINER = { user | CURRENT_USER }]
+				//     FUNCTION sp_name ([func_parameter[,...]])
+				//     RETURNS type
+				//     [characteristic ...] routine_body
+				DB::getPDO()->execSql("
+					CREATE DEFINER=`root`@`localhost`
+					FUNCTION `{$dbName}`.`isInstance_{$idInstance}` ( pk INT(11) )
+					RETURNS TINYINT(1)
+					{$sqlFunBody}
+				");
+			}
+		}
+	}
+
 }

+ 8 - 6
SE/se-lib/Route/Storage/AclStruct.php

@@ -558,14 +558,16 @@ class Route_Storage_AclStruct extends RouteBase {
 		}
 
 		if ('setFieldRefConfig' === V::get('_postTask', '', $_POST)) {
-			$field = V::get('field', '', $_POST);
+			$childTypeName = V::get('field', '', $_POST);
 			$source = V::get('source', '', $_POST);
+			$toUpdateField = array_filter($item['field'], function ($field) use ($childTypeName) { return ($childTypeName === $field['fieldNamespace']); });
+			$toUpdateField = (!empty($toUpdateField)) ? reset($toUpdateField) : null;
+			if (!$toUpdateField) throw new Exception("Field not found '{$childTypeName}'");
+			$typeField = Type_Field::build($toUpdateField);
+			$refConfig = RefConfig::fetch($objectNamespace, $childTypeName);
 			switch ($source) {
-				case 'view':
-					$refSelect = ACL::generateRefSelectSqlByFlatRelationCache($namespace, $field);
-					ACL::setRefSource($namespace, $field, 'view', $refSelect);
-					break;
-				case 'table': ACL::setRefSource($namespace, $field, 'table'); break;
+				case 'view': RefConfig::installRefView($objectNamespace, $childTypeName, $typeField, $refConfig); break;
+				case 'table': RefConfig::installRefTable($objectNamespace, $childTypeName, $typeField, $refConfig); break;
 			}
 		}
 		if ('preview' === V::get('_postTask', '', $_POST)) {

+ 141 - 0
SE/se-lib/Type/Field.php

@@ -0,0 +1,141 @@
+<?php
+
+/*
+	{
+		'type': string,
+		'minOccurs': xsd:nonNegativeInteger, default: 1,
+		'maxOccurs': xsd:allNNI, default: 1,
+		'restrictions': [], // TODO: array of type Restriction
+		'appInfo': [], // TODO: define type for appInfo
+		'source': p5:enum (values: 'table', 'view', 'backRef'), default: 'table'
+	}
+ */
+
+class Type_Field_Simple extends Type_Field {}
+class Type_Field_Ref extends Type_Field {}
+
+class Type_Field {
+
+	var $_data = [];
+
+	static function build($field = []) {
+		if (!$field) throw new Exception("Missing data in build Type Field");
+		if (empty($field['type']) && empty($field['xsdType'])) throw new Exception("Missing type in Type Field build");
+		$type = (!empty($field['type'])) ? $field['type'] : $field['xsdType'];
+		// TODO: validate $field['type']
+
+		$instance = ('ref:' === substr($type, 0, 4))
+		 	? new Type_Field_Ref($field)
+			: new Type_Field_Simple($field)
+		;
+		$instance->type = $type;
+		$instance->minOccurs = V::get('minOccurs', 1, $field, 'int');
+		$instance->maxOccurs = ('unbounded' === V::get('maxOccurs', 1, $field)) ? 'unbounded' : V::get('maxOccurs', 1, $field);
+		$instance->restrictions = (!empty($field['restrictions']))
+			? self::parseRestriction($field['restrictions'])
+			: []
+		;
+		$instance->appInfo = (!empty($field['appInfo']))
+			? self::parseAppInfo($field['appInfo'])
+			: []
+		;
+		$instance->source = self::getSourceFromAppInfo($instance->appInfo);
+		return $instance;
+	}
+
+	static function parseRestriction($restriction) {
+		$ret = [];
+		if (!empty($restrictions)) {
+			if (is_string($restrictions)) {
+				$ret = @json_decode($restrictions, $assoc = true);
+				if (null === $ret && 0 !== json_last_error()) {
+					throw new Exception("Parse json error for restrictions: " . json_last_error());
+				}
+			} else if (is_array($restrictions)) {
+				$ret = $restrictions;
+			} else {
+				throw new Exception("Not upported restrictions");
+			}
+		}
+		return $ret;
+	}
+
+	static function parseAppInfo($appInfo) {
+		$ret = [];
+		if (!empty($appInfo)) {
+			if (is_string($appInfo)) {
+				$ret = @json_decode($appInfo, $assoc = true);
+				if (null === $ret && 0 !== json_last_error()) {
+					throw new Exception("Parse json error for appInfo: " . json_last_error());
+				}
+			} else if (is_array($appInfo)) {
+				$ret = $appInfo;
+			} else {
+				throw new Exception("Not upported appInfo");
+			}
+		}
+		return $ret;
+	}
+
+	static function getSourceFromAppInfo($appInfo) {
+		if (!$appInfo) return 'table';
+
+		// [appInfo] => Array:
+		// 		[flat_relation_cache] => Array:
+		// 				[@backref_evaluate] => true
+		// 				[source] => Array:
+		// 						[@ref_engine] => view
+		$source_view = (!empty($appInfo['flat_relation_cache']['source']) && 'view' === V::get('@ref_engine', '', $appInfo['flat_relation_cache']['source']));
+		$source_backRef = (!empty($appInfo['flat_relation_cache']) && V::get('@backref_evaluate', false, $appInfo['flat_relation_cache']));
+
+		if ($source_backRef) return 'backRef';
+		if ($source_view) return 'view';
+		return 'table';
+	}
+
+	function __isset($name) {
+		return (array_key_exists($name, $this->_data));
+	}
+
+	function __get($name) {
+		if (array_key_exists($name, $this->_data)) {
+			return $this->_data[$name];
+		}
+		return null;
+	}
+
+	function __set($name, $value) {
+		$this->_data[$name] = $value;
+	}
+
+	function toArray() {
+		return $this->_data;
+	}
+
+	function __toString() {
+		return str_replace('"', '',
+			str_replace([ '{', '}', '":', ',"' ], [ '{ ', ' }', ': ', ', ' ], json_encode($this->_data))
+		);
+	}
+
+}
+
+/*
+case: install Ref based on backRef - example namespace=default_db/BI_audit_ENERGA_PRACOWNICY/BI_audit_ENERGA_PRACOWNICY
+
+<xs:element ref="default_db__x3A__BI_audit_ENERGA_RUM_KONTRAHENCI_POWIAZANIA_row_object:BI_audit_ENERGA_RUM_KONTRAHENCI_POWIAZANIA_row_object">
+	<xs:annotation>
+		<xs:appinfo>
+			<system_cache__appinfo:flat_relation_cache system_cache__appinfo:backref_evaluate="true">
+				<system_cache__appinfo:source system_cache__appinfo:ref_engine="view"/>
+			</system_cache__appinfo:flat_relation_cache>
+		</xs:appinfo>
+	</xs:annotation>
+</xs:element>
+// [appInfo] => Array:
+// 		[flat_relation_cache] => Array:
+// 				[@backref_evaluate] => true
+// 				[source] => Array:
+// 						[@ref_engine] => view
+
+*/

+ 56 - 0
SE/se-lib/Type/RefConfig.php

@@ -0,0 +1,56 @@
+<?php
+
+/* TODO: ACL::getChildRefFullList($namespace); returns array with:
+	// [0] => Array:
+	//     [namespace] => default_db__x3A__BI_audit_ENERGA_PRACOWNICY_adresy:BI_audit_ENERGA_PRACOWNICY_adresy // TODO: childName
+	//     [A_STATUS] => NORMAL
+	//     [ID] => 27
+	//     [SOURCE] => backRef
+
+*/
+
+class Type_RefConfig {
+
+	var $_data = [];
+
+	static function build($refConfig = []) {
+		if (!$refConfig) throw new Exception("Missing data in build Type RefConfig");
+		// if (!$refConfig['ID']) throw new Exception("Missing ID in Type RefConfig build"); // TODO: allow missing ID?
+		$instance = new Type_RefConfig();
+		if (!empty($refConfig['ID'])) $instance->id = (int)$refConfig['ID'];
+		if (!empty($refConfig['ROOT_OBJECT_NS'])) $instance->objectNamespace = $refConfig['ROOT_OBJECT_NS']; // namespace
+		if (!empty($refConfig['CHILD_NAME'])) $instance->childName = $refConfig['CHILD_NAME']; // typeName
+		if (!empty($refConfig['CHILD_NS'])) $instance->childNamespace = $refConfig['CHILD_NS']; // namespace
+		$instance->source = $refConfig['SOURCE'];
+		$instance->status = $refConfig['A_STATUS'];
+		$instance->version = $refConfig['VERSION'];
+
+		return $instance;
+	}
+
+	function __isset($name) {
+		return (array_key_exists($name, $this->_data));
+	}
+
+	function __get($name) {
+		if (array_key_exists($name, $this->_data)) {
+			return $this->_data[$name];
+		}
+		return null;
+	}
+
+	function __set($name, $value) {
+		$this->_data[$name] = $value;
+	}
+
+	function toArray() {
+		return $this->_data;
+	}
+
+	function __toString() {
+		return str_replace('"', '',
+			str_replace([ '{', '}', '":', ',"' ], [ '{ ', ' }', ': ', ', ' ], json_encode($this->_data))
+		);
+	}
+
+}