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

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