InstanceConfig.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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', $charset = 'utf8') { // @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, $charset) {
  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. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$fieldName}` in (" . implode(",", array_map(function ($option) use ($charset) {
  155. return ($charset && $charset !== 'utf8')
  156. ? "CONVERT(" . DB::getPDO()->quote($option) . " using {$charset})"
  157. : DB::getPDO()->quote($option);
  158. }, array_keys($xsdRestriction['enumeration']))) . ")";
  159. }
  160. }
  161. return $sqlRestrictions;
  162. }, array_keys($fieldsWithRestrictions), array_values($fieldsWithRestrictions)),
  163. function ($ret, $cur) {
  164. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  165. },
  166. []
  167. );
  168. }
  169. static function generateSqlWhereFromFieldRestrictions($fields, $sqlTablePrefix = 't') {
  170. return (!empty($fields))
  171. ? array_reduce(
  172. array_map(function ($field) use ($sqlTablePrefix) {
  173. $sqlRestrictions = [];
  174. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  175. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  176. if (!empty($restrictions)) {
  177. if (!empty($restrictions['enumeration'])) {
  178. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  179. }
  180. }
  181. return $sqlRestrictions;
  182. }, $fields),
  183. function ($ret, $cur) {
  184. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  185. },
  186. []
  187. )
  188. : ''
  189. ;
  190. }
  191. static function generateIsInstanceFunctionBody($namespace, $item = null) { // TODO: NOT USED
  192. if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  193. if (!in_array( $item['_type'], [ 'AntAcl' ] )) return null;
  194. $localFieldsWithRestrictions = array_filter($item['field'], function ($field) {
  195. if (!$field['isLocal']) return false;
  196. if (empty($field['xsdRestrictions'])) return false;
  197. if ('[]' == $field['xsdRestrictions']) return false;
  198. return true;
  199. });
  200. // TODO: get fields with minOccurs > 1 (may require select by ref)
  201. $sqlTablePrefix = 'root';
  202. DBG::nicePrint($localFieldsWithRestrictions, "\$localFieldsWithRestrictions");
  203. $sqlWhereFromRestrictions = ACL::generateSqlWhereFromFieldRestrictions($localFieldsWithRestrictions, $sqlTablePrefix);
  204. DBG::nicePrint($sqlWhereFromRestrictions, "\$sqlWhereFromRestrictions");
  205. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
  206. $pkField = 'ID'; // TODO: primaryKeyField into SystemObject structure
  207. $rootTableName = $item['_rootTableName'];
  208. $sqlFunBody = ("1=1" !== $sqlWhereFromRestrictions)
  209. ? " RETURN IF(
  210. (select count(1) as cnt from `{$rootTableName}` root where root.`{$pkField}` = pk and {$sqlWhereFromRestrictions}) > 0
  211. , 1, 0)
  212. "
  213. : " RETURN 1; ";
  214. return $sqlFunBody;
  215. }
  216. static function generateIsInstanceView($namespace, $item = null, Type_InstanceConfig $conf = null) {
  217. if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  218. if (!in_array( $item['_type'], [ 'AntAcl' ] )) return null;
  219. $idInstance = ($conf) ? $conf->id : self::getInstanceId($namespace);
  220. $localFieldsWithRestrictions = array_filter($item['field'], function ($field) {
  221. if (!$field['isLocal']) return false;
  222. if (empty($field['xsdRestrictions'])) return false;
  223. if ('[]' == $field['xsdRestrictions']) return false;
  224. return true;
  225. });
  226. // TODO: get fields with minOccurs > 1 (may require select by ref)
  227. $fieldsWithRestrictions = array_reduce($localFieldsWithRestrictions, function ($ret, $field) {
  228. $fieldName = $field['fieldNamespace'];
  229. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  230. $ret[$fieldName] = (!empty($restrictions)) ? $restrictions : null;
  231. return $ret;
  232. }, []);
  233. $sqlTablePrefix = "instance_{$idInstance}";
  234. $appInfo = (!empty($item['appInfo'])) ? @json_decode($item['appInfo'], $assoc = true) : null;
  235. $charset = (!empty($appInfo) && !empty($appInfo['table_structure']['@charset'])) ? $appInfo['table_structure']['@charset'] : null;
  236. $sqlListWhere = self::generateSqlWhereFromFieldsWithRestrictions($fieldsWithRestrictions, $sqlTablePrefix, $charset);
  237. $sqlWhere = (!empty($sqlListWhere)) ? implode("\n\t and ", $sqlListWhere) : "1=1";
  238. $rootTableName = $item['_rootTableName'];
  239. $primaryKey = V::get('primaryKey', 'ID', $item);
  240. return implode("\n\t", [
  241. "select {$primaryKey}",
  242. "from `{$rootTableName}` `{$sqlTablePrefix}`",
  243. "where {$sqlWhere}",
  244. ]);
  245. }
  246. }