text.lib.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * This is the text library for Chamilo.
  5. * It is loaded during the global initialization,
  6. * so the functions below are available everywhere.
  7. *
  8. * @package chamilo.library
  9. */
  10. define('EXERCISE_NUMBER_OF_DECIMALS', 2);
  11. /**
  12. * This function generates a unique name for a file on a given location
  13. * file names are changed to name_#.ext
  14. *
  15. * @param string $path
  16. * @param string $name
  17. *
  18. * @deprecated
  19. *
  20. * @return string new unique name
  21. */
  22. function unique_name($path, $name)
  23. {
  24. $ext = substr(strrchr($name, '.'), 0);
  25. $name_no_ext = substr($name, 0, strlen($name) - strlen(strstr($name, $ext)));
  26. $n = 0;
  27. $unique = '';
  28. while (file_exists($path . $name_no_ext . $unique . $ext)) {
  29. $unique = '_' . ++$n;
  30. }
  31. return $name_no_ext . $unique . $ext;
  32. }
  33. /**
  34. * Changes the file name extension from .php to .phps
  35. * Useful for securing a site.
  36. *
  37. * @author - Hugues Peeters <peeters@ipm.ucl.ac.be>
  38. * @param - file_name (string) name of a file
  39. * @return - the filenam phps'ized
  40. */
  41. function php2phps($file_name) {
  42. return preg_replace('/\.(php.?|phtml.?)(\.){0,1}.*$/i', '.phps', $file_name);
  43. }
  44. /**
  45. * Renames .htaccess & .HTACCESS to htaccess.txt
  46. *
  47. * @param string $filename
  48. * @return string
  49. */
  50. function htaccess2txt($filename) {
  51. return str_replace(array('.htaccess', '.HTACCESS'), array('htaccess.txt', 'htaccess.txt'), $filename);
  52. }
  53. /**
  54. * This function strips all html-tags found in the input string and outputs a pure text.
  55. * Mostly, the function is to be used before language or encoding detection of the input string.
  56. * @param string $string The input string with html-tags to be converted to plain text.
  57. * @return string The returned plain text as a result.
  58. */
  59. function api_html_to_text($string)
  60. {
  61. // These purifications have been found experimentally, for nice looking output.
  62. $string = preg_replace('/<br[^>]*>/i', "\n", $string);
  63. $string = preg_replace('/<\/?(div|p|h[1-6]|table|ol|ul|blockquote)[^>]*>/i', "\n", $string);
  64. $string = preg_replace('/<\/(tr|li)[^>]*>/i', "\n", $string);
  65. $string = preg_replace('/<\/(td|th)[^>]*>/i', "\t", $string);
  66. $string = strip_tags($string);
  67. // Line endings unification and cleaning.
  68. $string = str_replace(array("\r\n", "\n\r", "\r"), "\n", $string);
  69. $string = preg_replace('/\s*\n/', "\n", $string);
  70. $string = preg_replace('/\n+/', "\n", $string);
  71. return trim($string);
  72. }
  73. /**
  74. * Detects encoding of html-formatted text.
  75. * @param string $string The input html-formatted text.
  76. * @return string Returns the detected encoding.
  77. */
  78. function api_detect_encoding_html($string) {
  79. if (@preg_match('/<head.*(<meta[^>]*content=[^>]*>).*<\/head>/si', $string, $matches)) {
  80. if (@preg_match('/<meta[^>]*charset=(.*)["\';][^>]*>/si', $matches[1], $matches)) {
  81. return trim($matches[1]);
  82. }
  83. }
  84. return api_detect_encoding(api_html_to_text($string));
  85. }
  86. /**
  87. * Converts the text of a html-document to a given encoding, the meta-tag is changed accordingly.
  88. * @param string $string The input full-html document.
  89. * @param string The new encoding value to be set.
  90. */
  91. function api_set_encoding_html(&$string, $encoding) {
  92. $old_encoding = api_detect_encoding_html($string);
  93. if (@preg_match('/(.*<head.*)(<meta[^>]*content=[^>]*>)(.*<\/head>.*)/si', $string, $matches)) {
  94. $meta = $matches[2];
  95. if (@preg_match("/(<meta[^>]*charset=)(.*)([\"';][^>]*>)/si", $meta, $matches1)) {
  96. $meta = $matches1[1] . $encoding . $matches1[3];
  97. $string = $matches[1] . $meta . $matches[3];
  98. } else {
  99. $string = $matches[1] . '<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/>' . $matches[3];
  100. }
  101. } else {
  102. $count = 1;
  103. $string = str_ireplace('</head>', '<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/></head>', $string, $count);
  104. }
  105. $string = api_convert_encoding($string, $encoding, $old_encoding);
  106. }
  107. /**
  108. * Returns the title of a html document.
  109. * @param string $string The contents of the input document.
  110. * @param string $input_encoding The encoding of the input document. If the value is not set, it is detected.
  111. * @param string $$output_encoding The encoding of the retrieved title. If the value is not set, the system encoding is assumend.
  112. * @return string The retrieved title, html-entities and extra-whitespace between the words are cleaned.
  113. */
  114. function api_get_title_html(&$string, $output_encoding = null, $input_encoding = null) {
  115. if (@preg_match('/<head.+<title[^>]*>(.*)<\/title>/msi', $string, $matches)) {
  116. if (empty($output_encoding)) {
  117. $output_encoding = api_get_system_encoding();
  118. }
  119. if (empty($input_encoding)) {
  120. $input_encoding = api_detect_encoding_html($string);
  121. }
  122. return trim(@preg_replace('/\s+/', ' ', api_html_entity_decode(api_convert_encoding($matches[1], $output_encoding, $input_encoding), ENT_QUOTES, $output_encoding)));
  123. }
  124. return '';
  125. }
  126. /* XML processing functions */
  127. // A regular expression for accessing declared encoding within xml-formatted text.
  128. // Published by Steve Minutillo,
  129. // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss/
  130. define('_PCRE_XML_ENCODING', '/<\?xml.*encoding=[\'"](.*?)[\'"].*\?>/m');
  131. /**
  132. * Detects encoding of xml-formatted text.
  133. * @param string $string The input xml-formatted text.
  134. * @param string $default_encoding This is the default encoding to be returned if there is no way the xml-text's encoding to be detected. If it not spesified, the system encoding is assumed then.
  135. * @return string Returns the detected encoding.
  136. * @todo The second parameter is to be eliminated. See api_detect_encoding_html().
  137. */
  138. function api_detect_encoding_xml($string, $default_encoding = null) {
  139. if (preg_match(_PCRE_XML_ENCODING, $string, $matches)) {
  140. return $matches[1];
  141. }
  142. if (api_is_valid_utf8($string)) {
  143. return 'UTF-8';
  144. }
  145. if (empty($default_encoding)) {
  146. $default_encoding = _api_mb_internal_encoding();
  147. }
  148. return $default_encoding;
  149. }
  150. /**
  151. * Converts character encoding of a xml-formatted text. If inside the text the encoding is declared, it is modified accordingly.
  152. * @param string $string The text being converted.
  153. * @param string $to_encoding The encoding that text is being converted to.
  154. * @param string $from_encoding (optional) The encoding that text is being converted from. If it is omited, it is tried to be detected then.
  155. * @return string Returns the converted xml-text.
  156. */
  157. function api_convert_encoding_xml($string, $to_encoding, $from_encoding = null) {
  158. return _api_convert_encoding_xml($string, $to_encoding, $from_encoding);
  159. }
  160. /**
  161. * Converts character encoding of a xml-formatted text into UTF-8. If inside the text the encoding is declared, it is set to UTF-8.
  162. * @param string $string The text being converted.
  163. * @param string $from_encoding (optional) The encoding that text is being converted from. If it is omited, it is tried to be detected then.
  164. * @return string Returns the converted xml-text.
  165. */
  166. function api_utf8_encode_xml($string, $from_encoding = null) {
  167. return _api_convert_encoding_xml($string, 'UTF-8', $from_encoding);
  168. }
  169. /**
  170. * Converts character encoding of a xml-formatted text from UTF-8 into a specified encoding. If inside the text the encoding is declared, it is modified accordingly.
  171. * @param string $string The text being converted.
  172. * @param string $to_encoding (optional) The encoding that text is being converted to. If it is omited, the platform character set is assumed.
  173. * @return string Returns the converted xml-text.
  174. */
  175. function api_utf8_decode_xml($string, $to_encoding = 'UTF-8') {
  176. return _api_convert_encoding_xml($string, $to_encoding, 'UTF-8');
  177. }
  178. /**
  179. * Converts character encoding of a xml-formatted text. If inside the text the encoding is declared, it is modified accordingly.
  180. * @param string $string The text being converted.
  181. * @param string $to_encoding The encoding that text is being converted to.
  182. * @param string $from_encoding (optional) The encoding that text is being converted from. If the value is empty, it is tried to be detected then.
  183. * @return string Returns the converted xml-text.
  184. */
  185. function _api_convert_encoding_xml(&$string, $to_encoding, $from_encoding) {
  186. if (empty($from_encoding)) {
  187. $from_encoding = api_detect_encoding_xml($string);
  188. }
  189. if (!preg_match('/<\?xml.*\?>/m', $string, $matches)) {
  190. return api_convert_encoding('<?xml version="1.0" encoding="'.$to_encoding.'"?>'."\n".$string, $to_encoding, $from_encoding);
  191. }
  192. if (!preg_match(_PCRE_XML_ENCODING, $string)) {
  193. if (strpos($matches[0], 'standalone') !== false) {
  194. // The encoding option should precede the standalone option, othewise DOMDocument fails to load the document.
  195. $replace = str_replace('standalone', ' encoding="'.$to_encoding.'" standalone' , $matches[0]);
  196. } else {
  197. $replace = str_replace('?>', ' encoding="'.$to_encoding.'"?>' , $matches[0]);
  198. }
  199. return api_convert_encoding(str_replace($matches[0], $replace, $string), $to_encoding, $from_encoding);
  200. }
  201. global $_api_encoding;
  202. $_api_encoding = $to_encoding;
  203. return api_convert_encoding(preg_replace_callback(_PCRE_XML_ENCODING, '_api_convert_encoding_xml_callback', $string), $to_encoding, $from_encoding);
  204. }
  205. /**
  206. * A callback for serving the function _api_convert_encoding_xml().
  207. * @param array $matches Input array of matches corresponding to the xml-declaration.
  208. * @return string Returns the xml-declaration with modified encoding.
  209. */
  210. function _api_convert_encoding_xml_callback($matches) {
  211. global $_api_encoding;
  212. return str_replace($matches[1], $_api_encoding, $matches[0]);
  213. }
  214. /* Functions for supporting ASCIIMathML mathematical formulas and ASCIIsvg maathematical graphics */
  215. /**
  216. * Dectects ASCIIMathML formula presence within a given html text.
  217. * @param string $html The input html text.
  218. * @return bool Returns TRUE when there is a formula found or FALSE otherwise.
  219. */
  220. function api_contains_asciimathml($html) {
  221. if (!preg_match_all('/<span[^>]*class\s*=\s*[\'"](.*?)[\'"][^>]*>/mi', $html, $matches)) {
  222. return false;
  223. }
  224. foreach ($matches[1] as $string) {
  225. $string = ' '.str_replace(',', ' ', $string).' ';
  226. if (preg_match('/\sAM\s/m', $string)) {
  227. return true;
  228. }
  229. }
  230. return false;
  231. }
  232. /**
  233. * Dectects ASCIIsvg graphics presence within a given html text.
  234. * @param string $html The input html text.
  235. * @return bool Returns TRUE when there is a graph found or FALSE otherwise.
  236. */
  237. function api_contains_asciisvg($html) {
  238. if (!preg_match_all('/<embed([^>]*?)>/mi', $html, $matches)) {
  239. return false;
  240. }
  241. foreach ($matches[1] as $string) {
  242. $string = ' '.str_replace(',', ' ', $string).' ';
  243. if (preg_match('/sscr\s*=\s*[\'"](.*?)[\'"]/m', $string)) {
  244. return true;
  245. }
  246. }
  247. return false;
  248. }
  249. /* Miscellaneous text processing functions */
  250. /**
  251. * Convers a string from camel case into underscore.
  252. * Works correctly with ASCII strings only, implementation for human-language strings is not necessary.
  253. * @param string $string The input string (ASCII)
  254. * @return string The converted result string
  255. */
  256. function api_camel_case_to_underscore($string) {
  257. return strtolower(preg_replace('/([a-z])([A-Z])/', "$1_$2", $string));
  258. }
  259. /**
  260. * Converts a string with underscores into camel case.
  261. * Works correctly with ASCII strings only, implementation for human-language strings is not necessary.
  262. * @param string $string The input string (ASCII)
  263. * @param bool $capitalise_first_char (optional) If true (default), the function capitalises the first char in the result string.
  264. * @return string The converted result string
  265. */
  266. function api_underscore_to_camel_case($string, $capitalise_first_char = true) {
  267. if ($capitalise_first_char) {
  268. $string = ucfirst($string);
  269. }
  270. return preg_replace_callback('/_([a-z])/', '_api_camelize', $string);
  271. }
  272. // A function for internal use, only for this library.
  273. function _api_camelize($match) {
  274. return strtoupper($match[1]);
  275. }
  276. /**
  277. * Truncates a string.
  278. *
  279. * @author Brouckaert Olivier
  280. * @param string $text The text to truncate.
  281. * @param integer $length The approximate desired length. The length of the suffix below is to be added to have the total length of the result string.
  282. * @param string $suffix A suffix to be added as a replacement.
  283. * @param string $encoding (optional) The encoding to be used. If it is omitted, the platform character set will be used by default.
  284. * @param boolean $middle If this parameter is true, truncation is done in the middle of the string.
  285. * @return string Truncated string, decorated with the given suffix (replacement).
  286. */
  287. function api_trunc_str($text, $length = 30, $suffix = '...', $middle = false, $encoding = null) {
  288. if (empty($encoding)) {
  289. $encoding = api_get_system_encoding();
  290. }
  291. $text_length = api_strlen($text, $encoding);
  292. if ($text_length <= $length) {
  293. return $text;
  294. }
  295. if ($middle) {
  296. return rtrim(api_substr($text, 0, round($length / 2), $encoding)).$suffix.ltrim(api_substr($text, - round($length / 2), $text_length, $encoding));
  297. }
  298. return rtrim(api_substr($text, 0, $length, $encoding)).$suffix;
  299. }
  300. /**
  301. * Handling simple and double apostrofe in order that strings be stored properly in database
  302. *
  303. * @author Denes Nagy
  304. * @param string variable - the variable to be revised
  305. */
  306. function domesticate($input) {
  307. $input = stripslashes($input);
  308. $input = str_replace("'", "''", $input);
  309. $input = str_replace('"', "''", $input);
  310. return ($input);
  311. }
  312. /**
  313. * function make_clickable($string)
  314. *
  315. * @desc Completes url contained in the text with "<a href ...".
  316. * However the function simply returns the submitted text without any
  317. * transformation if it already contains some "<a href:" or "<img src=".
  318. * @param string $text text to be converted
  319. * @return text after conversion
  320. * @author Rewritten by Nathan Codding - Feb 6, 2001.
  321. * completed by Hugues Peeters - July 22, 2002
  322. *
  323. * Actually this function is taken from the PHP BB 1.4 script
  324. * - Goes through the given string, and replaces xxxx://yyyy with an HTML <a> tag linking
  325. * to that URL
  326. * - Goes through the given string, and replaces www.xxxx.yyyy[zzzz] with an HTML <a> tag linking
  327. * to http://www.xxxx.yyyy[/zzzz]
  328. * - Goes through the given string, and replaces xxxx@yyyy with an HTML mailto: tag linking
  329. * to that email address
  330. * - Only matches these 2 patterns either after a space, or at the beginning of a line
  331. *
  332. * Notes: the email one might get annoying - it's easy to make it more restrictive, though.. maybe
  333. * have it require something like xxxx@yyyy.zzzz or such. We'll see.
  334. */
  335. /**
  336. * Callback to convert URI match to HTML A element.
  337. *
  338. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link
  339. * make_clickable()}.
  340. *
  341. * @since Wordpress 2.3.2
  342. * @access private
  343. *
  344. * @param array $matches Single Regex Match.
  345. * @return string HTML A element with URI address.
  346. */
  347. function _make_url_clickable_cb($matches) {
  348. $url = $matches[2];
  349. if ( ')' == $matches[3] && strpos( $url, '(' ) ) {
  350. // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
  351. // Then we can let the parenthesis balancer do its thing below.
  352. $url .= $matches[3];
  353. $suffix = '';
  354. } else {
  355. $suffix = $matches[3];
  356. }
  357. // Include parentheses in the URL only if paired
  358. while ( substr_count( $url, '(' ) < substr_count( $url, ')' ) ) {
  359. $suffix = strrchr( $url, ')' ) . $suffix;
  360. $url = substr( $url, 0, strrpos( $url, ')' ) );
  361. }
  362. $url = esc_url($url);
  363. if ( empty($url) )
  364. return $matches[0];
  365. return $matches[1] . "<a href=\"$url\" rel=\"nofollow\">$url</a>" . $suffix;
  366. }
  367. /**
  368. * Checks and cleans a URL.
  369. *
  370. * A number of characters are removed from the URL. If the URL is for displaying
  371. * (the default behaviour) ampersands are also replaced. The 'clean_url' filter
  372. * is applied to the returned cleaned URL.
  373. *
  374. * @since wordpress 2.8.0
  375. * @uses wp_kses_bad_protocol() To only permit protocols in the URL set
  376. * via $protocols or the common ones set in the function.
  377. *
  378. * @param string $url The URL to be cleaned.
  379. * @param array $protocols Optional. An array of acceptable protocols.
  380. * Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
  381. * @param string $_context Private. Use esc_url_raw() for database usage.
  382. * @return string The cleaned $url after the 'clean_url' filter is applied.
  383. */
  384. function esc_url( $url, $protocols = null, $_context = 'display' ) {
  385. //$original_url = $url;
  386. if ( '' == $url )
  387. return $url;
  388. $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
  389. $strip = array('%0d', '%0a', '%0D', '%0A');
  390. $url = _deep_replace($strip, $url);
  391. $url = str_replace(';//', '://', $url);
  392. /* If the URL doesn't appear to contain a scheme, we
  393. * presume it needs http:// appended (unless a relative
  394. * link starting with /, # or ? or a php file).
  395. */
  396. if ( strpos($url, ':') === false && ! in_array( $url[0], array( '/', '#', '?' ) ) &&
  397. ! preg_match('/^[a-z0-9-]+?\.php/i', $url) )
  398. $url = 'http://' . $url;
  399. return Security::remove_XSS($url);
  400. /*// Replace ampersands and single quotes only when displaying.
  401. if ( 'display' == $_context ) {
  402. $url = wp_kses_normalize_entities( $url );
  403. $url = str_replace( '&amp;', '&#038;', $url );
  404. $url = str_replace( "'", '&#039;', $url );
  405. }
  406. if ( '/' === $url[0] ) {
  407. $good_protocol_url = $url;
  408. } else {
  409. if ( ! is_array( $protocols ) )
  410. $protocols = wp_allowed_protocols();
  411. $good_protocol_url = wp_kses_bad_protocol( $url, $protocols );
  412. if ( strtolower( $good_protocol_url ) != strtolower( $url ) )
  413. return '';
  414. }
  415. /**
  416. * Filter a string cleaned and escaped for output as a URL.
  417. *
  418. * @since 2.3.0
  419. *
  420. * @param string $good_protocol_url The cleaned URL to be returned.
  421. * @param string $original_url The URL prior to cleaning.
  422. * @param string $_context If 'display', replace ampersands and single quotes only.
  423. */
  424. //return apply_filters( 'clean_url', $good_protocol_url, $original_url, $_context );98
  425. }
  426. /**
  427. * Perform a deep string replace operation to ensure the values in $search are no longer present
  428. *
  429. * Repeats the replacement operation until it no longer replaces anything so as to remove "nested" values
  430. * e.g. $subject = '%0%0%0DDD', $search ='%0D', $result ='' rather than the '%0%0DD' that
  431. * str_replace would return
  432. *
  433. * @since wordpress 2.8.1
  434. * @access private
  435. *
  436. * @param string|array $search The value being searched for, otherwise known as the needle. An array may be used to designate multiple needles.
  437. * @param string $subject The string being searched and replaced on, otherwise known as the haystack.
  438. * @return string The string with the replaced svalues.
  439. */
  440. function _deep_replace( $search, $subject ) {
  441. $subject = (string) $subject;
  442. $count = 1;
  443. while ( $count ) {
  444. $subject = str_replace( $search, '', $subject, $count );
  445. }
  446. return $subject;
  447. }
  448. /**
  449. * Callback to convert URL match to HTML A element.
  450. *
  451. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link
  452. * make_clickable()}.
  453. *
  454. * @since wordpress 2.3.2
  455. * @access private
  456. *
  457. * @param array $matches Single Regex Match.
  458. * @return string HTML A element with URL address.
  459. */
  460. function _make_web_ftp_clickable_cb($matches) {
  461. $ret = '';
  462. $dest = $matches[2];
  463. $dest = 'http://' . $dest;
  464. $dest = esc_url($dest);
  465. if ( empty($dest) )
  466. return $matches[0];
  467. // removed trailing [.,;:)] from URL
  468. if ( in_array( substr($dest, -1), array('.', ',', ';', ':', ')') ) === true ) {
  469. $ret = substr($dest, -1);
  470. $dest = substr($dest, 0, strlen($dest)-1);
  471. }
  472. return $matches[1] . "<a href=\"$dest\" rel=\"nofollow\">$dest</a>$ret";
  473. }
  474. /**
  475. * Callback to convert email address match to HTML A element.
  476. *
  477. * This function was backported from 2.5.0 to 2.3.2. Regex callback for {@link
  478. * make_clickable()}.
  479. *
  480. * @since wordpress 2.3.2
  481. * @access private
  482. *
  483. * @param array $matches Single Regex Match.
  484. * @return string HTML A element with email address.
  485. */
  486. function _make_email_clickable_cb($matches) {
  487. $email = $matches[2] . '@' . $matches[3];
  488. return $matches[1] . "<a href=\"mailto:$email\">$email</a>";
  489. }
  490. /**
  491. * Convert plaintext URI to HTML links.
  492. *
  493. * Converts URI, www and ftp, and email addresses. Finishes by fixing links
  494. * within links.
  495. *
  496. * @since wordpress 0.71
  497. *
  498. * @param string $text Content to convert URIs.
  499. * @return string Content with converted URIs.
  500. */
  501. function make_clickable( $text ) {
  502. $r = '';
  503. $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
  504. $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
  505. foreach ( $textarr as $piece ) {
  506. if ( preg_match( '|^<code[\s>]|i', $piece ) || preg_match( '|^<pre[\s>]|i', $piece ) )
  507. $nested_code_pre++;
  508. elseif ( ( '</code>' === strtolower( $piece ) || '</pre>' === strtolower( $piece ) ) && $nested_code_pre )
  509. $nested_code_pre--;
  510. if ( $nested_code_pre || empty( $piece ) || ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) ) ) {
  511. $r .= $piece;
  512. continue;
  513. }
  514. // Long strings might contain expensive edge cases ...
  515. if ( 10000 < strlen( $piece ) ) {
  516. // ... break it up
  517. foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
  518. if ( 2101 < strlen( $chunk ) ) {
  519. $r .= $chunk; // Too big, no whitespace: bail.
  520. } else {
  521. $r .= make_clickable( $chunk );
  522. }
  523. }
  524. } else {
  525. $ret = " $piece "; // Pad with whitespace to simplify the regexes
  526. $url_clickable = '~
  527. ([\\s(<.,;:!?]) # 1: Leading whitespace, or punctuation
  528. ( # 2: URL
  529. [\\w]{1,20}+:// # Scheme and hier-part prefix
  530. (?=\S{1,2000}\s) # Limit to URLs less than about 2000 characters long
  531. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]*+ # Non-punctuation URL character
  532. (?: # Unroll the Loop: Only allow puctuation URL character if followed by a non-punctuation URL character
  533. [\'.,;:!?)] # Punctuation URL character
  534. [\\w\\x80-\\xff#%\\~/@\\[\\]*(+=&$-]++ # Non-punctuation URL character
  535. )*
  536. )
  537. (\)?) # 3: Trailing closing parenthesis (for parethesis balancing post processing)
  538. ~xS'; // The regex is a non-anchored pattern and does not have a single fixed starting character.
  539. // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
  540. $ret = preg_replace_callback( $url_clickable, '_make_url_clickable_cb', $ret );
  541. $ret = preg_replace_callback( '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret );
  542. $ret = preg_replace_callback( '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret );
  543. $ret = substr( $ret, 1, -1 ); // Remove our whitespace padding.
  544. $r .= $ret;
  545. }
  546. }
  547. // Cleanup of accidental links within links
  548. $r = preg_replace( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $r );
  549. return $r;
  550. }
  551. /**
  552. * Breaks a string into chunks by splitting at whitespace characters.
  553. * The length of each returned chunk is as close to the specified length goal as possible,
  554. * with the caveat that each chunk includes its trailing delimiter.
  555. * Chunks longer than the goal are guaranteed to not have any inner whitespace.
  556. *
  557. * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
  558. *
  559. * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters)
  560. *
  561. * <code>
  562. * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234 890 123456789 1234567890a 45678 1 3 5 7 90 ", 10 ) ==
  563. * array (
  564. * 0 => '1234 67890 ', // 11 characters: Perfect split
  565. * 1 => '1234 ', // 5 characters: '1234 67890a' was too long
  566. * 2 => '67890a cd ', // 10 characters: '67890a cd 1234' was too long
  567. * 3 => '1234 890 ', // 11 characters: Perfect split
  568. * 4 => '123456789 ', // 10 characters: '123456789 1234567890a' was too long
  569. * 5 => '1234567890a ', // 12 characters: Too long, but no inner whitespace on which to split
  570. * 6 => ' 45678 ', // 11 characters: Perfect split
  571. * 7 => '1 3 5 7 9', // 9 characters: End of $string
  572. * );
  573. * </code>
  574. *
  575. * @since wordpress 3.4.0
  576. * @access private
  577. *
  578. * @param string $string The string to split.
  579. * @param int $goal The desired chunk length.
  580. * @return array Numeric array of chunks.
  581. */
  582. function _split_str_by_whitespace( $string, $goal ) {
  583. $chunks = array();
  584. $string_nullspace = strtr( $string, "\r\n\t\v\f ", "\000\000\000\000\000\000" );
  585. while ( $goal < strlen( $string_nullspace ) ) {
  586. $pos = strrpos( substr( $string_nullspace, 0, $goal + 1 ), "\000" );
  587. if ( false === $pos ) {
  588. $pos = strpos( $string_nullspace, "\000", $goal + 1 );
  589. if ( false === $pos ) {
  590. break;
  591. }
  592. }
  593. $chunks[] = substr( $string, 0, $pos + 1 );
  594. $string = substr( $string, $pos + 1 );
  595. $string_nullspace = substr( $string_nullspace, $pos + 1 );
  596. }
  597. if ( $string ) {
  598. $chunks[] = $string;
  599. }
  600. return $chunks;
  601. }
  602. /**
  603. * This functions cuts a paragraph
  604. * i.e cut('Merry Xmas from Lima',13) = "Merry Xmas fr..."
  605. * @param string The text to "cut"
  606. * @param int Count of chars
  607. * @param bool Whether to embed in a <span title="...">...</span>
  608. * @return string
  609. * */
  610. function cut($text, $maxchar, $embed = false) {
  611. if (api_strlen($text) > $maxchar) {
  612. if ($embed) {
  613. return '<p title="'.$text.'">'.api_substr($text, 0, $maxchar).'...</p>';
  614. }
  615. return api_substr($text, 0, $maxchar).' ...';
  616. }
  617. return $text;
  618. }
  619. /**
  620. * Show a number as only integers if no decimals, but will show 2 decimals if exist.
  621. *
  622. * @param mixed Number to convert
  623. * @param int Decimal points 0=never, 1=if needed, 2=always
  624. * @return mixed An integer or a float depends on the parameter
  625. */
  626. function float_format($number, $flag = 1) {
  627. if (is_numeric($number)) {
  628. if (!$number) {
  629. $result = ($flag == 2 ? '0.'.str_repeat('0', EXERCISE_NUMBER_OF_DECIMALS) : '0');
  630. } else {
  631. if (floor($number) == $number) {
  632. $result = number_format($number, ($flag == 2 ? EXERCISE_NUMBER_OF_DECIMALS : 0));
  633. } else {
  634. $result = number_format(round($number, 2), ($flag == 0 ? 0 : EXERCISE_NUMBER_OF_DECIMALS));
  635. }
  636. }
  637. return $result;
  638. }
  639. }
  640. // TODO: To be checked for correct timezone management.
  641. /**
  642. * Function to obtain last week timestamps
  643. * @return array Times for every day inside week
  644. */
  645. function get_last_week() {
  646. $week = date('W');
  647. $year = date('Y');
  648. $lastweek = $week - 1;
  649. if ($lastweek == 0) {
  650. $week = 52;
  651. $year--;
  652. }
  653. $lastweek = sprintf("%02d", $lastweek);
  654. $arrdays = array();
  655. for ($i = 1; $i <= 7; $i++) {
  656. $arrdays[] = strtotime("$year"."W$lastweek"."$i");
  657. }
  658. return $arrdays;
  659. }
  660. /**
  661. * Gets the week from a day
  662. * @param string Date in UTC (2010-01-01 12:12:12)
  663. * @return int Returns an integer with the week number of the year
  664. */
  665. function get_week_from_day($date) {
  666. if (!empty($date)) {
  667. $time = api_strtotime($date,'UTC');
  668. return date('W', $time);
  669. } else {
  670. return date('W');
  671. }
  672. }
  673. /**
  674. * This function splits the string into words and then joins them back together again one by one.
  675. * Example: "Test example of a long string"
  676. * substrwords(5) = Test ... *
  677. * @param string
  678. * @param int the max number of character
  679. * @param string how the string will be end
  680. * @return a reduce string
  681. */
  682. function substrwords($text,$maxchar,$end='...')
  683. {
  684. if(strlen($text)>$maxchar)
  685. {
  686. $words=explode(" ",$text);
  687. $output = '';
  688. $i=0;
  689. while(1)
  690. {
  691. $length = (strlen($output)+strlen($words[$i]));
  692. if($length>$maxchar)
  693. {
  694. break;
  695. }
  696. else
  697. {
  698. $output = $output." ".$words[$i];
  699. $i++;
  700. };
  701. };
  702. }
  703. else
  704. {
  705. $output = $text;
  706. return $output;
  707. }
  708. return $output.$end;
  709. }
  710. function implode_with_key($glue, $array) {
  711. if (!empty($array)) {
  712. $string = '';
  713. foreach($array as $key => $value) {
  714. if (empty($value)) {
  715. $value = 'null';
  716. }
  717. $string .= $key." : ".$value." $glue ";
  718. }
  719. return $string;
  720. }
  721. return '';
  722. }
  723. /**
  724. * Transform the file size in a human readable format.
  725. *
  726. * @param int $file_size Size of the file in bytes
  727. * @return string A human readable representation of the file size
  728. */
  729. function format_file_size($file_size)
  730. {
  731. $file_size = intval($file_size);
  732. if ($file_size >= 1073741824) {
  733. $file_size = round($file_size / 1073741824 * 100) / 100 . 'G';
  734. } elseif($file_size >= 1048576) {
  735. $file_size = round($file_size / 1048576 * 100) / 100 . 'M';
  736. } elseif($file_size >= 1024) {
  737. $file_size = round($file_size / 1024 * 100) / 100 . 'k';
  738. } else {
  739. $file_size = $file_size . 'B';
  740. }
  741. return $file_size;
  742. }
  743. function return_datetime_from_array($array)
  744. {
  745. $year = '0000';
  746. $month = $day = $hours = $minutes = $seconds = '00';
  747. if (isset($array['Y']) && (isset($array['F']) || isset($array['M'])) && isset($array['d']) && isset($array['H']) && isset($array['i'])) {
  748. $year = $array['Y'];
  749. $month = isset($array['F'])?$array['F']:$array['M'];
  750. if (intval($month) < 10 ) $month = '0'.$month;
  751. $day = $array['d'];
  752. if (intval($day) < 10 ) $day = '0'.$day;
  753. $hours = $array['H'];
  754. if (intval($hours) < 10 ) $hours = '0'.$hours;
  755. $minutes = $array['i'];
  756. if (intval($minutes) < 10 ) $minutes = '0'.$minutes;
  757. }
  758. if (checkdate($month,$day,$year)) {
  759. $datetime = $year.'-'.$month.'-'.$day.' '.$hours.':'.$minutes.':'.$seconds;
  760. }
  761. return $datetime;
  762. }
  763. /**
  764. * Converts an string CLEANYO[admin][amann,acostea]
  765. * into an array:
  766. *
  767. * array(
  768. * CLEANYO
  769. * admin
  770. * amann,acostea
  771. * )
  772. *
  773. * @param $array
  774. * @return array
  775. */
  776. function bracketsToArray($array)
  777. {
  778. return preg_split('/[\[\]]+/', $array, -1, PREG_SPLIT_NO_EMPTY);
  779. }
  780. /**
  781. * @param string $string
  782. * @param bool $capitalizeFirstCharacter
  783. * @return mixed
  784. */
  785. function underScoreToCamelCase($string, $capitalizeFirstCharacter = true)
  786. {
  787. $str = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
  788. if (!$capitalizeFirstCharacter) {
  789. $str[0] = strtolower($str[0]);
  790. }
  791. return $str;
  792. }