management.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /**
  3. * Insipid
  4. * Personal web-bookmark-system
  5. *
  6. * Copyright 2016-2019 Johannes Keßler
  7. *
  8. * Development starting from 2011: Johannes Keßler
  9. * https://www.bananas-playground.net/projekt/insipid/
  10. *
  11. * creator:
  12. * Luke Reeves <luke@neuro-tech.net>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License as published by
  16. * the Free Software Foundation, either version 3 of the License, or
  17. * (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU General Public License
  25. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  26. *
  27. */
  28. class Management {
  29. const LINK_QUERY_STATUS = 2;
  30. const COMBINED_SELECT_VALUES = "any_value(`id`) as id,
  31. any_value(`link`) as link,
  32. any_value(`created`) as created,
  33. any_value(`status`) as `status`,
  34. any_value(`description`) as description,
  35. any_value(`title`) as title,
  36. any_value(`image`) as image,
  37. any_value(`hash`) as hash,
  38. any_value(`tag`) as tag,
  39. any_value(`category`) as category,
  40. any_value(`categoryId`) as categoryId,
  41. any_value(`tagId`) as tagId";
  42. /**
  43. * the database object
  44. * @var object
  45. */
  46. private $DB;
  47. /**
  48. * Type of links based on status to show
  49. * @var bool
  50. */
  51. private $_queryStatus = self::LINK_QUERY_STATUS;
  52. public function __construct($databaseConnectionObject) {
  53. $this->DB = $databaseConnectionObject;
  54. }
  55. /**
  56. * Show private links or not
  57. * @param $bool
  58. */
  59. public function setShowPrivate($bool) {
  60. $this->_queryStatus = self::LINK_QUERY_STATUS;
  61. if($bool === true) {
  62. $this->_queryStatus = 1;
  63. }
  64. }
  65. /**
  66. * Show awaiting moderation links or not
  67. * @param $bool
  68. */
  69. public function setShowAwm($bool) {
  70. $this->_queryStatus = self::LINK_QUERY_STATUS;
  71. if($bool === true) {
  72. $this->_queryStatus = 3;
  73. }
  74. }
  75. /**
  76. * get all the available categories from the DB.
  77. * optional limit
  78. * optional stats
  79. * @param bool | int $limit
  80. * @param bool $stats
  81. * @return array
  82. */
  83. public function categories($limit=false, $stats=false) {
  84. $ret = array();
  85. $statsInfo = array();
  86. if($stats === true) {
  87. $queryStr = "SELECT
  88. COUNT(*) AS amount,
  89. any_value(cr.categoryid) AS categoryId
  90. FROM `".DB_PREFIX."_categoryrelation` AS cr, `".DB_PREFIX."_link` AS t
  91. WHERE cr.linkid = t.id";
  92. $queryStr .= " AND ".$this->_decideLinkTypeForQuery();
  93. $queryStr .= " GROUP BY categoryid";
  94. $query = $this->DB->query($queryStr);
  95. if(!empty($query)) {
  96. while($result = $query->fetch_assoc()) {
  97. $statsInfo[$result['categoryId']] = $result['amount'];
  98. }
  99. }
  100. }
  101. $queryStr = "SELECT
  102. any_value(`id`) as id,
  103. any_value(`name`) as name
  104. FROM `".DB_PREFIX."_category`
  105. ORDER BY `name` ASC";
  106. if(!empty($limit)) {
  107. $queryStr .= " LIMIT $limit";
  108. }
  109. $query = $this->DB->query($queryStr);
  110. if(!empty($query)) {
  111. while($result = $query->fetch_assoc()) {
  112. if($stats === true && isset($statsInfo[$result['id']])) {
  113. $ret[$result['id']] = array('name' => $result['name'], 'amount' => $statsInfo[$result['id']]);
  114. }
  115. else {
  116. $ret[$result['id']] = array('name' => $result['name'], 'amount' => 0);
  117. }
  118. }
  119. }
  120. return $ret;
  121. }
  122. /**
  123. * get all the available tags from the DB.
  124. * optional limit
  125. * optional stats
  126. * @param bool | int $limit
  127. * @param bool $stats
  128. * @return array
  129. */
  130. public function tags($limit=false, $stats=false) {
  131. $ret = array();
  132. $statsInfo = array();
  133. if($stats === true) {
  134. $queryStr = "SELECT
  135. COUNT(*) AS amount,
  136. any_value(tr.tagid) AS tagId
  137. FROM `".DB_PREFIX."_tagrelation` AS tr, `".DB_PREFIX."_link` AS t
  138. WHERE tr.linkid = t.id";
  139. $queryStr .= " AND ".$this->_decideLinkTypeForQuery();
  140. $queryStr .= "GROUP BY tagId";
  141. $query = $this->DB->query($queryStr);
  142. if(!empty($query)) {
  143. while($result = $query->fetch_assoc()) {
  144. $statsInfo[$result['tagId']] = $result['amount'];
  145. }
  146. }
  147. }
  148. $queryStr = "SELECT
  149. any_value(`id`) as id,
  150. any_value(`name`) as name
  151. FROM `".DB_PREFIX."_tag`
  152. ORDER BY `name` ASC";
  153. if(!empty($limit)) {
  154. $queryStr .= " LIMIT $limit";
  155. }
  156. $query = $this->DB->query($queryStr);
  157. if(!empty($query)) {
  158. while($result = $query->fetch_assoc()) {
  159. if($stats === true && isset($statsInfo[$result['id']])) {
  160. $ret[$result['id']] = array('name' => $result['name'], 'amount' => $statsInfo[$result['id']]);
  161. }
  162. else {
  163. $ret[$result['id']] = array('name' => $result['name'], 'amount' => 0);
  164. }
  165. }
  166. }
  167. return $ret;
  168. }
  169. /**
  170. * return the latest added links
  171. * @param int $limit
  172. * @return array
  173. */
  174. public function latestLinks($limit=5) {
  175. $ret = array();
  176. $queryStr = "SELECT `title`, `link` FROM `".DB_PREFIX."_link` AS t";
  177. $queryStr .= " WHERE ".$this->_decideLinkTypeForQuery();
  178. $queryStr .= " ORDER BY `created` DESC";
  179. if(!empty($limit)) {
  180. $queryStr .= " LIMIT $limit";
  181. }
  182. $query = $this->DB->query($queryStr);
  183. if(!empty($query) && $query->num_rows > 0) {
  184. $ret = $query->fetch_all(MYSQLI_ASSOC);
  185. }
  186. return $ret;
  187. }
  188. /**
  189. * get all the categories ordered by link added date
  190. */
  191. public function categoriesByDateAdded() {
  192. $ret = array();
  193. $categories = $this->categories();
  194. foreach($categories as $k=>$v) {
  195. $latestLink = $this->latestLinkForCategory($k);
  196. if(!empty($latestLink)) {
  197. array_push($ret, array('created' => $latestLink[0]['created'], 'id' => $k, 'name' => $v['name']));
  198. }
  199. }
  200. $_created = array_column($ret, 'created');
  201. array_multisort($_created, SORT_DESC, $ret);
  202. return $ret;
  203. }
  204. /**
  205. * find all links by given category string or id.
  206. * Return array sorted by creation date DESC
  207. * @param int $id
  208. * @param string $string
  209. * @param int $limit
  210. * @param bool $offset
  211. * @return array
  212. */
  213. public function linksByCategory($id, $string, $limit=5, $offset=false) {
  214. $ret = array();
  215. $querySelect = "SELECT ".self::COMBINED_SELECT_VALUES;
  216. $queryFrom = " FROM `".DB_PREFIX."_combined` AS t";
  217. $queryWhere = " WHERE ".$this->_decideLinkTypeForQuery();
  218. if(!empty($id) && is_numeric($id)) {
  219. $queryWhere .= " AND t.categoryId = '" . $this->DB->real_escape_string($id) . "'";
  220. }
  221. elseif(!empty($string) && is_string($string)) {
  222. $queryWhere .= " AND t.category = '" . $this->DB->real_escape_string($string) . "'";
  223. }
  224. else {
  225. return $ret;
  226. }
  227. $queryOrder = "GROUP BY t.hash
  228. ORDER BY t.created DESC";
  229. $queryLimit = '';
  230. if(!empty($limit)) {
  231. $queryLimit .= " LIMIT $limit";
  232. if($offset !== false) {
  233. $queryLimit .= " OFFSET $offset";
  234. }
  235. }
  236. $query = $this->DB->query($querySelect.$queryFrom.$queryWhere.$queryOrder.$queryLimit);
  237. if(!empty($query) && $query->num_rows > 0) {
  238. while($result = $query->fetch_assoc()) {
  239. $linkObj = new Link($this->DB);
  240. $ret['results'][] = $linkObj->loadShortInfo($result['hash']);
  241. unset($linkObj);
  242. }
  243. $query = $this->DB->query("SELECT COUNT(DISTINCT(t.hash)) AS amount ".$queryFrom.$queryWhere);
  244. $result = $query->fetch_assoc();
  245. $ret['amount'] = $result['amount'];
  246. }
  247. return $ret;
  248. }
  249. /**
  250. * find all links by given tag string or id.
  251. * Return array sorted by creation date DESC
  252. * @param int $id
  253. * @param string $string
  254. * @param int $limit
  255. * @param bool $offset
  256. * @return array
  257. */
  258. public function linksByTag($id, $string, $limit=5, $offset=false) {
  259. $ret = array();
  260. $querySelect = "SELECT ".self::COMBINED_SELECT_VALUES;
  261. $queryFrom = " FROM `".DB_PREFIX."_combined` AS t";
  262. $queryWhere = " WHERE ".$this->_decideLinkTypeForQuery();
  263. if(!empty($id) && is_numeric($id)) {
  264. $queryWhere .= " AND t.tagId = '" . $this->DB->real_escape_string($id) . "'";
  265. }
  266. elseif(!empty($string) && is_string($string)) {
  267. $queryWhere .= " AND t.tag = '" . $this->DB->real_escape_string($string) . "'";
  268. }
  269. else {
  270. return $ret;
  271. }
  272. $queryOrder = "GROUP BY t.hash
  273. ORDER BY t.created DESC";
  274. $queryLimit = '';
  275. if(!empty($limit)) {
  276. $queryLimit .= " LIMIT $limit";
  277. if($offset !== false) {
  278. $queryLimit .= " OFFSET $offset";
  279. }
  280. }
  281. $query = $this->DB->query($querySelect.$queryFrom.$queryWhere.$queryOrder.$queryLimit);
  282. if(!empty($query) && $query->num_rows > 0) {
  283. while($result = $query->fetch_assoc()) {
  284. $linkObj = new Link($this->DB);
  285. $ret['results'][] = $linkObj->loadShortInfo($result['hash']);
  286. unset($linkObj);
  287. }
  288. $query = $this->DB->query("SELECT COUNT(DISTINCT(t.hash)) AS amount ".$queryFrom.$queryWhere);
  289. $result = $query->fetch_assoc();
  290. $ret['amount'] = $result['amount'];
  291. }
  292. return $ret;
  293. }
  294. /**
  295. * return all links and Info we have from the combined view
  296. * @param bool | int $limit
  297. * @param bool $offset
  298. * @return array
  299. */
  300. public function links($limit=10,$offset=false) {
  301. $ret = array();
  302. $querySelect = "SELECT `hash`";
  303. $queryFrom = " FROM `".DB_PREFIX."_link` AS t";
  304. $queryWhere = " WHERE ".$this->_decideLinkTypeForQuery();
  305. $queryOrder = " ORDER BY `created` DESC";
  306. $queryLimit = "";
  307. if(!empty($limit)) {
  308. $queryLimit = " LIMIT $limit";
  309. if($offset !== false) {
  310. $queryLimit .= " OFFSET $offset";
  311. }
  312. }
  313. $query = $this->DB->query($querySelect.$queryFrom.$queryWhere.$queryOrder.$queryLimit);
  314. if(!empty($query) && $query->num_rows > 0) {
  315. while($result = $query->fetch_assoc()) {
  316. $linkObj = new Link($this->DB);
  317. $ret['results'][] = $linkObj->loadShortInfo($result['hash']);
  318. unset($linkObj);
  319. }
  320. $query = $this->DB->query("SELECT COUNT(t.hash) AS amount ".$queryFrom.$queryWhere);
  321. $result = $query->fetch_assoc();
  322. $ret['amount'] = $result['amount'];
  323. }
  324. return $ret;
  325. }
  326. /**
  327. * return the latest added link for given category id
  328. * @param int $categoryid
  329. * @return array
  330. */
  331. public function latestLinkForCategory($categoryid) {
  332. $ret = array();
  333. if(!empty($categoryid) && is_numeric($categoryid)) {
  334. $queryStr = "SELECT ".self::COMBINED_SELECT_VALUES."
  335. FROM `".DB_PREFIX."_combined` AS t";
  336. $queryStr .= " WHERE ".$this->_decideLinkTypeForQuery();
  337. $queryStr .= " AND t.categoryId = '" . $this->DB->real_escape_string($categoryid) . "'
  338. ORDER BY t.created DESC
  339. LIMIT 1";
  340. $query = $this->DB->query($queryStr);
  341. if(!empty($query) && $query->num_rows > 0) {
  342. $ret = $query->fetch_all(MYSQLI_ASSOC);
  343. }
  344. }
  345. return $ret;
  346. }
  347. /**
  348. * Search for the given url in the links table
  349. * @param $url
  350. * @return mixed
  351. */
  352. public function searchForLinkByURL($url) {
  353. $ret = false;
  354. if(!empty($url)) {
  355. $queryStr = "SELECT * FROM `".DB_PREFIX."_link` AS t";
  356. $queryStr .= " WHERE ".$this->_decideLinkTypeForQuery();
  357. $queryStr .= " AND t.link = '".$this->DB->real_escape_string($url)."'";
  358. $query = $this->DB->query($queryStr);
  359. if(!empty($query) && $query->num_rows > 0) {
  360. $ret = $query->fetch_all(MYSQLI_ASSOC);
  361. }
  362. }
  363. return $ret;
  364. }
  365. /**
  366. * search for given searchstring in the search data of the links
  367. * @param $searchStr
  368. * @return mixed
  369. */
  370. public function searchForLinkBySearchData($searchStr) {
  371. $ret = false;
  372. if(!empty($searchStr)) {
  373. $queryStr = "SELECT *,
  374. MATCH (`search`) AGAINST ('".$this->DB->real_escape_string($searchStr)."' IN BOOLEAN MODE) AS score
  375. FROM `".DB_PREFIX."_link` AS t
  376. WHERE MATCH (`search`) AGAINST ('".$this->DB->real_escape_string($searchStr)."' IN BOOLEAN MODE)";
  377. $queryStr .= " WHERE ".$this->_decideLinkTypeForQuery();
  378. $queryStr .= " ORDER BY score DESC";
  379. $query = $this->DB->query($queryStr);
  380. if(!empty($query) && $query->num_rows > 0) {
  381. $ret = $query->fetch_all(MYSQLI_ASSOC);
  382. }
  383. }
  384. return $ret;
  385. }
  386. /**
  387. * amount of links in the DB. Status 1 and 2 only
  388. * @return int
  389. */
  390. public function linkAmount() {
  391. $ret = 0;
  392. $queryStr = "SELECT COUNT(*) AS amount
  393. FROM `".DB_PREFIX."_link` AS t";
  394. $queryStr .= " WHERE ".$this->_decideLinkTypeForQuery();
  395. $query = $this->DB->query($queryStr);
  396. if(!empty($query) && $query->num_rows > 0) {
  397. $result = $query->fetch_assoc();
  398. $ret = $result['amount'];
  399. }
  400. return $ret;
  401. }
  402. /**
  403. * amount of tags
  404. * @return int
  405. */
  406. public function tagAmount() {
  407. $ret = 0;
  408. $queryStr = "SELECT COUNT(*) AS amount FROM `".DB_PREFIX."_tag`";
  409. $query = $this->DB->query($queryStr);
  410. if(!empty($query) && $query->num_rows > 0) {
  411. $result = $query->fetch_assoc();
  412. $ret = $result['amount'];
  413. }
  414. return $ret;
  415. }
  416. /**
  417. * amount of categories
  418. * @return int
  419. */
  420. public function categoryAmount() {
  421. $ret = 0;
  422. $queryStr = "SELECT COUNT(*) AS amount FROM `".DB_PREFIX."_category`";
  423. $query = $this->DB->query($queryStr);
  424. if(!empty($query) && $query->num_rows > 0) {
  425. $result = $query->fetch_assoc();
  426. $ret = $result['amount'];
  427. }
  428. return $ret;
  429. }
  430. /**
  431. * Amount of links need moderation
  432. * @return int
  433. */
  434. public function moderationAmount() {
  435. $ret = 0;
  436. $queryStr = "SELECT COUNT(*) AS amount FROM `".DB_PREFIX."_link`";
  437. $queryStr .= " WHERE `status` = 3";
  438. $query = $this->DB->query($queryStr);
  439. if(!empty($query) && $query->num_rows > 0) {
  440. $result = $query->fetch_assoc();
  441. $ret = $result['amount'];
  442. }
  443. return $ret;
  444. }
  445. /**
  446. * get the used disk space for local image storage
  447. * @return false|int
  448. */
  449. public function storageAmount() {
  450. $ret = 0;
  451. $_storageFolder = ABSOLUTE_PATH.'/'.LOCAL_STORAGE;
  452. if(file_exists($_storageFolder) && is_readable($_storageFolder)) {
  453. $ret = Summoner::folderSize($_storageFolder);
  454. }
  455. return $ret;
  456. }
  457. /**
  458. * empties the local storage directory
  459. * @return bool
  460. */
  461. public function clearLocalStorage() {
  462. $ret = false;
  463. $_storageFolder = ABSOLUTE_PATH.'/'.LOCAL_STORAGE;
  464. if(file_exists($_storageFolder) && is_writable($_storageFolder)) {
  465. $ret = Summoner::recursive_remove_directory($_storageFolder,true);
  466. }
  467. return $ret;
  468. }
  469. /**
  470. * Load link by given hash. Do not use Link class directly.
  471. * Otherwise the authentication will be ignored.
  472. * @param $hash
  473. * @param bool $fullInfo
  474. * @param $withObject
  475. * @return array|mixed
  476. */
  477. public function loadLink($hash,$fullInfo=true,$withObject=false) {
  478. $ret = array();
  479. if (!empty($hash)) {
  480. $querySelect = "SELECT `hash`";
  481. $queryFrom = " FROM `".DB_PREFIX."_link` AS t";
  482. $queryWhere = " WHERE ".$this->_decideLinkTypeForQuery();
  483. $queryWhere .= " AND t.hash = '".$this->DB->real_escape_string($hash)."'";
  484. $query = $this->DB->query($querySelect.$queryFrom.$queryWhere);
  485. if (!empty($query) && $query->num_rows == 1) {
  486. $linkObj = new Link($this->DB);
  487. if($fullInfo === true) {
  488. $ret = $linkObj->load($hash);
  489. }
  490. else {
  491. $ret = $linkObj->loadShortInfo($hash);
  492. }
  493. if($withObject === true) {
  494. $ret = array(
  495. 'data' => $ret,
  496. 'obj' => $linkObj
  497. );
  498. }
  499. }
  500. }
  501. return $ret;
  502. }
  503. /**
  504. * Delete link by given hash
  505. * @param $hash
  506. * @return bool
  507. */
  508. public function deleteLink($hash) {
  509. $ret = false;
  510. if (!empty($hash)) {
  511. $linkData = $this->loadLink($hash,false,true);
  512. if(!empty($linkData)) {
  513. $linkData['obj']->deleteRelations();
  514. $queryStr = "DELETE FROM `" . DB_PREFIX . "_link`
  515. WHERE `hash` = '" . $this->DB->real_escape_string($hash) . "'";
  516. $query = $this->DB->query($queryStr);
  517. if (!empty($query)) {
  518. $ret = true;
  519. }
  520. }
  521. }
  522. return $ret;
  523. }
  524. /**
  525. * for simpler management we have the search data in a separate column
  526. * it is not fancy or even technical nice but it damn works
  527. */
  528. private function _updateSearchIndex() {
  529. $allLinks = array();
  530. $queryStr = "SELECT hash FROM `".DB_PREFIX."_link`";
  531. $query = $this->DB->query($queryStr);
  532. if(!empty($query) && $query->num_rows > 0) {
  533. $allLinks = $query->fetch_all(MYSQLI_ASSOC);
  534. }
  535. if(!empty($allLinks)) {
  536. foreach($allLinks as $link) {
  537. $LinkObj = new Link($this->DB);
  538. $l = $LinkObj->load($link['hash']);
  539. $searchStr = $l['title'];
  540. $searchStr .= ' '.$l['description'];
  541. foreach($l['tags'] as $t) {
  542. $searchStr .= ' '.$t['tag'];
  543. }
  544. foreach($l['categories'] as $c) {
  545. $searchStr .= ' '.$c['category'];
  546. }
  547. # now update the search string
  548. $queryStr = "UPDATE `".DB_PREFIX."_link`
  549. SET `search` = '".$this->DB->real_escape_string($searchStr)."'
  550. WHERE `hash` = '".$this->DB->real_escape_string($link['hash'])."'";
  551. $this->DB->query($queryStr);
  552. unset($LinkObj,$l,$searchStr,$t,$c,$queryStr);
  553. }
  554. }
  555. }
  556. /**
  557. * Return the query string for the correct status type
  558. * @return string
  559. */
  560. private function _decideLinkTypeForQuery() {
  561. switch ($this->_queryStatus) {
  562. case 1:
  563. $ret = "t.status IN (2,1)";
  564. break;
  565. case 3:
  566. $ret = "t.status = 3";
  567. break;
  568. default:
  569. $ret = "t.status = 2";
  570. }
  571. return $ret;
  572. }
  573. }