text.lib.php 38 KB

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