Oppaitime'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 15KB

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