manageentry.class.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  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. class Manageentry {
  21. /**
  22. * The database object
  23. *
  24. * @var mysqli
  25. */
  26. private mysqli $_DB;
  27. /**
  28. * The user object to query with
  29. *
  30. * @var Doomguy
  31. */
  32. private Doomguy $_User;
  33. /**
  34. * Currently loaded collection to manage entries from
  35. *
  36. * @var string Number
  37. */
  38. private string $_collectionId;
  39. /**
  40. * Placeholder in query strings for inserted DB id
  41. *
  42. * @var string
  43. */
  44. private string $_replaceEntryString = 'REPLACE_ENTERY';
  45. /**
  46. * Store edit fields info for runtime
  47. *
  48. * @var array
  49. */
  50. private array $_cacheEditFields = array();
  51. /**
  52. * ManageCollections constructor.
  53. *
  54. * @param mysqli $databaseConnectionObject
  55. * @param Doomguy $userObj
  56. */
  57. public function __construct(mysqli $databaseConnectionObject, Doomguy $userObj) {
  58. $this->_DB = $databaseConnectionObject;
  59. $this->_User = $userObj;
  60. }
  61. /**
  62. * Set the collection to manage entries from
  63. *
  64. * @param string $collectionId Number
  65. */
  66. public function setCollection(string $collectionId) {
  67. if(!empty($collectionId)) {
  68. $this->_collectionId = $collectionId;
  69. }
  70. }
  71. /**
  72. * Load the fields for the loaded collection
  73. * Also load additional data based on fieldtype and _loadField_ method
  74. *
  75. * @param bool $refresh
  76. * @return array
  77. */
  78. public function getEditFields(bool $refresh=false): array {
  79. if($refresh === false && !empty($this->_cacheEditFields)) {
  80. return $this->_cacheEditFields;
  81. }
  82. if(!empty($this->_collectionId)) {
  83. $queryStr = "SELECT `cf`.`fk_field_id` AS id, `sf`.`type`, `sf`.`displayname`, `sf`.`identifier`,
  84. `sf`.`value`, `sf`.`inputValidation`, `sf`.`searchtype`
  85. FROM `".DB_PREFIX."_collection_fields_".$this->_DB->real_escape_string($this->_collectionId)."` AS cf
  86. LEFT JOIN `".DB_PREFIX."_sys_fields` AS sf ON `cf`.`fk_field_id` = `sf`.`id`
  87. ORDER BY `cf`.`sort`";
  88. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  89. try {
  90. $query = $this->_DB->query($queryStr);
  91. if($query !== false && $query->num_rows > 0) {
  92. while(($result = $query->fetch_assoc()) != false) {
  93. $_methodName = '_loadField_'.$result['type'];
  94. if(method_exists($this, $_methodName)) {
  95. $result = $this->$_methodName($result);
  96. }
  97. $this->_cacheEditFields[$result['id']] = $result;
  98. }
  99. }
  100. }
  101. catch (Exception $e) {
  102. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  103. }
  104. }
  105. return $this->_cacheEditFields;
  106. }
  107. /**
  108. * Load required data for edit. Uses some functions from Mancubus but has
  109. * different data layout. Checks write edit too
  110. *
  111. * @param string $entryId Number
  112. * @return array
  113. */
  114. public function getEditData(string $entryId): array {
  115. $ret = array();
  116. if(!empty($this->_collectionId) && !empty($entryId)) {
  117. $queryStr = "SELECT *
  118. FROM `".DB_PREFIX."_collection_entry_".$this->_DB->real_escape_string($this->_collectionId)."`
  119. WHERE ".$this->_User->getSQLRightsString("write")."
  120. AND `id` = '".$this->_DB->real_escape_string($entryId)."'";
  121. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  122. try {
  123. $query = $this->_DB->query($queryStr);
  124. if($query !== false && $query->num_rows > 0) {
  125. $_entryFields = $this->getEditFields();
  126. if(($result = $query->fetch_assoc()) != false) {
  127. $ret = $this->_mergeEntryWithFields($result, $_entryFields);
  128. $ret['rights'] = Summoner::prepareRightsArray($result['rights']);
  129. $ret['_canDelete'] = $this->_canDelete($entryId);
  130. $ret['_isOwner'] = $this->_isOwner($result);
  131. }
  132. }
  133. }
  134. catch (Exception $e) {
  135. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  136. }
  137. }
  138. return $ret;
  139. }
  140. /**
  141. * Create an entry with given data
  142. *
  143. * @param array $data
  144. * @param string $owner Number
  145. * @param string $group Number
  146. * @param string $rights
  147. * @param mixed|false $update Either false for no update or the ID to update
  148. * @return int
  149. */
  150. public function create(array $data, string $owner, string $group, string $rights, mixed $update=false): int {
  151. $ret = 0;
  152. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." data: ".Summoner::cleanForLog($data));
  153. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." update: ".Summoner::cleanForLog($update));
  154. if(!empty($data) && !empty($owner) && !empty($group) && !empty($rights)) {
  155. // create the queryData array
  156. // init is the entry in the table. Needed for after stuff
  157. // after returns query and upload which then calls the extra methods
  158. $queryData['init'] = array();
  159. $queryData['after'] = array();
  160. foreach ($data as $i=>$d) {
  161. $_methodName = '_saveField_'.$d['type'];
  162. $_methodNameSpecial = $_methodName.'__'.$d['identifier'];
  163. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." methodname: ".Summoner::cleanForLog($_methodName));
  164. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." methodnamespecial: ".Summoner::cleanForLog($_methodNameSpecial));
  165. if(method_exists($this, $_methodNameSpecial)) {
  166. $queryData = $this->$_methodNameSpecial($d, $queryData, $data);
  167. }
  168. elseif(method_exists($this, $_methodName)) {
  169. $queryData = $this->$_methodName($d, $queryData);
  170. }
  171. else {
  172. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." Missing query function for: ".Summoner::cleanForLog($d));
  173. }
  174. }
  175. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." queryData: ".Summoner::cleanForLog($queryData));
  176. if(!empty($queryData['init']) || ($update !== false && is_numeric($update))) {
  177. $queryStr = "INSERT INTO `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  178. SET `modificationuser` = '".$this->_DB->real_escape_string($owner)."',
  179. `owner` = '".$this->_DB->real_escape_string($owner)."',
  180. `group` = '".$this->_DB->real_escape_string($group)."',
  181. `rights`= '".$this->_DB->real_escape_string($rights)."',";
  182. if($update !== false && is_numeric($update)) {
  183. $queryStr = "UPDATE `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  184. SET `modificationuser` = '".$this->_DB->real_escape_string($owner)."',
  185. `rights`= '".$this->_DB->real_escape_string($rights)."',";
  186. }
  187. $queryStr .= implode(", ",$queryData['init']);
  188. $queryStr = trim($queryStr,",");
  189. if($update !== false && is_numeric($update)) {
  190. $queryStr .= " WHERE `id` = '".$this->_DB->real_escape_string($update)."'";
  191. }
  192. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  193. try {
  194. $this->_DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  195. $this->_DB->query($queryStr);
  196. if($update !== false && is_numeric($update)) {
  197. $newId = $update;
  198. }
  199. else {
  200. $newId = $this->_DB->insert_id;
  201. }
  202. if(!empty($newId)) {
  203. if(!empty($queryData['after']) && isset($queryData['after']['query'])) {
  204. foreach ($queryData['after']['query'] as $q) {
  205. $this->_runAfter_query($q, $newId);
  206. }
  207. }
  208. if(!empty($queryData['after']) && isset($queryData['after']['upload'])) {
  209. foreach ($queryData['after']['upload'] as $q) {
  210. $this->_runAfter_upload($q, $newId);
  211. }
  212. }
  213. }
  214. else {
  215. throw new Exception('Failed to create entry');
  216. }
  217. $ret = $newId;
  218. $this->_DB->commit();
  219. }
  220. catch (Exception $e) {
  221. $this->_DB->rollback();
  222. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  223. }
  224. }
  225. else {
  226. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." empty init in: ".Summoner::cleanForLog($queryData));
  227. }
  228. }
  229. return $ret;
  230. }
  231. /**
  232. * Delete given entryId from currently loaded collection
  233. * Checks userrights too.
  234. *
  235. * @param string $entryId Number
  236. * @return bool
  237. */
  238. public function delete(string $entryId): bool {
  239. $ret = false;
  240. if(!empty($entryId) && !empty($this->_collectionId)) {
  241. if ($this->_canDelete($entryId)) {
  242. try {
  243. $this->_DB->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
  244. // remove assets
  245. $_path = PATH_STORAGE.'/'.$this->_collectionId.'/'.$entryId;
  246. if(is_dir($_path) && is_readable($_path)) {
  247. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." remove assets :".$_path);
  248. $rmDir = Summoner::recursive_remove_directory($_path);
  249. if($rmDir === false) {
  250. throw new Exception("Failed to delete path: ".$_path);
  251. }
  252. }
  253. // delete data from lookup fields
  254. $queryStr = "DELETE
  255. FROM `".DB_PREFIX."_collection_entry2lookup_".$this->_DB->real_escape_string($this->_collectionId)."`
  256. WHERE `fk_entry` = '".$this->_DB->real_escape_string($entryId)."'";
  257. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." remove lookup queryStr: ".Summoner::cleanForLog($queryStr));
  258. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  259. $this->_DB->query($queryStr);
  260. // delete entry
  261. $queryStr = "DELETE
  262. FROM `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  263. WHERE `id` = '".$this->_DB->real_escape_string($entryId)."'
  264. AND " . $this->_User->getSQLRightsString("delete") . "";
  265. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  266. $this->_DB->query($queryStr);
  267. $this->_DB->commit();
  268. $ret = true;
  269. }
  270. catch (Exception $e) {
  271. $this->_DB->rollback();
  272. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  273. }
  274. }
  275. }
  276. return $ret;
  277. }
  278. /**
  279. * Validates that current use can write the given Entry
  280. *
  281. * @param string $entryId Number
  282. * @return bool
  283. */
  284. public function canEditEntry(string $entryId): bool {
  285. $ret = false;
  286. if(!empty($entryId) && !empty($this->_collectionId)) {
  287. $queryStr = "SELECT `id`
  288. FROM `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  289. WHERE `id` = '".$this->_DB->real_escape_string($entryId)."'
  290. AND ".$this->_User->getSQLRightsString("write")."";
  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. if (($result = $query->fetch_assoc()) != false) {
  296. $ret = true;
  297. }
  298. }
  299. }
  300. catch (Exception $e) {
  301. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  302. }
  303. }
  304. return $ret;
  305. }
  306. /**
  307. * Check for duplicates based on the given entryData.
  308. * Could be extended to use more attributes from the entry
  309. * Currently uses the title field, which is a hard dependency
  310. *
  311. * @param array $entryData
  312. * @return array
  313. */
  314. public function checkForDuplicates(array $entryData): array {
  315. $ret = array();
  316. $queryStr = "SELECT `id`, `title`
  317. FROM `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  318. WHERE `title` LIKE '".$this->_DB->real_escape_string($entryData['title'])."%'
  319. AND `id` != '".$this->_DB->real_escape_string($entryData['id'])."'
  320. AND ".$this->_User->getSQLRightsString()."";
  321. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  322. try {
  323. $query = $this->_DB->query($queryStr);
  324. if ($query !== false && $query->num_rows > 0) {
  325. if (($row = $query->fetch_assoc()) != false) {
  326. $ret[] = $row;
  327. }
  328. }
  329. }
  330. catch (Exception $e) {
  331. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  332. }
  333. return $ret;
  334. }
  335. /**
  336. * Check if given entryid can be deleted from current collection
  337. * and user
  338. *
  339. * @param string $entryId Number
  340. * @return bool
  341. */
  342. private function _canDelete(string $entryId): bool {
  343. $ret = false;
  344. if(!empty($entryId) && !empty($this->_collectionId)) {
  345. $queryStr = "SELECT `id`
  346. FROM `".DB_PREFIX."_collection_entry_".$this->_collectionId."`
  347. WHERE `id` = '".$this->_DB->real_escape_string($entryId)."'
  348. AND ".$this->_User->getSQLRightsString("delete")."";
  349. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  350. try {
  351. $query = $this->_DB->query($queryStr);
  352. if ($query !== false && $query->num_rows > 0) {
  353. if (($result = $query->fetch_assoc()) != false) {
  354. $ret = true;
  355. }
  356. }
  357. }
  358. catch (Exception $e) {
  359. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  360. }
  361. }
  362. return $ret;
  363. }
  364. /**
  365. * Merge the loaded entryData with the to look up entryFields data
  366. * In this case only the fields which have a _loadFieldValue_ method
  367. * are loaded. More is not needed here.
  368. *
  369. * @param array $entryData
  370. * @param array $entryFields
  371. * @return array
  372. */
  373. private function _mergeEntryWithFields(array $entryData, array $entryFields): array {
  374. if(!empty($entryFields)) {
  375. foreach($entryFields as $f) {
  376. $_mnValue = '_loadFieldValue_'.$f['type'];
  377. if(!isset($entryData[$f['identifier']]) && method_exists($this, $_mnValue) && isset($entryData['id']) ) {
  378. $entryData[$f['identifier']] = $this->$_mnValue($entryData['id'], $f);
  379. }
  380. }
  381. }
  382. return $entryData;
  383. }
  384. /**
  385. * Load the values for given $entryId for $fieldData
  386. * lookup function for field type lookupmultiple
  387. *
  388. * @param string $entryId Number
  389. * @param array $fieldData
  390. * @return array
  391. * @see Mancubus
  392. */
  393. private function _loadFieldValue_lookupmultiple(string $entryId, array $fieldData): array {
  394. $ret = array();
  395. if(!empty($entryId) && !empty($fieldData) && !empty($this->_collectionId)) {
  396. $queryStr = "SELECT `value`
  397. FROM `".DB_PREFIX."_collection_entry2lookup_".$this->_DB->real_escape_string($this->_collectionId)."`
  398. WHERE `fk_field` = '".$this->_DB->real_escape_string($fieldData['id'])."'
  399. AND `fk_entry` = '".$this->_DB->real_escape_string($entryId)."'";
  400. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  401. try {
  402. $query = $this->_DB->query($queryStr);
  403. if($query !== false && $query->num_rows > 0) {
  404. while(($result = $query->fetch_assoc()) != false) {
  405. $ret[] = $result['value'];
  406. }
  407. }
  408. }
  409. catch (Exception $e) {
  410. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  411. }
  412. }
  413. return $ret;
  414. }
  415. /**
  416. * Get the single upload file from storage location
  417. * lookup function for field type upload
  418. *
  419. * @param string $entryId Number
  420. * @param array $fieldData
  421. * @return string
  422. * @see Mancubus
  423. */
  424. private function _loadFieldValue_upload(string $entryId, array $fieldData): string {
  425. $ret = "";
  426. if(!empty($entryId) && !empty($fieldData) && !empty($this->_collectionId)) {
  427. $uploadedFile = glob(PATH_STORAGE.'/'.$this->_collectionId.'/'.$entryId.'/'.$fieldData['identifier'].'-*');
  428. if(!empty($uploadedFile)) {
  429. foreach ($uploadedFile as $f) {
  430. $ret = basename($f);
  431. break;
  432. }
  433. }
  434. }
  435. return $ret;
  436. }
  437. /**
  438. * Get the multiple upload files from storage location
  439. * lookup function for field type upload_multiple
  440. *
  441. * @param string $entryId Number
  442. * @param array $fieldData
  443. * @return array
  444. * @see Mancubus
  445. */
  446. private function _loadFieldValue_upload_multiple(string $entryId, array $fieldData): array {
  447. $ret = array();
  448. if(!empty($entryId) && !empty($fieldData) && !empty($this->_collectionId)) {
  449. $uploadedFile = glob(PATH_STORAGE.'/'.$this->_collectionId.'/'.$entryId.'/'.$fieldData['identifier'].'-*');
  450. if(!empty($uploadedFile)) {
  451. foreach ($uploadedFile as $f) {
  452. $ret[] = basename($f);
  453. }
  454. }
  455. }
  456. return $ret;
  457. }
  458. /**
  459. * Provide the options for a selection field by processing the $data['value']
  460. * since the values are stored in the entry DB as a list
  461. *
  462. * @param array $data
  463. * @return array
  464. */
  465. private function _loadField_selection(array $data): array {
  466. if(!empty($data) && isset($data['value']) && !empty($data['value'])) {
  467. if(strstr($data['value'], ",")) {
  468. $data['options'] = explode(",", $data['value']);
  469. }
  470. }
  471. return $data;
  472. }
  473. /**
  474. * Load suggestions based on the existing data for this field
  475. *
  476. * @param array $data Field data
  477. * @return array
  478. */
  479. private function _loadField_lookupmultiple(array $data): array {
  480. if(!empty($data) && isset($data['id']) && !empty($data['id'])) {
  481. $queryStr = "SELECT DISTINCT(`value`)
  482. FROM `".DB_PREFIX."_collection_entry2lookup_".$this->_DB->real_escape_string($this->_collectionId)."`
  483. WHERE `fk_field` = '".$this->_DB->real_escape_string($data['id'])."'";
  484. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  485. try {
  486. $query = $this->_DB->query($queryStr);
  487. if ($query !== false && $query->num_rows > 0) {
  488. while (($result = $query->fetch_assoc()) != false) {
  489. $data['suggestion'][] = $result['value'];
  490. }
  491. }
  492. }
  493. catch (Exception $e) {
  494. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  495. }
  496. }
  497. return $data;
  498. }
  499. /**
  500. * Create part of the insert statement for field type text
  501. *
  502. * @param array $data Field data
  503. * @param array $queryData Query data array
  504. * @return array
  505. */
  506. private function _saveField_text(array $data, array $queryData): array {
  507. $queryData['init'][] = "`".$data['identifier']."` = '".$this->_DB->real_escape_string($data['valueToSave'])."'";
  508. return $queryData;
  509. }
  510. /**
  511. * Create part of the insert statement for field type text3
  512. *
  513. * @param array $data Field data
  514. * @param array $queryData Query data array
  515. * @return array
  516. */
  517. private function _saveField_text3(array $data, array $queryData): array {
  518. return $this->_saveField_text($data, $queryData);
  519. }
  520. /**
  521. * Create part of the insert statement for field type textarea
  522. *
  523. * @param array $data Field data
  524. * @param array $queryData Query data array
  525. * @return array
  526. */
  527. private function _saveField_textarea(array $data, array $queryData): array {
  528. return $this->_saveField_text($data, $queryData);
  529. }
  530. /**
  531. * Create part of the insert statement for field type selection
  532. *
  533. * @param array $data Field data
  534. * @param array $queryData Query data array
  535. * @return array
  536. */
  537. private function _saveField_selection(array $data, array $queryData): array {
  538. return $this->_saveField_text($data, $queryData);
  539. }
  540. /**
  541. * Create part of the insert statement for field type hidden
  542. *
  543. * @param array $data
  544. * @param array $queryData
  545. * @return array
  546. */
  547. private function _saveField_hidden(array $data, array $queryData): array {
  548. return $this->_saveField_text($data, $queryData);
  549. }
  550. /**
  551. * Create part of the insert statement for field type hidden and identifier combSearch
  552. * Creates it contents of the other fields as a combined search field. Ignores any input
  553. *
  554. * @see ManageCollections->updateSearchData()
  555. *
  556. * @param array $data
  557. * @param array $queryData
  558. * @param array $allInputData All the POST data if needed
  559. * @return array
  560. */
  561. private function _saveField_hidden__combSearch(array $data, array $queryData, array $allInputData): array {
  562. $searchData = '';
  563. foreach($allInputData as $f=>$_d) {
  564. if(isset($_d['searchtype']) && str_contains($_d['searchtype'], 'Text')) {
  565. $searchData .= " ".$_d['valueToSave'];
  566. }
  567. }
  568. $data['valueToSave'] = implode(" ", Summoner::words($searchData));
  569. $queryData['init'][] = "`".$data['identifier']."` = '".$this->_DB->real_escape_string($data['valueToSave'])."'";
  570. return $queryData;
  571. }
  572. /**
  573. * Create part of the insert statement for field type year
  574. * Uses some simple 4 digit patter to extract the year if the input is
  575. * something like 2001-02-03
  576. *
  577. * @param array $data Field data
  578. * @param array $queryData Query data array
  579. * @return array
  580. */
  581. private function _saveField_year(array $data, array $queryData): array {
  582. preg_match('/[0-9]{4}/', $data['valueToSave'], $matches);
  583. if(isset($matches[0]) && !empty($matches[0])) {
  584. $data['valueToSave'] = $matches[0];
  585. }
  586. return $this->_saveField_number($data, $queryData);
  587. }
  588. /**
  589. * Create part of the insert statement for field type number
  590. * Strips everything what is not a digit from it.
  591. *
  592. * @param array $data
  593. * @param array $queryData
  594. * @return mixed
  595. */
  596. private function _saveField_number(array $data, array $queryData): array {
  597. // make sure there is something (int) to save
  598. if(empty($data['valueToSave'])) {
  599. $data['valueToSave'] = 0;
  600. }
  601. $data['valueToSave'] = preg_replace('/[^\p{N}]/u', '', $data['valueToSave']);
  602. $queryData['init'][] = "`".$data['identifier']."` = '".$this->_DB->real_escape_string($data['valueToSave'])."'";
  603. return $queryData;
  604. }
  605. /**
  606. * Create part of the insert statement for field type lookupmultiple
  607. *
  608. * @param array $data Field data
  609. * @param array $queryData Query data array
  610. * @return array
  611. */
  612. private function _saveField_lookupmultiple(array $data, array $queryData): array {
  613. $_d = trim($data['valueToSave']);
  614. $_d = trim($_d, ",");
  615. // first clean since the new data is everything
  616. $queryData['after']['query'][] = "DELETE FROM `".DB_PREFIX."_collection_entry2lookup_".$this->_collectionId."`
  617. WHERE `fk_field` = '".$this->_DB->real_escape_string($data['id'])."'
  618. AND `fk_entry` = '".$this->_replaceEntryString."'";
  619. if(!empty($_d)) {
  620. $_process = array($_d);
  621. if (strstr($data['valueToSave'], ",")) {
  622. $_process = explode(",", $data['valueToSave']);
  623. }
  624. foreach ($_process as $p) {
  625. $queryData['after']['query'][] = "INSERT IGNORE INTO `".DB_PREFIX."_collection_entry2lookup_".$this->_collectionId."`
  626. SET `fk_field` = '".$this->_DB->real_escape_string($data['id'])."',
  627. `fk_entry` = '".$this->_replaceEntryString."',
  628. `value` = '".$this->_DB->real_escape_string($p)."'";
  629. }
  630. }
  631. return $queryData;
  632. }
  633. /**
  634. * Single upload field
  635. *
  636. * @param array $data The data from _FILES
  637. * @param array $queryData
  638. * @return array
  639. */
  640. private function _saveField_upload(array $data, array $queryData): array {
  641. $_up = $data['uploadData'];
  642. // delete the single upload
  643. // this way the after query method is triggered without any upload
  644. if(isset($data['deleteData'])) {
  645. $queryData['after']['upload'][] = array(
  646. 'identifier' => $data['identifier'],
  647. 'multiple' => false,
  648. 'deleteData' => $data['deleteData']
  649. );
  650. }
  651. if($_up['error'][$data['identifier']] === 0) {
  652. $_ext = pathinfo($_up['name'][$data['identifier']],PATHINFO_EXTENSION);
  653. $newFilename = sha1($_up['name'][$data['identifier']]).".".$_ext;
  654. if(!isset($_up['rebuildUpload'][$data['identifier']])) {
  655. $_up['rebuildUpload'][$data['identifier']] = false;
  656. }
  657. $queryData['after']['upload'][] = array(
  658. 'identifier' => $data['identifier'],
  659. 'name' => $newFilename,
  660. 'tmp_name' => $_up['tmp_name'][$data['identifier']],
  661. 'multiple' => false,
  662. 'rebuildUpload' => $_up['rebuildUpload'][$data['identifier']]
  663. );
  664. }
  665. return $queryData;
  666. }
  667. /**
  668. * Multiple upload field
  669. *
  670. * @param array $data The data from _FILES
  671. * @param array $queryData
  672. * @return array
  673. */
  674. private function _saveField_upload_multiple(array $data, array $queryData): array {
  675. $_up = $data['uploadData'];
  676. if(isset($data['deleteData'])) {
  677. $queryData['after']['upload'][] = array(
  678. 'identifier' => $data['identifier'],
  679. 'multiple' => true,
  680. 'deleteData' => $data['deleteData']
  681. );
  682. }
  683. foreach ($_up['error'][$data['identifier']] as $k=>$v) {
  684. if($v === 0) {
  685. $_ext = pathinfo($_up['name'][$data['identifier']][$k],PATHINFO_EXTENSION);
  686. $newFilename = sha1($_up['name'][$data['identifier']][$k]).".".$_ext;
  687. if(!isset($_up['rebuildUpload'][$data['identifier']][$k])) {
  688. $_up['rebuildUpload'][$data['identifier']][$k] = false;
  689. }
  690. $queryData['after']['upload'][] = array(
  691. 'identifier' => $data['identifier'],
  692. 'name' => $newFilename,
  693. 'tmp_name' => $_up['tmp_name'][$data['identifier']][$k],
  694. 'multiple' => true,
  695. 'rebuildUpload' => $_up['rebuildUpload'][$data['identifier']][$k]
  696. );
  697. }
  698. }
  699. return $queryData;
  700. }
  701. /**
  702. * Special for single upload and subtype coverimage.
  703. * Uses the theme settings for image resize. Modifies the result from _saveField_upload if it is an image
  704. *
  705. * @param array $data
  706. * @param array $queryData
  707. * @param array $allInputData All the POST data if needed
  708. * @return array
  709. */
  710. private function _saveField_upload__coverimage(array $data, array $queryData, array $allInputData): array {
  711. $queryData = $this->_saveField_upload($data, $queryData);
  712. if(!isset($queryData['after']['upload'])) {
  713. return $queryData;
  714. }
  715. $workWith = $queryData['after']['upload'][0]['tmp_name'];
  716. if(file_exists($workWith)) {
  717. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  718. $mime = finfo_file($finfo, $workWith);
  719. finfo_close($finfo);
  720. if(str_contains('image/jpeg, image/png, image/webp', $mime)) {
  721. list($width, $height) = getimagesize($workWith);
  722. $_maxThemeWidth = Summoner::themeConfig('coverImageMaxWidth', UI_THEME);
  723. if(!empty($_maxThemeWidth) && ($width > $_maxThemeWidth)) {
  724. $_ratio = $_maxThemeWidth/$width;
  725. $newWidth = (int) $_maxThemeWidth;
  726. $newHeight = (int) $height * $_ratio;
  727. if(DEBUG)Summoner::sysLog("[DEBUG] ".__METHOD__." image ratio: ".$_ratio);
  728. if(DEBUG)Summoner::sysLog("[DEBUG] ".__METHOD__." image width: ".$width);
  729. if(DEBUG)Summoner::sysLog("[DEBUG] ".__METHOD__." image height: ".$height);
  730. if(DEBUG)Summoner::sysLog("[DEBUG] ".__METHOD__." image new width: ".$newWidth);
  731. if(DEBUG)Summoner::sysLog("[DEBUG] ".__METHOD__." image new height: ".$newHeight);
  732. $_tmp_image = imagecreatetruecolor($newWidth, $newHeight);
  733. switch($mime) {
  734. case 'image/jpeg':
  735. $src = imagecreatefromjpeg($workWith);
  736. imagecopyresampled($_tmp_image, $src, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
  737. imagejpeg($_tmp_image, $workWith, 100);
  738. break;
  739. case 'image/png':
  740. $src = imagecreatefrompng($workWith);
  741. imagecopyresampled($_tmp_image, $src, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
  742. imagepng($_tmp_image, $workWith, 0);
  743. break;
  744. case 'image/webp':
  745. $src = imagecreatefromwebp($workWith);
  746. imagecopyresampled($_tmp_image, $src, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
  747. imagewebp($_tmp_image, $workWith,100);
  748. break;
  749. }
  750. imagedestroy($_tmp_image);
  751. imagedestroy($src);
  752. }
  753. }
  754. }
  755. return $queryData;
  756. }
  757. /**
  758. * runs the query and throws query exception if false
  759. *
  760. * @param string $queryString
  761. * @param string $insertId Number
  762. */
  763. private function _runAfter_query(string $queryString, string $insertId): void {
  764. if(!empty($queryString) && !empty($insertId)) {
  765. // replace only once to avoid replacing actual data
  766. $queryStr = Summoner::replaceOnce($queryString,$this->_replaceEntryString, $insertId);
  767. if(QUERY_DEBUG) Summoner::sysLog("[QUERY] ".__METHOD__." query: ".Summoner::cleanForLog($queryStr));
  768. try {
  769. $this->_DB->query($queryStr);
  770. }
  771. catch (Exception $e) {
  772. Summoner::sysLog("[ERROR] ".__METHOD__." mysql catch: ".$e->getMessage());
  773. }
  774. }
  775. }
  776. /**
  777. * Move uploaded into right directory
  778. * If single upload (multiple=false) then remove all the files for this type field first. Works the same
  779. * if you want to remove the upload via edit
  780. *
  781. * Also removes the defined uploads from multiple upload field
  782. *
  783. * @param array $uploadData
  784. * @param string $insertId Number
  785. * @throws Exception
  786. */
  787. private function _runAfter_upload(array $uploadData, string $insertId): void {
  788. if(!empty($uploadData) && !empty($insertId)) {
  789. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." uploadata: ".Summoner::cleanForLog($uploadData));
  790. $_path = PATH_STORAGE.'/'.$this->_collectionId.'/'.$insertId;
  791. if(!is_dir($_path)) {
  792. if(!mkdir($_path, 0777, true)) {
  793. throw new Exception("Failed to create storage path: ".$_path);
  794. }
  795. }
  796. if($uploadData['multiple'] === false) {
  797. // single upload. Delete existing first.
  798. // also triggered if the single needs to be deleted
  799. $_existingFiles = glob($_path.'/'.$uploadData['identifier'].'-*');
  800. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." remove single existing: ".Summoner::cleanForLog($_existingFiles));
  801. if(!empty($_existingFiles)) {
  802. foreach ($_existingFiles as $f) {
  803. unlink($f);
  804. }
  805. clearstatcache();
  806. }
  807. }
  808. if($uploadData['multiple'] === true && isset($uploadData['deleteData'])) {
  809. if(DEBUG) Summoner::sysLog("[DEBUG] ".__METHOD__." remove multiple existing: ".Summoner::cleanForLog($uploadData['deleteData']));
  810. foreach ($uploadData['deleteData'] as $k=>$v) {
  811. $_file = $_path.'/'.$v;
  812. if(file_exists($_file)) {
  813. unlink($_file);
  814. }
  815. clearstatcache();
  816. }
  817. }
  818. if(isset($uploadData['tmp_name']) && isset($uploadData['name'])) {
  819. // special case if the image is already uploaded and not a real POST/FILES request
  820. if(isset($uploadData['rebuildUpload']) && $uploadData['rebuildUpload'] === true) {
  821. if(!rename($uploadData['tmp_name'],$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name'])) {
  822. throw new Exception("Can not rename file to: ".$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name']);
  823. }
  824. }
  825. elseif(!move_uploaded_file($uploadData['tmp_name'],$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name'])) {
  826. throw new Exception("Can not move file to: ".$_path.'/'.$uploadData['identifier'].'-'.$uploadData['name']);
  827. }
  828. }
  829. }
  830. }
  831. /**
  832. * If the given entry has the current user as its owner
  833. * or if root
  834. *
  835. * @param $data array The entry data from getEditData
  836. * @return bool
  837. */
  838. private function _isOwner(array $data): bool {
  839. $ret = false;
  840. if($this->_User->param('isRoot')) {
  841. $ret = true;
  842. }
  843. elseif($data['owner'] == $this->_User->param('id')) {
  844. $ret = true;
  845. }
  846. return $ret;
  847. }
  848. }