managecollectionfields.class.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. /**
  3. * Bibliotheca
  4. *
  5. * Copyright 2018-2023 Johannes Keßler
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  19. */
  20. /**
  21. * Class ManageCollectionFields to manage fields from a existing collection
  22. */
  23. class ManageCollectionFields {
  24. /**
  25. * The database object
  26. *
  27. * @var mysqli
  28. */
  29. private mysqli $_DB;
  30. /**
  31. * The user object to query with
  32. *
  33. * @var Doomguy
  34. */
  35. private Doomguy $_User;
  36. /**
  37. * The collection we are working with
  38. *
  39. * @var string
  40. */
  41. private string $_collectionId;
  42. /**
  43. * Which db cols should not be removed
  44. *
  45. * @var array
  46. */
  47. private array $_protectedDBCols = array(
  48. 'id','created','modified','modificationuser','owner','group','rights'
  49. );
  50. /**
  51. * Store existing fields info for runtime
  52. *
  53. * @var array
  54. */
  55. private array $_cacheExistingSysFields = array();
  56. /**
  57. * Store available fields info for runtime
  58. *
  59. * @var array
  60. */
  61. private array $_cacheAvailableFields = array();
  62. /**
  63. * ManageCollections constructor
  64. *
  65. * @param mysqli $databaseConnectionObject
  66. * @param Doomguy $userObj
  67. */
  68. public function __construct(mysqli $databaseConnectionObject, Doomguy $userObj) {
  69. $this->_DB = $databaseConnectionObject;
  70. $this->_User = $userObj;
  71. }
  72. /**
  73. * The id from the collection we are working with
  74. *
  75. * @param string $id
  76. */
  77. public function setCollection(string $id): void {
  78. if(!empty($id)) {
  79. $this->_collectionId = $id;
  80. }
  81. }
  82. /**
  83. * Get available fields based on user
  84. *
  85. * @param bool $refresh
  86. * @return array
  87. * @todo No rights implemented yet. Maybe not needed. Management done by hand directly on DB
  88. */
  89. public function getAvailableFields(bool $refresh=false): array {
  90. if($refresh === false && !empty($this->_cacheAvailableFields)) {
  91. return $this->_cacheAvailableFields;
  92. }
  93. $queryStr = "SELECT `id`, `identifier`, `displayname`, `type`,
  94. `createstring`, `value`
  95. FROM `".DB_PREFIX."_sys_fields`
  96. ORDER BY `displayname`";
  97. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  98. try {
  99. $query = $this->_DB->query($queryStr);
  100. if($query !== false && $query->num_rows > 0) {
  101. while(($result = $query->fetch_assoc()) != false) {
  102. $this->_cacheAvailableFields[$result['id']] = $result;
  103. }
  104. }
  105. }
  106. catch (Exception $e) {
  107. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  108. }
  109. return $this->_cacheAvailableFields;
  110. }
  111. /**
  112. * Simple comma separated number string
  113. *
  114. * @param string $string
  115. * @return bool
  116. */
  117. public function validateFieldSortString(string $string): bool {
  118. $ret = false;
  119. $_t = str_replace(",","",$string);
  120. if(Summoner::validate($_t, 'digit')) {
  121. $ret = true;
  122. }
  123. return $ret;
  124. }
  125. /**
  126. * Deletes relations and data from the collection!
  127. *
  128. * $fieldsSortString have to be validated already
  129. *
  130. * @param string $fieldsSortString
  131. * @return bool
  132. */
  133. public function updateFields(string $fieldsSortString): bool {
  134. $ret = false;
  135. $ids = array();
  136. $fieldsSortString = trim($fieldsSortString, ", ");
  137. if(strstr($fieldsSortString, ",")) {
  138. $ids = explode(",", $fieldsSortString);
  139. }
  140. else {
  141. $ids[] = $fieldsSortString;
  142. }
  143. if(!empty($ids)) {
  144. $_newColumns = $this->_getSQLForCollectionColumns($ids);
  145. $_existingFields = $this->getExistingFields();
  146. // use the createsting info to determine if the field needs to be remove
  147. // from entry table or lookup table
  148. $_fieldsToCheckForDelete = $_existingFields;
  149. $queriesDeleteEntryTable = array();
  150. foreach($ids as $_id) {
  151. if(isset($_fieldsToCheckForDelete[$_id])) {
  152. unset($_fieldsToCheckForDelete[$_id]);
  153. }
  154. }
  155. if(!empty($_fieldsToCheckForDelete)) {
  156. foreach($_fieldsToCheckForDelete as $k=>$v) {
  157. if(!empty($v['createstring'])) {
  158. $queriesDeleteEntryTable[] = "ALTER TABLE `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  159. DROP `".$v['identifier']."`";
  160. }
  161. }
  162. }
  163. $queryStrDeleteFields = "DELETE FROM `".DB_PREFIX."_collection_fields_".$this->_collectionId."`
  164. WHERE `fk_field_id` NOT IN (".implode(",",$ids).")";
  165. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStrDeleteFields));
  166. $queryStrDeletee2l = "DELETE FROM `".DB_PREFIX."_collection_entry2lookup_".$this->_collectionId."`
  167. WHERE `fk_field` NOT IN (".implode(",",$ids).")";
  168. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStrDeletee2l));
  169. $queryStrInsertFields = "INSERT INTO `".DB_PREFIX."_collection_fields_".$this->_collectionId."` (`fk_field_id`,`sort`) VALUES ";
  170. foreach ($ids as $k => $v) {
  171. $queryStrInsertFields .= "('".$this->_DB->real_escape_string($v)."','".$this->_DB->real_escape_string($k)."'),";
  172. }
  173. $queryStrInsertFields = trim($queryStrInsertFields, ",");
  174. $queryStrInsertFields .= " ON DUPLICATE KEY UPDATE `sort` = VALUES(`sort`)";
  175. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStrInsertFields));
  176. if(!empty($_newColumns)) {
  177. $queryStrAlterEntry = array();
  178. foreach($_newColumns as $k=>$v) {
  179. $queryStrAlterEntry[] = "ALTER TABLE `".DB_PREFIX."_collection_entry_".$this->_collectionId."` ADD ".$v['createstring']."";
  180. }
  181. }
  182. // this is not good. mysql implicit commit is triggered with alter table.
  183. // needs a rewrite without alter table to fully use transactions..
  184. try {
  185. $this->_DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  186. $this->_DB->query($queryStrDeleteFields);
  187. $this->_DB->query($queryStrDeletee2l);
  188. $this->_DB->commit();
  189. // mysql implicit commit
  190. if(!empty($queriesDeleteEntryTable)) {
  191. foreach($queriesDeleteEntryTable as $q) {
  192. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::sysLog($q));
  193. $this->_DB->query($q);
  194. }
  195. }
  196. $this->_DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  197. $this->_DB->query($queryStrInsertFields);
  198. $this->_DB->commit();
  199. // mysql implicit commit
  200. if(!empty($_newColumns)) {
  201. foreach ($queryStrAlterEntry as $q1) {
  202. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($q1));
  203. $this->_DB->query($q1);
  204. }
  205. }
  206. $ret = true;
  207. }
  208. catch (Exception $e) {
  209. $this->_DB->rollback();
  210. Summoner::sysLog("[ERROR] update fields ".__METHOD__." mysql catch: ".$e->getMessage());
  211. }
  212. }
  213. return $ret;
  214. }
  215. /**
  216. * Get the fields for currently loaded collection.
  217. *
  218. * @param bool $refresh True to reload from DB
  219. * @param bool $sortAZ
  220. * @return array
  221. */
  222. public function getExistingFields(bool $refresh=false, bool $sortAZ=false): array {
  223. if($refresh === false && !empty($this->_cacheExistingSysFields)) {
  224. return $this->_cacheExistingSysFields;
  225. }
  226. $this->_cacheExistingSysFields = array();
  227. $queryStr = "SELECT `cf`.`fk_field_id` AS id,
  228. `sf`.`type`,
  229. `sf`.`displayname`,
  230. `sf`.`identifier`,
  231. `sf`.`createstring`,
  232. `sf`.`searchtype`
  233. FROM `".DB_PREFIX."_collection_fields_".$this->_collectionId."` AS cf
  234. LEFT JOIN `".DB_PREFIX."_sys_fields` AS sf ON `cf`.`fk_field_id` = `sf`.`id`";
  235. if($sortAZ === true) {
  236. $queryStr .= " ORDER BY `sf`.`displayname`";
  237. }
  238. else {
  239. $queryStr .= " ORDER BY `cf`.`sort`";
  240. }
  241. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  242. try {
  243. $query = $this->_DB->query($queryStr);
  244. if($query !== false && $query->num_rows > 0) {
  245. while(($result = $query->fetch_assoc()) != false) {
  246. $this->_cacheExistingSysFields[$result['id']] = $result;
  247. }
  248. }
  249. }
  250. catch (Exception $e) {
  251. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  252. }
  253. return $this->_cacheExistingSysFields;
  254. }
  255. /**
  256. * return the simple search fields for loaded collection
  257. * Every field witch has a column in the entry table is a simple search field.
  258. * Name starts with entry
  259. *
  260. * @see Trite->getSimpleSearchFields()
  261. *
  262. * @return array
  263. */
  264. public function getSimpleSearchFields(): array {
  265. $ret = array();
  266. $fields = $this->getExistingFields();
  267. if(!empty($fields)) {
  268. foreach($fields as $k=>$v) {
  269. if(isset($v['searchtype']) && strpos($v['searchtype'],'entry') !== false) {
  270. $ret[$k] = $v;
  271. }
  272. }
  273. }
  274. // add systemfields
  275. $def['created'] = array('identifier' => 'created', 'displayname' => 'sysfield.created', 'type' => 'systemfield');
  276. $def['modified'] = array('identifier' => 'modified', 'displayname' => 'sysfield.modified', 'type' => 'systemfield');
  277. return $def + $ret;
  278. }
  279. /**
  280. * Get the column names from current collection entry table
  281. *
  282. * @return array
  283. */
  284. private function _getExistingCollectionColumns(): array {
  285. $ret = array();
  286. $queryStr = "SHOW COLUMNS FROM `".DB_PREFIX."_collection_entry_".$this->_collectionId."`";
  287. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  288. try {
  289. $query = $this->_DB->query($queryStr);
  290. if($query !== false && $query->num_rows > 0) {
  291. while(($result = $query->fetch_assoc()) != false) {
  292. if(!in_array($result['Field'], $this->_protectedDBCols, true)) {
  293. $ret[$result['Field']] = $result['Field'];
  294. }
  295. }
  296. }
  297. }
  298. catch (Exception $e) {
  299. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  300. }
  301. return $ret;
  302. }
  303. /**
  304. * Get the required SQL information from given field ids
  305. * to create columns in entry table.
  306. *
  307. * @param array $columnIds sort=>fk_field_id
  308. * @return array
  309. */
  310. private function _getSQLForCollectionColumns(array $columnIds): array {
  311. $_fields = array();
  312. // enrich with information
  313. $_sysFields = $this->getAvailableFields();
  314. $_existingDBColumns = $this->_getExistingCollectionColumns();
  315. if(!empty($columnIds)) {
  316. foreach($columnIds as $sort=>$fieldId) {
  317. if(isset($_sysFields[$fieldId])) {
  318. $_fd = $_sysFields[$fieldId];
  319. if(isset($_existingDBColumns[$_fd['identifier']])) continue;
  320. if(empty($_fd['createstring'])) continue;
  321. $_fields[$fieldId] = $_fd;
  322. }
  323. }
  324. }
  325. return $_fields;
  326. }
  327. }