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.

users.class.php 32KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. <?php
  2. #declare(strict_types=1);
  3. class Users
  4. {
  5. # Needed for OPS compatibility?
  6. # Re: JSON API keys implementation
  7. public function __construct()
  8. {
  9. return Users::user_info($UserID);
  10. }
  11. /**
  12. * Get $Classes (list of classes keyed by ID) and $ClassLevels
  13. * (list of classes keyed by level)
  14. * @return array ($Classes, $ClassLevels)
  15. */
  16. public static function get_classes()
  17. {
  18. global $Debug;
  19. // Get permissions
  20. list($Classes, $ClassLevels) = G::$Cache->get_value('classes');
  21. if (!$Classes || !$ClassLevels) {
  22. $QueryID = G::$DB->get_query_id();
  23. G::$DB->query('
  24. SELECT `ID`, `Name`, `Abbreviation`, `Level`, `Secondary`
  25. FROM `permissions`
  26. ORDER BY `Level`
  27. ');
  28. $Classes = G::$DB->to_array('ID');
  29. $ClassLevels = G::$DB->to_array('Level');
  30. G::$DB->set_query_id($QueryID);
  31. G::$Cache->cache_value('classes', [$Classes, $ClassLevels], 0);
  32. }
  33. $Debug->set_flag('Loaded permissions');
  34. return [$Classes, $ClassLevels];
  35. }
  36. /**
  37. * Get user info, is used for the current user and usernames all over the site.
  38. *
  39. * @param $UserID int The UserID to get info for
  40. * @return array with the following keys:
  41. * int ID
  42. * string Username
  43. * int PermissionID
  44. * array Paranoia - $Paranoia array sent to paranoia.class
  45. * boolean Artist
  46. * boolean Donor
  47. * string Warned - When their warning expires in international time format
  48. * string Avatar - URL
  49. * boolean Enabled
  50. * string Title
  51. * string CatchupTime - When they last caught up on forums
  52. * boolean Visible - If false, they don't show up on peer lists
  53. * array ExtraClasses - Secondary classes.
  54. * int EffectiveClass - the highest level of their main and secondary classes
  55. * array Badges - list of all the user's badges of the form BadgeID => Displayed
  56. */
  57. public static function user_info($UserID)
  58. {
  59. global $Classes;
  60. $UserInfo = G::$Cache->get_value("user_info_".$UserID);
  61. // the !isset($UserInfo['Paranoia']) can be removed after a transition period
  62. if (empty($UserInfo) || empty($UserInfo['ID']) || empty($UserInfo['Class'])) {
  63. $OldQueryID = G::$DB->get_query_id();
  64. G::$DB->query("
  65. SELECT
  66. m.`ID`,
  67. m.`Username`,
  68. m.`PermissionID`,
  69. m.`Paranoia`,
  70. i.`Artist`,
  71. i.`Donor`,
  72. i.`Warned`,
  73. i.`Avatar`,
  74. m.`Enabled`,
  75. m.`Title`,
  76. i.`CatchupTime`,
  77. m.`Visible`,
  78. la.`Type` AS LockedAccount,
  79. GROUP_CONCAT(ul.`PermissionID` SEPARATOR ',') AS Levels
  80. FROM
  81. `users_main` AS m
  82. INNER JOIN `users_info` AS i
  83. ON
  84. i.`UserID` = m.`ID`
  85. LEFT JOIN `locked_accounts` AS la
  86. ON
  87. la.`UserID` = m.`ID`
  88. LEFT JOIN `users_levels` AS ul
  89. ON
  90. ul.`UserID` = m.`ID`
  91. WHERE
  92. m.`ID` = '$UserID'
  93. GROUP BY
  94. m.`ID`
  95. ");
  96. if (!G::$DB->has_results()) { // Deleted user, maybe?
  97. $UserInfo = [
  98. 'ID' => $UserID,
  99. 'Username' => '',
  100. 'PermissionID' => 0,
  101. 'Paranoia' => [],
  102. 'Artist' => false,
  103. 'Donor' => false,
  104. 'Warned' => null,
  105. 'Avatar' => '',
  106. 'Enabled' => 0,
  107. 'Title' => '',
  108. 'CatchupTime' => 0,
  109. 'Visible' => '1',
  110. 'Levels' => '',
  111. 'Class' => 0
  112. ];
  113. } else {
  114. $UserInfo = G::$DB->next_record(MYSQLI_ASSOC, ['Paranoia', 'Title']);
  115. $UserInfo['CatchupTime'] = strtotime($UserInfo['CatchupTime']);
  116. if (!is_array($UserInfo['Paranoia'])) {
  117. $UserInfo['Paranoia'] = json_decode($UserInfo['Paranoia'], true);
  118. }
  119. if (!$UserInfo['Paranoia']) {
  120. $UserInfo['Paranoia'] = [];
  121. }
  122. $UserInfo['Class'] = $Classes[$UserInfo['PermissionID']]['Level'];
  123. # Badges
  124. G::$DB->query("
  125. SELECT
  126. `BadgeID`,
  127. `Displayed`
  128. FROM
  129. `users_badges`
  130. WHERE
  131. `UserID` = $UserID
  132. ");
  133. $Badges = [];
  134. if (G::$DB->has_results()) {
  135. while (list($BadgeID, $Displayed) = G::$DB->next_record()) {
  136. $Badges[$BadgeID] = $Displayed;
  137. }
  138. }
  139. $UserInfo['Badges'] = $Badges;
  140. }
  141. # Locked?
  142. if (isset($UserInfo['LockedAccount']) && $UserInfo['LockedAccount'] === '') {
  143. unset($UserInfo['LockedAccount']);
  144. }
  145. # Classes and levels
  146. if (!empty($UserInfo['Levels'])) {
  147. $UserInfo['ExtraClasses'] = array_fill_keys(explode(',', $UserInfo['Levels']), 1);
  148. } else {
  149. $UserInfo['ExtraClasses'] = [];
  150. }
  151. unset($UserInfo['Levels']);
  152. $EffectiveClass = $UserInfo['Class'];
  153. foreach ($UserInfo['ExtraClasses'] as $Class => $Val) {
  154. $EffectiveClass = max($EffectiveClass, $Classes[$Class]['Level']);
  155. }
  156. $UserInfo['EffectiveClass'] = $EffectiveClass;
  157. G::$Cache->cache_value("user_info_$UserID", $UserInfo, 2592000);
  158. G::$DB->set_query_id($OldQueryID);
  159. }
  160. # Warned?
  161. if (strtotime($UserInfo['Warned']) < time()) {
  162. $UserInfo['Warned'] = null;
  163. G::$Cache->cache_value("user_info_$UserID", $UserInfo, 2592000);
  164. }
  165. return $UserInfo;
  166. }
  167. /**
  168. * Gets the heavy user info
  169. * Only used for current user
  170. *
  171. * @param $UserID The userid to get the information for
  172. * @return fetched heavy info.
  173. * Just read the goddamn code, I don't have time to comment this shit.
  174. */
  175. public static function user_heavy_info($UserID)
  176. {
  177. $HeavyInfo = G::$Cache->get_value("user_info_heavy_$UserID");
  178. if (empty($HeavyInfo)) {
  179. $QueryID = G::$DB->get_query_id();
  180. G::$DB->query("
  181. SELECT
  182. m.`Invites`,
  183. m.`torrent_pass`,
  184. m.`IP`,
  185. m.`CustomPermissions`,
  186. m.`can_leech` AS CanLeech,
  187. i.`AuthKey`,
  188. i.`RatioWatchEnds`,
  189. i.`RatioWatchDownload`,
  190. i.`StyleID`,
  191. i.`StyleURL`,
  192. i.`DisableInvites`,
  193. i.`DisablePosting`,
  194. i.`DisableUpload`,
  195. i.`DisableWiki`,
  196. i.`DisableAvatar`,
  197. i.`DisablePM`,
  198. i.`DisablePoints`,
  199. i.`DisablePromotion`,
  200. i.`DisableRequests`,
  201. i.`DisableForums`,
  202. i.`DisableTagging`,
  203. i.`SiteOptions`,
  204. i.`LastReadNews`,
  205. i.`LastReadBlog`,
  206. i.`RestrictedForums`,
  207. i.`PermittedForums`,
  208. m.`FLTokens`,
  209. m.`BonusPoints`,
  210. m.`HnR`,
  211. m.`PermissionID`
  212. FROM
  213. `users_main` AS m
  214. INNER JOIN `users_info` AS i
  215. ON
  216. i.`UserID` = m.`ID`
  217. WHERE
  218. m.`ID` = '$UserID'
  219. ");
  220. $HeavyInfo = G::$DB->next_record(MYSQLI_ASSOC, ['CustomPermissions', 'SiteOptions']);
  221. $HeavyInfo['CustomPermissions'] = [];
  222. if (!empty($HeavyInfo['CustomPermissions'])) {
  223. $HeavyInfo['CustomPermissions'] = json_decode($HeavyInfo['CustomPermissions'], true);
  224. }
  225. # Allowed and denied forums
  226. $RestrictedForums = [];
  227. if (!empty($HeavyInfo['RestrictedForums'])) {
  228. $RestrictedForums = array_map('trim', explode(',', $HeavyInfo['RestrictedForums']));
  229. }
  230. unset($HeavyInfo['RestrictedForums']);
  231. $PermittedForums = [];
  232. if (!empty($HeavyInfo['PermittedForums'])) {
  233. $PermittedForums = array_map('trim', explode(',', $HeavyInfo['PermittedForums']));
  234. }
  235. unset($HeavyInfo['PermittedForums']);
  236. G::$DB->query("
  237. SELECT `PermissionID`
  238. FROM `users_levels`
  239. WHERE `UserID` = $UserID
  240. ");
  241. $PermIDs = G::$DB->collect('PermissionID');
  242. foreach ($PermIDs as $PermID) {
  243. $Perms = Permissions::get_permissions($PermID);
  244. if (!empty($Perms['PermittedForums'])) {
  245. $PermittedForums = array_merge($PermittedForums, array_map('trim', explode(',', $Perms['PermittedForums'])));
  246. }
  247. }
  248. $Perms = Permissions::get_permissions($HeavyInfo['PermissionID']);
  249. unset($HeavyInfo['PermissionID']);
  250. if (!empty($Perms['PermittedForums'])) {
  251. $PermittedForums = array_merge($PermittedForums, array_map('trim', explode(',', $Perms['PermittedForums'])));
  252. }
  253. $HeavyInfo['CustomForums'] = null;
  254. if (!empty($PermittedForums) || !empty($RestrictedForums)) {
  255. $HeavyInfo['CustomForums'] = [];
  256. foreach ($RestrictedForums as $ForumID) {
  257. $HeavyInfo['CustomForums'][$ForumID] = 0;
  258. }
  259. foreach ($PermittedForums as $ForumID) {
  260. $HeavyInfo['CustomForums'][$ForumID] = 1;
  261. }
  262. }
  263. if (isset($HeavyInfo['CustomForums'][''])) {
  264. unset($HeavyInfo['CustomForums']['']);
  265. }
  266. $HeavyInfo['SiteOptions'] = json_decode($HeavyInfo['SiteOptions'], true);
  267. if (!empty($HeavyInfo['SiteOptions'])) {
  268. $HeavyInfo = array_merge($HeavyInfo, $HeavyInfo['SiteOptions']);
  269. }
  270. unset($HeavyInfo['SiteOptions']);
  271. G::$DB->set_query_id($QueryID);
  272. G::$Cache->cache_value("user_info_heavy_$UserID", $HeavyInfo, 0);
  273. }
  274. return $HeavyInfo;
  275. }
  276. /**
  277. * Updates the site options in the database
  278. *
  279. * @param int $UserID the UserID to set the options for
  280. * @param array $NewOptions the new options to set
  281. * @return false if $NewOptions is empty, true otherwise
  282. */
  283. public static function update_site_options($UserID, $NewOptions)
  284. {
  285. if (!is_number($UserID)) {
  286. error(0);
  287. }
  288. if (empty($NewOptions)) {
  289. return false;
  290. }
  291. $QueryID = G::$DB->get_query_id();
  292. // Get SiteOptions
  293. G::$DB->query("
  294. SELECT
  295. `SiteOptions`
  296. FROM
  297. `users_info`
  298. WHERE
  299. `UserID` = $UserID
  300. ");
  301. list($SiteOptions) = G::$DB->next_record(MYSQLI_NUM, false);
  302. $SiteOptions = json_decode($SiteOptions, true);
  303. // Get HeavyInfo
  304. $HeavyInfo = Users::user_heavy_info($UserID);
  305. // Insert new/replace old options
  306. $SiteOptions = array_merge($SiteOptions, $NewOptions);
  307. $HeavyInfo = array_merge($HeavyInfo, $NewOptions);
  308. // Update DB
  309. G::$DB->query("
  310. UPDATE users_info
  311. SET SiteOptions = '".db_string(json_encode($SiteOptions, true))."'
  312. WHERE UserID = $UserID");
  313. G::$DB->set_query_id($QueryID);
  314. // Update cache
  315. G::$Cache->cache_value("user_info_heavy_$UserID", $HeavyInfo, 0);
  316. // Update G::$LoggedUser if the options are changed for the current
  317. if (G::$LoggedUser['ID'] == $UserID) {
  318. G::$LoggedUser = array_merge(G::$LoggedUser, $NewOptions);
  319. G::$LoggedUser['ID'] = $UserID; // We don't want to allow userid switching
  320. }
  321. return true;
  322. }
  323. /**
  324. * Generate a random string
  325. *
  326. * @param Length
  327. * @return random alphanumeric string
  328. */
  329. public function make_secret($Length = 32)
  330. {
  331. # strrev() to obscure bcrypt format
  332. $Secret = strrev(
  333. password_hash(
  334. random_bytes(256),
  335. PASSWORD_DEFAULT
  336. )
  337. );
  338. return substr(
  339. preg_filter(
  340. '/[^a-z0-9]/i',
  341. '',
  342. $Secret
  343. ),
  344. 1,
  345. $Length
  346. );
  347. /*
  348. $Secret = '';
  349. $Chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  350. for ($i = 0; $i < $Length; $i++) {
  351. $Secret .= $Chars[random_int(0, strlen($Chars)-1)];
  352. }
  353. return str_shuffle($Secret);
  354. */
  355. }
  356. /**
  357. * Verify a password against a password hash
  358. *
  359. * @param $Password password
  360. * @param $Hash password hash
  361. * @return true on correct password
  362. */
  363. public static function check_password($Password, $Hash)
  364. {
  365. if (!$Password || !$Hash) {
  366. return false;
  367. }
  368. return password_verify(
  369. str_replace(
  370. "\0",
  371. "",
  372. hash("sha512", $Password, true)
  373. ),
  374. $Hash
  375. );
  376. }
  377. /**
  378. * Create salted hash for a given string
  379. *
  380. * @param $Str string to hash
  381. * @return salted hash
  382. */
  383. public static function make_sec_hash($Str)
  384. {
  385. return password_hash(
  386. str_replace(
  387. "\0",
  388. "",
  389. hash("sha512", $Str, true)
  390. ),
  391. PASSWORD_DEFAULT
  392. );
  393. }
  394. /**
  395. * Returns a username string for display
  396. *
  397. * @param int $UserID
  398. * @param boolean $Badges whether or not badges (donor, warned, enabled) should be shown
  399. * @param boolean $IsWarned
  400. * @param boolean $IsEnabled
  401. * @param boolean $Class whether or not to show the class
  402. * @param boolean $Title whether or not to show the title
  403. * @param boolean $IsDonorForum for displaying donor forum honorific prefixes and suffixes
  404. * @return HTML formatted username
  405. */
  406. public static function format_username($UserID, $Badges = false, $IsWarned = true, $IsEnabled = true, $Class = false, $Title = false, $IsDonorForum = false)
  407. {
  408. global $Classes;
  409. # Scripts may pass strings
  410. if ((int) $UserID === 0) {
  411. return 'System';
  412. }
  413. $UserInfo = self::user_info($UserID);
  414. if ($UserInfo['Username'] === '') {
  415. return "Unknown [$UserID]";
  416. }
  417. # Here we go
  418. $Str = '';
  419. $Username = $UserInfo['Username'];
  420. $Paranoia = $UserInfo['Paranoia'];
  421. if ($UserInfo['Class'] < $Classes[MOD]['Level']) {
  422. $OverrideParanoia = check_perms('users_override_paranoia', $UserInfo['Class']);
  423. } else {
  424. // Don't override paranoia for mods who don't want to show their donor heart
  425. $OverrideParanoia = false;
  426. }
  427. # Show donor icon?
  428. $ShowDonorIcon = (!in_array('hide_donor_heart', $Paranoia) || $OverrideParanoia);
  429. if ($IsDonorForum) {
  430. list($Prefix, $Suffix, $HasComma) = Donations::get_titles($UserID);
  431. $Username = "$Prefix $Username" . ($HasComma ? ', ' : ' ') . "$Suffix ";
  432. }
  433. if ($Title) {
  434. $Str .= "<strong><a href='user.php?id=$UserID'>$Username</a></strong>";
  435. } else {
  436. $Str .= "<a href='user.php?id=$UserID'>$Username</a>";
  437. }
  438. if ($Badges) {
  439. $DonorRank = Donations::get_rank($UserID);
  440. if ($DonorRank === 0 && $UserInfo['Donor'] === 1) {
  441. $DonorRank = 1;
  442. }
  443. if ($ShowDonorIcon && $DonorRank > 0) {
  444. $IconLink = 'donate.php';
  445. $IconImage = 'donor.png';
  446. $IconText = 'Donor';
  447. $DonorHeart = $DonorRank;
  448. $SpecialRank = Donations::get_special_rank($UserID);
  449. $EnabledRewards = Donations::get_enabled_rewards($UserID);
  450. $DonorRewards = Donations::get_rewards($UserID);
  451. if ($EnabledRewards['HasDonorIconMouseOverText'] && !empty($DonorRewards['IconMouseOverText'])) {
  452. $IconText = display_str($DonorRewards['IconMouseOverText']);
  453. }
  454. if ($EnabledRewards['HasDonorIconLink'] && !empty($DonorRewards['CustomIconLink'])) {
  455. $IconLink = display_str($DonorRewards['CustomIconLink']);
  456. }
  457. if ($EnabledRewards['HasCustomDonorIcon'] && !empty($DonorRewards['CustomIcon'])) {
  458. $IconImage = ImageTools::process($DonorRewards['CustomIcon']);
  459. } else {
  460. if ($SpecialRank === MAX_SPECIAL_RANK) {
  461. $DonorHeart = 6;
  462. } elseif ($DonorRank === 5) {
  463. $DonorHeart = 4; // Two points between rank 4 and 5
  464. } elseif ($DonorRank >= MAX_RANK) {
  465. $DonorHeart = 5;
  466. }
  467. if ($DonorHeart === 1) {
  468. $IconImage = STATIC_SERVER . 'common/symbols/donor.png';
  469. } else {
  470. $IconImage = STATIC_SERVER . "common/symbols/donor_{$DonorHeart}.png";
  471. }
  472. }
  473. $Str .= "<a target='_blank' href='$IconLink'><img class='donor_icon tooltip' src='$IconImage' alt='$IconText' title='$IconText' /></a>";
  474. }
  475. $Str .= Badges::display_badges(Badges::get_displayed_badges($UserID), true);
  476. }
  477. # Warned?
  478. $Str .= ($IsWarned && $UserInfo['Warned'])
  479. ? '<a href="wiki.php?action=article&amp;name=warnings"'.'><img src="'.STATIC_SERVER.'common/symbols/warned.png" alt="Warned" title="Warned'.(G::$LoggedUser['ID'] === $UserID ? ' - Expires '.date('Y-m-d H:i', strtotime($UserInfo['Warned']))
  480. : '').'" class="tooltip" /></a>'
  481. : '';
  482. $Str .= ($IsEnabled && $UserInfo['Enabled'] === 2)
  483. ? '<a href="rules.php"><img src="'.STATIC_SERVER.'common/symbols/disabled.png" alt="Banned" title="Disabled" class="tooltip" /></a>'
  484. : '';
  485. if ($Class) {
  486. foreach (array_keys($UserInfo['ExtraClasses']) as $ExtraClass) {
  487. $Str .= ' ['.Users::make_class_abbrev_string($ExtraClass).']';
  488. }
  489. if ($Title) {
  490. $Str .= ' <strong>('.Users::make_class_string($UserInfo['PermissionID']).')</strong>';
  491. } else {
  492. $Str .= ' ('.Users::make_class_string($UserInfo['PermissionID']).')';
  493. }
  494. }
  495. if ($Title) {
  496. // Image proxy CTs
  497. if (check_perms('site_proxy_images') && !empty($UserInfo['Title'])) {
  498. $UserInfo['Title'] = preg_replace_callback(
  499. '~src=("?)(http.+?)(["\s>])~',
  500. function ($Matches) {
  501. return 'src=' . $Matches[1] . ImageTools::process($Matches[2]) . $Matches[3];
  502. },
  503. $UserInfo['Title']
  504. );
  505. }
  506. if ($UserInfo['Title']) {
  507. $Str .= ' <span class="user_title">('.$UserInfo['Title'].')</span>';
  508. }
  509. }
  510. return $Str;
  511. }
  512. /**
  513. * Given a class ID, return its name.
  514. *
  515. * @param int $ClassID
  516. * @return string name
  517. */
  518. public static function make_class_string($ClassID)
  519. {
  520. global $Classes;
  521. return $Classes[$ClassID]['Name'];
  522. }
  523. public static function make_class_abbrev_string($ClassID)
  524. {
  525. global $Classes;
  526. return '<abbr title="'.$Classes[$ClassID]['Name'].'">'.$Classes[$ClassID]['Abbreviation'].'</abbr>';
  527. }
  528. /**
  529. * Returns an array with User Bookmark data: group IDs, collage data, torrent data
  530. * @param string|int $UserID
  531. * @return array Group IDs, Bookmark Data, Torrent List
  532. */
  533. public static function get_bookmarks($UserID)
  534. {
  535. $UserID = (int)$UserID;
  536. if (($Data = G::$Cache->get_value("bookmarks_group_ids_$UserID"))) {
  537. list($GroupIDs, $BookmarkData) = $Data;
  538. } else {
  539. $QueryID = G::$DB->get_query_id();
  540. G::$DB->query("
  541. SELECT GroupID, Sort, `Time`
  542. FROM bookmarks_torrents
  543. WHERE UserID = $UserID
  544. ORDER BY Sort, `Time` ASC");
  545. $GroupIDs = G::$DB->collect('GroupID');
  546. $BookmarkData = G::$DB->to_array('GroupID', MYSQLI_ASSOC);
  547. G::$DB->set_query_id($QueryID);
  548. G::$Cache->cache_value("bookmarks_group_ids_$UserID", [$GroupIDs, $BookmarkData], 3600);
  549. }
  550. $TorrentList = Torrents::get_groups($GroupIDs);
  551. return [$GroupIDs, $BookmarkData, $TorrentList];
  552. }
  553. /**
  554. * Generate HTML for a user's avatar or just return the avatar URL
  555. * @param unknown $Avatar
  556. * @param unknown $UserID
  557. * @param unknown $Username
  558. * @param unknown $Setting
  559. * @param number $Size
  560. * @param string $ReturnHTML
  561. * @return string
  562. */
  563. public static function show_avatar($Avatar, $UserID, $Username, $Setting, $Size = 120, $ReturnHTML = true)
  564. {
  565. $Avatar = ImageTools::process($Avatar, 'avatar');
  566. $Style = 'style="max-height: 300px;"';
  567. $AvatarMouseOverText = '';
  568. $SecondAvatar = '';
  569. $Class = 'class="double_avatar"';
  570. $EnabledRewards = Donations::get_enabled_rewards($UserID);
  571. if ($EnabledRewards['HasAvatarMouseOverText']) {
  572. $Rewards = Donations::get_rewards($UserID);
  573. $AvatarMouseOverText = $Rewards['AvatarMouseOverText'];
  574. }
  575. if (!empty($AvatarMouseOverText)) {
  576. $AvatarMouseOverText = "title=\"$AvatarMouseOverText\" alt=\"$AvatarMouseOverText\"";
  577. } else {
  578. $AvatarMouseOverText = "alt=\"$Username's avatar\"";
  579. }
  580. if ($EnabledRewards['HasSecondAvatar'] && !empty($Rewards['SecondAvatar'])) {
  581. $SecondAvatar = ' data-gazelle-second-avatar="' . ImageTools::process($Rewards['SecondAvatar'], 'avatar') . '"';
  582. }
  583. // Case 1 is avatars disabled
  584. switch ($Setting) {
  585. case 0:
  586. if (!empty($Avatar)) {
  587. $ToReturn = ($ReturnHTML ? "<a href=\"user.php?id=$UserID\"><img src=\"$Avatar\" ".($Size?"width=\"$Size\" ":"")."$Style $AvatarMouseOverText$SecondAvatar $Class /></a>" : $Avatar);
  588. } else {
  589. $URL = STATIC_SERVER.'common/avatars/default.png';
  590. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar />" : $URL);
  591. }
  592. break;
  593. case 2:
  594. $ShowAvatar = true;
  595. // no break
  596. case 3:
  597. switch (G::$LoggedUser['Identicons']) {
  598. case 0:
  599. $Type = 'identicon';
  600. break;
  601. case 1:
  602. $Type = 'monsterid';
  603. break;
  604. case 2:
  605. $Type = 'wavatar';
  606. break;
  607. case 3:
  608. $Type = 'retro';
  609. break;
  610. case 4:
  611. $Type = '1';
  612. $Robot = true;
  613. break;
  614. case 5:
  615. $Type = '2';
  616. $Robot = true;
  617. break;
  618. case 6:
  619. $Type = '3';
  620. $Robot = true;
  621. break;
  622. default:
  623. $Type = 'identicon';
  624. }
  625. $Rating = 'pg';
  626. if (!isset($Robot) || !$Robot) {
  627. $URL = 'https://secure.gravatar.com/avatar/'.md5(strtolower(trim($Username)))."?s=$Size&amp;d=$Type&amp;r=$Rating";
  628. } else {
  629. $URL = 'https://robohash.org/'.md5($Username)."?set=set$Type&amp;size={$Size}x$Size";
  630. }
  631. if ($ShowAvatar === true && !empty($Avatar)) {
  632. $ToReturn = ($ReturnHTML ? "<img src=\"$Avatar\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar $Class />" : $Avatar);
  633. } else {
  634. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText $Class />" : $URL);
  635. }
  636. break;
  637. default:
  638. $URL = STATIC_SERVER.'common/avatars/default.png';
  639. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar $Class/>" : $URL);
  640. }
  641. return $ToReturn;
  642. }
  643. public static function has_avatars_enabled()
  644. {
  645. global $HeavyInfo;
  646. return isset($HeavyInfo['DisableAvatars']) && ($HeavyInfo['DisableAvatars'] !== 1);
  647. }
  648. /**
  649. * Checks whether user has autocomplete enabled
  650. *
  651. * 0 - Enabled everywhere (default), 1 - Disabled, 2 - Searches only
  652. *
  653. * @param string $Type the type of the input.
  654. * @param boolean $Output echo out HTML
  655. * @return boolean
  656. */
  657. public static function has_autocomplete_enabled($Type, $Output = true)
  658. {
  659. $Enabled = false;
  660. if (empty(G::$LoggedUser['AutoComplete'])) {
  661. $Enabled = true;
  662. } elseif (G::$LoggedUser['AutoComplete'] !== 1) {
  663. switch ($Type) {
  664. case 'search':
  665. if (G::$LoggedUser['AutoComplete'] === 2) {
  666. $Enabled = true;
  667. }
  668. break;
  669. case 'other':
  670. if (G::$LoggedUser['AutoComplete'] !== 2) {
  671. $Enabled = true;
  672. }
  673. break;
  674. }
  675. }
  676. if ($Enabled && $Output) {
  677. return ' data-gazelle-autocomplete="true"';
  678. }
  679. if (!$Output) {
  680. // Don't return a boolean if you're echoing HTML
  681. return $Enabled;
  682. }
  683. }
  684. /*
  685. * Initiate a password reset
  686. *
  687. * @param int $UserID The user ID
  688. * @param string $Username The username
  689. * @param string $Email The email address
  690. */
  691. public static function reset_password($UserID, $Username, $Email)
  692. {
  693. $ResetKey = Users::make_secret();
  694. G::$DB->query("
  695. UPDATE users_info
  696. SET
  697. ResetKey = '" . db_string($ResetKey) . "',
  698. ResetExpires = '" . time_plus(60 * 60) . "'
  699. WHERE UserID = '$UserID'");
  700. require_once SERVER_ROOT . '/classes/templates.class.php';
  701. $TPL = new TEMPLATE;
  702. $TPL->open(SERVER_ROOT . '/templates/password_reset.tpl'); // Password reset template
  703. $TPL->set('Username', $Username);
  704. $TPL->set('ResetKey', $ResetKey);
  705. $TPL->set('IP', $_SERVER['REMOTE_ADDR']);
  706. $TPL->set('SITE_NAME', $ENV->SITE_NAME);
  707. $TPL->set('SITE_DOMAIN', SITE_DOMAIN);
  708. Misc::send_email($Email, 'Password reset information for ' . $ENV->SITE_NAME, $TPL->get(), 'noreply');
  709. }
  710. /*
  711. * Authorize a new location
  712. *
  713. * @param int $UserID The user ID
  714. * @param string $Username The username
  715. * @param int $ASN The ASN
  716. * @param string $Email The email address
  717. */
  718. public static function auth_location($UserID, $Username, $ASN, $Email)
  719. {
  720. $ENV = ENV::go();
  721. $AuthKey = Users::make_secret();
  722. G::$Cache->cache_value('new_location_'.$AuthKey, ['UserID'=>$UserID, 'ASN'=>$ASN], 3600*2);
  723. require_once SERVER_ROOT . '/classes/templates.class.php';
  724. $TPL = new TEMPLATE;
  725. $TPL->open(SERVER_ROOT . '/templates/new_location.tpl');
  726. $TPL->set('Username', $Username);
  727. $TPL->set('AuthKey', $AuthKey);
  728. $TPL->set('IP', $_SERVER['REMOTE_ADDR']);
  729. $TPL->set('SITE_NAME', $ENV->SITE_NAME);
  730. $TPL->set('SITE_DOMAIN', SITE_DOMAIN);
  731. Misc::send_email($Email, 'Login from new location for '.$ENV->SITE_NAME, $TPL->get(), 'noreply');
  732. }
  733. /*
  734. * @return array of strings that can be added to next source flag ( [current, old] )
  735. */
  736. public static function get_upload_sources()
  737. {
  738. $ENV = ENV::go();
  739. if (!($SourceKey = G::$Cache->get_value('source_key_new'))) {
  740. G::$Cache->cache_value('source_key_new', $SourceKey = [Users::make_secret(), time()]);
  741. }
  742. $SourceKeyOld = G::$Cache->get_value('source_key_old');
  743. if ($SourceKey[1]-time() > 3600) {
  744. G::$Cache->cache_value('source_key_old', $SourceKeyOld = $SourceKey);
  745. G::$Cache->cache_value('source_key_new', $SourceKey = [Users::make_secret(), time()]);
  746. }
  747. G::$DB->query(
  748. "
  749. SELECT
  750. COUNT(`ID`)
  751. FROM
  752. `torrents`
  753. WHERE
  754. `UserID` = ".G::$LoggedUser['ID']
  755. );
  756. list($Uploads) = G::$DB->next_record();
  757. $Source[0] = $ENV->SITE_NAME.'-'.substr(hash('sha256', $SourceKey[0].G::$LoggedUser['ID'].$Uploads), 0, 10);
  758. $Source[1] = $SourceKeyOld ? $ENV->SITE_NAME.'-'.substr(hash('sha256', $SourceKeyOld[0].G::$LoggedUser['ID'].$Uploads), 0, 10) : $Source[0];
  759. return $Source;
  760. }
  761. /**
  762. * createApiToken
  763. * @see https://github.com/OPSnet/Gazelle/commit/7c208fc4c396a16c77289ef886d0015db65f2af1
  764. */
  765. public function createApiToken(int $id, string $name, string $key): string
  766. {
  767. $suffix = sprintf('%014d', $id);
  768. while (true) {
  769. // prevent collisions with an existing token name
  770. $token = base64UrlEncode(Crypto::encrypt(random_bytes(32) . $suffix, $key));
  771. $hash = password_hash($token, PASSWORD_DEFAULT);
  772. if (!Users::hasApiToken($id, $token)) {
  773. break;
  774. }
  775. }
  776. G::$DB->prepare_query("
  777. INSERT INTO `api_user_tokens`
  778. (`UserID`, `Name`, `Token`)
  779. VALUES
  780. ('$id', '$name', '$hash')
  781. ");
  782. G::$DB->exec_prepared_query();
  783. return $token;
  784. }
  785. /**
  786. * hasTokenByName
  787. */
  788. public function hasTokenByName(int $id, string $name)
  789. {
  790. return G::$DB->scalar("
  791. SELECT
  792. 1
  793. FROM
  794. `api_user_tokens`
  795. WHERE
  796. `UserID` = '$id'
  797. AND `Name` = '$name'
  798. ") === 1;
  799. }
  800. /**
  801. * hasApiToken
  802. */
  803. public function hasApiToken(int $id, string $token): bool
  804. {
  805. /*
  806. return G::$DB->scalar("
  807. SELECT 1 FROM `api_user_tokens` WHERE `UserID` = '$id' AND `Token` = '$token'
  808. ") === 1;
  809. */
  810. G::$DB->prepare_query("
  811. SELECT
  812. `ID`,
  813. `Token`
  814. FROM
  815. `api_user_tokens`
  816. WHERE
  817. `UserID` = '$id'
  818. AND `Revoked` = '0'
  819. ");
  820. # AND `Token` = '$hash'
  821. G::$DB->exec_prepared_query();
  822. [$ID, $Hash] = G::$DB->next_record();
  823. if (password_verify($token, $Hash)) {
  824. return true;
  825. }
  826. return false;
  827. }
  828. /**
  829. * revokeApiTokenById
  830. */
  831. public function revokeApiTokenById(int $id, int $tokenId): int
  832. {
  833. G::$DB->prepare_query("
  834. UPDATE
  835. `api_user_tokens`
  836. SET
  837. `Revoked` = '1'
  838. WHERE
  839. `UserID` = '$id'
  840. AND `ID` = '$tokenId'
  841. ");
  842. G::$DB->exec_prepared_query();
  843. return G::$DB->affected_rows();
  844. }
  845. /**
  846. * enabledState
  847. *
  848. * Used in classes/script_start.php
  849. * @see https://github.com/OPSnet/Gazelle/blob/master/app/User.php
  850. */
  851. protected function enabledState(int $id): int
  852. {
  853. #if ($this->forceCacheFlush || ($enabled = G::$Cache->get_value("enabled_$id")) === false) {
  854. if ($enabled = G::$Cache->get_value("enabled_$id") === false) {
  855. G::$DB->prepare_query("
  856. SELECT
  857. `Enabled`
  858. FROM
  859. `users_main`
  860. WHERE `ID` = '$id'
  861. ");
  862. G::$DB->exec_prepared_query();
  863. [$enabled] = G::$DB->next_record(MYSQLI_NUM);
  864. G::$Cache->cache_value('enabled_' . $id, (int) $enabled, 86400 * 3);
  865. }
  866. return $enabled;
  867. }
  868. /**
  869. * isUnconfirmed
  870. */
  871. public function isUnconfirmed(int $id)
  872. {
  873. return Users::enabledState($id) === 0;
  874. }
  875. /**
  876. * isEnabled
  877. */
  878. public function isEnabled(int $id)
  879. {
  880. return Users::enabledState($id) === 1;
  881. }
  882. /**
  883. * isDisabled
  884. */
  885. public function isDisabled(int $id)
  886. {
  887. return Users::enabledState($id) === 2;
  888. }
  889. }