Oppaitime'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

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