InstanceConfig.php 10 KB

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