InstanceConfig.php 11 KB

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