InstanceConfig.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. Lib::loadClass('SchemaVersionUpgrade');
  3. Lib::loadClass('Type_InstanceConfig');
  4. Lib::loadClass('SchemaFactory');
  5. class InstanceConfig {
  6. static function getInstanceTable($namespace) { // @return string - tableName with struct { pk, idInstance, _createdAt }
  7. return self::getInstanceConfig($namespace)->tableName;
  8. }
  9. static function getInstanceId($namespace) { // @return int - idInstance
  10. return self::getInstanceConfig($namespace)->id;
  11. }
  12. static function getInstanceNamespaceById($idInstance) { // @return string - namespace
  13. $instanceRow = DB::getPDO()->fetchFirst("
  14. select c.*
  15. from `CRM_INSTANCE_CONFIG` c
  16. where c.id = :id
  17. ", [
  18. ':id' => $idInstance
  19. ]);
  20. if (!$instanceRow) throw new HttpException("Not found instance {$idInstance}", 404);
  21. return Type_InstanceConfig::build($instanceRow)->namespace;
  22. }
  23. static function getInstanceConfig($namespace) { // @return Type_InstanceConfig or throw Exception
  24. SchemaVersionUpgrade::upgradeSchema();
  25. $conf = self::fetchInstanceConfig($namespace);
  26. if (!$conf) {
  27. $id = DB::getPDO()->insert("CRM_INSTANCE_CONFIG", [
  28. 'namespace' => $namespace,
  29. 'rootNamespace' => self::getRootNamespace($namespace),
  30. '_createdAt' => 'NOW()',
  31. ]);
  32. $conf = self::fetchInstanceConfig($namespace);
  33. }
  34. if (!$conf) throw new Exception("Instance not found in config table '{$namespace}'");
  35. if ('NORMAL' !== $conf->status) {
  36. try {
  37. $objectItem = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  38. } catch (Exception $e) {
  39. throw new Exception("Object not installed '{$namespace}'");
  40. }
  41. $conf = self::createInstanceTable($conf, $objectItem);
  42. }
  43. return $conf;
  44. }
  45. static function fetchInstanceConfig($namespace) { // @return Type_InstanceConfig | null
  46. $instanceRow = DB::getPDO()->fetchFirst("
  47. select c.*
  48. from `CRM_INSTANCE_CONFIG` c
  49. where c.namespace = :namespace
  50. ", [
  51. ':namespace' => $namespace
  52. ]);
  53. return ($instanceRow) ? Type_InstanceConfig::build($instanceRow) : null;
  54. }
  55. static function createInstanceTable(Type_InstanceConfig $conf, $objectItem) {
  56. switch ($objectItem['instanceTableSource']) {
  57. case 'table': {
  58. self::_createInstanceTable($conf, $objectItem);
  59. DB::getPDO()->update('CRM_INSTANCE_CONFIG', 'ID', $conf->id, [
  60. 'A_STATUS' => "NORMAL",
  61. 'version' => 1,
  62. 'source' => "table"
  63. ]);
  64. return self::fetchInstanceConfig($conf->namespace);
  65. }
  66. case 'view': {
  67. self::_createInstanceView($conf, $objectItem);
  68. DB::getPDO()->update('CRM_INSTANCE_CONFIG', 'ID', $conf->id, [
  69. 'A_STATUS' => "NORMAL",
  70. 'version' => 1,
  71. 'source' => "view"
  72. ]);
  73. return self::fetchInstanceConfig($conf->namespace);
  74. }
  75. default: {
  76. DBG::log(['$objectItem'=>$objectItem, '$conf'=>$conf], 'array', "Not implemented instance table source type '{$objectItem['instanceTableSource']}'");
  77. throw new Exception("Not implemented instance table source type '{$objectItem['instanceTableSource']}'");
  78. }
  79. }
  80. return self::fetchInstanceConfig($conf->namespace);
  81. }
  82. static function _createInstanceTable(Type_InstanceConfig $conf, $objectItem) {
  83. $instanceTableName = "CRM__#INSTANCE_TABLE__{$conf->id}";
  84. // // TODO: fetch primaryKeyType - TODO: store primaryKey and primaryKeyType in SystemObject item
  85. // $pkType = 'int';
  86. DB::getPDO()->exec("
  87. CREATE TABLE IF NOT EXISTS `{$instanceTableName}` (
  88. `pk` int(11) NOT NULL COMMENT 'primary key'
  89. , `idInstance` int(11) NOT NULL
  90. , `_createdAt` datetime NOT NULL
  91. , KEY `pk` (`pk`)
  92. , KEY `idInstance` (`idInstance`)
  93. ) ENGINE=MyISAM DEFAULT CHARSET=latin2 COMMENT='{$conf->namespace} #INSTANCE';
  94. ");
  95. // TODO: add author, idTransaction?, convert to EventSourcing
  96. $affected = DB::getPDO()->update("CRM_INSTANCE_CONFIG", 'rootNamespace', $rootNs, [
  97. 'SOURCE' => 'table',
  98. 'VERSION' => 1,
  99. ]);
  100. // return $instanceTableName;
  101. }
  102. static function _createInstanceView(Type_InstanceConfig $conf, $objectItem) {
  103. $idInstance = $conf->id;
  104. $dbName = DB::getPDO()->getDatabaseName();
  105. $sqlIsInstanceView = self::generateIsInstanceView($objectItem['namespace'], $objectItem, $conf);
  106. $instanceViewTableName = "CRM__#INSTANCE_TABLE__{$idInstance}_VIEW";
  107. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$dbName}`.`{$instanceViewTableName}` AS {$sqlIsInstanceView} ");
  108. }
  109. static function getRootNamespace($namespace) { // TODO: works only for relative urls! - mv to Acl->getRootNamespace
  110. Lib::loadClass('SchemaFactory');
  111. try {
  112. $objectItem = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace);
  113. } catch (Exception $e) {
  114. throw new Exception("Object not installed '{$namespace}'");
  115. }
  116. if (!$objectItem['isStructInstalled']) throw new Exception("Object structure not installed '{$namespace}'");
  117. if ($objectItem['idDatabase'] != DB::getPDO()->getZasobId()) {
  118. if ('StorageAcl' === $objectItem['_type']) {
  119. DBG::log("getRootNamespace...");
  120. return $objectItem['namespace'];
  121. }
  122. else {
  123. throw new Exception("Only default_db supported"); // TODO: support more Sources
  124. }
  125. }
  126. return "default_db/{$objectItem['_rootTableName']}";
  127. }
  128. static function getNamespaceSiblings($namespace) {
  129. return array_map(function ($row) {
  130. return $row['namespace'];
  131. }, DB::getPDO()->fetchAll("
  132. select s.namespace
  133. from CRM_INSTANCE_CONFIG c
  134. join CRM_INSTANCE_CONFIG s on ( s.rootNamespace = c.rootNamespace and s.namespace != c.rootNamespace )
  135. where c.namespace = :namespace
  136. ", [
  137. 'namespace' => $namespace
  138. ]));
  139. }
  140. static function getFeatureNamespaces($namespace, $pk) {
  141. $instanceTable = self::getInstanceTable($namespace);
  142. return array_map(function ($row) {
  143. return $row['namespace'];
  144. }, DB::getPDO()->fetchAll("
  145. select c.namespace
  146. from `{$instanceTable}` i
  147. join `CRM_INSTANCE_CONFIG` c on ( c.id = i.idInstance )
  148. where i.pk = :pk
  149. ", [
  150. 'pk' => $pk,
  151. ]));
  152. }
  153. static function generateSqlWhereFromFieldsWithRestrictions($fieldsRestrictions, $sqlTablePrefix = 't', $charset = 'utf8') { // @param $fieldsRestrictions Array [ fieldName => xsdRestrictions | Null ]
  154. // TODO: add @param $acl to check if field is local, and if not then generate remote query (join)
  155. if (empty($fieldsRestrictions)) return '';
  156. $fieldsWithRestrictions = array_filter($fieldsRestrictions, ['V', 'filterNotEmpty']);
  157. if (empty($fieldsWithRestrictions)) return '';
  158. return array_reduce(
  159. array_map(function ($fieldName, $xsdRestriction) use ($sqlTablePrefix, $charset) {
  160. $sqlRestrictions = [];
  161. if (false !== strpos($fieldName, ':')) return $sqlRestrictions; // SKIP ref fields - TODO: generate remote query, require $acl
  162. if (!empty($xsdRestriction)) {
  163. if (!empty($xsdRestriction['enumeration'])) {
  164. // $sqlRestrictions[] = "{$sqlTablePrefix}.`{$fieldName}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($xsdRestriction['enumeration']))) . ")";
  165. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$fieldName}` in (" . implode(",", array_map(function ($option) use ($charset) {
  166. return ($charset && $charset !== 'utf8')
  167. ? "CONVERT(" . DB::getPDO()->quote($option) . " using {$charset})"
  168. : DB::getPDO()->quote($option);
  169. }, array_keys($xsdRestriction['enumeration']))) . ")";
  170. }
  171. if (array_key_exists('minInclusive', $xsdRestriction)) {
  172. $minInclusive = (int)$xsdRestriction['minInclusive'];
  173. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$fieldName}` > {$minInclusive}";
  174. }
  175. }
  176. return $sqlRestrictions;
  177. }, array_keys($fieldsWithRestrictions), array_values($fieldsWithRestrictions)),
  178. function ($ret, $cur) {
  179. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  180. },
  181. []
  182. );
  183. }
  184. static function generateSqlWhereFromFieldRestrictions($fields, $sqlTablePrefix = 't') {
  185. return (!empty($fields))
  186. ? array_reduce(
  187. array_map(function ($field) use ($sqlTablePrefix) {
  188. $sqlRestrictions = [];
  189. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  190. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  191. if (!empty($restrictions)) {
  192. if (!empty($restrictions['enumeration'])) {
  193. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  194. }
  195. }
  196. return $sqlRestrictions;
  197. }, $fields),
  198. function ($ret, $cur) {
  199. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  200. },
  201. []
  202. )
  203. : ''
  204. ;
  205. }
  206. static function generateIsInstanceFunctionBody($namespace, $item = null) { // TODO: NOT USED
  207. if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  208. if (!in_array( $item['_type'], [ 'AntAcl' ] )) return null;
  209. $localFieldsWithRestrictions = array_filter($item['field'], function ($field) {
  210. if (!$field['isLocal']) return false;
  211. if (empty($field['xsdRestrictions'])) return false;
  212. if ('[]' == $field['xsdRestrictions']) return false;
  213. return true;
  214. });
  215. // TODO: get fields with minOccurs > 1 (may require select by ref)
  216. $sqlTablePrefix = 'root';
  217. DBG::nicePrint($localFieldsWithRestrictions, "\$localFieldsWithRestrictions");
  218. $sqlWhereFromRestrictions = ACL::generateSqlWhereFromFieldRestrictions($localFieldsWithRestrictions, $sqlTablePrefix);
  219. DBG::nicePrint($sqlWhereFromRestrictions, "\$sqlWhereFromRestrictions");
  220. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
  221. $pkField = 'ID'; // TODO: primaryKeyField into SystemObject structure
  222. $rootTableName = $item['_rootTableName'];
  223. $sqlFunBody = ("1=1" !== $sqlWhereFromRestrictions)
  224. ? " RETURN IF(
  225. (select count(1) as cnt from `{$rootTableName}` root where root.`{$pkField}` = pk and {$sqlWhereFromRestrictions}) > 0
  226. , 1, 0)
  227. "
  228. : " RETURN 1; ";
  229. return $sqlFunBody;
  230. }
  231. static function generateIsInstanceView($namespace, $item = null, Type_InstanceConfig $conf = null) {
  232. if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  233. if (!in_array( $item['_type'], [ 'AntAcl', 'StorageAcl' ] )) return null;
  234. $idInstance = ($conf) ? $conf->id : self::getInstanceId($namespace);
  235. $localFieldsWithRestrictions = array_filter($item['field'], function ($field) {
  236. if (!$field['isLocal']) return false;
  237. if (empty($field['xsdRestrictions'])) return false;
  238. if ('[]' == $field['xsdRestrictions']) return false;
  239. return true;
  240. });
  241. // TODO: get fields with minOccurs > 1 (may require select by ref)
  242. $fieldsWithRestrictions = array_reduce($localFieldsWithRestrictions, function ($ret, $field) {
  243. $fieldName = $field['fieldNamespace'];
  244. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  245. $ret[$fieldName] = (!empty($restrictions)) ? $restrictions : null;
  246. return $ret;
  247. }, []);
  248. $sqlTablePrefix = "instance_{$idInstance}";
  249. $appInfo = (!empty($item['appInfo'])) ? @json_decode($item['appInfo'], $assoc = true) : null;
  250. $charset = (!empty($appInfo) && !empty($appInfo['table_structure']['@charset'])) ? $appInfo['table_structure']['@charset'] : null;
  251. $sqlListWhere = self::generateSqlWhereFromFieldsWithRestrictions($fieldsWithRestrictions, $sqlTablePrefix, $charset);
  252. $sqlWhere = (!empty($sqlListWhere)) ? implode("\n\t and ", $sqlListWhere) : "1=1";
  253. $rootTableName = $item['_rootTableName'];
  254. $primaryKey = V::get('primaryKey', 'ID', $item);
  255. return implode("\n\t", [
  256. "select {$primaryKey}",
  257. "from `{$rootTableName}` `{$sqlTablePrefix}`",
  258. "where {$sqlWhere}",
  259. ]);
  260. }
  261. }