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.

misc.class.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <?php
  2. #declare(strict_types=1);
  3. class Misc
  4. {
  5. /**
  6. * Send an email.
  7. *
  8. * @param string $To the email address to send it to.
  9. * @param string $Subject
  10. * @param string $Body
  11. * @param string $From The user part of the user@$ENV->SITE_DOMAIN email address.
  12. * @param string $ContentType text/plain or text/html
  13. */
  14. public static function send_email($To, $Subject, $Body, $From = 'noreply', $ContentType = 'text/plain')
  15. {
  16. $ENV = ENV::go();
  17. # todo: <<<EOT
  18. $Headers = "MIME-Version: 1.0\r\n";
  19. $Headers .= "Content-type: $ContentType; charset=utf-8\r\n";
  20. $Headers .= "From: $ENV->SITE_NAME <$From@$ENV->SITE_DOMAIN>\r\n";
  21. $Headers .= "Reply-To: $From@$ENV->SITE_DOMAIN\r\n";
  22. $Headers .= "X-Mailer: Project Gazelle\r\n";
  23. $Headers .= "Message-Id: <".Users::make_secret()."@$ENV->SITE_DOMAIN>\r\n";
  24. $Headers .= "X-Priority: 3\r\n";
  25. // Check if email is enabled
  26. if ($ENV->FEATURE_SEND_EMAIL) {
  27. mail($To, $Subject, $Body, $Headers, "-f $From@$ENV->SITE_DOMAIN");
  28. }
  29. }
  30. /**
  31. * Sanitize a string to be allowed as a filename.
  32. *
  33. * @param string $EscapeStr the string to escape
  34. * @return the string with all banned characters removed.
  35. */
  36. public static function file_string($EscapeStr)
  37. {
  38. $ENV = ENV::go();
  39. return str_replace($ENV->BAD_CHARS, '', $EscapeStr);
  40. }
  41. /**
  42. * Sends a PM from $FromId to $ToId.
  43. *
  44. * @param string $ToID ID of user to send PM to. If $ToID is an array and $ConvID is empty, a message will be sent to multiple users.
  45. * @param string $FromID ID of user to send PM from, 0 to send from system
  46. * @param string $Subject
  47. * @param string $Body
  48. * @param int $ConvID The conversation the message goes in. Leave blank to start a new conversation.
  49. * @return
  50. */
  51. public static function send_pm($ToID, $FromID, $Subject, $Body, $ConvID = '')
  52. {
  53. global $Time;
  54. $UnescapedSubject = $Subject;
  55. $UnescapedBody = $Body;
  56. $Subject = db_string($Subject);
  57. $Body = Crypto::encrypt(substr($Body, 0, 49135)); // 49135 -> encryption -> 65536 (max length in mysql)
  58. if ($ToID === 0) {
  59. // Don't allow users to send messages to the system
  60. return;
  61. }
  62. $QueryID = G::$DB->get_query_id();
  63. if ($ConvID === '') {
  64. // Create a new conversation.
  65. G::$DB->query("
  66. INSERT INTO pm_conversations (Subject)
  67. VALUES ('$Subject')");
  68. $ConvID = G::$DB->inserted_id();
  69. G::$DB->query("
  70. INSERT INTO pm_conversations_users
  71. (UserID, ConvID, InInbox, InSentbox, SentDate, ReceivedDate, UnRead)
  72. VALUES
  73. ('$ToID', '$ConvID', '1','0', NOW(), NOW(), '1')");
  74. if ($FromID === $ToID) {
  75. G::$DB->query(
  76. "
  77. UPDATE pm_conversations_users
  78. SET InSentbox = '1'
  79. WHERE ConvID = '$ConvID'"
  80. );
  81. } elseif ($FromID !== 0) {
  82. G::$DB->query("
  83. INSERT INTO pm_conversations_users
  84. (UserID, ConvID, InInbox, InSentbox, SentDate, ReceivedDate, UnRead)
  85. VALUES
  86. ('$FromID', '$ConvID', '0','1', NOW(), NOW(), '0')");
  87. }
  88. $ToID = array($ToID);
  89. } else {
  90. // Update the pre-existing conversations
  91. G::$DB->query("
  92. UPDATE pm_conversations_users
  93. SET
  94. InInbox = '1',
  95. UnRead = '1',
  96. ReceivedDate = NOW()
  97. WHERE UserID IN (".implode(',', $ToID).")
  98. AND ConvID = '$ConvID'");
  99. G::$DB->query("
  100. UPDATE pm_conversations_users
  101. SET
  102. InSentbox = '1',
  103. SentDate = NOW()
  104. WHERE UserID = '$FromID'
  105. AND ConvID = '$ConvID'");
  106. }
  107. // Now that we have a $ConvID for sure, send the message
  108. G::$DB->query("
  109. INSERT INTO pm_messages
  110. (SenderID, ConvID, SentDate, Body)
  111. VALUES
  112. ('$FromID', '$ConvID', NOW(), '$Body')");
  113. // Update the cached new message count
  114. foreach ($ToID as $ID) {
  115. G::$DB->query("
  116. SELECT COUNT(ConvID)
  117. FROM pm_conversations_users
  118. WHERE UnRead = '1'
  119. AND UserID = '$ID'
  120. AND InInbox = '1'");
  121. list($UnRead) = G::$DB->next_record();
  122. G::$Cache->cache_value("inbox_new_$ID", $UnRead);
  123. }
  124. G::$DB->query("
  125. SELECT Username
  126. FROM users_main
  127. WHERE ID = '$FromID'");
  128. list($SenderName) = G::$DB->next_record();
  129. foreach ($ToID as $ID) {
  130. G::$DB->query("
  131. SELECT COUNT(ConvID)
  132. FROM pm_conversations_users
  133. WHERE UnRead = '1'
  134. AND UserID = '$ID'
  135. AND InInbox = '1'");
  136. list($UnRead) = G::$DB->next_record();
  137. G::$Cache->cache_value("inbox_new_$ID", $UnRead);
  138. }
  139. G::$DB->set_query_id($QueryID);
  140. return $ConvID;
  141. }
  142. /**
  143. * Create thread function, things should already be escaped when sent here.
  144. *
  145. * @param int $ForumID
  146. * @param int $AuthorID ID of the user creating the post.
  147. * @param string $Title
  148. * @param string $PostBody
  149. * @return -1 on error, -2 on user not existing, thread id on success.
  150. */
  151. public static function create_thread($ForumID, $AuthorID, $Title, $PostBody)
  152. {
  153. global $Time;
  154. if (!$ForumID || !$AuthorID || !is_number($AuthorID) || !$Title || !$PostBody) {
  155. return -1;
  156. }
  157. $QueryID = G::$DB->get_query_id();
  158. G::$DB->query("
  159. SELECT Username
  160. FROM users_main
  161. WHERE ID = $AuthorID");
  162. if (!G::$DB->has_results()) {
  163. G::$DB->set_query_id($QueryID);
  164. return -2;
  165. }
  166. list($AuthorName) = G::$DB->next_record();
  167. $ThreadInfo = [];
  168. $ThreadInfo['IsLocked'] = 0;
  169. $ThreadInfo['IsSticky'] = 0;
  170. G::$DB->query("
  171. INSERT INTO forums_topics
  172. (Title, AuthorID, ForumID, LastPostTime, LastPostAuthorID, CreatedTime)
  173. VALUES
  174. ('$Title', '$AuthorID', '$ForumID', NOW(), '$AuthorID', NOW())");
  175. $TopicID = G::$DB->inserted_id();
  176. $Posts = 1;
  177. G::$DB->query("
  178. INSERT INTO forums_posts
  179. (TopicID, AuthorID, AddedTime, Body)
  180. VALUES
  181. ('$TopicID', '$AuthorID', NOW(), '$PostBody')");
  182. $PostID = G::$DB->inserted_id();
  183. G::$DB->query("
  184. UPDATE forums
  185. SET
  186. NumPosts = NumPosts + 1,
  187. NumTopics = NumTopics + 1,
  188. LastPostID = '$PostID',
  189. LastPostAuthorID = '$AuthorID',
  190. LastPostTopicID = '$TopicID',
  191. LastPostTime = NOW()
  192. WHERE ID = '$ForumID'");
  193. G::$DB->query("
  194. UPDATE forums_topics
  195. SET
  196. NumPosts = NumPosts + 1,
  197. LastPostID = '$PostID',
  198. LastPostAuthorID = '$AuthorID',
  199. LastPostTime = NOW()
  200. WHERE ID = '$TopicID'");
  201. // Bump this topic to head of the cache
  202. list($Forum, , , $Stickies) = G::$Cache->get_value("forums_$ForumID");
  203. if (!empty($Forum)) {
  204. if (count($Forum) === TOPICS_PER_PAGE && $Stickies < TOPICS_PER_PAGE) {
  205. array_pop($Forum);
  206. }
  207. G::$DB->query("
  208. SELECT IsLocked, IsSticky, NumPosts
  209. FROM forums_topics
  210. WHERE ID ='$TopicID'");
  211. list($IsLocked, $IsSticky, $NumPosts) = G::$DB->next_record();
  212. $Part1 = array_slice($Forum, 0, $Stickies, true); // Stickies
  213. $Part2 = array(
  214. $TopicID => array(
  215. 'ID' => $TopicID,
  216. 'Title' => $Title,
  217. 'AuthorID' => $AuthorID,
  218. 'IsLocked' => $IsLocked,
  219. 'IsSticky' => $IsSticky,
  220. 'NumPosts' => $NumPosts,
  221. 'LastPostID' => $PostID,
  222. 'LastPostTime' => sqltime(),
  223. 'LastPostAuthorID' => $AuthorID,
  224. )
  225. ); // Bumped thread
  226. $Part3 = array_slice($Forum, $Stickies, TOPICS_PER_PAGE, true); //Rest of page
  227. if ($Stickies > 0) {
  228. $Part1 = array_slice($Forum, 0, $Stickies, true); //Stickies
  229. $Part3 = array_slice($Forum, $Stickies, TOPICS_PER_PAGE - $Stickies - 1, true); //Rest of page
  230. } else {
  231. $Part1 = [];
  232. $Part3 = $Forum;
  233. }
  234. if (is_null($Part1)) {
  235. $Part1 = [];
  236. }
  237. if (is_null($Part3)) {
  238. $Part3 = [];
  239. }
  240. $Forum = $Part1 + $Part2 + $Part3;
  241. G::$Cache->cache_value("forums_$ForumID", array($Forum, '', 0, $Stickies), 0);
  242. }
  243. // Update the forum root
  244. G::$Cache->begin_transaction('forums_list');
  245. $UpdateArray = array(
  246. 'NumPosts' => '+1',
  247. 'NumTopics' => '+1',
  248. 'LastPostID' => $PostID,
  249. 'LastPostAuthorID' => $AuthorID,
  250. 'LastPostTopicID' => $TopicID,
  251. 'LastPostTime' => sqltime(),
  252. 'Title' => $Title,
  253. 'IsLocked' => $ThreadInfo['IsLocked'],
  254. 'IsSticky' => $ThreadInfo['IsSticky']
  255. );
  256. $UpdateArray['NumTopics'] = '+1';
  257. G::$Cache->update_row($ForumID, $UpdateArray);
  258. G::$Cache->commit_transaction(0);
  259. $CatalogueID = floor((POSTS_PER_PAGE * ceil($Posts / POSTS_PER_PAGE) - POSTS_PER_PAGE) / THREAD_CATALOGUE);
  260. G::$Cache->begin_transaction('thread_'.$TopicID.'_catalogue_'.$CatalogueID);
  261. $Post = array(
  262. 'ID' => $PostID,
  263. 'AuthorID' => G::$LoggedUser['ID'],
  264. 'AddedTime' => sqltime(),
  265. 'Body' => $PostBody,
  266. 'EditedUserID' => 0,
  267. 'EditedTime' => null,
  268. 'Username' => ''
  269. );
  270. G::$Cache->insert('', $Post);
  271. G::$Cache->commit_transaction(0);
  272. G::$Cache->begin_transaction('thread_'.$TopicID.'_info');
  273. G::$Cache->update_row(false, array('Posts' => '+1', 'LastPostAuthorID' => $AuthorID));
  274. G::$Cache->commit_transaction(0);
  275. G::$DB->set_query_id($QueryID);
  276. return $TopicID;
  277. }
  278. /**
  279. * If the suffix of $Haystack is $Needle
  280. *
  281. * @param string $Haystack String to search in
  282. * @param string $Needle String to search for
  283. * @return boolean True if $Needle is a suffix of $Haystack
  284. */
  285. public static function ends_with($Haystack, $Needle)
  286. {
  287. return substr($Haystack, strlen($Needle) * -1) === $Needle;
  288. }
  289. /**
  290. * If the prefix of $Haystack is $Needle
  291. *
  292. * @param string $Haystack String to search in
  293. * @param string $Needle String to search for
  294. * @return boolean True if $Needle is a prefix of $Haystack
  295. */
  296. public static function starts_with($Haystack, $Needle)
  297. {
  298. return strpos($Haystack, $Needle) === 0;
  299. }
  300. /**
  301. * Variant of in_array() with trailing wildcard support
  302. *
  303. * @param string $Needle, array $Haystack
  304. * @return boolean true if (substring of) $Needle exists in $Haystack
  305. */
  306. public static function in_array_partial($Needle, $Haystack)
  307. {
  308. static $Searches = [];
  309. if (array_key_exists($Needle, $Searches)) {
  310. return $Searches[$Needle];
  311. }
  312. foreach ($Haystack as $String) {
  313. if (substr($String, -1) === '*') {
  314. if (!strncmp($Needle, $String, strlen($String) - 1)) {
  315. $Searches[$Needle] = true;
  316. return true;
  317. }
  318. } elseif (!strcmp($Needle, $String)) {
  319. $Searches[$Needle] = true;
  320. return true;
  321. }
  322. }
  323. $Searches[$Needle] = false;
  324. return false;
  325. }
  326. /**
  327. * Used to check if keys in $_POST and $_GET are all set, and throws an error if not.
  328. * This reduces 'if' statement redundancy for a lot of variables
  329. *
  330. * @param array $Request Either $_POST or $_GET, or whatever other array you want to check.
  331. * @param array $Keys The keys to ensure are set.
  332. * @param boolean $AllowEmpty If set to true, a key that is in the request but blank will not throw an error.
  333. * @param int $Error The error code to throw if one of the keys isn't in the array.
  334. */
  335. public static function assert_isset_request($Request, $Keys = null, $AllowEmpty = false, $Error = 0)
  336. {
  337. if (isset($Keys)) {
  338. foreach ($Keys as $K) {
  339. if (!isset($Request[$K]) || ($AllowEmpty === false && $Request[$K] === '')) {
  340. error($Error);
  341. break;
  342. }
  343. }
  344. } else {
  345. foreach ($Request as $R) {
  346. if (!isset($R) || ($AllowEmpty === false && $R === '')) {
  347. error($Error);
  348. break;
  349. }
  350. }
  351. }
  352. }
  353. /**
  354. * Given an array of tags, return an array of their IDs.
  355. *
  356. * @param array $TagNames
  357. * @return array IDs
  358. */
  359. public static function get_tags($TagNames)
  360. {
  361. $TagIDs = [];
  362. foreach ($TagNames as $Index => $TagName) {
  363. $Tag = G::$Cache->get_value("tag_id_$TagName");
  364. if (is_array($Tag)) {
  365. unset($TagNames[$Index]);
  366. $TagIDs[$Tag['ID']] = $Tag['Name'];
  367. }
  368. }
  369. if (count($TagNames) > 0) {
  370. $QueryID = G::$DB->get_query_id();
  371. G::$DB->query("
  372. SELECT ID, Name
  373. FROM tags
  374. WHERE Name IN ('".implode("', '", $TagNames)."')");
  375. $SQLTagIDs = G::$DB->to_array();
  376. G::$DB->set_query_id($QueryID);
  377. foreach ($SQLTagIDs as $Tag) {
  378. $TagIDs[$Tag['ID']] = $Tag['Name'];
  379. G::$Cache->cache_value('tag_id_'.$Tag['Name'], $Tag, 0);
  380. }
  381. }
  382. return($TagIDs);
  383. }
  384. /**
  385. * Gets the alias of the tag; if there is no alias, silently returns the original tag.
  386. *
  387. * @param string $BadTag the tag we want to alias
  388. * @return string The aliased tag.
  389. */
  390. public static function get_alias_tag($BadTag)
  391. {
  392. $QueryID = G::$DB->get_query_id();
  393. G::$DB->query("
  394. SELECT AliasTag
  395. FROM tag_aliases
  396. WHERE BadTag = '$BadTag'
  397. LIMIT 1");
  398. if (G::$DB->has_results()) {
  399. list($AliasTag) = G::$DB->next_record();
  400. } else {
  401. $AliasTag = $BadTag;
  402. }
  403. G::$DB->set_query_id($QueryID);
  404. return $AliasTag;
  405. }
  406. /*
  407. * Write a message to the system log.
  408. *
  409. * @param string $Message the message to write.
  410. */
  411. public static function write_log($Message)
  412. {
  413. global $Time;
  414. $QueryID = G::$DB->get_query_id();
  415. G::$DB->query("
  416. INSERT INTO log (Message, Time)
  417. VALUES (?, NOW())", $Message);
  418. G::$DB->set_query_id($QueryID);
  419. }
  420. /**
  421. * Get a tag ready for database input and display.
  422. *
  423. * @param string $Str
  424. * @return sanitized version of $Str
  425. */
  426. public static function sanitize_tag($Str)
  427. {
  428. $Str = strtolower($Str);
  429. $Str = preg_replace('/[^a-z0-9:.]/', '', $Str);
  430. $Str = preg_replace('/(^[.,]*)|([.,]*$)/', '', $Str);
  431. $Str = htmlspecialchars($Str);
  432. $Str = db_string(trim($Str));
  433. return $Str;
  434. }
  435. /**
  436. * HTML escape an entire array for output.
  437. * @param array $Array, what we want to escape
  438. * @param boolean/array $Escape
  439. * if true, all keys escaped
  440. * if false, no escaping.
  441. * If array, it's a list of array keys not to escape.
  442. * @return mutated version of $Array with values escaped.
  443. */
  444. public static function display_array($Array, $Escape = [])
  445. {
  446. foreach ($Array as $Key => $Val) {
  447. if ((!is_array($Escape) && $Escape === true) || !in_array($Key, $Escape)) {
  448. $Array[$Key] = display_str($Val);
  449. }
  450. }
  451. return $Array;
  452. }
  453. /**
  454. * Searches for a key/value pair in an array.
  455. *
  456. * @return array of results
  457. */
  458. public static function search_array($Array, $Key, $Value)
  459. {
  460. $Results = [];
  461. if (is_array($Array)) {
  462. if (isset($Array[$Key]) && $Array[$Key] === $Value) {
  463. $Results[] = $Array;
  464. }
  465. foreach ($Array as $subarray) {
  466. $Results = array_merge($Results, self::search_array($subarray, $Key, $Value));
  467. }
  468. }
  469. return $Results;
  470. }
  471. /**
  472. * Search for $Needle in the string $Haystack which is a list of values separated by $Separator.
  473. * @param string $Haystack
  474. * @param string $Needle
  475. * @param string $Separator
  476. * @param boolean $Strict
  477. * @return boolean
  478. */
  479. public static function search_joined_string($Haystack, $Needle, $Separator = '|', $Strict = true)
  480. {
  481. return (array_search($Needle, explode($Separator, $Haystack), $Strict) !== false);
  482. }
  483. /**
  484. * Check for a ":" in the beginning of a torrent meta data string
  485. * to see if it's stored in the old base64-encoded format
  486. *
  487. * @param string $Torrent the torrent data
  488. * @return true if the torrent is stored in binary format
  489. */
  490. public static function is_new_torrent(&$Data)
  491. {
  492. return strpos(substr($Data, 0, 10), ':') !== false;
  493. }
  494. public static function display_recommend($ID, $Type, $Hide = true)
  495. {
  496. if ($Hide) {
  497. $Hide = ' style="display: none;"';
  498. } ?>
  499. <div id="recommendation_div" data-id="<?=$ID?>"
  500. data-type="<?=$Type?>" <?=$Hide?> class="center">
  501. <div style="display: inline-block;">
  502. <strong>Recommend to:</strong>
  503. <select id="friend" name="friend">
  504. <option value="0" selected="selected">Choose friend</option>
  505. </select>
  506. <input type="text" id="recommendation_note" placeholder="Add note..." />
  507. <button id="send_recommendation" disabled="disabled">Send</button>
  508. </div>
  509. <div class="new" id="recommendation_status"><br /></div>
  510. </div>
  511. <?php
  512. }
  513. public static function is_valid_url($URL)
  514. {
  515. return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $URL);
  516. }
  517. }