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.

subscriptions.class.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. #declare(strict_types=1);
  3. class Subscriptions
  4. {
  5. /**
  6. * Parse a post/comment body for quotes and notify all quoted users that have quote notifications enabled.
  7. * @param string $Body
  8. * @param int $PostID
  9. * @param string $Page
  10. * @param int $PageID
  11. */
  12. public static function quote_notify($Body, $PostID, $Page, $PageID)
  13. {
  14. $QueryID = G::$DB->get_query_id();
  15. /*
  16. * Explanation of the parameters PageID and Page: Page contains where
  17. * this quote comes from and can be forums, artist, collages, requests
  18. * or torrents. The PageID contains the additional value that is
  19. * necessary for the users_notify_quoted table. The PageIDs for the
  20. * different Page are: forums: TopicID artist: ArtistID collages:
  21. * CollageID requests: RequestID torrents: GroupID
  22. */
  23. $Matches = [];
  24. preg_match_all('/\[quote(?:=(.*)(?:\|.*)?)?]|\[\/quote]/iU', $Body, $Matches, PREG_SET_ORDER);
  25. if (count($Matches)) {
  26. $Usernames = [];
  27. $Level = 0;
  28. foreach ($Matches as $M) {
  29. if ($M[0] != '[/quote]') {
  30. if ($Level == 0 && isset($M[1]) && strlen($M[1]) > 0 && preg_match(USERNAME_REGEX, $M[1])) {
  31. $Usernames[] = preg_replace('/(^[.,]*)|([.,]*$)/', '', $M[1]); // wut?
  32. }
  33. ++$Level;
  34. } else {
  35. --$Level;
  36. }
  37. }
  38. }
  39. // remove any dupes in the array (the fast way)
  40. $Usernames = array_flip(array_flip($Usernames));
  41. G::$DB->query("
  42. SELECT m.ID
  43. FROM users_main AS m
  44. LEFT JOIN users_info AS i ON i.UserID = m.ID
  45. WHERE m.Username IN ('" . implode("', '", $Usernames) . "')
  46. AND i.NotifyOnQuote = '1'
  47. AND i.UserID != " . G::$LoggedUser['ID']);
  48. $Results = G::$DB->to_array();
  49. foreach ($Results as $Result) {
  50. $UserID = db_string($Result['ID']);
  51. $QuoterID = db_string(G::$LoggedUser['ID']);
  52. $Page = db_string($Page);
  53. $PageID = db_string($PageID);
  54. $PostID = db_string($PostID);
  55. G::$DB->query(
  56. "
  57. INSERT IGNORE INTO users_notify_quoted
  58. (UserID, QuoterID, Page, PageID, PostID, Date)
  59. VALUES
  60. ( ?, ?, ?, ?, ?, NOW())",
  61. $Result['ID'],
  62. G::$LoggedUser['ID'],
  63. $Page,
  64. $PageID,
  65. $PostID
  66. );
  67. G::$Cache->delete_value("notify_quoted_$UserID");
  68. if ($Page == 'forums') {
  69. $URL = site_url() . "forums.php?action=viewthread&postid=$PostID";
  70. } else {
  71. $URL = site_url() . "comments.php?action=jump&postid=$PostID";
  72. }
  73. }
  74. G::$DB->set_query_id($QueryID);
  75. }
  76. /**
  77. * (Un)subscribe from a forum thread.
  78. * If UserID == 0, G::$LoggedUser[ID] is used
  79. * @param int $TopicID
  80. * @param int $UserID
  81. */
  82. public static function subscribe($TopicID, $UserID = 0)
  83. {
  84. if ($UserID == 0) {
  85. $UserID = G::$LoggedUser['ID'];
  86. }
  87. $QueryID = G::$DB->get_query_id();
  88. $UserSubscriptions = self::get_subscriptions();
  89. $Key = self::has_subscribed($TopicID);
  90. if ($Key !== false) {
  91. G::$DB->query('
  92. DELETE FROM users_subscriptions
  93. WHERE UserID = ' . db_string($UserID) . '
  94. AND TopicID = ' . db_string($TopicID));
  95. unset($UserSubscriptions[$Key]);
  96. } else {
  97. G::$DB->query("
  98. INSERT IGNORE INTO users_subscriptions (UserID, TopicID)
  99. VALUES ($UserID, " . db_string($TopicID) . ")");
  100. array_push($UserSubscriptions, $TopicID);
  101. }
  102. G::$Cache->replace_value("subscriptions_user_$UserID", $UserSubscriptions, 0);
  103. G::$Cache->delete_value("subscriptions_user_new_$UserID");
  104. G::$DB->set_query_id($QueryID);
  105. }
  106. /**
  107. * (Un)subscribe from comments.
  108. * If UserID == 0, G::$LoggedUser[ID] is used
  109. * @param string $Page 'artist', 'collages', 'requests' or 'torrents'
  110. * @param int $PageID ArtistID, CollageID, RequestID or GroupID
  111. * @param int $UserID
  112. */
  113. public static function subscribe_comments($Page, $PageID, $UserID = 0)
  114. {
  115. if ($UserID == 0) {
  116. $UserID = G::$LoggedUser['ID'];
  117. }
  118. $QueryID = G::$DB->get_query_id();
  119. $UserCommentSubscriptions = self::get_comment_subscriptions();
  120. $Key = self::has_subscribed_comments($Page, $PageID);
  121. if ($Key !== false) {
  122. G::$DB->query("
  123. DELETE FROM users_subscriptions_comments
  124. WHERE UserID = " . db_string($UserID) . "
  125. AND Page = '" . db_string($Page) . "'
  126. AND PageID = " . db_string($PageID));
  127. unset($UserCommentSubscriptions[$Key]);
  128. } else {
  129. G::$DB->query("
  130. INSERT IGNORE INTO users_subscriptions_comments
  131. (UserID, Page, PageID)
  132. VALUES
  133. ($UserID, '" . db_string($Page) . "', " . db_string($PageID) . ")");
  134. array_push($UserCommentSubscriptions, array($Page, $PageID));
  135. }
  136. G::$Cache->replace_value("subscriptions_comments_user_$UserID", $UserCommentSubscriptions, 0);
  137. G::$Cache->delete_value("subscriptions_comments_user_new_$UserID");
  138. G::$DB->set_query_id($QueryID);
  139. }
  140. /**
  141. * Read $UserID's subscriptions. If the cache key isn't set, it gets filled.
  142. * If UserID == 0, G::$LoggedUser[ID] is used
  143. * @param int $UserID
  144. * @return array Array of TopicIDs
  145. */
  146. public static function get_subscriptions($UserID = 0)
  147. {
  148. if ($UserID == 0) {
  149. $UserID = G::$LoggedUser['ID'];
  150. }
  151. $QueryID = G::$DB->get_query_id();
  152. $UserSubscriptions = G::$Cache->get_value("subscriptions_user_$UserID");
  153. if ($UserSubscriptions === false) {
  154. G::$DB->query('
  155. SELECT TopicID
  156. FROM users_subscriptions
  157. WHERE UserID = ' . db_string($UserID));
  158. $UserSubscriptions = G::$DB->collect(0);
  159. G::$Cache->cache_value("subscriptions_user_$UserID", $UserSubscriptions, 0);
  160. }
  161. G::$DB->set_query_id($QueryID);
  162. return $UserSubscriptions;
  163. }
  164. /**
  165. * Same as self::get_subscriptions, but for comment subscriptions
  166. * @param int $UserID
  167. * @return array Array of ($Page, $PageID)
  168. */
  169. public static function get_comment_subscriptions($UserID = 0)
  170. {
  171. if ($UserID == 0) {
  172. $UserID = G::$LoggedUser['ID'];
  173. }
  174. $QueryID = G::$DB->get_query_id();
  175. $UserCommentSubscriptions = G::$Cache->get_value("subscriptions_comments_user_$UserID");
  176. if ($UserCommentSubscriptions === false) {
  177. G::$DB->query('
  178. SELECT Page, PageID
  179. FROM users_subscriptions_comments
  180. WHERE UserID = ' . db_string($UserID));
  181. $UserCommentSubscriptions = G::$DB->to_array(false, MYSQLI_NUM);
  182. G::$Cache->cache_value("subscriptions_comments_user_$UserID", $UserCommentSubscriptions, 0);
  183. }
  184. G::$DB->set_query_id($QueryID);
  185. return $UserCommentSubscriptions;
  186. }
  187. /**
  188. * Returns whether or not the current user has new subscriptions. This handles both forum and comment subscriptions.
  189. * @return int Number of unread subscribed threads/comments
  190. */
  191. public static function has_new_subscriptions()
  192. {
  193. $QueryID = G::$DB->get_query_id();
  194. $NewSubscriptions = G::$Cache->get_value('subscriptions_user_new_' . G::$LoggedUser['ID']);
  195. if ($NewSubscriptions === false) {
  196. // forum subscriptions
  197. G::$DB->query("
  198. SELECT COUNT(1)
  199. FROM users_subscriptions AS s
  200. LEFT JOIN forums_last_read_topics AS l ON l.UserID = s.UserID AND l.TopicID = s.TopicID
  201. JOIN forums_topics AS t ON t.ID = s.TopicID
  202. JOIN forums AS f ON f.ID = t.ForumID
  203. WHERE " . Forums::user_forums_sql() . "
  204. AND IF(t.IsLocked = '1' AND t.IsSticky = '0'" . ", t.LastPostID, IF(l.PostID IS NULL, 0, l.PostID)) < t.LastPostID
  205. AND s.UserID = " . G::$LoggedUser['ID']);
  206. list($NewForumSubscriptions) = G::$DB->next_record();
  207. // comment subscriptions
  208. G::$DB->query("
  209. SELECT COUNT(1)
  210. FROM users_subscriptions_comments AS s
  211. LEFT JOIN users_comments_last_read AS lr ON lr.UserID = s.UserID AND lr.Page = s.Page AND lr.PageID = s.PageID
  212. LEFT JOIN comments AS c ON c.ID = (SELECT MAX(ID) FROM comments WHERE Page = s.Page AND PageID = s.PageID)
  213. LEFT JOIN collages AS co ON s.Page = 'collages' AND co.ID = s.PageID
  214. WHERE s.UserID = " . G::$LoggedUser['ID'] . "
  215. AND (s.Page != 'collages' OR co.Deleted = '0')
  216. AND IF(lr.PostID IS NULL, 0, lr.PostID) < c.ID");
  217. list($NewCommentSubscriptions) = G::$DB->next_record();
  218. $NewSubscriptions = $NewForumSubscriptions + $NewCommentSubscriptions;
  219. G::$Cache->cache_value('subscriptions_user_new_' . G::$LoggedUser['ID'], $NewSubscriptions, 0);
  220. }
  221. G::$DB->set_query_id($QueryID);
  222. return (int)$NewSubscriptions;
  223. }
  224. /**
  225. * Returns whether or not the current user has new quote notifications.
  226. * @return int Number of unread quote notifications
  227. */
  228. public static function has_new_quote_notifications()
  229. {
  230. $QuoteNotificationsCount = G::$Cache->get_value('notify_quoted_' . G::$LoggedUser['ID']);
  231. if ($QuoteNotificationsCount === false) {
  232. $sql = "
  233. SELECT COUNT(1)
  234. FROM users_notify_quoted AS q
  235. LEFT JOIN forums_topics AS t ON t.ID = q.PageID
  236. LEFT JOIN forums AS f ON f.ID = t.ForumID
  237. LEFT JOIN collages AS c ON q.Page = 'collages' AND c.ID = q.PageID
  238. WHERE q.UserID = " . G::$LoggedUser['ID'] . "
  239. AND q.UnRead
  240. AND (q.Page != 'forums' OR " . Forums::user_forums_sql() . ")
  241. AND (q.Page != 'collages' OR c.Deleted = '0')";
  242. $QueryID = G::$DB->get_query_id();
  243. G::$DB->query($sql);
  244. list($QuoteNotificationsCount) = G::$DB->next_record();
  245. G::$DB->set_query_id($QueryID);
  246. G::$Cache->cache_value('notify_quoted_' . G::$LoggedUser['ID'], $QuoteNotificationsCount, 0);
  247. }
  248. return (int)$QuoteNotificationsCount;
  249. }
  250. /**
  251. * Returns the key which holds this $TopicID in the subscription array.
  252. * Use type-aware comparison operators with this! (ie. if (self::has_subscribed($TopicID) !== false) { ... })
  253. * @param int $TopicID
  254. * @return bool|int
  255. */
  256. public static function has_subscribed($TopicID)
  257. {
  258. $UserSubscriptions = self::get_subscriptions();
  259. return array_search($TopicID, $UserSubscriptions);
  260. }
  261. /**
  262. * Same as has_subscribed, but for comment subscriptions.
  263. * @param string $Page 'artist', 'collages', 'requests' or 'torrents'
  264. * @param int $PageID
  265. * @return bool|int
  266. */
  267. public static function has_subscribed_comments($Page, $PageID)
  268. {
  269. $UserCommentSubscriptions = self::get_comment_subscriptions();
  270. return array_search(array($Page, $PageID), $UserCommentSubscriptions);
  271. }
  272. /**
  273. * Clear the subscription cache for all subscribers of a forum thread or artist/collage/request/torrent comments.
  274. * @param type $Page 'forums', 'artist', 'collages', 'requests' or 'torrents'
  275. * @param type $PageID TopicID, ArtistID, CollageID, RequestID or GroupID, respectively
  276. */
  277. public static function flush_subscriptions($Page, $PageID)
  278. {
  279. $QueryID = G::$DB->get_query_id();
  280. if ($Page == 'forums') {
  281. G::$DB->query("
  282. SELECT UserID
  283. FROM users_subscriptions
  284. WHERE TopicID = '$PageID'");
  285. } else {
  286. G::$DB->query("
  287. SELECT UserID
  288. FROM users_subscriptions_comments
  289. WHERE Page = '$Page'
  290. AND PageID = '$PageID'");
  291. }
  292. $Subscribers = G::$DB->collect('UserID');
  293. foreach ($Subscribers as $Subscriber) {
  294. G::$Cache->delete_value("subscriptions_user_new_$Subscriber");
  295. }
  296. G::$DB->set_query_id($QueryID);
  297. }
  298. /**
  299. * Move all $Page subscriptions from $OldPageID to $NewPageID (for example when merging torrent groups).
  300. * Passing $NewPageID = null will delete the subscriptions.
  301. * @param string $Page 'forums', 'artist', 'collages', 'requests' or 'torrents'
  302. * @param int $OldPageID TopicID, ArtistID, CollageID, RequestID or GroupID, respectively
  303. * @param int|null $NewPageID As $OldPageID, or null to delete the subscriptions
  304. */
  305. public static function move_subscriptions($Page, $OldPageID, $NewPageID)
  306. {
  307. self::flush_subscriptions($Page, $OldPageID);
  308. $QueryID = G::$DB->get_query_id();
  309. if ($Page == 'forums') {
  310. if ($NewPageID !== null) {
  311. G::$DB->query("
  312. UPDATE IGNORE users_subscriptions
  313. SET TopicID = '$NewPageID'
  314. WHERE TopicID = '$OldPageID'");
  315. // explanation see below
  316. G::$DB->query("
  317. UPDATE IGNORE forums_last_read_topics
  318. SET TopicID = $NewPageID
  319. WHERE TopicID = $OldPageID");
  320. G::$DB->query("
  321. SELECT UserID, MIN(PostID)
  322. FROM forums_last_read_topics
  323. WHERE TopicID IN ($OldPageID, $NewPageID)
  324. GROUP BY UserID
  325. HAVING COUNT(1) = 2");
  326. $Results = G::$DB->to_array(false, MYSQLI_NUM);
  327. foreach ($Results as $Result) {
  328. G::$DB->query("
  329. UPDATE forums_last_read_topics
  330. SET PostID = $Result[1]
  331. WHERE TopicID = $NewPageID
  332. AND UserID = $Result[0]");
  333. }
  334. }
  335. G::$DB->query("
  336. DELETE FROM users_subscriptions
  337. WHERE TopicID = '$OldPageID'");
  338. G::$DB->query("
  339. DELETE FROM forums_last_read_topics
  340. WHERE TopicID = $OldPageID");
  341. } else {
  342. if ($NewPageID !== null) {
  343. G::$DB->query("
  344. UPDATE IGNORE users_subscriptions_comments
  345. SET PageID = '$NewPageID'
  346. WHERE Page = '$Page'
  347. AND PageID = '$OldPageID'");
  348. // last read handling
  349. // 1) update all rows that have no key collisions (i.e. users that haven't previously read both pages or if there are only comments on one page)
  350. G::$DB->query("
  351. UPDATE IGNORE users_comments_last_read
  352. SET PageID = '$NewPageID'
  353. WHERE Page = '$Page'
  354. AND PageID = $OldPageID");
  355. // 2) get all last read records with key collisions (i.e. there are records for one user for both PageIDs)
  356. G::$DB->query("
  357. SELECT UserID, MIN(PostID)
  358. FROM users_comments_last_read
  359. WHERE Page = '$Page'
  360. AND PageID IN ($OldPageID, $NewPageID)
  361. GROUP BY UserID
  362. HAVING COUNT(1) = 2");
  363. $Results = G::$DB->to_array(false, MYSQLI_NUM);
  364. // 3) update rows for those people found in 2) to the earlier post
  365. foreach ($Results as $Result) {
  366. G::$DB->query("
  367. UPDATE users_comments_last_read
  368. SET PostID = $Result[1]
  369. WHERE Page = '$Page'
  370. AND PageID = $NewPageID
  371. AND UserID = $Result[0]");
  372. }
  373. }
  374. G::$DB->query("
  375. DELETE FROM users_subscriptions_comments
  376. WHERE Page = '$Page'
  377. AND PageID = '$OldPageID'");
  378. G::$DB->query("
  379. DELETE FROM users_comments_last_read
  380. WHERE Page = '$Page'
  381. AND PageID = '$OldPageID'");
  382. }
  383. G::$DB->set_query_id($QueryID);
  384. }
  385. /**
  386. * Clear the quote notification cache for all subscribers of a forum thread or artist/collage/request/torrent comments.
  387. * @param string $Page 'forums', 'artist', 'collages', 'requests' or 'torrents'
  388. * @param int $PageID TopicID, ArtistID, CollageID, RequestID or GroupID, respectively
  389. */
  390. public static function flush_quote_notifications($Page, $PageID)
  391. {
  392. $QueryID = G::$DB->get_query_id();
  393. G::$DB->query("
  394. SELECT UserID
  395. FROM users_notify_quoted
  396. WHERE Page = '$Page'
  397. AND PageID = $PageID");
  398. $Subscribers = G::$DB->collect('UserID');
  399. foreach ($Subscribers as $Subscriber) {
  400. G::$Cache->delete_value("notify_quoted_$Subscriber");
  401. }
  402. G::$DB->set_query_id($QueryID);
  403. }
  404. }