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 17KB

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