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

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