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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  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. * @return HTML formatted username
  404. */
  405. public static function format_username($UserID, $Badges = false, $IsWarned = true, $IsEnabled = true, $Class = false, $Title = false)
  406. {
  407. global $Classes;
  408. # Scripts may pass strings
  409. if ((int) $UserID === 0) {
  410. return 'System';
  411. }
  412. $UserInfo = self::user_info($UserID);
  413. if ($UserInfo['Username'] === '') {
  414. return "Unknown [$UserID]";
  415. }
  416. # Here we go
  417. $Str = '';
  418. $Username = $UserInfo['Username'];
  419. $Paranoia = $UserInfo['Paranoia'];
  420. if ($UserInfo['Class'] < $Classes[MOD]['Level']) {
  421. $OverrideParanoia = check_perms('users_override_paranoia', $UserInfo['Class']);
  422. } else {
  423. // Don't override paranoia for mods who don't want to show their donor heart
  424. $OverrideParanoia = false;
  425. }
  426. # Show donor icon?
  427. $ShowDonorIcon = (!in_array('hide_donor_heart', $Paranoia) || $OverrideParanoia);
  428. if ($Title) {
  429. $Str .= "<strong><a href='user.php?id=$UserID'>$Username</a></strong>";
  430. } else {
  431. $Str .= "<a href='user.php?id=$UserID'>$Username</a>";
  432. }
  433. if ($Badges) {
  434. $DonorRank = Donations::get_rank($UserID);
  435. if ($DonorRank === 0 && $UserInfo['Donor'] === 1) {
  436. $DonorRank = 1;
  437. }
  438. if ($ShowDonorIcon && $DonorRank > 0) {
  439. $IconLink = 'donate.php';
  440. $IconImage = 'donor.png';
  441. $IconText = 'Donor';
  442. $DonorHeart = $DonorRank;
  443. $SpecialRank = Donations::get_special_rank($UserID);
  444. $EnabledRewards = Donations::get_enabled_rewards($UserID);
  445. $DonorRewards = Donations::get_rewards($UserID);
  446. if ($EnabledRewards['HasDonorIconMouseOverText'] && !empty($DonorRewards['IconMouseOverText'])) {
  447. $IconText = display_str($DonorRewards['IconMouseOverText']);
  448. }
  449. if ($EnabledRewards['HasDonorIconLink'] && !empty($DonorRewards['CustomIconLink'])) {
  450. $IconLink = display_str($DonorRewards['CustomIconLink']);
  451. }
  452. if ($EnabledRewards['HasCustomDonorIcon'] && !empty($DonorRewards['CustomIcon'])) {
  453. $IconImage = ImageTools::process($DonorRewards['CustomIcon']);
  454. } else {
  455. if ($SpecialRank === MAX_SPECIAL_RANK) {
  456. $DonorHeart = 6;
  457. } elseif ($DonorRank === 5) {
  458. $DonorHeart = 4; // Two points between rank 4 and 5
  459. } elseif ($DonorRank >= MAX_RANK) {
  460. $DonorHeart = 5;
  461. }
  462. if ($DonorHeart === 1) {
  463. $IconImage = STATIC_SERVER . 'common/symbols/donor.png';
  464. } else {
  465. $IconImage = STATIC_SERVER . "common/symbols/donor_{$DonorHeart}.png";
  466. }
  467. }
  468. $Str .= "<a target='_blank' href='$IconLink'><img class='donor_icon tooltip' src='$IconImage' alt='$IconText' title='$IconText' /></a>";
  469. }
  470. $Str .= Badges::display_badges(Badges::get_displayed_badges($UserID), true);
  471. }
  472. # Warned?
  473. $Str .= ($IsWarned && $UserInfo['Warned'])
  474. ? '<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']))
  475. : '').'" class="tooltip" /></a>'
  476. : '';
  477. $Str .= ($IsEnabled && $UserInfo['Enabled'] === 2)
  478. ? '<a href="rules.php"><img src="'.STATIC_SERVER.'common/symbols/disabled.png" alt="Banned" title="Disabled" class="tooltip" /></a>'
  479. : '';
  480. if ($Class) {
  481. foreach (array_keys($UserInfo['ExtraClasses']) as $ExtraClass) {
  482. $Str .= ' ['.Users::make_class_abbrev_string($ExtraClass).']';
  483. }
  484. if ($Title) {
  485. $Str .= ' <strong>('.Users::make_class_string($UserInfo['PermissionID']).')</strong>';
  486. } else {
  487. $Str .= ' ('.Users::make_class_string($UserInfo['PermissionID']).')';
  488. }
  489. }
  490. if ($Title) {
  491. // Image proxy CTs
  492. if (check_perms('site_proxy_images') && !empty($UserInfo['Title'])) {
  493. $UserInfo['Title'] = preg_replace_callback(
  494. '~src=("?)(http.+?)(["\s>])~',
  495. function ($Matches) {
  496. return 'src=' . $Matches[1] . ImageTools::process($Matches[2]) . $Matches[3];
  497. },
  498. $UserInfo['Title']
  499. );
  500. }
  501. if ($UserInfo['Title']) {
  502. $Str .= ' <span class="user_title">('.$UserInfo['Title'].')</span>';
  503. }
  504. }
  505. return $Str;
  506. }
  507. /**
  508. * Given a class ID, return its name.
  509. *
  510. * @param int $ClassID
  511. * @return string name
  512. */
  513. public static function make_class_string($ClassID)
  514. {
  515. global $Classes;
  516. return $Classes[$ClassID]['Name'];
  517. }
  518. public static function make_class_abbrev_string($ClassID)
  519. {
  520. global $Classes;
  521. return '<abbr title="'.$Classes[$ClassID]['Name'].'">'.$Classes[$ClassID]['Abbreviation'].'</abbr>';
  522. }
  523. /**
  524. * Returns an array with User Bookmark data: group IDs, collage data, torrent data
  525. * @param string|int $UserID
  526. * @return array Group IDs, Bookmark Data, Torrent List
  527. */
  528. public static function get_bookmarks($UserID)
  529. {
  530. $UserID = (int)$UserID;
  531. if (($Data = G::$Cache->get_value("bookmarks_group_ids_$UserID"))) {
  532. list($GroupIDs, $BookmarkData) = $Data;
  533. } else {
  534. $QueryID = G::$DB->get_query_id();
  535. G::$DB->query("
  536. SELECT GroupID, Sort, `Time`
  537. FROM bookmarks_torrents
  538. WHERE UserID = $UserID
  539. ORDER BY Sort, `Time` ASC");
  540. $GroupIDs = G::$DB->collect('GroupID');
  541. $BookmarkData = G::$DB->to_array('GroupID', MYSQLI_ASSOC);
  542. G::$DB->set_query_id($QueryID);
  543. G::$Cache->cache_value("bookmarks_group_ids_$UserID", [$GroupIDs, $BookmarkData], 3600);
  544. }
  545. $TorrentList = Torrents::get_groups($GroupIDs);
  546. return [$GroupIDs, $BookmarkData, $TorrentList];
  547. }
  548. /**
  549. * Generate HTML for a user's avatar or just return the avatar URL
  550. * @param unknown $Avatar
  551. * @param unknown $UserID
  552. * @param unknown $Username
  553. * @param unknown $Setting
  554. * @param number $Size
  555. * @param string $ReturnHTML
  556. * @return string
  557. */
  558. public static function show_avatar($Avatar, $UserID, $Username, $Setting, $Size = 120, $ReturnHTML = true)
  559. {
  560. $Avatar = ImageTools::process($Avatar, 'avatar');
  561. $Style = 'style="max-height: 300px;"';
  562. $AvatarMouseOverText = '';
  563. $SecondAvatar = '';
  564. $Class = 'class="double_avatar"';
  565. $EnabledRewards = Donations::get_enabled_rewards($UserID);
  566. if ($EnabledRewards['HasAvatarMouseOverText']) {
  567. $Rewards = Donations::get_rewards($UserID);
  568. $AvatarMouseOverText = $Rewards['AvatarMouseOverText'];
  569. }
  570. if (!empty($AvatarMouseOverText)) {
  571. $AvatarMouseOverText = "title=\"$AvatarMouseOverText\" alt=\"$AvatarMouseOverText\"";
  572. } else {
  573. $AvatarMouseOverText = "alt=\"$Username's avatar\"";
  574. }
  575. if ($EnabledRewards['HasSecondAvatar'] && !empty($Rewards['SecondAvatar'])) {
  576. $SecondAvatar = ' data-gazelle-second-avatar="' . ImageTools::process($Rewards['SecondAvatar'], 'avatar') . '"';
  577. }
  578. // Case 1 is avatars disabled
  579. switch ($Setting) {
  580. case 0:
  581. if (!empty($Avatar)) {
  582. $ToReturn = ($ReturnHTML ? "<a href=\"user.php?id=$UserID\"><img src=\"$Avatar\" ".($Size?"width=\"$Size\" ":"")."$Style $AvatarMouseOverText$SecondAvatar $Class /></a>" : $Avatar);
  583. } else {
  584. $URL = STATIC_SERVER.'common/avatars/default.png';
  585. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar />" : $URL);
  586. }
  587. break;
  588. case 2:
  589. $ShowAvatar = true;
  590. // no break
  591. case 3:
  592. switch (G::$LoggedUser['Identicons']) {
  593. case 0:
  594. $Type = 'identicon';
  595. break;
  596. case 1:
  597. $Type = 'monsterid';
  598. break;
  599. case 2:
  600. $Type = 'wavatar';
  601. break;
  602. case 3:
  603. $Type = 'retro';
  604. break;
  605. case 4:
  606. $Type = '1';
  607. $Robot = true;
  608. break;
  609. case 5:
  610. $Type = '2';
  611. $Robot = true;
  612. break;
  613. case 6:
  614. $Type = '3';
  615. $Robot = true;
  616. break;
  617. default:
  618. $Type = 'identicon';
  619. }
  620. $Rating = 'pg';
  621. if (!isset($Robot) || !$Robot) {
  622. $URL = 'https://secure.gravatar.com/avatar/'.md5(strtolower(trim($Username)))."?s=$Size&amp;d=$Type&amp;r=$Rating";
  623. } else {
  624. $URL = 'https://robohash.org/'.md5($Username)."?set=set$Type&amp;size={$Size}x$Size";
  625. }
  626. if ($ShowAvatar === true && !empty($Avatar)) {
  627. $ToReturn = ($ReturnHTML ? "<img src=\"$Avatar\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar $Class />" : $Avatar);
  628. } else {
  629. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText $Class />" : $URL);
  630. }
  631. break;
  632. default:
  633. $URL = STATIC_SERVER.'common/avatars/default.png';
  634. $ToReturn = ($ReturnHTML ? "<img src=\"$URL\" width=\"$Size\" $Style $AvatarMouseOverText$SecondAvatar $Class/>" : $URL);
  635. }
  636. return $ToReturn;
  637. }
  638. public static function has_avatars_enabled()
  639. {
  640. global $HeavyInfo;
  641. return isset($HeavyInfo['DisableAvatars']) && ($HeavyInfo['DisableAvatars'] !== 1);
  642. }
  643. /**
  644. * Checks whether user has autocomplete enabled
  645. *
  646. * 0 - Enabled everywhere (default), 1 - Disabled, 2 - Searches only
  647. *
  648. * @param string $Type the type of the input.
  649. * @param boolean $Output echo out HTML
  650. * @return boolean
  651. */
  652. public static function has_autocomplete_enabled($Type, $Output = true)
  653. {
  654. $Enabled = false;
  655. if (empty(G::$LoggedUser['AutoComplete'])) {
  656. $Enabled = true;
  657. } elseif (G::$LoggedUser['AutoComplete'] !== 1) {
  658. switch ($Type) {
  659. case 'search':
  660. if (G::$LoggedUser['AutoComplete'] === 2) {
  661. $Enabled = true;
  662. }
  663. break;
  664. case 'other':
  665. if (G::$LoggedUser['AutoComplete'] !== 2) {
  666. $Enabled = true;
  667. }
  668. break;
  669. }
  670. }
  671. if ($Enabled && $Output) {
  672. return ' data-gazelle-autocomplete="true"';
  673. }
  674. if (!$Output) {
  675. // Don't return a boolean if you're echoing HTML
  676. return $Enabled;
  677. }
  678. }
  679. /*
  680. * Initiate a password reset
  681. *
  682. * @param int $UserID The user ID
  683. * @param string $Username The username
  684. * @param string $Email The email address
  685. */
  686. public static function reset_password($UserID, $Username, $Email)
  687. {
  688. $ResetKey = Users::make_secret();
  689. G::$DB->query("
  690. UPDATE users_info
  691. SET
  692. ResetKey = '" . db_string($ResetKey) . "',
  693. ResetExpires = '" . time_plus(60 * 60) . "'
  694. WHERE UserID = '$UserID'");
  695. require_once SERVER_ROOT . '/classes/templates.class.php';
  696. $TPL = new TEMPLATE;
  697. $TPL->open(SERVER_ROOT . '/templates/password_reset.tpl'); // Password reset template
  698. $TPL->set('Username', $Username);
  699. $TPL->set('ResetKey', $ResetKey);
  700. $TPL->set('IP', $_SERVER['REMOTE_ADDR']);
  701. $TPL->set('SITE_NAME', $ENV->SITE_NAME);
  702. $TPL->set('SITE_DOMAIN', SITE_DOMAIN);
  703. Misc::send_email($Email, 'Password reset information for ' . $ENV->SITE_NAME, $TPL->get(), 'noreply');
  704. }
  705. /*
  706. * Authorize a new location
  707. *
  708. * @param int $UserID The user ID
  709. * @param string $Username The username
  710. * @param int $ASN The ASN
  711. * @param string $Email The email address
  712. */
  713. public static function auth_location($UserID, $Username, $ASN, $Email)
  714. {
  715. $ENV = ENV::go();
  716. $AuthKey = Users::make_secret();
  717. G::$Cache->cache_value('new_location_'.$AuthKey, ['UserID'=>$UserID, 'ASN'=>$ASN], 3600*2);
  718. require_once SERVER_ROOT . '/classes/templates.class.php';
  719. $TPL = new TEMPLATE;
  720. $TPL->open(SERVER_ROOT . '/templates/new_location.tpl');
  721. $TPL->set('Username', $Username);
  722. $TPL->set('AuthKey', $AuthKey);
  723. $TPL->set('IP', $_SERVER['REMOTE_ADDR']);
  724. $TPL->set('SITE_NAME', $ENV->SITE_NAME);
  725. $TPL->set('SITE_DOMAIN', SITE_DOMAIN);
  726. Misc::send_email($Email, 'Login from new location for '.$ENV->SITE_NAME, $TPL->get(), 'noreply');
  727. }
  728. /*
  729. * @return array of strings that can be added to next source flag ( [current, old] )
  730. */
  731. public static function get_upload_sources()
  732. {
  733. $ENV = ENV::go();
  734. if (!($SourceKey = G::$Cache->get_value('source_key_new'))) {
  735. G::$Cache->cache_value('source_key_new', $SourceKey = [Users::make_secret(), time()]);
  736. }
  737. $SourceKeyOld = G::$Cache->get_value('source_key_old');
  738. if ($SourceKey[1]-time() > 3600) {
  739. G::$Cache->cache_value('source_key_old', $SourceKeyOld = $SourceKey);
  740. G::$Cache->cache_value('source_key_new', $SourceKey = [Users::make_secret(), time()]);
  741. }
  742. G::$DB->query(
  743. "
  744. SELECT
  745. COUNT(`ID`)
  746. FROM
  747. `torrents`
  748. WHERE
  749. `UserID` = ".G::$LoggedUser['ID']
  750. );
  751. list($Uploads) = G::$DB->next_record();
  752. $Source[0] = $ENV->SITE_NAME.'-'.substr(hash('sha256', $SourceKey[0].G::$LoggedUser['ID'].$Uploads), 0, 10);
  753. $Source[1] = $SourceKeyOld ? $ENV->SITE_NAME.'-'.substr(hash('sha256', $SourceKeyOld[0].G::$LoggedUser['ID'].$Uploads), 0, 10) : $Source[0];
  754. return $Source;
  755. }
  756. /**
  757. * createApiToken
  758. * @see https://github.com/OPSnet/Gazelle/commit/7c208fc4c396a16c77289ef886d0015db65f2af1
  759. */
  760. public function createApiToken(int $id, string $name, string $key): string
  761. {
  762. $suffix = sprintf('%014d', $id);
  763. while (true) {
  764. // prevent collisions with an existing token name
  765. $token = base64UrlEncode(Crypto::encrypt(random_bytes(32) . $suffix, $key));
  766. $hash = password_hash($token, PASSWORD_DEFAULT);
  767. if (!Users::hasApiToken($id, $token)) {
  768. break;
  769. }
  770. }
  771. G::$DB->prepare_query("
  772. INSERT INTO `api_user_tokens`
  773. (`UserID`, `Name`, `Token`)
  774. VALUES
  775. ('$id', '$name', '$hash')
  776. ");
  777. G::$DB->exec_prepared_query();
  778. return $token;
  779. }
  780. /**
  781. * hasTokenByName
  782. */
  783. public function hasTokenByName(int $id, string $name)
  784. {
  785. return G::$DB->scalar("
  786. SELECT
  787. 1
  788. FROM
  789. `api_user_tokens`
  790. WHERE
  791. `UserID` = '$id'
  792. AND `Name` = '$name'
  793. ") === 1;
  794. }
  795. /**
  796. * hasApiToken
  797. */
  798. public function hasApiToken(int $id, string $token): bool
  799. {
  800. /*
  801. return G::$DB->scalar("
  802. SELECT 1 FROM `api_user_tokens` WHERE `UserID` = '$id' AND `Token` = '$token'
  803. ") === 1;
  804. */
  805. G::$DB->prepare_query("
  806. SELECT
  807. `ID`,
  808. `Token`
  809. FROM
  810. `api_user_tokens`
  811. WHERE
  812. `UserID` = '$id'
  813. AND `Revoked` = '0'
  814. ");
  815. # AND `Token` = '$hash'
  816. G::$DB->exec_prepared_query();
  817. [$ID, $Hash] = G::$DB->next_record();
  818. if (password_verify($token, $Hash)) {
  819. return true;
  820. }
  821. return false;
  822. }
  823. /**
  824. * revokeApiTokenById
  825. */
  826. public function revokeApiTokenById(int $id, int $tokenId): int
  827. {
  828. G::$DB->prepare_query("
  829. UPDATE
  830. `api_user_tokens`
  831. SET
  832. `Revoked` = '1'
  833. WHERE
  834. `UserID` = '$id'
  835. AND `ID` = '$tokenId'
  836. ");
  837. G::$DB->exec_prepared_query();
  838. return G::$DB->affected_rows();
  839. }
  840. /**
  841. * enabledState
  842. *
  843. * Used in classes/script_start.php
  844. * @see https://github.com/OPSnet/Gazelle/blob/master/app/User.php
  845. */
  846. protected function enabledState(int $id): int
  847. {
  848. #if ($this->forceCacheFlush || ($enabled = G::$Cache->get_value("enabled_$id")) === false) {
  849. if ($enabled = G::$Cache->get_value("enabled_$id") === false) {
  850. G::$DB->prepare_query("
  851. SELECT
  852. `Enabled`
  853. FROM
  854. `users_main`
  855. WHERE `ID` = '$id'
  856. ");
  857. G::$DB->exec_prepared_query();
  858. [$enabled] = G::$DB->next_record(MYSQLI_NUM);
  859. G::$Cache->cache_value('enabled_' . $id, (int) $enabled, 86400 * 3);
  860. }
  861. return $enabled;
  862. }
  863. /**
  864. * isUnconfirmed
  865. */
  866. public function isUnconfirmed(int $id)
  867. {
  868. return Users::enabledState($id) === 0;
  869. }
  870. /**
  871. * isEnabled
  872. */
  873. public function isEnabled(int $id)
  874. {
  875. return Users::enabledState($id) === 1;
  876. }
  877. /**
  878. * isDisabled
  879. */
  880. public function isDisabled(int $id)
  881. {
  882. return Users::enabledState($id) === 2;
  883. }
  884. }