BioTorrents.de’s version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

comments.class.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <?php
  2. #declare(strict_types=1);
  3. class Comments
  4. {
  5. /*
  6. * For all functions:
  7. * $Page = 'artist', 'collages', 'requests' or 'torrents'
  8. * $PageID = ArtistID, CollageID, RequestID or GroupID, respectively
  9. */
  10. /**
  11. * Post a comment on an artist, request or torrent page.
  12. * @param string $Page
  13. * @param int $PageID
  14. * @param string $Body
  15. * @return int ID of the new comment
  16. */
  17. public static function post($Page, $PageID, $Body)
  18. {
  19. $QueryID = G::$DB->get_query_id();
  20. G::$DB->query("
  21. SELECT
  22. CEIL(
  23. (
  24. SELECT
  25. COUNT(`ID`) + 1
  26. FROM
  27. `comments`
  28. WHERE
  29. `Page` = '$Page' AND `PageID` = $PageID
  30. ) / ".TORRENT_COMMENTS_PER_PAGE."
  31. ) AS Pages
  32. ");
  33. list($Pages) = G::$DB->next_record();
  34. G::$DB->query("
  35. INSERT INTO `comments`(
  36. `Page`,
  37. `PageID`,
  38. `AuthorID`,
  39. `AddedTime`,
  40. `Body`
  41. )
  42. VALUES(
  43. '$Page',
  44. $PageID,
  45. ".G::$LoggedUser['ID'].",
  46. NOW(), '".db_string($Body)."')
  47. ");
  48. $PostID = G::$DB->inserted_id();
  49. $CatalogueID = floor((TORRENT_COMMENTS_PER_PAGE * $Pages - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  50. G::$Cache->delete_value($Page.'_comments_'.$PageID.'_catalogue_'.$CatalogueID);
  51. G::$Cache->delete_value($Page.'_comments_'.$PageID);
  52. Subscriptions::flush_subscriptions($Page, $PageID);
  53. Subscriptions::quote_notify($Body, $PostID, $Page, $PageID);
  54. G::$DB->set_query_id($QueryID);
  55. return $PostID;
  56. }
  57. /**
  58. * Edit a comment
  59. * @param int $PostID
  60. * @param string $NewBody
  61. * @param bool $SendPM If true, send a PM to the author of the comment informing him about the edit
  62. *
  63. * todo: Move permission check out of here/remove hardcoded error(404)
  64. */
  65. public static function edit($PostID, $NewBody, $SendPM = false)
  66. {
  67. $QueryID = G::$DB->get_query_id();
  68. G::$DB->query("
  69. SELECT
  70. `Body`,
  71. `AuthorID`,
  72. `Page`,
  73. `PageID`,
  74. `AddedTime`
  75. FROM
  76. `comments`
  77. WHERE
  78. `ID` = $PostID
  79. ");
  80. if (!G::$DB->has_results()) {
  81. return false;
  82. }
  83. list($OldBody, $AuthorID, $Page, $PageID, $AddedTime) = G::$DB->next_record();
  84. if (G::$LoggedUser['ID'] != $AuthorID && !check_perms('site_moderate_forums')) {
  85. return false;
  86. }
  87. G::$DB->query("
  88. SELECT
  89. CEIL(
  90. COUNT(`ID`) / ".TORRENT_COMMENTS_PER_PAGE."
  91. ) AS Page
  92. FROM
  93. `comments`
  94. WHERE
  95. `Page` = '$Page' AND `PageID` = $PageID AND `ID` <= $PostID
  96. ");
  97. list($CommPage) = G::$DB->next_record();
  98. // Perform the update
  99. G::$DB->query("
  100. UPDATE
  101. `comments`
  102. SET
  103. `Body` = '".db_string($NewBody)."',
  104. `EditedUserID` = ".G::$LoggedUser['ID'].",
  105. `EditedTime` = NOW()
  106. WHERE
  107. `ID` = $PostID
  108. ");
  109. // Update the cache
  110. $CatalogueID = floor((TORRENT_COMMENTS_PER_PAGE * $CommPage - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  111. G::$Cache->delete_value($Page . '_comments_' . $PageID . '_catalogue_' . $CatalogueID);
  112. if ($Page === 'collages') {
  113. // On collages, we also need to clear the collage key (collage_$CollageID), because it has the comments in it... (why??)
  114. G::$Cache->delete_value("collage_$PageID");
  115. }
  116. G::$DB->query("
  117. INSERT INTO `comments_edits`(
  118. `Page`,
  119. `PostID`,
  120. `EditUser`,
  121. `EditTime`,
  122. `Body`
  123. )
  124. VALUES(
  125. '$Page',
  126. $PostID,
  127. ".G::$LoggedUser['ID'].",
  128. NOW(), '".db_string($OldBody)."')
  129. ");
  130. G::$DB->set_query_id($QueryID);
  131. if ($SendPM && G::$LoggedUser['ID'] !== $AuthorID) {
  132. // Send a PM to the user to notify them of the edit
  133. $PMSubject = "Your comment #$PostID has been edited";
  134. $PMurl = site_url()."comments.php?action=jump&postid=$PostID";
  135. $ProfLink = '[url='.site_url().'user.php?id='.G::$LoggedUser['ID'].']'.G::$LoggedUser['Username'].'[/url]';
  136. $PMBody = "One of your comments has been edited by $ProfLink: [url]{$PMurl}[/url]";
  137. Misc::send_pm($AuthorID, 0, $PMSubject, $PMBody);
  138. }
  139. return true; // todo: This should reflect whether or not the update was actually successful, e.g., by checking G::$DB->affected_rows after the UPDATE query
  140. }
  141. /**
  142. * Delete a comment
  143. * @param int $PostID
  144. */
  145. public static function delete($PostID)
  146. {
  147. $QueryID = G::$DB->get_query_id();
  148. // Get page, pageid
  149. G::$DB->query("
  150. SELECT
  151. `Page`,
  152. `PageID`
  153. FROM
  154. `comments`
  155. WHERE
  156. `ID` = $PostID
  157. ");
  158. if (!G::$DB->has_results()) {
  159. // no such comment?
  160. G::$DB->set_query_id($QueryID);
  161. return false;
  162. }
  163. list($Page, $PageID) = G::$DB->next_record();
  164. // Get number of pages
  165. G::$DB->query("
  166. SELECT
  167. CEIL(
  168. COUNT(`ID`) / ".TORRENT_COMMENTS_PER_PAGE."
  169. ) AS Pages,
  170. CEIL(
  171. SUM(IF(`ID` <= $PostID, 1, 0)) / ".TORRENT_COMMENTS_PER_PAGE."
  172. ) AS Page
  173. FROM
  174. `comments`
  175. WHERE
  176. `Page` = '$Page' AND `PageID` = $PageID
  177. GROUP BY
  178. `PageID`
  179. ");
  180. if (!G::$DB->has_results()) {
  181. // The comment $PostID was probably not posted on $Page
  182. G::$DB->set_query_id($QueryID);
  183. return false;
  184. }
  185. list($CommPages, $CommPage) = G::$DB->next_record();
  186. // $CommPages = number of pages in the thread
  187. // $CommPage = which page the post is on
  188. // These are set for cache clearing.
  189. G::$DB->query("
  190. DELETE
  191. FROM
  192. `comments`
  193. WHERE
  194. `ID` = $PostID
  195. ");
  196. G::$DB->query("
  197. DELETE
  198. FROM
  199. `comments_edits`
  200. WHERE
  201. `Page` = '$Page' AND `PostID` = $PostID
  202. ");
  203. G::$DB->query("
  204. DELETE
  205. FROM
  206. `users_notify_quoted`
  207. WHERE
  208. `Page` = '$Page' AND `PostID` = $PostID
  209. ");
  210. Subscriptions::flush_subscriptions($Page, $PageID);
  211. Subscriptions::flush_quote_notifications($Page, $PageID);
  212. // We need to clear all subsequential catalogues as they've all been bumped with the absence of this post
  213. $ThisCatalogue = floor((TORRENT_COMMENTS_PER_PAGE * $CommPage - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  214. $LastCatalogue = floor((TORRENT_COMMENTS_PER_PAGE * $CommPages - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  215. for ($i = $ThisCatalogue; $i <= $LastCatalogue; ++$i) {
  216. G::$Cache->delete_value($Page . '_comments_' . $PageID . '_catalogue_' . $i);
  217. }
  218. G::$Cache->delete_value($Page . '_comments_' . $PageID);
  219. if ($Page === 'collages') {
  220. // On collages, we also need to clear the collage key (collage_$CollageID), because it has the comments in it... (why??)
  221. G::$Cache->delete_value("collage_$PageID");
  222. }
  223. G::$DB->set_query_id($QueryID);
  224. return true;
  225. }
  226. /**
  227. * Get the URL to a comment, already knowing the Page and PostID
  228. * @param string $Page
  229. * @param int $PageID
  230. * @param int $PostID
  231. * @return string|bool The URL to the comment or false on error
  232. */
  233. public static function get_url($Page, $PageID, $PostID = null)
  234. {
  235. $Post = (!empty($PostID) ? "&postid=$PostID#post$PostID" : '');
  236. switch ($Page) {
  237. case 'artist':
  238. return "artist.php?id=$PageID$Post";
  239. case 'collages':
  240. return "collages.php?action=comments&collageid=$PageID$Post";
  241. case 'requests':
  242. return "requests.php?action=view&id=$PageID$Post";
  243. case 'torrents':
  244. return "torrents.php?id=$PageID$Post";
  245. default:
  246. return false;
  247. }
  248. }
  249. /**
  250. * Get the URL to a comment
  251. * @param int $PostID
  252. * @return string|bool The URL to the comment or false on error
  253. */
  254. public static function get_url_query($PostID)
  255. {
  256. $QueryID = G::$DB->get_query_id();
  257. G::$DB->query("
  258. SELECT
  259. `Page`,
  260. `PageID`
  261. FROM
  262. `comments`
  263. WHERE
  264. `ID` = $PostID
  265. ");
  266. if (!G::$DB->has_results()) {
  267. error(404);
  268. }
  269. list($Page, $PageID) = G::$DB->next_record();
  270. G::$DB->set_query_id($QueryID);
  271. return self::get_url($Page, $PageID, $PostID);
  272. }
  273. /**
  274. * Load a page's comments. This takes care of `postid` and (indirectly) `page` parameters passed in $_GET.
  275. * Quote notifications and last read are also handled here, unless $HandleSubscriptions = false is passed.
  276. * @param string $Page
  277. * @param int $PageID
  278. * @param bool $HandleSubscriptions Whether or not to handle subscriptions (last read & quote notifications)
  279. * @return array ($NumComments, $Page, $Thread, $LastRead)
  280. * $NumComments: the total number of comments on this artist/request/torrent group
  281. * $Page: the page we're currently on
  282. * $Thread: an array of all posts on this page
  283. * $LastRead: ID of the last comment read by the current user in this thread;
  284. * will be false if $HandleSubscriptions == false or if there are no comments on this page
  285. */
  286. public static function load($Page, $PageID, $HandleSubscriptions = true)
  287. {
  288. $QueryID = G::$DB->get_query_id();
  289. // Get the total number of comments
  290. $NumComments = G::$Cache->get_value($Page . "_comments_$PageID");
  291. if ($NumComments === false) {
  292. G::$DB->query("
  293. SELECT
  294. COUNT(`ID`)
  295. FROM
  296. `comments`
  297. WHERE
  298. `Page` = '$Page' AND `PageID` = $PageID
  299. ");
  300. list($NumComments) = G::$DB->next_record();
  301. G::$Cache->cache_value($Page."_comments_$PageID", $NumComments, 0);
  302. }
  303. // If a postid was passed, we need to determine which page that comment is on.
  304. // Format::page_limit handles a potential $_GET['page']
  305. if (isset($_GET['postid']) && is_number($_GET['postid']) && $NumComments > TORRENT_COMMENTS_PER_PAGE) {
  306. G::$DB->query("
  307. SELECT
  308. COUNT(`ID`)
  309. FROM
  310. `comments`
  311. WHERE
  312. `Page` = '$Page' AND `PageID` = $PageID AND `ID` <= $_GET[postid]
  313. ");
  314. list($PostNum) = G::$DB->next_record();
  315. list($CommPage, $Limit) = Format::page_limit(TORRENT_COMMENTS_PER_PAGE, $PostNum);
  316. } else {
  317. list($CommPage, $Limit) = Format::page_limit(TORRENT_COMMENTS_PER_PAGE, $NumComments);
  318. }
  319. // Get the cache catalogue
  320. $CatalogueID = floor((TORRENT_COMMENTS_PER_PAGE * $CommPage - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  321. // Cache catalogue from which the page is selected, allows block caches and future ability to specify posts per page
  322. $Catalogue = G::$Cache->get_value($Page.'_comments_'.$PageID.'_catalogue_'.$CatalogueID);
  323. if ($Catalogue === false) {
  324. $CatalogueLimit = $CatalogueID * THREAD_CATALOGUE . ', ' . THREAD_CATALOGUE;
  325. G::$DB->query("
  326. SELECT
  327. c.`ID`, c.`AuthorID`,
  328. c.`AddedTime`,
  329. c.`Body`,
  330. c.`EditedUserID`,
  331. c.`EditedTime`,
  332. u.`Username`
  333. FROM
  334. `comments` AS c
  335. LEFT JOIN `users_main` AS u
  336. ON
  337. u.`ID` = c.`EditedUserID`
  338. WHERE
  339. c.`Page` = '$Page' AND c.`PageID` = $PageID
  340. ORDER BY
  341. c.`ID`
  342. LIMIT $CatalogueLimit
  343. ");
  344. $Catalogue = G::$DB->to_array(false, MYSQLI_ASSOC);
  345. G::$Cache->cache_value($Page.'_comments_'.$PageID.'_catalogue_'.$CatalogueID, $Catalogue, 0);
  346. }
  347. // This is a hybrid to reduce the catalogue down to the page elements: We use the page limit % catalogue
  348. $Thread = array_slice($Catalogue, ((TORRENT_COMMENTS_PER_PAGE * $CommPage - TORRENT_COMMENTS_PER_PAGE) % THREAD_CATALOGUE), TORRENT_COMMENTS_PER_PAGE, true);
  349. if ($HandleSubscriptions && count($Thread) > 0) {
  350. // Quote notifications
  351. $LastPost = end($Thread);
  352. $LastPost = $LastPost['ID'];
  353. $FirstPost = reset($Thread);
  354. $FirstPost = $FirstPost['ID'];
  355. G::$DB->query("
  356. UPDATE
  357. `users_notify_quoted`
  358. SET
  359. `UnRead` = FALSE
  360. WHERE
  361. `UserID` = ".G::$LoggedUser['ID']."
  362. AND `Page` = '$Page'
  363. AND `PageID` = $PageID
  364. AND `PostID` >= $FirstPost
  365. AND `PostID` <= $LastPost
  366. ");
  367. if (G::$DB->affected_rows()) {
  368. G::$Cache->delete_value('notify_quoted_' . G::$LoggedUser['ID']);
  369. }
  370. // Last read
  371. G::$DB->query("
  372. SELECT
  373. `PostID`
  374. FROM
  375. `users_comments_last_read`
  376. WHERE
  377. `UserID` = ".G::$LoggedUser['ID']."
  378. AND `Page` = '$Page'
  379. AND `PageID` = $PageID
  380. ");
  381. list($LastRead) = G::$DB->next_record();
  382. if ($LastRead < $LastPost) {
  383. G::$DB->query("
  384. INSERT INTO `users_comments_last_read`(`UserID`, `Page`, `PageID`, `PostID`)
  385. VALUES(
  386. ".G::$LoggedUser['ID'].",
  387. '$Page',
  388. $PageID,
  389. $LastPost
  390. )
  391. ON DUPLICATE KEY
  392. UPDATE
  393. `PostID` = $LastPost
  394. ");
  395. G::$Cache->delete_value('subscriptions_user_new_' . G::$LoggedUser['ID']);
  396. }
  397. } else {
  398. $LastRead = false;
  399. }
  400. G::$DB->set_query_id($QueryID);
  401. return array($NumComments, $CommPage, $Thread, $LastRead);
  402. }
  403. /**
  404. * Merges all comments from $Page/$PageID into $Page/$TargetPageID. This also takes care of quote notifications, subscriptions and cache.
  405. * @param type $Page
  406. * @param type $PageID
  407. * @param type $TargetPageID
  408. */
  409. public static function merge($Page, $PageID, $TargetPageID)
  410. {
  411. $QueryID = G::$DB->get_query_id();
  412. G::$DB->query("
  413. UPDATE
  414. `comments`
  415. SET
  416. `PageID` = $TargetPageID
  417. WHERE
  418. `Page` = '$Page' AND `PageID` = $PageID
  419. ");
  420. // Quote notifications
  421. G::$DB->query("
  422. UPDATE
  423. `users_notify_quoted`
  424. SET
  425. `PageID` = $TargetPageID
  426. WHERE
  427. `Page` = '$Page' AND `PageID` = $PageID
  428. ");
  429. // Comment subscriptions
  430. Subscriptions::move_subscriptions($Page, $PageID, $TargetPageID);
  431. // Cache (we need to clear all comment catalogues)
  432. G::$DB->query("
  433. SELECT
  434. CEIL(
  435. COUNT(`ID`) / ".TORRENT_COMMENTS_PER_PAGE."
  436. ) AS Pages
  437. FROM
  438. `comments`
  439. WHERE
  440. `Page` = '$Page' AND `PageID` = $TargetPageID
  441. GROUP BY
  442. `PageID`
  443. ");
  444. list($CommPages) = G::$DB->next_record();
  445. $LastCatalogue = floor((TORRENT_COMMENTS_PER_PAGE * $CommPages - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  446. for ($i = 0; $i <= $LastCatalogue; ++$i) {
  447. G::$Cache->delete_value($Page . "_comments_$TargetPageID" . "_catalogue_$i");
  448. }
  449. G::$Cache->delete_value($Page . "_comments_$TargetPageID");
  450. G::$DB->set_query_id($QueryID);
  451. }
  452. /**
  453. * Delete all comments on $Page/$PageID (deals with quote notifications and subscriptions as well)
  454. * @param string $Page
  455. * @param int $PageID
  456. * @return boolean
  457. */
  458. public static function delete_page($Page, $PageID)
  459. {
  460. $QueryID = G::$DB->get_query_id();
  461. // get number of pages
  462. G::$DB->query("
  463. SELECT
  464. CEIL(
  465. COUNT(`ID`) / ".TORRENT_COMMENTS_PER_PAGE."
  466. ) AS Pages
  467. FROM
  468. `comments`
  469. WHERE
  470. `Page` = '$Page' AND `PageID` = $PageID
  471. GROUP BY
  472. `PageID`
  473. ");
  474. if (!G::$DB->has_results()) {
  475. return false;
  476. }
  477. list($CommPages) = G::$DB->next_record();
  478. // Delete comments
  479. G::$DB->query("
  480. DELETE
  481. FROM
  482. `comments`
  483. WHERE
  484. `Page` = '$Page' AND `PageID` = $PageID
  485. ");
  486. // Delete quote notifications
  487. Subscriptions::flush_quote_notifications($Page, $PageID);
  488. G::$DB->query("
  489. DELETE
  490. FROM
  491. `users_notify_quoted`
  492. WHERE
  493. `Page` = '$Page' AND `PageID` = $PageID
  494. ");
  495. // Deal with subscriptions
  496. Subscriptions::move_subscriptions($Page, $PageID, null);
  497. // Clear cache
  498. $LastCatalogue = floor((TORRENT_COMMENTS_PER_PAGE * $CommPages - TORRENT_COMMENTS_PER_PAGE) / THREAD_CATALOGUE);
  499. for ($i = 0; $i <= $LastCatalogue; ++$i) {
  500. G::$Cache->delete_value($Page."_comments_$PageID"."_catalogue_$i");
  501. }
  502. G::$Cache->delete_value($Page."_comments_$PageID");
  503. G::$DB->set_query_id($QueryID);
  504. return true;
  505. }
  506. }