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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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 ($Divisor === 0 && $Dividend === 0) {
  150. return false;
  151. }
  152. if ($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. // A9 magic. Some other poor soul can write the phpdoc.
  215. // For data stored in memcached catalogues (giant arrays), e.g., forum threads
  216. public static function catalogue_limit($Page, $PerPage, $CatalogueSize = 500)
  217. {
  218. $CatalogueID = floor(($PerPage * $Page - $PerPage) / $CatalogueSize);
  219. $CatalogueLimit = ($CatalogueID * $CatalogueSize).", $CatalogueSize";
  220. return array($CatalogueID, $CatalogueLimit);
  221. }
  222. public static function catalogue_select($Catalogue, $Page, $PerPage, $CatalogueSize = 500)
  223. {
  224. return array_slice($Catalogue, (($PerPage * $Page - $PerPage) % $CatalogueSize), $PerPage, true);
  225. }
  226. /*
  227. * Get pages
  228. * Returns a page list, given certain information about the pages.
  229. *
  230. * @param int $StartPage: The current record the page you're on starts with.
  231. * e.g. if you're on page 2 of a forum thread with 25 posts per page, $StartPage is 25.
  232. * If you're on page 1, $StartPage is 0.
  233. * @param int $TotalRecords: The total number of records in the result set.
  234. * e.g. if you're on a forum thread with 152 posts, $TotalRecords is 152.
  235. * @param int $ItemsPerPage: Self-explanatory. The number of records shown on each page
  236. * e.g. if there are 25 posts per forum page, $ItemsPerPage is 25.
  237. * @param int $ShowPages: The number of page links that are shown.
  238. * e.g. If there are 20 pages that exist, but $ShowPages is only 11, only 11 links will be shown.
  239. * @param string $Anchor A URL fragment to attach to the links.
  240. * e.g. '#comment12'
  241. * @return A sanitized HTML page listing.
  242. */
  243. public static function get_pages($StartPage, $TotalRecords, $ItemsPerPage, $ShowPages = 11, $Anchor = '')
  244. {
  245. global $Document, $Method, $Mobile;
  246. $Location = "$Document.php";
  247. $StartPage = ceil($StartPage);
  248. $TotalPages = 0;
  249. if ($TotalRecords > 0) {
  250. $StartPage = min($StartPage, ceil($TotalRecords / $ItemsPerPage));
  251. $ShowPages--;
  252. $TotalPages = ceil($TotalRecords / $ItemsPerPage);
  253. if ($TotalPages > $ShowPages) {
  254. $StartPosition = $StartPage - round($ShowPages / 2);
  255. if ($StartPosition <= 0) {
  256. $StartPosition = 1;
  257. } else {
  258. if ($StartPosition >= ($TotalPages - $ShowPages)) {
  259. $StartPosition = $TotalPages - $ShowPages;
  260. }
  261. }
  262. $StopPage = $ShowPages + $StartPosition;
  263. } else {
  264. $StopPage = $TotalPages;
  265. $StartPosition = 1;
  266. }
  267. $StartPosition = max($StartPosition, 1);
  268. $QueryString = self::get_url(array('page', 'post'));
  269. if ($QueryString !== '') {
  270. $QueryString = "&amp;$QueryString";
  271. }
  272. $Pages = '';
  273. if ($StartPage > 1) {
  274. $Pages .= "<a href=\"$Location?page=1$QueryString$Anchor\"><strong>« First</strong></a> ";
  275. $Pages .= "<a href=\"$Location?page=".($StartPage - 1).$QueryString.$Anchor.'" class="pager_prev"><strong>‹ Prev</strong></a> | ';
  276. }
  277. // End change
  278. if (!$Mobile) {
  279. for ($i = $StartPosition; $i <= $StopPage; $i++) {
  280. if ($i !== $StartPage) {
  281. $Pages .= "<a href=\"$Location?page=$i$QueryString$Anchor\">";
  282. }
  283. $Pages .= '<strong>';
  284. if ($i * $ItemsPerPage > $TotalRecords) {
  285. $Pages .= ((($i - 1) * $ItemsPerPage) + 1)."-$TotalRecords";
  286. } else {
  287. $Pages .= ((($i - 1) * $ItemsPerPage) + 1).'-'.($i * $ItemsPerPage);
  288. }
  289. $Pages .= '</strong>';
  290. if ($i !== $StartPage) {
  291. $Pages .= '</a>';
  292. }
  293. if ($i < $StopPage) {
  294. $Pages .= ' | ';
  295. }
  296. }
  297. } else {
  298. $Pages .= $StartPage;
  299. }
  300. if ($StartPage && $StartPage < $TotalPages) {
  301. $Pages .= " | <a href=\"$Location?page=".($StartPage + 1).$QueryString.$Anchor.'" class="pager_next"><strong>Next ›</strong></a> ';
  302. $Pages .= "<a href=\"$Location?page=$TotalPages$QueryString$Anchor\"><strong> Last »</strong></a>";
  303. }
  304. }
  305. if ($TotalPages > 1) {
  306. return $Pages;
  307. }
  308. }
  309. /**
  310. * Format a size in bytes as a human readable string in KiB/MiB/...
  311. * Note: KiB, MiB, etc. are the IEC units, which are in base 2.
  312. * KB, MB are the SI units, which are in base 10.
  313. *
  314. * @param int $Size
  315. * @param int $Levels Number of decimal places. Defaults to 2, unless the size >= 1TB, in which case it defaults to 4.
  316. * @return string formatted number.
  317. */
  318. public static function get_size($Size, $Levels = 2)
  319. {
  320. $Units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
  321. $Size = (double)$Size;
  322. for ($Steps = 0; abs($Size) >= 1024 && $Steps < count($Units); $Size /= 1024, $Steps++) {
  323. }
  324. if (func_num_args() === 1 && $Steps >= 4) {
  325. $Levels++;
  326. }
  327. return number_format($Size, $Levels) . ' ' . $Units[$Steps];
  328. }
  329. /**
  330. * Format a number as a multiple of its highest power of 1000 (e.g. 10035 -> '10.04k')
  331. *
  332. * @param int $Number
  333. * @return string formatted number.
  334. */
  335. public static function human_format($Number)
  336. {
  337. $Steps = 0;
  338. while ($Number >= 1000) {
  339. $Steps++;
  340. $Number = $Number / 1000;
  341. }
  342. switch ($Steps) {
  343. case 0: return round($Number); break;
  344. case 1: return round($Number, 2).'k'; break;
  345. case 2: return round($Number, 2).'M'; break;
  346. case 3: return round($Number, 2).'G'; break;
  347. case 4: return round($Number, 2).'T'; break;
  348. case 5: return round($Number, 2).'P'; break;
  349. default:
  350. return round($Number, 2).'E + '.$Steps * 3;
  351. }
  352. }
  353. /**
  354. * Given a formatted string of a size, get the number of bytes it represents.
  355. *
  356. * @param string $Size formatted size string, e.g. 123.45k
  357. * @return Number of bytes it represents, e.g. (123.45 * 1024)
  358. */
  359. public static function get_bytes($Size)
  360. {
  361. list($Value, $Unit) = sscanf($Size, "%f%s");
  362. $Unit = ltrim($Unit);
  363. if (empty($Unit)) {
  364. return $Value ? round($Value) : 0;
  365. }
  366. switch (strtolower($Unit[0])) {
  367. case 'k': return round($Value * 1024);
  368. case 'm': return round($Value * 1048576);
  369. case 'g': return round($Value * 1073741824);
  370. case 't': return round($Value * 1099511627776);
  371. default: return 0;
  372. }
  373. }
  374. /**
  375. * Reverse the effects of display_str - un-sanitize HTML.
  376. * Use sparingly.
  377. *
  378. * @param string $Str the string to unsanitize
  379. * @return unsanitized string
  380. */
  381. // Use sparingly
  382. public static function undisplay_str($Str)
  383. {
  384. return mb_convert_encoding($Str, 'UTF-8', 'HTML-ENTITIES');
  385. }
  386. /**
  387. * Echo data sent in a GET form field, useful for text areas.
  388. *
  389. * @param string $Index the name of the form field
  390. * @param boolean $Return if set to true, value is returned instead of echoed.
  391. * @return Sanitized value of field index if $Return == true
  392. */
  393. public static function form($Index, $Return = false)
  394. {
  395. if (!empty($_GET[$Index])) {
  396. if ($Return) {
  397. return display_str($_GET[$Index]);
  398. } else {
  399. echo display_str($_GET[$Index]);
  400. }
  401. }
  402. }
  403. /**
  404. * Convenience function to echo out selected="selected" and checked="checked" so you don't have to.
  405. *
  406. * @param string $Name the name of the option in the select (or field in $Array)
  407. * @param mixed $Value the value that the option must be for the option to be marked as selected or checked
  408. * @param string $Attribute The value returned/echoed is $Attribute="$Attribute" with a leading space
  409. * @param array $Array The array the option is in, defaults to GET.
  410. * @return void
  411. */
  412. public static function selected($Name, $Value, $Attribute = 'selected', $Array = [])
  413. {
  414. if (empty($Array)) {
  415. $Array = $_GET;
  416. }
  417. if (isset($Array[$Name]) && $Array[$Name] !== '') {
  418. if ($Array[$Name] === $Value) {
  419. echo " $Attribute=\"$Attribute\"";
  420. }
  421. }
  422. }
  423. /**
  424. * Return a CSS class name if certain conditions are met. Mainly useful to mark links as 'active'
  425. *
  426. * @param mixed $Target The variable to compare all values against
  427. * @param mixed $Tests The condition values. Type and dimension determines test type
  428. * Scalar: $Tests must be equal to $Target for a match
  429. * Vector: All elements in $Tests must correspond to equal values in $Target
  430. * 2-dimensional array: At least one array must be identical to $Target
  431. * @param string $ClassName CSS class name to return
  432. * @param bool $AddAttribute Whether to include the "class" attribute in the output
  433. * @param string $UserIDKey Key in _REQUEST for a user ID parameter, which if given will be compared to G::$LoggedUser[ID]
  434. *
  435. * @return class name on match, otherwise an empty string
  436. */
  437. public static function add_class($Target, $Tests, $ClassName, $AddAttribute, $UserIDKey = false)
  438. {
  439. if ($UserIDKey && isset($_REQUEST[$UserIDKey]) && G::$LoggedUser['ID'] != $_REQUEST[$UserIDKey]) {
  440. return '';
  441. }
  442. $Pass = true;
  443. if (!is_array($Tests)) {
  444. // Scalars are nice and easy
  445. $Pass = $Tests === $Target;
  446. } elseif (!is_array($Tests[0])) {
  447. // Test all values in vectors
  448. foreach ($Tests as $Type => $Part) {
  449. if (!isset($Target[$Type]) || $Target[$Type] !== $Part) {
  450. $Pass = false;
  451. break;
  452. }
  453. }
  454. } else {
  455. // Loop to the end of the array or until we find a matching test
  456. foreach ($Tests as $Test) {
  457. $Pass = true;
  458. // If $Pass remains true after this test, it's a match
  459. foreach ($Test as $Type => $Part) {
  460. if (!isset($Target[$Type]) || $Target[$Type] !== $Part) {
  461. $Pass = false;
  462. break;
  463. }
  464. }
  465. if ($Pass) {
  466. break;
  467. }
  468. }
  469. }
  470. if (!$Pass) {
  471. return '';
  472. }
  473. if ($AddAttribute) {
  474. return " class=\"$ClassName\"";
  475. }
  476. return " $ClassName";
  477. }
  478. /**
  479. * Detect the encoding of a string and transform it to UTF-8.
  480. *
  481. * @param string $Str
  482. * @return UTF-8 encoded version of $Str
  483. */
  484. public static function make_utf8($Str)
  485. {
  486. if ($Str !== '') {
  487. if (self::is_utf8($Str)) {
  488. $Encoding = 'UTF-8';
  489. }
  490. if (empty($Encoding)) {
  491. $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1');
  492. }
  493. if (empty($Encoding)) {
  494. $Encoding = 'ISO-8859-1';
  495. }
  496. if ($Encoding === 'UTF-8') {
  497. return $Str;
  498. } else {
  499. return @mb_convert_encoding($Str, 'UTF-8', $Encoding);
  500. }
  501. }
  502. }
  503. /**
  504. * Magical function.
  505. *
  506. * @param string $Str function to detect encoding on.
  507. * @return true if the string is in UTF-8.
  508. */
  509. public static function is_utf8($Str)
  510. {
  511. return preg_match(
  512. '%^(?:
  513. [\x09\x0A\x0D\x20-\x7E] // ASCII
  514. | [\xC2-\xDF][\x80-\xBF] // Non-overlong 2-byte
  515. | \xE0[\xA0-\xBF][\x80-\xBF] // Excluding overlongs
  516. | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} // Straight 3-byte
  517. | \xED[\x80-\x9F][\x80-\xBF] // Excluding surrogates
  518. | \xF0[\x90-\xBF][\x80-\xBF]{2} // Planes 1-3
  519. | [\xF1-\xF3][\x80-\xBF]{3} // Planes 4-15
  520. | \xF4[\x80-\x8F][\x80-\xBF]{2} // Plane 16
  521. )*$%xs',
  522. $Str
  523. );
  524. }
  525. /**
  526. * Modified accessor for the $TorrentLabels array
  527. *
  528. * Converts $Text to lowercase and strips non-word characters
  529. *
  530. * @param string $Text Search string
  531. * @return string CSS class(es)
  532. */
  533. public static function find_torrent_label_class($Text)
  534. {
  535. $Index = mb_eregi_replace('(?:[^\w\d\s]+)', '', strtolower($Text));
  536. if (isset(self::$TorrentLabels[$Index])) {
  537. return self::$TorrentLabels[$Index];
  538. } else {
  539. return self::$TorrentLabels['default'];
  540. }
  541. }
  542. /**
  543. * Creates a strong element that notes the torrent's state.
  544. * E.g.: snatched/freeleech/neutral leech/reported
  545. *
  546. * The CSS class is inferred using find_torrent_label_class($Text)
  547. *
  548. * @param string $Text Display text
  549. * @param string $Class Custom CSS class
  550. * @return string <strong> element
  551. */
  552. public static function torrent_label($Text, $Class = '')
  553. {
  554. if (empty($Class)) {
  555. $Class = self::find_torrent_label_class($Text);
  556. }
  557. return sprintf(
  558. '<strong class="torrent_label tooltip %1$s" title="%2$s" style="white-space: nowrap;">%2$s</strong>',
  559. display_str($Class),
  560. display_str($Text)
  561. );
  562. }
  563. /**
  564. * Formats a CSS class name from a Category ID
  565. * @global array $Categories
  566. * @param int|string $CategoryID This number will be subtracted by one
  567. * @return string
  568. */
  569. public static function css_category($CategoryID = 1)
  570. {
  571. global $Categories;
  572. return 'cats_' . strtolower(str_replace(
  573. array('-', ' '),
  574. '',
  575. $Categories[$CategoryID - 1]
  576. ));
  577. }
  578. /**
  579. * Formats a CSS class name from a Category ID
  580. * @global array $Categories
  581. * @param int|string $CategoryID This number will be subtracted by one
  582. * @return string
  583. */
  584. public static function pretty_category($CategoryID = 1)
  585. {
  586. global $Categories;
  587. return ucwords(str_replace('-', ' ', $Categories[$CategoryID - 1]));
  588. }
  589. }