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.

format.class.php 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. <?php
  2. #declare(strict_types=1);
  3. class Format
  4. {
  5. /**
  6. * Torrent Labels
  7. * Map a common display string to a CSS class
  8. * Indexes are lower case
  9. * Note the "tl_" prefix for "torrent label"
  10. *
  11. * There are five basic types:
  12. * - tl_free (leech status)
  13. * - tl_snatched
  14. * - tl_reported
  15. * - tl_approved
  16. * - tl_notice (default)
  17. *
  18. * @var array Strings
  19. */
  20. private static $TorrentLabels = array(
  21. 'default' => 'tl_notice',
  22. 'snatched' => 'tl_snatched',
  23. 'seeding' => 'tl_seeding',
  24. 'leeching' => 'tl_leeching',
  25. 'freeleech' => 'tl_free',
  26. 'neutral leech' => 'tl_free tl_neutral',
  27. 'personal freeleech' => 'tl_free tl_personal',
  28. 'reported' => 'tl_reported',
  29. 'bad tags' => 'tl_reported tl_bad_tags',
  30. 'bad folders' => 'tl_reported tl_bad_folders',
  31. 'bad file names' => 'tl_reported tl_bad_file_names',
  32. 'uncensored' => 'tl_notice'
  33. );
  34. /**
  35. * Shorten a string
  36. *
  37. * @param $Str string to cut
  38. * @param $Length cut at length
  39. * @param $Hard force cut at length instead of at closest word
  40. * @param $ShowDots Show dots at the end
  41. * @return string formatted string
  42. */
  43. public static function cut_string($Str, $Length, $Hard = false, $ShowDots = true)
  44. {
  45. if (mb_strlen($Str, 'UTF-8') > $Length) {
  46. if ($Hard === 0) {
  47. // Not hard, cut at closest word
  48. $CutDesc = mb_substr($Str, 0, $Length, 'UTF-8');
  49. $DescArr = explode(' ', $CutDesc);
  50. if (count($DescArr) > 1) {
  51. array_pop($DescArr);
  52. $CutDesc = implode(' ', $DescArr);
  53. }
  54. if ($ShowDots) {
  55. $CutDesc .= '…';
  56. }
  57. } else {
  58. $CutDesc = mb_substr($Str, 0, $Length, 'UTF-8');
  59. if ($ShowDots) {
  60. $CutDesc .= '…';
  61. }
  62. }
  63. return $CutDesc;
  64. } else {
  65. return $Str;
  66. }
  67. }
  68. /**
  69. * Gets the CSS class corresponding to a ratio
  70. *
  71. * @param $Ratio ratio to get the css class for
  72. * @return string the CSS class corresponding to the ratio range
  73. */
  74. public static function get_ratio_color($Ratio)
  75. {
  76. if ($Ratio < 0.1) {
  77. return 'r00';
  78. }
  79. if ($Ratio < 0.2) {
  80. return 'r01';
  81. }
  82. if ($Ratio < 0.3) {
  83. return 'r02';
  84. }
  85. if ($Ratio < 0.4) {
  86. return 'r03';
  87. }
  88. if ($Ratio < 0.5) {
  89. return 'r04';
  90. }
  91. if ($Ratio < 0.6) {
  92. return 'r05';
  93. }
  94. if ($Ratio < 0.7) {
  95. return 'r06';
  96. }
  97. if ($Ratio < 0.8) {
  98. return 'r07';
  99. }
  100. if ($Ratio < 0.9) {
  101. return 'r08';
  102. }
  103. if ($Ratio < 1) {
  104. return 'r09';
  105. }
  106. if ($Ratio < 2) {
  107. return 'r10';
  108. }
  109. if ($Ratio < 5) {
  110. return 'r20';
  111. }
  112. return 'r50';
  113. }
  114. /**
  115. * Calculates and formats a ratio.
  116. *
  117. * @param int $Dividend AKA numerator
  118. * @param int $Divisor
  119. * @param boolean $Color if true, ratio will be coloured.
  120. * @return string formatted ratio HTML
  121. */
  122. public static function get_ratio_html($Dividend, $Divisor, $Color = true)
  123. {
  124. $Ratio = self::get_ratio($Dividend, $Divisor);
  125. if ($Ratio === false) {
  126. return '&ndash;';
  127. }
  128. if ($Ratio === '∞') {
  129. return '<span class="tooltip r99" title="Infinite">∞</span>';
  130. }
  131. if ($Color) {
  132. $Ratio = sprintf(
  133. '<span class="tooltip %s" title="%s">%s</span>',
  134. self::get_ratio_color($Ratio),
  135. self::get_ratio($Dividend, $Divisor, 5),
  136. $Ratio
  137. );
  138. }
  139. return $Ratio;
  140. }
  141. /**
  142. * Returns ratio
  143. * @param int $Dividend
  144. * @param int $Divisor
  145. * @param int $Decimal floor to n decimals (e.g. subtract .005 to floor to 2 decimals)
  146. * @return boolean|string
  147. */
  148. public static function get_ratio($Dividend, $Divisor, $Decimal = 2)
  149. {
  150. if ((int) $Divisor === 0 && (int) $Dividend === 0) {
  151. return false;
  152. }
  153. if ((int) $Divisor === 0) {
  154. return '∞';
  155. }
  156. return number_format(max($Dividend / $Divisor - (0.5 / pow(10, $Decimal)), 0), $Decimal);
  157. }
  158. /**
  159. * Gets the query string of the current page, minus the parameters in $Exclude
  160. *
  161. * @param array $Exclude Query string parameters to leave out, or blank to include all parameters.
  162. * @param bool $Escape Whether to return a string prepared for HTML output
  163. * @param bool $Sort Whether to sort the parameters by key
  164. * @return An optionally HTML sanatized query string
  165. */
  166. public static function get_url($Exclude = false, $Escape = true, $Sort = false)
  167. {
  168. if ($Exclude !== false) {
  169. $Separator = $Escape ? '&amp;' : '&';
  170. $QueryItems = null;
  171. parse_str($_SERVER['QUERY_STRING'], $QueryItems);
  172. foreach ($Exclude as $Key) {
  173. unset($QueryItems[$Key]);
  174. }
  175. if ($Sort) {
  176. ksort($QueryItems);
  177. }
  178. return http_build_query($QueryItems, '', $Separator);
  179. } else {
  180. return $Escape ? display_str($_SERVER['QUERY_STRING']) : $_SERVER['QUERY_STRING'];
  181. }
  182. }
  183. /**
  184. * Finds what page we're on and gives it to us, as well as the LIMIT clause for SQL
  185. * Takes in $_GET['page'] as an additional input
  186. *
  187. * @param $PerPage Results to show per page
  188. * @param $DefaultResult Optional, which result's page we want if no page is specified
  189. * If this parameter is not specified, we will default to page 1
  190. *
  191. * @return array(int, string) What page we are on, and what to use in the LIMIT section of a query
  192. * e.g. "SELECT […] LIMIT $Limit;"
  193. */
  194. public static function page_limit($PerPage, $DefaultResult = 1)
  195. {
  196. if (!isset($_GET['page'])) {
  197. $Page = ceil($DefaultResult / $PerPage);
  198. # todo: Strict equality breaks comment fetching
  199. if ($Page == 0) {
  200. $Page = 1;
  201. }
  202. $Limit = $PerPage;
  203. } else {
  204. if (!is_number($_GET['page'])) {
  205. error(0);
  206. }
  207. $Page = $_GET['page'];
  208. if ($Page <= 0) {
  209. $Page = 1;
  210. }
  211. $Limit = $PerPage * $Page - $PerPage . ", $PerPage";
  212. }
  213. return array($Page, $Limit);
  214. }
  215. /**
  216. * catalogue_limit()
  217. *
  218. * A9 magic. Some other poor soul can write the phpdoc.
  219. * For data stored in memcached catalogues (giant arrays), e.g., forum threads
  220. */
  221. public static function catalogue_limit($Page, $PerPage, $CatalogueSize = 500)
  222. {
  223. $CatalogueID = floor(($PerPage * $Page - $PerPage) / $CatalogueSize);
  224. $CatalogueLimit = ($CatalogueID * $CatalogueSize).", $CatalogueSize";
  225. return array($CatalogueID, $CatalogueLimit);
  226. }
  227. /**
  228. * catalogue_select()
  229. */
  230. public static function catalogue_select($Catalogue, $Page, $PerPage, $CatalogueSize = 500)
  231. {
  232. return array_slice($Catalogue, (($PerPage * $Page - $PerPage) % $CatalogueSize), $PerPage, true);
  233. }
  234. /*
  235. * Get pages
  236. * Returns a page list, given certain information about the pages.
  237. *
  238. * @param int $StartPage: The current record the page you're on starts with.
  239. * e.g. if you're on page 2 of a forum thread with 25 posts per page, $StartPage is 25.
  240. * If you're on page 1, $StartPage is 0.
  241. * @param int $TotalRecords: The total number of records in the result set.
  242. * e.g. if you're on a forum thread with 152 posts, $TotalRecords is 152.
  243. * @param int $ItemsPerPage: Self-explanatory. The number of records shown on each page
  244. * e.g. if there are 25 posts per forum page, $ItemsPerPage is 25.
  245. * @param int $ShowPages: The number of page links that are shown.
  246. * e.g. If there are 20 pages that exist, but $ShowPages is only 11, only 11 links will be shown.
  247. * @param string $Anchor A URL fragment to attach to the links.
  248. * e.g. '#comment12'
  249. * @return A sanitized HTML page listing.
  250. */
  251. public static function get_pages($StartPage, $TotalRecords, $ItemsPerPage, $ShowPages = 11, $Anchor = '')
  252. {
  253. global $Document, $Method, $Mobile;
  254. $Location = "$Document.php";
  255. $StartPage = ceil($StartPage);
  256. $TotalPages = 0;
  257. if ($TotalRecords > 0) {
  258. $StartPage = min($StartPage, ceil($TotalRecords / $ItemsPerPage));
  259. $ShowPages--;
  260. $TotalPages = ceil($TotalRecords / $ItemsPerPage);
  261. if ($TotalPages > $ShowPages) {
  262. $StartPosition = $StartPage - round($ShowPages / 2);
  263. if ($StartPosition <= 0) {
  264. $StartPosition = 1;
  265. } else {
  266. if ($StartPosition >= ($TotalPages - $ShowPages)) {
  267. $StartPosition = $TotalPages - $ShowPages;
  268. }
  269. }
  270. $StopPage = $ShowPages + $StartPosition;
  271. } else {
  272. $StopPage = $TotalPages;
  273. $StartPosition = 1;
  274. }
  275. $StartPosition = max($StartPosition, 1);
  276. $QueryString = self::get_url(array('page', 'post'));
  277. if ($QueryString !== '') {
  278. $QueryString = "&amp;$QueryString";
  279. }
  280. $Pages = '';
  281. if ($StartPage > 1) {
  282. $Pages .= "<a href='$Location?page=1$QueryString$Anchor'><strong>&laquo; First</strong></a> ";
  283. $Pages .= "<a href='$Location?page=".($StartPage - 1).$QueryString.$Anchor."' class='pager_prev'><strong>&lsaquo; Prev</strong></a> | ";
  284. }
  285. // End change
  286. if (!$Mobile) {
  287. for ($i = $StartPosition; $i <= $StopPage; $i++) {
  288. if ($i !== $StartPage) {
  289. $Pages .= "<a href='$Location?page=$i$QueryString$Anchor'>";
  290. }
  291. $Pages .= '<strong>';
  292. if ($i * $ItemsPerPage > $TotalRecords) {
  293. $Pages .= ((($i - 1) * $ItemsPerPage) + 1)."-$TotalRecords";
  294. } else {
  295. $Pages .= ((($i - 1) * $ItemsPerPage) + 1).'-'.($i * $ItemsPerPage);
  296. }
  297. $Pages .= '</strong>';
  298. if ($i !== $StartPage) {
  299. $Pages .= '</a>';
  300. }
  301. if ($i < $StopPage) {
  302. $Pages .= ' | ';
  303. }
  304. }
  305. } else {
  306. $Pages .= $StartPage;
  307. }
  308. if ($StartPage && $StartPage < $TotalPages) {
  309. $Pages .= " | <a href='$Location?page=".($StartPage + 1).$QueryString.$Anchor."' class='pager_next'><strong>Next &rsaquo;</strong></a> ";
  310. $Pages .= "<a href='$Location?page=$TotalPages$QueryString$Anchor'><strong> Last&nbsp;&raquo;</strong></a>";
  311. }
  312. }
  313. if ($TotalPages > 1) {
  314. return $Pages;
  315. }
  316. }
  317. /**
  318. * Format a size in bytes as a human readable string in KiB/MiB/…
  319. * Note: KiB, MiB, etc. are the IEC units, which are in base 2.
  320. * KB, MB are the SI units, which are in base 10.
  321. *
  322. * @param int $Size
  323. * @param int $Levels Number of decimal places. Defaults to 2, unless the size >= 1TB, in which case it defaults to 4.
  324. * @return string formatted number.
  325. */
  326. public static function get_size($Size, $Levels = 2)
  327. {
  328. $Units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
  329. $Size = (double) $Size;
  330. for ($Steps = 0; abs($Size) >= 1024 && $Steps < count($Units); $Size /= 1024, $Steps++) {
  331. }
  332. if (func_num_args() === 1 && $Steps >= 4) {
  333. $Levels++;
  334. }
  335. return number_format($Size, $Levels) . ' ' . $Units[$Steps];
  336. }
  337. /**
  338. * Format a number as a multiple of its highest power of 1000 (e.g. 10035 -> '10.04k')
  339. *
  340. * @param int $Number
  341. * @return string formatted number.
  342. */
  343. public static function human_format($Number)
  344. {
  345. $Steps = 0;
  346. while ($Number >= 1000) {
  347. $Steps++;
  348. $Number = $Number / 1000;
  349. }
  350. switch ($Steps) {
  351. case 0: return round($Number); break;
  352. case 1: return round($Number, 2).'k'; break;
  353. case 2: return round($Number, 2).'M'; break;
  354. case 3: return round($Number, 2).'G'; break;
  355. case 4: return round($Number, 2).'T'; break;
  356. case 5: return round($Number, 2).'P'; break;
  357. default: return round($Number, 2).'E + '.$Steps * 3;
  358. }
  359. }
  360. /**
  361. * Given a formatted string of a size, get the number of bytes it represents.
  362. *
  363. * @param string $Size formatted size string, e.g. 123.45k
  364. * @return Number of bytes it represents, e.g. (123.45 * 1024)
  365. */
  366. public static function get_bytes($Size)
  367. {
  368. list($Value, $Unit) = sscanf($Size, "%f%s");
  369. $Unit = ltrim($Unit);
  370. if (empty($Unit)) {
  371. return $Value ? round($Value) : 0;
  372. }
  373. switch (strtolower($Unit[0])) {
  374. case 'k': return round($Value * 1024);
  375. case 'm': return round($Value * 1048576);
  376. case 'g': return round($Value * 1073741824);
  377. case 't': return round($Value * 1099511627776);
  378. default: return 0;
  379. }
  380. }
  381. /**
  382. * Reverse the effects of display_str - un-sanitize HTML.
  383. * Use sparingly.
  384. *
  385. * @param string $Str the string to unsanitize
  386. * @return unsanitized string
  387. */
  388. public static function undisplay_str($Str)
  389. {
  390. return mb_convert_encoding($Str, 'UTF-8', 'HTML-ENTITIES');
  391. }
  392. /**
  393. * Echo data sent in a GET form field, useful for text areas.
  394. *
  395. * @param string $Index the name of the form field
  396. * @param boolean $Return if set to true, value is returned instead of echoed.
  397. * @return Sanitized value of field index if $Return == true
  398. */
  399. public static function form($Index, $Return = false)
  400. {
  401. if (!empty($_GET[$Index])) {
  402. if ($Return) {
  403. return display_str($_GET[$Index]);
  404. } else {
  405. echo display_str($_GET[$Index]);
  406. }
  407. }
  408. }
  409. /**
  410. * Convenience function to echo out selected="selected" and checked="checked" so you don't have to.
  411. *
  412. * @param string $Name the name of the option in the select (or field in $Array)
  413. * @param mixed $Value the value that the option must be for the option to be marked as selected or checked
  414. * @param string $Attribute The value returned/echoed is $Attribute="$Attribute" with a leading space
  415. * @param array $Array The array the option is in, defaults to GET.
  416. * @return void
  417. */
  418. public static function selected($Name, $Value, $Attribute = 'selected', $Array = [])
  419. {
  420. if (empty($Array)) {
  421. $Array = $_GET;
  422. }
  423. if (isset($Array[$Name]) && $Array[$Name] !== '') {
  424. if ($Array[$Name] === $Value) {
  425. echo " $Attribute='$Attribute'";
  426. }
  427. }
  428. }
  429. /**
  430. * Return a CSS class name if certain conditions are met. Mainly useful to mark links as 'active'
  431. *
  432. * @param mixed $Target The variable to compare all values against
  433. *
  434. * @param mixed $Tests The condition values. Type and dimension determines test type
  435. * - Scalar: $Tests must be equal to $Target for a match
  436. * - Vector: All elements in $Tests must correspond to equal values in $Target
  437. * - 2-dimensional array: At least one array must be identical to $Target
  438. *
  439. * @param string $ClassName CSS class name to return
  440. * @param bool $AddAttribute Whether to include the "class" attribute in the output
  441. * @param string $UserIDKey Key in _REQUEST for a user ID parameter, which if given will be compared to G::$LoggedUser[ID]
  442. *
  443. * @return class name on match, otherwise an empty string
  444. */
  445. public static function add_class($Target, $Tests, $ClassName, $AddAttribute, $UserIDKey = false)
  446. {
  447. if ($UserIDKey && isset($_REQUEST[$UserIDKey]) && G::$LoggedUser['ID'] !== $_REQUEST[$UserIDKey]) {
  448. return '';
  449. }
  450. $Pass = true;
  451. if (!is_array($Tests)) {
  452. // Scalars are nice and easy
  453. $Pass = $Tests === $Target;
  454. } elseif (!is_array($Tests[0])) {
  455. // Test all values in vectors
  456. foreach ($Tests as $Type => $Part) {
  457. if (!isset($Target[$Type]) || $Target[$Type] !== $Part) {
  458. $Pass = false;
  459. break;
  460. }
  461. }
  462. } else {
  463. // Loop to the end of the array or until we find a matching test
  464. foreach ($Tests as $Test) {
  465. $Pass = true;
  466. // If $Pass remains true after this test, it's a match
  467. foreach ($Test as $Type => $Part) {
  468. if (!isset($Target[$Type]) || $Target[$Type] !== $Part) {
  469. $Pass = false;
  470. break;
  471. }
  472. }
  473. if ($Pass) {
  474. break;
  475. }
  476. }
  477. }
  478. if (!$Pass) {
  479. return '';
  480. }
  481. if ($AddAttribute) {
  482. return " class='$ClassName'";
  483. }
  484. return " $ClassName";
  485. }
  486. /**
  487. * Detect the encoding of a string and transform it to UTF-8.
  488. *
  489. * @param string $Str
  490. * @return UTF-8 encoded version of $Str
  491. */
  492. public static function make_utf8($Str)
  493. {
  494. if ($Str !== '') {
  495. if (self::is_utf8($Str)) {
  496. $Encoding = 'UTF-8';
  497. }
  498. if (empty($Encoding)) {
  499. $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1');
  500. }
  501. if (empty($Encoding)) {
  502. $Encoding = 'ISO-8859-1';
  503. }
  504. if ($Encoding === 'UTF-8') {
  505. return $Str;
  506. } else {
  507. return @mb_convert_encoding($Str, 'UTF-8', $Encoding);
  508. }
  509. }
  510. }
  511. /**
  512. * Magical function.
  513. *
  514. * @param string $Str function to detect encoding on.
  515. * @return true if the string is in UTF-8.
  516. */
  517. public static function is_utf8($Str)
  518. {
  519. return preg_match(
  520. '%^(?:
  521. [\x09\x0A\x0D\x20-\x7E] // ASCII
  522. | [\xC2-\xDF][\x80-\xBF] // Non-overlong 2-byte
  523. | \xE0[\xA0-\xBF][\x80-\xBF] // Excluding overlongs
  524. | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} // Straight 3-byte
  525. | \xED[\x80-\x9F][\x80-\xBF] // Excluding surrogates
  526. | \xF0[\x90-\xBF][\x80-\xBF]{2} // Planes 1-3
  527. | [\xF1-\xF3][\x80-\xBF]{3} // Planes 4-15
  528. | \xF4[\x80-\x8F][\x80-\xBF]{2} // Plane 16
  529. )*$%xs',
  530. $Str
  531. );
  532. }
  533. /**
  534. * Modified accessor for the $TorrentLabels array
  535. *
  536. * Converts $Text to lowercase and strips non-word characters
  537. *
  538. * @param string $Text Search string
  539. * @return string CSS class(es)
  540. */
  541. public static function find_torrent_label_class($Text)
  542. {
  543. $Index = mb_eregi_replace('(?:[^\w\d\s]+)', '', strtolower($Text));
  544. if (isset(self::$TorrentLabels[$Index])) {
  545. return self::$TorrentLabels[$Index];
  546. } else {
  547. return self::$TorrentLabels['default'];
  548. }
  549. }
  550. /**
  551. * Creates a strong element that notes the torrent's state.
  552. * E.g.: snatched/freeleech/neutral leech/reported
  553. *
  554. * The CSS class is inferred using find_torrent_label_class($Text)
  555. *
  556. * @param string $Text Display text
  557. * @param string $Class Custom CSS class
  558. * @return string <strong> element
  559. */
  560. public static function torrent_label($Text, $Class = '')
  561. {
  562. if (empty($Class)) {
  563. $Class = self::find_torrent_label_class($Text);
  564. }
  565. return sprintf(
  566. '<strong class="torrent_label tooltip %1$s" title="%2$s" style="white-space: nowrap;">%2$s</strong>',
  567. display_str($Class),
  568. display_str($Text)
  569. );
  570. }
  571. /**
  572. * Formats a CSS class name from a Category ID
  573. * @global array $Categories
  574. * @param int|string $CategoryID This number will be subtracted by one
  575. * @return string
  576. */
  577. public static function css_category($CategoryID = 1)
  578. {
  579. global $Categories;
  580. return 'cats_' . strtolower(str_replace(
  581. array('-', ' '),
  582. '',
  583. $Categories[$CategoryID - 1]
  584. ));
  585. }
  586. /**
  587. * Formats a CSS class name from a Category ID
  588. * @global array $Categories
  589. * @param int|string $CategoryID This number will be subtracted by one
  590. * @return string
  591. */
  592. public static function pretty_category($CategoryID = 1)
  593. {
  594. global $Categories;
  595. return ucwords(str_replace('-', ' ', $Categories[$CategoryID - 1]));
  596. }
  597. }