text.lib.php 31 KB

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