PageRenderTime 74ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/system/classes/utils.php

https://github.com/HabariMag/habarimag-old
PHP | 1155 lines | 710 code | 65 blank | 380 comment | 127 complexity | 52d840f60740d5898a2c9a4c2256f259 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * @package Habari
  4. *
  5. */
  6. /**
  7. * Habari Utility Class
  8. *
  9. */
  10. class Utils
  11. {
  12. public static $debug_defined = false;
  13. /**
  14. * Utils constructor
  15. * This class should not be instantiated.
  16. */
  17. private function __construct()
  18. {
  19. }
  20. /**
  21. * function get_params
  22. * Returns an associative array of parameters, whether the input value is
  23. * a querystring or an associative array.
  24. * @param mixed An associative array or querystring parameter list
  25. * @return array An associative array of parameters
  26. */
  27. public static function get_params( $params )
  28. {
  29. if ( is_array( $params ) || $params instanceof ArrayObject || $params instanceof ArrayIterator ) {
  30. return $params;
  31. }
  32. $paramarray = array();
  33. parse_str( $params, $paramarray );
  34. return $paramarray;
  35. }
  36. /**
  37. * function end_in_slash
  38. * Forces a string to end in a single slash
  39. * @param string A string, usually a path
  40. * @return string The string with the slash added or extra slashes removed, but with one slash only
  41. */
  42. public static function end_in_slash( $value )
  43. {
  44. return rtrim( $value, '\\/' ) . '/';
  45. }
  46. /**
  47. * function redirect
  48. * Redirects the request to a new URL
  49. * @param string $url The URL to redirect to, or omit to redirect to the current url
  50. * @param boolean $continue Whether to continue processing the script (default false for security reasons, cf. #749)
  51. */
  52. public static function redirect( $url = '', $continue = false )
  53. {
  54. if ( $url == '' ) {
  55. $url = Controller::get_full_url();
  56. }
  57. header( 'Location: ' . $url, true, 302 );
  58. if ( ! $continue ) exit;
  59. }
  60. /**
  61. * function atomtime
  62. * Returns RFC-3339 time from a time string or integer timestamp
  63. * @param mixed A string of time or integer timestamp
  64. * @return string An RFC-3339 formatted time
  65. */
  66. public static function atomtime( $t )
  67. {
  68. if ( ! is_numeric( $t ) ) {
  69. $t = strtotime( $t );
  70. }
  71. $vdate = date( DATE_ATOM, $t );
  72. // If the date format used for timezone was O instead of P...
  73. if ( substr( $vdate, -3, 1 ) != ':' ) {
  74. $vdate = substr( $vdate, 0, -2 ) . ':' . substr( $vdate, -2, 2 );
  75. }
  76. return $vdate;
  77. }
  78. /**
  79. * function nonce
  80. * Returns a random 12-digit hex number
  81. */
  82. public static function nonce()
  83. {
  84. return sprintf( '%06x', rand( 0, 16776960 ) ) . sprintf( '%06x', rand( 0, 16776960 ) );
  85. }
  86. /**
  87. * function WSSE
  88. * returns an array of tokens used for WSSE authentication
  89. * http://www.xml.com/pub/a/2003/12/17/dive.html
  90. * http://www.sixapart.com/developers/atom/protocol/atom_authentication.html
  91. * @param String a nonce
  92. * @param String a timestamp
  93. * @return Array an array of WSSE authentication elements
  94. */
  95. public static function WSSE( $nonce = '', $timestamp = '' )
  96. {
  97. if ( '' === $nonce ) {
  98. $nonce = Utils::crypt( Options::get( 'GUID' ) . Utils::nonce() );
  99. }
  100. if ( '' === $timestamp ) {
  101. $timestamp = date( 'c' );
  102. }
  103. $user = User::identify();
  104. $wsse = array(
  105. 'nonce' => $nonce,
  106. 'timestamp' => $timestamp,
  107. 'digest' => base64_encode( pack( 'H*', sha1( $nonce . $timestamp . $user->password ) ) )
  108. );
  109. return $wsse;
  110. }
  111. /**
  112. * function stripslashes
  113. * Removes slashes from escaped strings, including strings in arrays
  114. */
  115. public static function stripslashes( $value )
  116. {
  117. if ( is_array( $value ) ) {
  118. $value = array_map( array( 'Utils', 'stripslashes' ), $value );
  119. }
  120. elseif ( !empty( $value ) && is_string( $value ) ) {
  121. $value = stripslashes( $value );
  122. }
  123. return $value;
  124. }
  125. /**
  126. * function addslashes
  127. * Adds slashes to escape strings, including strings in arrays
  128. */
  129. public static function addslashes( $value )
  130. {
  131. if ( is_array( $value ) ) {
  132. $value = array_map( array( 'Utils', 'addslashes' ), $value );
  133. }
  134. else if ( !empty( $value ) && is_string( $value ) ) {
  135. $value = addslashes( $value );
  136. }
  137. return $value;
  138. }
  139. /**
  140. * function de_amp
  141. * Returns &amp; entities in a URL querystring to their previous & glory, for use in redirects
  142. * @param string $value A URL, maybe with a querystring
  143. */
  144. public static function de_amp( $value )
  145. {
  146. $url = InputFilter::parse_url( $value );
  147. $url[ 'query' ] = str_replace( '&amp;', '&', $url[ 'query' ] );
  148. return InputFilter::glue_url( $url );
  149. }
  150. /**
  151. * function revert_magic_quotes_gpc
  152. * Reverts magicquotes_gpc behavior
  153. */
  154. public static function revert_magic_quotes_gpc()
  155. {
  156. /* We should only revert the magic quotes once per page hit */
  157. static $revert = true;
  158. if ( get_magic_quotes_gpc() && $revert ) {
  159. $_GET = self::stripslashes( $_GET );
  160. $_POST = self::stripslashes( $_POST );
  161. $_COOKIE = self::stripslashes( $_COOKIE );
  162. $revert = false;
  163. }
  164. }
  165. /**
  166. * function quote_spaced
  167. * Adds quotes around values that have spaces in them
  168. * @param string A string value that might have spaces
  169. * @return string The string value, quoted if it has spaces
  170. */
  171. public static function quote_spaced( $value )
  172. {
  173. return ( strpos( $value, ' ' ) === false ) ? $value : '"' . $value . '"';
  174. }
  175. /**
  176. * function implode_quoted
  177. * Behaves like the implode() function, except it quotes values that contain spaces
  178. * @param string A separator between each value
  179. * @param array An array of values to separate
  180. * @return string The concatenated string
  181. */
  182. public static function implode_quoted( $separator, $values )
  183. {
  184. if ( ! is_array( $values ) ) {
  185. $values = array();
  186. }
  187. $values = array_map( array( 'Utils', 'quote_spaced' ), $values );
  188. return implode( $separator, $values );
  189. }
  190. /**
  191. * Returns a string of question mark parameter
  192. * placeholders.
  193. *
  194. * Useful when building, for instance, an IN() list for SQL
  195. *
  196. * @param count Number of placeholders to put in the string
  197. * @return string Placeholder string
  198. */
  199. public static function placeholder_string( $count )
  200. {
  201. if ( Utils::is_traversable( $count ) ) {
  202. $count = count( $count );
  203. }
  204. return rtrim( str_repeat( '?,', $count ), ',' );
  205. }
  206. /**
  207. * function archive_pages
  208. * Returns the number of pages in an archive using the number of items per page set in options
  209. * @param integer Number of items in the archive
  210. * @param integer Number of items per page
  211. * @returns integer Number of pages based on pagination option.
  212. */
  213. public static function archive_pages( $item_total, $items_per_page = null )
  214. {
  215. if ( $items_per_page ) {
  216. return ceil( $item_total / $items_per_page );
  217. }
  218. return ceil( $item_total / Options::get( 'pagination' ) );
  219. }
  220. /**
  221. * Used with array_map to create an array of PHP stringvar-style search/replace strings using optional pre/postfixes
  222. * <code>
  223. * $mapped_values= array_map(array('Utils', 'map_array'), $values);
  224. * </code>
  225. * @param string $value The value to wrap
  226. * @param string $prefix The prefix for the returned value
  227. * @param string $postfix The postfix for the returned value
  228. * @return string The wrapped value
  229. */
  230. public static function map_array( $value, $prefix = '{$', $postfix = '}' )
  231. {
  232. return $prefix . $value . $postfix;
  233. }
  234. /**
  235. * Helper function used by debug()
  236. * Not for external use.
  237. */
  238. public static function debug_reveal( $show, $hide, $debugid, $close = false )
  239. {
  240. $reshow = $restyle = $restyle2 = '';
  241. if ( $close ) {
  242. $reshow = "onclick=\"debugtoggle('debugshow-{$debugid}');debugtoggle('debughide-{$debugid}');return false;\"";
  243. $restyle = "<span class=\"utils__block\">";
  244. $restyle2 = "</span>";
  245. }
  246. return "<span class=\"utils__arg\"><a href=\"#\" id=\"debugshow-{$debugid}\" onclick=\"debugtoggle('debugshow-{$debugid}');debugtoggle('debughide-{$debugid}');return false;\">$show</a><span style=\"display:none;\" id=\"debughide-{$debugid}\" {$reshow} >{$restyle}$hide{$restyle2}</span></span>";
  247. }
  248. /**
  249. * Outputs a call stack with parameters, and a dump of the parameters passed.
  250. * @params mixed Any number of parameters to output in the debug box.
  251. */
  252. public static function debug()
  253. {
  254. $debugid = md5( microtime() );
  255. $tracect = 0;
  256. $fooargs = func_get_args();
  257. echo "<div class=\"utils__debugger\">";
  258. if ( !self::$debug_defined ) {
  259. $output = "<script type=\"text/javascript\">
  260. debuggebi = function(id) {return document.getElementById(id);}
  261. debugtoggle = function(id) {debuggebi(id).style.display = debuggebi(id).style.display=='none'?'inline':'none';}
  262. </script>
  263. <style type=\"text/css\">
  264. .utils__debugger{background-color:#550000;border:1px solid red;text-align:left;}
  265. .utils__debugger pre{margin:5px;background-color:#000;overflow-x:scroll}
  266. .utils__debugger pre em{color:#dddddd;}
  267. .utils__debugger table{background-color:#770000;color:white;width:100%;}
  268. .utils__debugger tr{background-color:#000000;}
  269. .utils__debugger td{padding-left: 10px;vertical-align:top;white-space: pre;font-family:Courier New,Courier,monospace;}
  270. .utils__debugger .utils__odd{background:#880000;}
  271. .utils__debugger .utils__arg a{color:#ff3333;}
  272. .utils__debugger .utils__arg span{display:none;}
  273. .utils__debugger .utils__arg span span{display:inline;}
  274. .utils__debugger .utils__arg span .utils__block{display:block;background:#990000;margin:0px 2em;border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:9px;padding:5px;}
  275. </style>
  276. ";
  277. echo $output;
  278. self::$debug_defined = true;
  279. }
  280. if ( function_exists( 'debug_backtrace' ) ) {
  281. $output = "<table>";
  282. $backtrace = array_reverse( debug_backtrace(), true );
  283. $odd = '';
  284. $tracect = 0;
  285. foreach ( $backtrace as $trace ) {
  286. $file = $line = $class = $type = $function = '';
  287. $args = array();
  288. extract( $trace );
  289. if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
  290. if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );
  291. $odd = $odd == '' ? 'class="utils__odd"' : '';
  292. $output .= "<tr {$odd}><td>{$file} ({$line}):</td><td>{$fname}(";
  293. $comma = '';
  294. foreach ( (array)$args as $arg ) {
  295. $tracect++;
  296. $argout = print_r( $arg, 1 );
  297. $output .= $comma . Utils::debug_reveal( gettype( $arg ), htmlentities( $argout ), $debugid . $tracect, true );
  298. $comma = ', ';
  299. }
  300. $output .= ");</td></tr>";
  301. }
  302. $output .= "</table>";
  303. echo Utils::debug_reveal( '<small>Call Stack</small>', $output, $debugid );
  304. }
  305. echo "<pre style=\"color:white;\">";
  306. foreach ( $fooargs as $arg1 ) {
  307. echo '<em>' . gettype( $arg1 ) . '</em> ';
  308. echo htmlentities( print_r( $arg1, true ) ) . "<br>";
  309. }
  310. echo "</pre></div>";
  311. }
  312. /**
  313. * Outputs debug information like ::debug() but using Firebug's Console.
  314. * @params mixed Any number of parameters to output in the debug box.
  315. */
  316. public static function firedebug()
  317. {
  318. $fooargs = func_get_args();
  319. $output = "<script type=\"text/javascript\">\nif (window.console){\n";
  320. $backtrace = array_reverse( debug_backtrace(), true );
  321. $output .= Utils::firebacktrace( $backtrace );
  322. foreach ( $fooargs as $arg1 ) {
  323. $output .= "console.info(\"%s: %s\", \"" . gettype( $arg1 ) . "\"";
  324. $output .= ", \"" . str_replace( "\n", '\n', addslashes( print_r( $arg1, 1 ) ) ) . "\");\n";
  325. }
  326. $output .= "console.groupEnd();\n}\n</script>";
  327. echo $output;
  328. }
  329. /**
  330. * Utils::firebacktrace()
  331. *
  332. * @param array $backtrace An array of backtrace details from debug_backtrace()
  333. * @return string Javascript output that will display the backtrace in the Firebug console.
  334. */
  335. public static function firebacktrace( $backtrace )
  336. {
  337. $output = '';
  338. extract( end( $backtrace ) );
  339. if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
  340. if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );
  341. $output .= "console.group(\"%s(%s): %s(&hellip;)\", \"" . basename( $file ) . "\", \"{$line}\", \"{$fname}\");\n";
  342. foreach ( $backtrace as $trace ) {
  343. $file = $line = $class = $type = $function = '';
  344. $args = array();
  345. extract( $trace );
  346. if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
  347. if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );
  348. $output .= "console.group(\"%s(%s): %s(%s)\", \"{$file}\", \"{$line}\", \"{$fname}\", \"";
  349. $output2 = $comma = $argtypes = '';
  350. foreach ( (array)$args as $arg ) {
  351. $argout = str_replace( "\n", '\n', addslashes( print_r( $arg, 1 ) ) );
  352. //$output .= $comma . Utils::debug_reveal( gettype($arg), htmlentities($argout), $debugid . $tracect, true );
  353. $argtypes .= $comma . gettype( $arg );
  354. $output2 .= "console.log(\"$argout\");\n";
  355. $comma = ', ';
  356. }
  357. $argtypes = trim( $argtypes );
  358. $output .= "{$argtypes}\");\n{$output2}";
  359. $output .= "console.groupEnd();\n";
  360. //$output .= ");</td></tr>";
  361. }
  362. return $output;
  363. }
  364. /**
  365. * Crypt a given password, or verify a given password against a given hash.
  366. *
  367. * @todo Enable best algo selection after DB schema change.
  368. *
  369. * @param string $password the password to crypt or verify
  370. * @param string $hash (optional) if given, verify $password against $hash
  371. * @return crypted password, or boolean for verification
  372. */
  373. public static function crypt( $password, $hash = null )
  374. {
  375. if ( $hash == null ) {
  376. return self::ssha512( $password, $hash );
  377. }
  378. elseif ( strlen( $hash ) > 3 ) { // need at least {, } and a char :p
  379. // verify
  380. if ( $hash{0} == '{' ) {
  381. // new hash from the block
  382. $algo = strtolower( substr( $hash, 1, strpos( $hash, '}', 1 ) - 1 ) );
  383. switch ( $algo ) {
  384. case 'sha1':
  385. case 'ssha':
  386. case 'ssha512':
  387. case 'md5':
  388. return self::$algo( $password, $hash );
  389. default:
  390. Error::raise( sprintf( _t( 'Unsupported digest algorithm "%s"' ), $algo ) );
  391. return false;
  392. }
  393. }
  394. else {
  395. // legacy sha1
  396. return ( sha1( $password ) == $hash );
  397. }
  398. }
  399. else {
  400. Error::raise( _t( 'Invalid hash' ) );
  401. }
  402. }
  403. /**
  404. * Crypt or verify a given password using SHA.
  405. *
  406. * Passwords should not be stored using this method, but legacy systems might require it.
  407. */
  408. public static function sha1( $password, $hash = null )
  409. {
  410. $marker = '{SHA1}';
  411. if ( $hash == null ) {
  412. return $marker . sha1( $password );
  413. }
  414. else {
  415. return ( sha1( $password ) == substr( $hash, strlen( $marker ) ) );
  416. }
  417. }
  418. /**
  419. * Crypt or verify a given password using MD5.
  420. *
  421. * Passwords should not be stored using this method, but legacy systems might require it.
  422. */
  423. public static function md5( $password, $hash = null )
  424. {
  425. $marker = '{MD5}';
  426. if ( $hash == null ) {
  427. return $marker . md5( $password );
  428. }
  429. else {
  430. return ( md5( $password ) == substr( $hash, strlen( $marker ) ) );
  431. }
  432. }
  433. /**
  434. * Crypt or verify a given password using SSHA.
  435. * Implements the {Seeded,Salted}-SHA algorithm as per RfC 2307.
  436. *
  437. * @param string $password the password to crypt or verify
  438. * @param string $hash (optional) if given, verify $password against $hash
  439. * @return crypted password, or boolean for verification
  440. */
  441. public static function ssha( $password, $hash = null )
  442. {
  443. $marker = '{SSHA}';
  444. if ( $hash == null ) { // encrypt
  445. // create salt (4 byte)
  446. $salt = '';
  447. for ( $i = 0; $i < 4; $i++ ) {
  448. $salt .= chr( mt_rand( 0, 255 ) );
  449. }
  450. // get digest
  451. $digest = sha1( $password . $salt, true );
  452. // b64 for storage
  453. return $marker . base64_encode( $digest . $salt );
  454. }
  455. else { // verify
  456. // is this a SSHA hash?
  457. if ( ! substr( $hash, 0, strlen( $marker ) ) == $marker ) {
  458. Error::raise( _t( 'Invalid hash' ) );
  459. return false;
  460. }
  461. // cut off {SSHA} marker
  462. $hash = substr( $hash, strlen( $marker ) );
  463. // b64 decode
  464. $hash = base64_decode( $hash );
  465. // split up
  466. $digest = substr( $hash, 0, 20 );
  467. $salt = substr( $hash, 20 );
  468. // compare
  469. return ( sha1( $password . $salt, true ) == $digest );
  470. }
  471. }
  472. /**
  473. * Crypt or verify a given password using SSHA512.
  474. * Implements a modified version of the {Seeded,Salted}-SHA algorithm
  475. * from RfC 2307, using SHA-512 instead of SHA-1.
  476. *
  477. * Requires the new hash*() functions.
  478. *
  479. * @param string $password the password to crypt or verify
  480. * @param string $hash (optional) if given, verify $password against $hash
  481. * @return crypted password, or boolean for verification
  482. */
  483. public static function ssha512( $password, $hash = null )
  484. {
  485. $marker = '{SSHA512}';
  486. if ( $hash == null ) { // encrypt
  487. $salt = '';
  488. for ( $i = 0; $i < 4; $i++ ) {
  489. $salt .= chr( mt_rand( 0, 255 ) );
  490. }
  491. $digest = hash( 'sha512', $password . $salt, true );
  492. return $marker . base64_encode( $digest . $salt );
  493. }
  494. else { // verify
  495. if ( ! substr( $hash, 0, strlen( $marker ) ) == $marker ) {
  496. Error::raise( _t( 'Invalid hash' ) );
  497. return false;
  498. }
  499. $hash = substr( $hash, strlen( $marker ) );
  500. $hash = base64_decode( $hash );
  501. $digest = substr( $hash, 0, 64 );
  502. $salt = substr( $hash, 64 );
  503. return ( hash( 'sha512', $password . $salt, true ) == $digest );
  504. }
  505. }
  506. /**
  507. * Return an array of date information
  508. * Just like getdate() but also returns 0-padded versions of day and month in mday0 and mon0
  509. * @param integer $timestamp A unix timestamp
  510. * @return array An array of date data
  511. */
  512. public static function getdate( $timestamp )
  513. {
  514. $info = getdate( $timestamp );
  515. $info[ 'mon0' ] = substr( '0' . $info[ 'mon' ], -2, 2 );
  516. $info[ 'mday0' ] = substr( '0' . $info[ 'mday' ], -2, 2 );
  517. return $info;
  518. }
  519. /**
  520. * Return a formatted date/time trying to use strftime() AND date()
  521. * @param string $format The format for the date. If it contains non-escaped percent signs, it uses strftime(), otherwise date()
  522. * @param integer $timestamp The unix timestamp of the time to format
  523. * @return string The formatted time
  524. */
  525. public static function locale_date( $format, $timestamp )
  526. {
  527. $matches = preg_split( '/((?<!\\\\)%[a-z]\\s*)/iu', $format, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
  528. $output = '';
  529. foreach ( $matches as $match ) {
  530. if ( $match{0} == '%' ) {
  531. $output .= strftime( $match, $timestamp );
  532. }
  533. else {
  534. $output .= date( $match, $timestamp );
  535. }
  536. }
  537. return $output;
  538. }
  539. /**
  540. * Return a sanitized slug, replacing non-alphanumeric characters to dashes
  541. * @param string $string The string to sanitize. Non-alphanumeric characters will be replaced by dashes
  542. * @param string $separator The slug separator, '-' by default
  543. * @return string The sanitized slug
  544. */
  545. public static function slugify( $string, $separator = '-' )
  546. {
  547. // Decode HTML entities
  548. // Replace non-alphanumeric characters to dashes. Exceptions: %, _, -
  549. // Note that multiple separators are collapsed automatically by the preg_replace.
  550. // Convert all characters to lowercase.
  551. // Trim spaces on both sides.
  552. $slug = rtrim( MultiByte::strtolower( preg_replace( '/[^\p{L}\p{N}_]+/u', $separator, preg_replace( '/\p{Po}/u', '', html_entity_decode( $string ) ) ) ), $separator );
  553. // Let people change the behavior.
  554. $slug = Plugins::filter( 'slugify', $slug, $string );
  555. return $slug;
  556. }
  557. /**
  558. * Create an HTML select tag with options and a current value
  559. *
  560. * @param string $name The name and id of the select control
  561. * @param array $options An associative array of values to use as the select options
  562. * @param string $current The value of the currently selected option
  563. * @param array $properties An associative array of additional properties to assign to the select control
  564. * @return string The select control markup
  565. */
  566. public static function html_select( $name, $options, $current = null, $properties = array() )
  567. {
  568. $output = '<select id="' . $name . '" name="' . $name . '"';
  569. foreach ( $properties as $key => $value ) {
  570. $output .= " {$key}=\"{$value}\"";
  571. }
  572. $output .= ">\n";
  573. foreach ( $options as $value => $text ) {
  574. $output .= '<option value="' . $value . '"';
  575. if ( $current == (string)$value ) {
  576. $output .= ' selected="selected"';
  577. }
  578. $output .= '>' . $text . "</option>\n";
  579. }
  580. $output .= '</select>';
  581. return $output;
  582. }
  583. /**
  584. * Creates one or more HTML checkboxes
  585. * @param string The name of the checkbox element. If there are
  586. * multiple checkboxes for the same name, this method will
  587. * automatically apply "[]" at the end of the name
  588. * @param array An array of checkbox options. Each element should be
  589. * an array containing "name" and "value". If the checkbox
  590. * should be checked, it should have a "checked" element.
  591. * @return string The HTML of the checkboxes
  592. */
  593. public static function html_checkboxes( $name, $options )
  594. {
  595. $output = '';
  596. $multi = false;
  597. if ( count( $options > 1 ) ) {
  598. $multi = true;
  599. }
  600. foreach ( $options as $option ) {
  601. $output .= '<input type="checkbox" id="' . $option[ 'name' ] . '" name="' . $option[ 'name' ];
  602. if ( $multi ) {
  603. $output .= '[]';
  604. }
  605. $output .= '" value="' . $option[ 'value' ] . '"';
  606. if ( isset( $option[ 'checked' ] ) ) {
  607. $output .= ' checked';
  608. }
  609. $output .= '>';
  610. }
  611. return $output;
  612. }
  613. /**
  614. * Trims longer phrases to shorter ones with elipsis in the middle
  615. * @param string The string to truncate
  616. * @param integer The length of the returned string
  617. * @param bool Whether to place the ellipsis in the middle (true) or
  618. * at the end (false)
  619. * @return string The truncated string
  620. */
  621. public static function truncate( $str, $len = 10, $middle = true )
  622. {
  623. // make sure $len is a positive integer
  624. if ( ! is_numeric( $len ) || ( 0 > $len ) ) {
  625. return $str;
  626. }
  627. // if the string is less than the length specified, bail out
  628. if ( MultiByte::strlen( $str ) <= $len ) {
  629. return $str;
  630. }
  631. // okay. Shuold we place the ellipse in the middle?
  632. if ( $middle ) {
  633. // yes, so compute the size of each half of the string
  634. $len = round( ( $len - 3 ) / 2 );
  635. // and place an ellipse in between the pieces
  636. return MultiByte::substr( $str, 0, $len ) . '&hellip;' . MultiByte::substr( $str, -$len );
  637. }
  638. else {
  639. // no, the ellipse goes at the end
  640. $len = $len - 3;
  641. return MultiByte::substr( $str, 0, $len ) . '&hellip;';
  642. }
  643. }
  644. /**
  645. * Check the PHP syntax of the specified code.
  646. * Performs a syntax (lint) check on the specified code testing for scripting errors.
  647. *
  648. * @param string $code The code string to be evaluated. It does not have to contain PHP opening tags.
  649. * @return bool Returns true if the lint check passed, and false if the link check failed.
  650. */
  651. public static function php_check_syntax( $code, &$error = null )
  652. {
  653. $b = 0;
  654. foreach ( token_get_all( $code ) as $token ) {
  655. if ( is_array( $token ) ) {
  656. $token = token_name( $token[0] );
  657. }
  658. switch ( $token ) {
  659. case 'T_CURLY_OPEN':
  660. case 'T_DOLLAR_OPEN_CURLY_BRACES':
  661. case 'T_CURLY_OPENT_VARIABLE': // This is not documented in the manual. (11.05.07)
  662. case '{':
  663. ++$b;
  664. break;
  665. case '}':
  666. --$b;
  667. break;
  668. }
  669. }
  670. if ( $b ) {
  671. $error = _t( 'Unbalanced braces.' );
  672. return false; // Unbalanced braces would break the eval below
  673. }
  674. else {
  675. ob_start(); // Catch potential parse error messages
  676. $display_errors = ini_set( 'display_errors', 'on' ); // Make sure we have something to catch
  677. $error_reporting = error_reporting( E_ALL ^ E_NOTICE );
  678. $code = eval( ' if (0){' . $code . '}' ); // Put $code in a dead code sandbox to prevent its execution
  679. ini_set( 'display_errors', $display_errors ); // be a good citizen
  680. error_reporting( $error_reporting );
  681. $error = ob_get_clean();
  682. return false !== $code;
  683. }
  684. }
  685. /**
  686. * Check the PHP syntax of (and execute) the specified file.
  687. *
  688. * @see Utils::php_check_syntax()
  689. */
  690. public static function php_check_file_syntax( $file, &$error = null )
  691. {
  692. // Prepend and append PHP opening tags to prevent eval() failures.
  693. $code = ' ?>' . file_get_contents( $file ) . '<?php ';
  694. return self::php_check_syntax( $code, $error );
  695. }
  696. /**
  697. * Replacement for system glob that returns an empty array if there are no results
  698. *
  699. * @param string $pattern The glob() file search pattern
  700. * @param integer $flags Standard glob() flags
  701. * @return array An array of result files, or an empty array if no results found
  702. */
  703. public static function glob( $pattern, $flags = 0 )
  704. {
  705. if ( ! defined( 'GLOB_NOBRACE' ) || ! ( ( $flags & GLOB_BRACE ) == GLOB_BRACE ) ) {
  706. // this platform supports GLOB_BRACE out of the box or GLOB_BRACE wasn't requested
  707. $results = glob( $pattern, $flags );
  708. }
  709. elseif ( ! preg_match_all( '/\{.*?\}/', $pattern, $m ) ) {
  710. // GLOB_BRACE used, but this pattern doesn't even use braces
  711. $results = glob( $pattern, $flags ^ GLOB_BRACE );
  712. }
  713. else {
  714. // pattern uses braces, but platform doesn't support GLOB_BRACE
  715. $braces = array();
  716. foreach ( $m[0] as $raw_brace ) {
  717. $braces[ preg_quote( $raw_brace ) ] = '(?:' . str_replace( ',', '|', preg_quote( substr( $raw_brace, 1, -1 ), '/' ) ) . ')';
  718. }
  719. $new_pattern = preg_replace( '/\{.*?\}/', '*', $pattern );
  720. $pattern = preg_quote( $pattern, '/' );
  721. $pattern = str_replace( '\\*', '.*', $pattern );
  722. $pattern = str_replace( '\\?', '.', $pattern );
  723. $regex = '/' . str_replace( array_keys( $braces ), array_values( $braces ), $pattern ) . '/';
  724. $results = preg_grep( $regex, Utils::glob( $new_pattern, $flags ^ GLOB_BRACE ) );
  725. }
  726. if ( $results === false ) $results = array();
  727. return $results;
  728. }
  729. /**
  730. * Produces a human-readable size string.
  731. * For example, converts 12345 into 12.34KB
  732. *
  733. * @param integer $bytesize Number of bytes
  734. * @return string Human-readable string
  735. */
  736. public static function human_size( $bytesize )
  737. {
  738. $sizes = array(
  739. ' bytes',
  740. 'KiB',
  741. 'MiB',
  742. 'GiB',
  743. 'TiB',
  744. 'PiB'
  745. );
  746. $tick = 0;
  747. $max_tick = count( $sizes ) - 1;
  748. while ( $bytesize > 1024 && $tick < $max_tick ) {
  749. $tick++;
  750. $bytesize /= 1024;
  751. }
  752. return sprintf( '%0.2f%s', $bytesize, $sizes[ $tick ] );
  753. }
  754. /**
  755. * Convert a single non-array variable into an array with that one element
  756. *
  757. * @param mixed $element Some value, either an array or not
  758. * @return array Either the original array value, or the passed value as the single element of an array
  759. */
  760. public static function single_array( $element )
  761. {
  762. if ( !is_array( $element ) ) {
  763. return array( $element );
  764. }
  765. return $element;
  766. }
  767. /**
  768. * Return the mimetype of a file
  769. *
  770. * @param string $filename the path of a file
  771. * @return string The mimetype of the file.
  772. */
  773. public static function mimetype( $filename )
  774. {
  775. $mimetype = null;
  776. if ( function_exists( 'finfo_open' ) ) {
  777. $finfo = finfo_open( FILEINFO_MIME );
  778. $mimetype = finfo_file( $finfo, $filename );
  779. /* FILEINFO_MIME Returns the mime type and mime encoding as defined by RFC 2045.
  780. * So only return the mime type, not the encoding.
  781. */
  782. if ( ( $pos = strpos( $mimetype, ';' ) ) !== false ) {
  783. $mimetype = substr( $mimetype, 0, $pos );
  784. }
  785. finfo_close( $finfo );
  786. }
  787. if ( empty( $mimetype ) ) {
  788. $pi = pathinfo( $filename );
  789. switch ( strtolower( $pi[ 'extension' ] ) ) {
  790. // hacky, hacky, kludge, kludge...
  791. case 'jpg':
  792. case 'jpeg':
  793. $mimetype = 'image/jpeg';
  794. break;
  795. case 'gif':
  796. $mimetype = 'image/gif';
  797. break;
  798. case 'png':
  799. $mimetype = 'image/png';
  800. break;
  801. case 'mp3':
  802. $mimetype = 'audio/mpeg3';
  803. break;
  804. case 'wav':
  805. $mimetype = 'audio/wav';
  806. break;
  807. case 'mpg':
  808. case 'mpeg':
  809. $mimetype = 'video/mpeg';
  810. break;
  811. case 'swf':
  812. $mimetype = 'application/x-shockwave-flash';
  813. break;
  814. }
  815. }
  816. $mimetype = Plugins::filter( 'get_mime_type', $mimetype, $filename );
  817. return $mimetype;
  818. }
  819. /**
  820. * Returns a trailing slash or a string, depending on the value passed in
  821. *
  822. * @param mixed $value A trailing string value
  823. * @return string A slash if true, the value if value passed, emptystring if false
  824. */
  825. public static function trail( $value = false )
  826. {
  827. if ( $value === true ) {
  828. return '/';
  829. }
  830. elseif ( $value ) {
  831. return $value;
  832. }
  833. return '';
  834. }
  835. /**
  836. * Send email
  837. *
  838. * @param string $to The destination address
  839. * @param string $subject The subject of the message
  840. * @param string $message The message itself
  841. * @param array $headers An array of key=>value pairs for additional email headers
  842. * @param string $parameters Additional parameters to mail()
  843. * @return boolean True if sending the message succeeded
  844. */
  845. public static function mail( $to, $subject, $message, $headers = array(), $parameters = '' )
  846. {
  847. $mail = array(
  848. 'to' => $to,
  849. 'subject' => $subject,
  850. 'message' => $message,
  851. 'headers' => $headers,
  852. 'parameters' => $parameters,
  853. );
  854. $mail = Plugins::filter( 'mail', $mail );
  855. $handled = false;
  856. $handled = Plugins::filter( 'send_mail', $handled, $mail );
  857. if ( $handled ) {
  858. return true;
  859. }
  860. else {
  861. $additional_headers = array();
  862. foreach ( $headers as $header_key => $header_value ) {
  863. $header_key = trim( $header_key );
  864. $header_value = trim( $header_value );
  865. if ( strpos( $header_key . $header_value, "\n" ) === false ) {
  866. $additional_headers[] = "{$header_key}: {$header_value}";
  867. }
  868. }
  869. $additional_headers = implode( "\r\n", $additional_headers );
  870. }
  871. return mail( $to, $subject, $message, $additional_headers, $parameters );
  872. }
  873. /**
  874. * Create a random password of a specific length
  875. *
  876. * @param integer $length Length of the password, if not provded, 10
  877. * @return string A random password
  878. */
  879. public static function random_password( $length = 10 )
  880. {
  881. $password = '';
  882. $character_set = '1234567890!@#$^*qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVNBM';
  883. $data = str_split( $character_set );
  884. for ( $i = 0; $i < $length; $i++ ) {
  885. $password .= $data[rand( 1, strlen( $character_set ) ) - 1];
  886. }
  887. return $password;
  888. }
  889. /**
  890. * Does a bitwise OR of all the numbers in an array
  891. * @param array $input An array of integers
  892. * @return int The bitwise OR of the input array
  893. */
  894. public static function array_or( $input )
  895. {
  896. return array_reduce( $input, array( 'Utils', 'ror' ), 0 );
  897. }
  898. /**
  899. * Helper function for array_or
  900. */
  901. public static function ror( $v, $w )
  902. {
  903. return $v |= $w;
  904. }
  905. /**
  906. * Checks whether the correct HTTP method was used for the request
  907. *
  908. * @param array $expected Expected HTTP methods for the request
  909. */
  910. public static function check_request_method( $expected )
  911. {
  912. if ( !in_array( $_SERVER['REQUEST_METHOD'], $expected ) ) {
  913. if ( in_array( $_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD', 'POST', 'PUT', 'DELETE' ) ) ) {
  914. header( 'HTTP/1.1 405 Method Not Allowed', true, 405 );
  915. }
  916. else {
  917. header( 'HTTP/1.1 501 Method Not Implemented', true, 501 );
  918. }
  919. header( 'Allow: ' . implode( ',', $expected ) );
  920. exit;
  921. }
  922. }
  923. /**
  924. * Returns a regex pattern equivalent to the given glob pattern
  925. *
  926. * @return string regex pattern with '/' delimiter
  927. */
  928. public static function glob_to_regex( $glob )
  929. {
  930. $pattern = $glob;
  931. // braces need more work
  932. $braces = array();
  933. if ( preg_match_all( '/\{.*?\}/', $pattern, $m ) ) {
  934. foreach ( $m[0] as $raw_brace ) {
  935. $braces[ preg_quote( $raw_brace ) ] = '(?:' . str_replace( ',', '|', preg_quote( substr( $raw_brace, 1, -1 ), '/' ) ) . ')';
  936. }
  937. }
  938. $pattern = preg_quote( $pattern, '/' );
  939. $pattern = str_replace( '\\*', '.*', $pattern );
  940. $pattern = str_replace( '\\?', '.', $pattern );
  941. $pattern = str_replace( array_keys( $braces ), array_values( $braces ), $pattern );
  942. return '/' . $pattern . '/';
  943. }
  944. /**
  945. * Return the port used for a specific URL scheme
  946. *
  947. * @param string $scheme The scheme in question
  948. * @return integer the port used for the scheme
  949. */
  950. public static function scheme_ports( $scheme = null )
  951. {
  952. $scheme_ports = array(
  953. 'ftp' => 21,
  954. 'ssh' => 22,
  955. 'telnet' => 23,
  956. 'http' => 80,
  957. 'pop3' => 110,
  958. 'nntp' => 119,
  959. 'news' => 119,
  960. 'irc' => 194,
  961. 'imap3' => 220,
  962. 'https' => 443,
  963. 'nntps' => 563,
  964. 'imaps' => 993,
  965. 'pop3s' => 995,
  966. );
  967. if ( is_null( $scheme ) ) {
  968. return $scheme_ports;
  969. }
  970. return $scheme_ports[ $scheme ];
  971. }
  972. /**
  973. * determines if the given that is travesable in foreach
  974. *
  975. * @param mixed $data
  976. * @return bool
  977. */
  978. public static function is_traversable( $data )
  979. {
  980. return ( is_array( $data ) || ( $data instanceof Traversable && $data instanceof Countable ) );
  981. }
  982. /**
  983. * Get the remote IP address, but try and take into account users who are
  984. * behind proxies, whether they know it or not.
  985. * @return The client's IP address.
  986. */
  987. public static function get_ip()
  988. {
  989. if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
  990. return $_SERVER['HTTP_CLIENT_IP'];
  991. }
  992. else if ( isset( $_SERVER['HTTP_FORWARDED'] ) ) {
  993. return $_SERVER['HTTP_FORWARDED'];
  994. }
  995. else if ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) {
  996. return $_SERVER['HTTP_X_FORWARDED'];
  997. }
  998. else if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
  999. return $_SERVER['HTTP_X_FORWARDED_FOR'];
  1000. }
  1001. else if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
  1002. return $_SERVER['REMOTE_ADDR'];
  1003. }
  1004. else {
  1005. return '0.0.0.0';
  1006. }
  1007. }
  1008. /**
  1009. * Call htmlspecialchars() with the correct flags and encoding,
  1010. * without double escaping strings.
  1011. * See http://php.net/manual/en/function.htmlspecialchars.php for details on the parameters
  1012. * and purpose of the function.
  1013. *
  1014. * @todo Should htmlspecialchars_decode() be used instead of html_entity_decode()?
  1015. *
  1016. * @param $string. string. The string to escape
  1017. * @param $quote_flag. integer. Sets what quotes and doublequotes are escaped
  1018. * @param $encoding. string. The encoding of the passed string
  1019. *
  1020. * @return The escaped string
  1021. */
  1022. public static function htmlspecialchars( $string, $quote_flag = ENT_COMPAT, $encoding = 'UTF-8' )
  1023. {
  1024. return htmlspecialchars( html_entity_decode( $string, ENT_QUOTES, $encoding ), $quote_flag, $encoding );
  1025. }
  1026. /**
  1027. * Convenience function to find a usable PCRE regular expression
  1028. * delimiter for a particular string. (I.e., some character that
  1029. * *isn't* found in the string.)
  1030. *
  1031. * @param $string. string. The string for which to find a delimiter.
  1032. * @param $choices. string. Delimiters from which to choose one.
  1033. * @param $encoding. string. The encoding of the passed string
  1034. *
  1035. * @return A valid regex delimiter, or null if none of the choices work.
  1036. */
  1037. public static function regexdelim( $string, $choices = null )
  1038. {
  1039. /*
  1040. * Supply some default possibilities for delimiters if we
  1041. * weren't given an explicit list.
  1042. */
  1043. if ( ! isset( $choices ) ) {
  1044. $choices = sprintf( '%c%c%c%c%c%c%c',
  1045. 167, /* § */
  1046. 164, /* ¤ */
  1047. 165, /* ¥ */
  1048. ord( '`' ),
  1049. ord( '~' ),
  1050. ord( '%' ),
  1051. ord( '#' )
  1052. );
  1053. }
  1054. $a_delims = str_split( $choices );
  1055. /*
  1056. * Default condition is 'we didn't find one.'
  1057. */
  1058. $delim = null;
  1059. /*
  1060. * Check for each possibility by scanning the text for it.
  1061. * If it isn't found, it's a valid choice, so break out of the
  1062. * loop.
  1063. */
  1064. foreach ( $a_delims as $tdelim ) {
  1065. if ( ! strstr( $string, $tdelim ) ) {
  1066. $delim = $tdelim;
  1067. break;
  1068. }
  1069. }
  1070. return $delim;
  1071. }
  1072. /**
  1073. * Create a list of html element attributes from an associative array
  1074. *
  1075. * @param array $attrs An associative array of parameters
  1076. * @return string The parameters turned into a string of tag attributes
  1077. */
  1078. public static function html_attr($attrs)
  1079. {
  1080. $out = '';
  1081. foreach($attrs as $key => $value) {
  1082. if($value != '') {
  1083. $out .= ($out == '' ? '' : ' ') . $key . '="' . Utils::htmlspecialchars($value) . '"';
  1084. }
  1085. }
  1086. return $out;
  1087. }
  1088. }
  1089. ?>