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

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