trite.class.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  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 Trite
  22. *
  23. * Everything related for using a collection.
  24. * For manage collection use Managecollection class
  25. *
  26. */
  27. class Trite {
  28. /**
  29. * The database object
  30. *
  31. * @var mysqli
  32. */
  33. private mysqli $_DB;
  34. /**
  35. * The user object to query with
  36. *
  37. * @var Doomguy
  38. */
  39. private Doomguy $_User;
  40. /**
  41. * Currently loaded collection to work with
  42. *
  43. * @var string
  44. */
  45. private string $_id;
  46. /**
  47. * Current loaded collection data as an array
  48. *
  49. * @var array
  50. */
  51. private array $_collectionData;
  52. /**
  53. * Options for db queries
  54. * 'limit' => int,
  55. * 'offset' => int,
  56. * 'orderby' => string,
  57. * 'sortDirection' => ASC|DESC
  58. *
  59. * @var array
  60. */
  61. private array $_queryOptions;
  62. /**
  63. * Cache for already loaded collection fields
  64. *
  65. * @var array
  66. */
  67. private array $_cacheExistingCollectionFields = array();
  68. /**
  69. * Trite constructor.
  70. *
  71. * @param mysqli $databaseConnectionObject
  72. * @param Doomguy $userObj
  73. */
  74. public function __construct(mysqli $databaseConnectionObject, Doomguy $userObj) {
  75. $this->_DB = $databaseConnectionObject;
  76. $this->_User = $userObj;
  77. $this->_setDefaults();
  78. }
  79. /**
  80. * Set the following options which can be used in DB queries
  81. * array(
  82. * 'limit' => RESULTS_PER_PAGE,
  83. * 'offset' => (RESULTS_PER_PAGE * ($_curPage-1)),
  84. * 'orderby' => $_sort,
  85. * 'sortDirection' => $_sortDirection
  86. * );
  87. *
  88. * @param array $options
  89. */
  90. public function setQueryOptions(array $options): void {
  91. if(!isset($options['limit'])) $options['limit'] = 5;
  92. if(!isset($options['offset'])) $options['offset'] = false;
  93. if(!isset($options['sort'])) $options['sort'] = false;
  94. if(!isset($options['sortDirection'])) $options['sortDirection'] = false;
  95. $this->_queryOptions = $options;
  96. }
  97. /**
  98. * Get information to display for given collection
  99. * based on current user and given rights
  100. *
  101. * @param string $id The collection ID to load
  102. * @param string $right The rights mode. read, write or delete
  103. * @return array
  104. */
  105. public function load(string $id, string $right="read"): array {
  106. $this->_collectionData = array();
  107. if(!empty($id) && Summoner::validate($id, 'digit')) {
  108. $queryStr = "SELECT `c`.`id`, `c`.`name`, `c`.`description`, `c`.`created`,
  109. `c`.`owner`, `c`.`group`, `c`.`rights`, `c`.`defaultSearchField`,
  110. `c`.`defaultSortField`,`c`.`advancedSearchTableFields`,
  111. `c`.`defaultSortOrder`,
  112. `u`.`name` AS username, `g`.`name` AS groupname
  113. FROM `".DB_PREFIX."_collection` AS c
  114. LEFT JOIN `".DB_PREFIX."_user` AS u ON `c`.`owner` = `u`.`id`
  115. LEFT JOIN `".DB_PREFIX."_group` AS g ON `c`.`group` = `g`.`id`
  116. WHERE ".$this->_User->getSQLRightsString($right, "c")."
  117. AND `c`.`id` = '".$this->_DB->real_escape_string($id)."'";
  118. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  119. try {
  120. $query = $this->_DB->query($queryStr);
  121. if ($query !== false && $query->num_rows > 0) {
  122. $this->_collectionData = $query->fetch_assoc();
  123. $this->_collectionData['advancedSearchTableFields'] = $this->_loadAdvancedSearchTableFields($this->_collectionData['advancedSearchTableFields']);
  124. $this->_id = $this->_collectionData['id'];
  125. }
  126. } catch (Exception $e) {
  127. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  128. }
  129. }
  130. return $this->_collectionData;
  131. }
  132. /**
  133. * get the value of the specified param from the collection data array
  134. *
  135. * @param string $param
  136. * @return string
  137. */
  138. public function param(string $param): string {
  139. $ret = '';
  140. $param = trim($param);
  141. if(!empty($param) && isset($this->_collectionData[$param])) {
  142. $ret = $this->_collectionData[$param];
  143. }
  144. return $ret;
  145. }
  146. /**
  147. * Get all available collections for display based on current user
  148. * and read mode
  149. *
  150. * @param string $rightsMode
  151. * @return array
  152. */
  153. public function getCollections(string $rightsMode="read"): array {
  154. $ret = array();
  155. $queryStr = "SELECT `c`.`id`, `c`.`name`, `c`.`description`
  156. FROM `".DB_PREFIX."_collection` AS c
  157. LEFT JOIN `".DB_PREFIX."_user` AS u ON `c`.`owner` = `u`.`id`
  158. LEFT JOIN `".DB_PREFIX."_group` AS g ON `c`.`group` = `g`.`id`
  159. WHERE ".$this->_User->getSQLRightsString($rightsMode, "c")."
  160. ORDER BY `c`.`name`";
  161. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  162. try {
  163. $query = $this->_DB->query($queryStr);
  164. if($query !== false && $query->num_rows > 0) {
  165. while(($result = $query->fetch_assoc()) != false) {
  166. $ret[$result['id']] = $result;
  167. }
  168. }
  169. }
  170. catch (Exception $e) {
  171. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  172. }
  173. return $ret;
  174. }
  175. /**
  176. * Fields for the loaded collection.
  177. *
  178. * Works only if collection is already loaded and thus rights are validated
  179. *
  180. * @return array
  181. */
  182. public function getCollectionFields(): array {
  183. if(empty($this->_id)) return array();
  184. if(!empty($this->_cacheExistingCollectionFields)) {
  185. return $this->_cacheExistingCollectionFields;
  186. }
  187. $this->_cacheExistingCollectionFields = array();
  188. $queryStr = "SELECT `cf`.`fk_field_id` AS id, `sf`.`type`, `sf`.`displayname`, `sf`.`identifier`,
  189. `sf`.`searchtype`
  190. FROM `".DB_PREFIX."_collection_fields_".$this->_id."` AS cf
  191. LEFT JOIN `".DB_PREFIX."_sys_fields` AS sf ON `cf`.`fk_field_id` = `sf`.`id`
  192. ORDER BY `cf`.`sort`";
  193. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  194. $query = $this->_DB->query($queryStr);
  195. try {
  196. if($query !== false && $query->num_rows > 0) {
  197. while(($result = $query->fetch_assoc()) != false) {
  198. $this->_cacheExistingCollectionFields[$result['identifier']] = $result;
  199. }
  200. }
  201. } catch (Exception $e) {
  202. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  203. }
  204. return $this->_cacheExistingCollectionFields;
  205. }
  206. /**
  207. * return the simple search fields for loaded collection
  208. * Every field whitch has a column in the entry table is a simple search field.
  209. * Name starts with entry. But no hidden fields
  210. *
  211. * @see ManageCollectionFields->getSimpleSearchFields()
  212. *
  213. * @return array
  214. */
  215. public function getSimpleSearchFields(): array {
  216. $ret = array();
  217. $fields = $this->getCollectionFields();
  218. if(!empty($fields)) {
  219. foreach($fields as $k=>$v) {
  220. if(isset($v['searchtype']) && strpos($v['searchtype'],'entry') !== false
  221. && $v['type'] !== 'hidden') {
  222. $ret[$k] = $v;
  223. }
  224. }
  225. }
  226. // add systemfields
  227. $def['created'] = array('identifier' => 'created', 'displayname' => 'sysfield.created', 'type' => 'systemfield');
  228. $def['modified'] = array('identifier' => 'modified', 'displayname' => 'sysfield.modified', 'type' => 'systemfield');
  229. return $def + $ret;
  230. }
  231. /**
  232. * Get the tag fields (searchtype = tag) and their values.
  233. * Possible optimization can be done here: Do not load everything at once, but per field
  234. * Needs also change in frontend to separate those calls
  235. *
  236. * Works only if collection is already loaded and thus rights are validated
  237. *
  238. * @param string $search String value to search value against
  239. * @return array
  240. */
  241. public function getTags(string $search=''): array {
  242. $ret = array();
  243. $queryStr = "SELECT `cf`.`fk_field_id` AS id,
  244. `sf`.`type`,
  245. `sf`.`displayname`,
  246. `sf`.`identifier`,
  247. `e2l`.`value`
  248. FROM `".DB_PREFIX."_collection_fields_".$this->_DB->real_escape_string($this->_id)."` AS cf
  249. LEFT JOIN `".DB_PREFIX."_sys_fields` AS sf ON `cf`.`fk_field_id` = `sf`.`id`
  250. LEFT JOIN `".DB_PREFIX."_collection_entry2lookup_".$this->_DB->real_escape_string($this->_id)."` AS e2l ON `e2l`.`fk_field` = `sf`.`id`
  251. WHERE `sf`.`searchtype` = 'tag'";
  252. if(!empty($search)) {
  253. $queryStr .= " AND MATCH (`e2l`.`value`) AGAINST ('".$this->_DB->real_escape_string($search)."' IN BOOLEAN MODE)";
  254. }
  255. else {
  256. $queryStr .= " ORDER BY `sf`.`displayname`, `e2l`.`value`";
  257. }
  258. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  259. $query = $this->_DB->query($queryStr);
  260. try {
  261. if($query !== false && $query->num_rows > 0) {
  262. while(($result = $query->fetch_assoc()) != false) {
  263. $ret[$result['id']]['id'] = $result['id'];
  264. $ret[$result['id']]['displayname'] = $result['displayname'];
  265. $ret[$result['id']]['identifier'] = $result['identifier'];
  266. $ret[$result['id']]['type'] = $result['type'];
  267. if(!empty($result['value'])) {
  268. $ret[$result['id']]['entries'][$result['value']] = $result['value'];
  269. }
  270. elseif(!isset($ret[$result['id']]['entries'])) {
  271. $ret[$result['id']]['entries'] = array();
  272. }
  273. }
  274. }
  275. } catch (Exception $e) {
  276. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  277. }
  278. return $ret;
  279. }
  280. /**
  281. * Load the tools configured for the current loaded collection
  282. *
  283. * @return array
  284. */
  285. public function getAvailableTools(): array {
  286. $ret = array();
  287. $queryStr = "SELECT `t`.`id`, `t`.`name`, `t`.`description`, `t`.`action`, `t`.`target`
  288. FROM `".DB_PREFIX."_tool2collection` AS t2c
  289. LEFT JOIN `".DB_PREFIX."_tool` AS t ON t.id = t2c.fk_tool_id
  290. WHERE t2c.fk_collection_id = '".$this->_DB->real_escape_string($this->_id)."'";
  291. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  292. try {
  293. $query = $this->_DB->query($queryStr);
  294. if($query !== false && $query->num_rows > 0) {
  295. while(($result = $query->fetch_assoc()) != false) {
  296. $ret[$result['id']] = $result;
  297. }
  298. }
  299. }
  300. catch (Exception $e) {
  301. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  302. }
  303. return $ret;
  304. }
  305. /**
  306. * Some statistics about the current collection.
  307. * Entries, tags, storage
  308. * Adds a stats array to _collectionData
  309. *
  310. * @return array
  311. */
  312. public function getStats(): array {
  313. if(empty($this->_id)) return array();
  314. $this->_collectionData['stats'] = array();
  315. $queryStr = "SELECT COUNT(*) AS entries FROM `".DB_PREFIX."_collection_entry_".$this->_id."`";
  316. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  317. try {
  318. $query = $this->_DB->query($queryStr);
  319. if($query !== false && $query->num_rows > 0) {
  320. $result = $query->fetch_assoc();
  321. $this->_collectionData['stats']['entriesCount'] = $result['entries'];
  322. }
  323. }
  324. catch (Exception $e) {
  325. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  326. }
  327. $tags = $this->getTags();
  328. $tagsCount = 0;
  329. foreach ($tags as $k=>$v) {
  330. $tagsCount += count($v['entries']);
  331. }
  332. $this->_collectionData['stats']['tagsCount'] = $tagsCount;
  333. $tableSize = 0; // in MB
  334. $queryStr = "SELECT (DATA_LENGTH + INDEX_LENGTH) AS `size`
  335. FROM information_schema.TABLES
  336. WHERE TABLE_SCHEMA = 'bibliotheca'
  337. AND TABLE_NAME LIKE 'bib_collection_%_".$this->_id."'
  338. ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC";
  339. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  340. try {
  341. $query = $this->_DB->query($queryStr);
  342. if($query !== false && $query->num_rows > 0) {
  343. while(($result = $query->fetch_assoc()) != false) {
  344. $tableSize += $result['size'];
  345. }
  346. }
  347. }
  348. catch (Exception $e) {
  349. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  350. }
  351. $this->_collectionData['stats']['tableSize'] = Summoner::bytesToHuman($tableSize);
  352. $this->_collectionData['stats']['storageSize'] = Summoner::bytesToHuman(Summoner::folderSize(PATH_STORAGE.'/'.$this->_id));
  353. return $this->_collectionData;
  354. }
  355. /**
  356. * set some defaults by init of the class
  357. *
  358. * @return void
  359. */
  360. private function _setDefaults(): void {
  361. // default query options
  362. $options['limit'] = 5;
  363. $options['offset'] = false;
  364. $options['sort'] = false;
  365. $options['sortDirection'] = false;
  366. $this->setQueryOptions($options);
  367. }
  368. /**
  369. * Make a key=>value array of a comma seperated string and use the value as key
  370. *
  371. * @param string $data
  372. * @return array
  373. */
  374. private function _loadAdvancedSearchTableFields(string $data): array {
  375. $ret = array();
  376. if(!strstr($data, ',')) {
  377. $ret[$data] = $data;
  378. }
  379. else {
  380. $_t = explode(',',$data);
  381. foreach($_t as $e) {
  382. $ret[$e] = $e;
  383. }
  384. }
  385. return $ret;
  386. }
  387. }