Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2174 lines
56KB

  1. <?php
  2. require_once("include/template_processor.php");
  3. require_once("include/friendica_smarty.php");
  4. require_once("include/Smilies.php");
  5. require_once("include/map.php");
  6. require_once("mod/proxy.php");
  7. if(! function_exists('replace_macros')) {
  8. /**
  9. * This is our template processor
  10. *
  11. * @param string|FriendicaSmarty $s the string requiring macro substitution,
  12. * or an instance of FriendicaSmarty
  13. * @param array $r key value pairs (search => replace)
  14. * @return string substituted string
  15. */
  16. function replace_macros($s,$r) {
  17. $stamp1 = microtime(true);
  18. $a = get_app();
  19. // pass $baseurl to all templates
  20. $r['$baseurl'] = $a->get_baseurl();
  21. $t = $a->template_engine();
  22. try {
  23. $output = $t->replace_macros($s,$r);
  24. } catch (Exception $e) {
  25. echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
  26. }
  27. $a->save_timestamp($stamp1, "rendering");
  28. return $output;
  29. }}
  30. // random string, there are 86 characters max in text mode, 128 for hex
  31. // output is urlsafe
  32. define('RANDOM_STRING_HEX', 0x00 );
  33. define('RANDOM_STRING_TEXT', 0x01 );
  34. if(! function_exists('random_string')) {
  35. function random_string($size = 64,$type = RANDOM_STRING_HEX) {
  36. // generate a bit of entropy and run it through the whirlpool
  37. $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
  38. $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
  39. return(substr($s,0,$size));
  40. }}
  41. if(! function_exists('notags')) {
  42. /**
  43. * This is our primary input filter.
  44. *
  45. * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
  46. * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
  47. * after cleansing, and angle chars with the high bit set could get through as markup.
  48. *
  49. * This is now disabled because it was interfering with some legitimate unicode sequences
  50. * and hopefully there aren't a lot of those browsers left.
  51. *
  52. * Use this on any text input where angle chars are not valid or permitted
  53. * They will be replaced with safer brackets. This may be filtered further
  54. * if these are not allowed either.
  55. *
  56. * @param string $string Input string
  57. * @return string Filtered string
  58. */
  59. function notags($string) {
  60. return(str_replace(array("<",">"), array('[',']'), $string));
  61. // High-bit filter no longer used
  62. // return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
  63. }}
  64. if(! function_exists('escape_tags')) {
  65. /**
  66. * use this on "body" or "content" input where angle chars shouldn't be removed,
  67. * and allow them to be safely displayed.
  68. * @param string $string
  69. * @return string
  70. */
  71. function escape_tags($string) {
  72. return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
  73. }}
  74. // generate a string that's random, but usually pronounceable.
  75. // used to generate initial passwords
  76. if(! function_exists('autoname')) {
  77. /**
  78. * generate a string that's random, but usually pronounceable.
  79. * used to generate initial passwords
  80. * @param int $len
  81. * @return string
  82. */
  83. function autoname($len) {
  84. if($len <= 0)
  85. return '';
  86. $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
  87. if(mt_rand(0,5) == 4)
  88. $vowels[] = 'y';
  89. $cons = array(
  90. 'b','bl','br',
  91. 'c','ch','cl','cr',
  92. 'd','dr',
  93. 'f','fl','fr',
  94. 'g','gh','gl','gr',
  95. 'h',
  96. 'j',
  97. 'k','kh','kl','kr',
  98. 'l',
  99. 'm',
  100. 'n',
  101. 'p','ph','pl','pr',
  102. 'qu',
  103. 'r','rh',
  104. 's','sc','sh','sm','sp','st',
  105. 't','th','tr',
  106. 'v',
  107. 'w','wh',
  108. 'x',
  109. 'z','zh'
  110. );
  111. $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
  112. 'nd','ng','nk','nt','rn','rp','rt');
  113. $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
  114. 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
  115. $start = mt_rand(0,2);
  116. if($start == 0)
  117. $table = $vowels;
  118. else
  119. $table = $cons;
  120. $word = '';
  121. for ($x = 0; $x < $len; $x ++) {
  122. $r = mt_rand(0,count($table) - 1);
  123. $word .= $table[$r];
  124. if($table == $vowels)
  125. $table = array_merge($cons,$midcons);
  126. else
  127. $table = $vowels;
  128. }
  129. $word = substr($word,0,$len);
  130. foreach($noend as $noe) {
  131. if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
  132. $word = substr($word,0,-1);
  133. break;
  134. }
  135. }
  136. if(substr($word,-1) == 'q')
  137. $word = substr($word,0,-1);
  138. return $word;
  139. }}
  140. // escape text ($str) for XML transport
  141. // returns escaped text.
  142. if(! function_exists('xmlify')) {
  143. /**
  144. * escape text ($str) for XML transport
  145. * @param string $str
  146. * @return string Escaped text.
  147. */
  148. function xmlify($str) {
  149. /* $buffer = '';
  150. $len = mb_strlen($str);
  151. for($x = 0; $x < $len; $x ++) {
  152. $char = mb_substr($str,$x,1);
  153. switch( $char ) {
  154. case "\r" :
  155. break;
  156. case "&" :
  157. $buffer .= '&amp;';
  158. break;
  159. case "'" :
  160. $buffer .= '&apos;';
  161. break;
  162. case "\"" :
  163. $buffer .= '&quot;';
  164. break;
  165. case '<' :
  166. $buffer .= '&lt;';
  167. break;
  168. case '>' :
  169. $buffer .= '&gt;';
  170. break;
  171. case "\n" :
  172. $buffer .= "\n";
  173. break;
  174. default :
  175. $buffer .= $char;
  176. break;
  177. }
  178. }*/
  179. /*
  180. $buffer = mb_ereg_replace("&", "&amp;", $str);
  181. $buffer = mb_ereg_replace("'", "&apos;", $buffer);
  182. $buffer = mb_ereg_replace('"', "&quot;", $buffer);
  183. $buffer = mb_ereg_replace("<", "&lt;", $buffer);
  184. $buffer = mb_ereg_replace(">", "&gt;", $buffer);
  185. */
  186. $buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
  187. $buffer = trim($buffer);
  188. return($buffer);
  189. }}
  190. if(! function_exists('unxmlify')) {
  191. /**
  192. * undo an xmlify
  193. * @param string $s xml escaped text
  194. * @return string unescaped text
  195. */
  196. function unxmlify($s) {
  197. // $ret = str_replace('&amp;','&', $s);
  198. // $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
  199. /*$ret = mb_ereg_replace('&amp;', '&', $s);
  200. $ret = mb_ereg_replace('&apos;', "'", $ret);
  201. $ret = mb_ereg_replace('&quot;', '"', $ret);
  202. $ret = mb_ereg_replace('&lt;', "<", $ret);
  203. $ret = mb_ereg_replace('&gt;', ">", $ret);
  204. */
  205. $ret = htmlspecialchars_decode($s, ENT_QUOTES);
  206. return $ret;
  207. }}
  208. if(! function_exists('hex2bin')) {
  209. /**
  210. * convenience wrapper, reverse the operation "bin2hex"
  211. * @param string $s
  212. * @return number
  213. */
  214. function hex2bin($s) {
  215. if(! (is_string($s) && strlen($s)))
  216. return '';
  217. if(! ctype_xdigit($s)) {
  218. return($s);
  219. }
  220. return(pack("H*",$s));
  221. }}
  222. if(! function_exists('paginate_data')) {
  223. /**
  224. * Automatica pagination data.
  225. *
  226. * @param App $a App instance
  227. * @param int $count [optional] item count (used with alt pager)
  228. * @return Array data for pagination template
  229. */
  230. function paginate_data(&$a, $count=null) {
  231. $stripped = preg_replace('/([&?]page=[0-9]*)/','',$a->query_string);
  232. $stripped = str_replace('q=','',$stripped);
  233. $stripped = trim($stripped,'/');
  234. $pagenum = $a->pager['page'];
  235. if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped))
  236. $stripped .= "&offset=".urlencode($a->page_offset);
  237. $url = $stripped;
  238. $data = array();
  239. function _l(&$d, $name, $url, $text, $class="") {
  240. if (!strpos($url, "?")) {
  241. if ($pos = strpos($url, "&"))
  242. $url = substr($url, 0, $pos)."?".substr($url, $pos + 1);
  243. }
  244. $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
  245. }
  246. if (!is_null($count)){
  247. // alt pager
  248. if($a->pager['page']>1)
  249. _l($data, "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
  250. if($count>0)
  251. _l($data, "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
  252. } else {
  253. // full pager
  254. if($a->pager['total'] > $a->pager['itemspage']) {
  255. if($a->pager['page'] != 1)
  256. _l($data, "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
  257. _l($data, "first", $url."&page=1", t('first'));
  258. $numpages = $a->pager['total'] / $a->pager['itemspage'];
  259. $numstart = 1;
  260. $numstop = $numpages;
  261. if($numpages > 14) {
  262. $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
  263. $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
  264. }
  265. $pages = array();
  266. for($i = $numstart; $i <= $numstop; $i++){
  267. if($i == $a->pager['page'])
  268. _l($pages, $i, "#", $i, "current");
  269. else
  270. _l($pages, $i, $url."&page=$i", $i, "n");
  271. }
  272. if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
  273. if($i == $a->pager['page'])
  274. _l($pages, $i, "#", $i, "current");
  275. else
  276. _l($pages, $i, $url."&page=$i", $i, "n");
  277. }
  278. $data['pages'] = $pages;
  279. $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
  280. _l($data, "last", $url."&page=$lastpage", t('last'));
  281. if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
  282. _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
  283. }
  284. }
  285. return $data;
  286. }}
  287. if(! function_exists('paginate')) {
  288. /**
  289. * Automatic pagination.
  290. *
  291. * To use, get the count of total items.
  292. * Then call $a->set_pager_total($number_items);
  293. * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
  294. * Then call paginate($a) after the end of the display loop to insert the pager block on the page
  295. * (assuming there are enough items to paginate).
  296. * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
  297. * will limit the results to the correct items for the current page.
  298. * The actual page handling is then accomplished at the application layer.
  299. *
  300. * @param App $a App instance
  301. * @return string html for pagination #FIXME remove html
  302. */
  303. function paginate(&$a) {
  304. $data = paginate_data($a);
  305. $tpl = get_markup_template("paginate.tpl");
  306. return replace_macros($tpl, array("pager" => $data));
  307. }}
  308. if(! function_exists('alt_pager')) {
  309. /**
  310. * Alternative pager
  311. * @param App $a App instance
  312. * @param int $i
  313. * @return string html for pagination #FIXME remove html
  314. */
  315. function alt_pager(&$a, $i) {
  316. $data = paginate_data($a, $i);
  317. $tpl = get_markup_template("paginate.tpl");
  318. return replace_macros($tpl, array('pager' => $data));
  319. }}
  320. if(! function_exists('scroll_loader')) {
  321. /**
  322. * Loader for infinite scrolling
  323. * @return string html for loader
  324. */
  325. function scroll_loader() {
  326. $tpl = get_markup_template("scroll_loader.tpl");
  327. return replace_macros($tpl, array(
  328. 'wait' => t('Loading more entries...'),
  329. 'end' => t('The end')
  330. ));
  331. }}
  332. if(! function_exists('expand_acl')) {
  333. /**
  334. * Turn user/group ACLs stored as angle bracketed text into arrays
  335. *
  336. * @param string $s
  337. * @return array
  338. */
  339. function expand_acl($s) {
  340. // turn string array of angle-bracketed elements into numeric array
  341. // e.g. "<1><2><3>" => array(1,2,3);
  342. $ret = array();
  343. if(strlen($s)) {
  344. $t = str_replace('<','',$s);
  345. $a = explode('>',$t);
  346. foreach($a as $aa) {
  347. if(intval($aa))
  348. $ret[] = intval($aa);
  349. }
  350. }
  351. return $ret;
  352. }}
  353. if(! function_exists('sanitise_acl')) {
  354. /**
  355. * Wrap ACL elements in angle brackets for storage
  356. * @param string $item
  357. */
  358. function sanitise_acl(&$item) {
  359. if(intval($item))
  360. $item = '<' . intval(notags(trim($item))) . '>';
  361. else
  362. unset($item);
  363. }}
  364. if(! function_exists('perms2str')) {
  365. /**
  366. * Convert an ACL array to a storable string
  367. *
  368. * Normally ACL permissions will be an array.
  369. * We'll also allow a comma-separated string.
  370. *
  371. * @param string|array $p
  372. * @return string
  373. */
  374. function perms2str($p) {
  375. $ret = '';
  376. if(is_array($p))
  377. $tmp = $p;
  378. else
  379. $tmp = explode(',',$p);
  380. if(is_array($tmp)) {
  381. array_walk($tmp,'sanitise_acl');
  382. $ret = implode('',$tmp);
  383. }
  384. return $ret;
  385. }}
  386. if(! function_exists('item_new_uri')) {
  387. /**
  388. * generate a guaranteed unique (for this domain) item ID for ATOM
  389. * safe from birthday paradox
  390. *
  391. * @param string $hostname
  392. * @param int $uid
  393. * @return string
  394. */
  395. function item_new_uri($hostname,$uid, $guid = "") {
  396. do {
  397. $dups = false;
  398. if ($guid == "")
  399. $hash = get_guid(32);
  400. else {
  401. $hash = $guid;
  402. $guid = "";
  403. }
  404. $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
  405. $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
  406. dbesc($uri));
  407. if(count($r))
  408. $dups = true;
  409. } while($dups == true);
  410. return $uri;
  411. }}
  412. // Generate a guaranteed unique photo ID.
  413. // safe from birthday paradox
  414. if(! function_exists('photo_new_resource')) {
  415. /**
  416. * Generate a guaranteed unique photo ID.
  417. * safe from birthday paradox
  418. *
  419. * @return string
  420. */
  421. function photo_new_resource() {
  422. do {
  423. $found = false;
  424. $resource = hash('md5',uniqid(mt_rand(),true));
  425. $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
  426. dbesc($resource)
  427. );
  428. if(count($r))
  429. $found = true;
  430. } while($found == true);
  431. return $resource;
  432. }}
  433. if(! function_exists('load_view_file')) {
  434. /**
  435. * @deprecated
  436. * wrapper to load a view template, checking for alternate
  437. * languages before falling back to the default
  438. *
  439. * @global string $lang
  440. * @global App $a
  441. * @param string $s view name
  442. * @return string
  443. */
  444. function load_view_file($s) {
  445. global $lang, $a;
  446. if(! isset($lang))
  447. $lang = 'en';
  448. $b = basename($s);
  449. $d = dirname($s);
  450. if(file_exists("$d/$lang/$b")) {
  451. $stamp1 = microtime(true);
  452. $content = file_get_contents("$d/$lang/$b");
  453. $a->save_timestamp($stamp1, "file");
  454. return $content;
  455. }
  456. $theme = current_theme();
  457. if(file_exists("$d/theme/$theme/$b")) {
  458. $stamp1 = microtime(true);
  459. $content = file_get_contents("$d/theme/$theme/$b");
  460. $a->save_timestamp($stamp1, "file");
  461. return $content;
  462. }
  463. $stamp1 = microtime(true);
  464. $content = file_get_contents($s);
  465. $a->save_timestamp($stamp1, "file");
  466. return $content;
  467. }}
  468. if(! function_exists('get_intltext_template')) {
  469. /**
  470. * load a view template, checking for alternate
  471. * languages before falling back to the default
  472. *
  473. * @global string $lang
  474. * @param string $s view path
  475. * @return string
  476. */
  477. function get_intltext_template($s) {
  478. global $lang;
  479. $a = get_app();
  480. $engine = '';
  481. if($a->theme['template_engine'] === 'smarty3')
  482. $engine = "/smarty3";
  483. if(! isset($lang))
  484. $lang = 'en';
  485. if(file_exists("view/lang/$lang$engine/$s")) {
  486. $stamp1 = microtime(true);
  487. $content = file_get_contents("view/lang/$lang$engine/$s");
  488. $a->save_timestamp($stamp1, "file");
  489. return $content;
  490. } elseif(file_exists("view/lang/en$engine/$s")) {
  491. $stamp1 = microtime(true);
  492. $content = file_get_contents("view/lang/en$engine/$s");
  493. $a->save_timestamp($stamp1, "file");
  494. return $content;
  495. } else {
  496. $stamp1 = microtime(true);
  497. $content = file_get_contents("view$engine/$s");
  498. $a->save_timestamp($stamp1, "file");
  499. return $content;
  500. }
  501. }}
  502. if(! function_exists('get_markup_template')) {
  503. /**
  504. * load template $s
  505. *
  506. * @param string $s
  507. * @param string $root
  508. * @return string
  509. */
  510. function get_markup_template($s, $root = '') {
  511. $stamp1 = microtime(true);
  512. $a = get_app();
  513. $t = $a->template_engine();
  514. try {
  515. $template = $t->get_template_file($s, $root);
  516. } catch (Exception $e) {
  517. echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
  518. }
  519. $a->save_timestamp($stamp1, "file");
  520. return $template;
  521. }}
  522. if(! function_exists("get_template_file")) {
  523. /**
  524. *
  525. * @param App $a
  526. * @param string $filename
  527. * @param string $root
  528. * @return string
  529. */
  530. function get_template_file($a, $filename, $root = '') {
  531. $theme = current_theme();
  532. // Make sure $root ends with a slash /
  533. if($root !== '' && $root[strlen($root)-1] !== '/')
  534. $root = $root . '/';
  535. if(file_exists("{$root}view/theme/$theme/$filename"))
  536. $template_file = "{$root}view/theme/$theme/$filename";
  537. elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
  538. $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
  539. elseif (file_exists("{$root}/$filename"))
  540. $template_file = "{$root}/$filename";
  541. else
  542. $template_file = "{$root}view/$filename";
  543. return $template_file;
  544. }}
  545. if(! function_exists('attribute_contains')) {
  546. /**
  547. * for html,xml parsing - let's say you've got
  548. * an attribute foobar="class1 class2 class3"
  549. * and you want to find out if it contains 'class3'.
  550. * you can't use a normal sub string search because you
  551. * might match 'notclass3' and a regex to do the job is
  552. * possible but a bit complicated.
  553. * pass the attribute string as $attr and the attribute you
  554. * are looking for as $s - returns true if found, otherwise false
  555. *
  556. * @param string $attr attribute value
  557. * @param string $s string to search
  558. * @return boolean True if found, False otherwise
  559. */
  560. function attribute_contains($attr,$s) {
  561. $a = explode(' ', $attr);
  562. if(count($a) && in_array($s,$a))
  563. return true;
  564. return false;
  565. }}
  566. if (! function_exists('logger')) {
  567. /* setup int->string log level map */
  568. $LOGGER_LEVELS = array();
  569. /**
  570. * @brief Logs the given message at the given log level
  571. *
  572. * log levels:
  573. * LOGGER_NORMAL (default)
  574. * LOGGER_TRACE
  575. * LOGGER_DEBUG
  576. * LOGGER_DATA
  577. * LOGGER_ALL
  578. *
  579. * @global App $a
  580. * @global dba $db
  581. * @global array $LOGGER_LEVELS
  582. * @param string $msg
  583. * @param int $level
  584. */
  585. function logger($msg, $level = 0) {
  586. $a = get_app();
  587. global $db;
  588. global $LOGGER_LEVELS;
  589. // turn off logger in install mode
  590. if (
  591. $a->module == 'install'
  592. || ! ($db && $db->connected)
  593. ) {
  594. return;
  595. }
  596. $debugging = get_config('system','debugging');
  597. $logfile = get_config('system','logfile');
  598. $loglevel = intval(get_config('system','loglevel'));
  599. if (
  600. ! $debugging
  601. || ! $logfile
  602. || $level > $loglevel
  603. ) {
  604. return;
  605. }
  606. if (count($LOGGER_LEVELS) == 0) {
  607. foreach (get_defined_constants() as $k => $v) {
  608. if (substr($k, 0, 7) == "LOGGER_") {
  609. $LOGGER_LEVELS[$v] = substr($k, 7, 7);
  610. }
  611. }
  612. }
  613. $process_id = session_id();
  614. if ($process_id == '') {
  615. $process_id = get_app()->process_id;
  616. }
  617. $callers = debug_backtrace();
  618. $logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
  619. datetime_convert(),
  620. $process_id,
  621. $LOGGER_LEVELS[$level],
  622. basename($callers[0]['file']),
  623. $callers[0]['line'],
  624. $callers[1]['function'],
  625. $msg
  626. );
  627. $stamp1 = microtime(true);
  628. @file_put_contents($logfile, $logline, FILE_APPEND);
  629. $a->save_timestamp($stamp1, "file");
  630. }}
  631. if(! function_exists('activity_match')) {
  632. /**
  633. * Compare activity uri. Knows about activity namespace.
  634. *
  635. * @param string $haystack
  636. * @param string $needle
  637. * @return boolean
  638. */
  639. function activity_match($haystack,$needle) {
  640. if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
  641. return true;
  642. return false;
  643. }}
  644. /**
  645. * @brief Pull out all #hashtags and @person tags from $string.
  646. *
  647. * We also get @person@domain.com - which would make
  648. * the regex quite complicated as tags can also
  649. * end a sentence. So we'll run through our results
  650. * and strip the period from any tags which end with one.
  651. * Returns array of tags found, or empty array.
  652. *
  653. * @param string $string Post content
  654. * @return array List of tag and person names
  655. */
  656. function get_tags($string) {
  657. $ret = array();
  658. // Convert hashtag links to hashtags
  659. $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
  660. // ignore anything in a code block
  661. $string = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $string);
  662. // Force line feeds at bbtags
  663. $string = str_replace(array('[', ']'), array("\n[", "]\n"), $string);
  664. // ignore anything in a bbtag
  665. $string = preg_replace('/\[(.*?)\]/sm', '', $string);
  666. // Match full names against @tags including the space between first and last
  667. // We will look these up afterward to see if they are full names or not recognisable.
  668. if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
  669. foreach ($matches[1] as $match) {
  670. if (strstr($match, ']')) {
  671. // we might be inside a bbcode color tag - leave it alone
  672. continue;
  673. }
  674. if (substr($match, -1, 1) === '.') {
  675. $ret[] = substr($match, 0, -1);
  676. } else {
  677. $ret[] = $match;
  678. }
  679. }
  680. }
  681. // Otherwise pull out single word tags. These can be @nickname, @first_last
  682. // and #hash tags.
  683. if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/', $string, $matches)) {
  684. foreach($matches[1] as $match) {
  685. if (strstr($match, ']')) {
  686. // we might be inside a bbcode color tag - leave it alone
  687. continue;
  688. }
  689. if (substr($match, -1, 1) === '.') {
  690. $match = substr($match,0,-1);
  691. }
  692. // ignore strictly numeric tags like #1
  693. if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
  694. continue;
  695. }
  696. // try not to catch url fragments
  697. if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
  698. continue;
  699. }
  700. $ret[] = $match;
  701. }
  702. }
  703. return $ret;
  704. }
  705. //
  706. if(! function_exists('qp')) {
  707. /**
  708. * quick and dirty quoted_printable encoding
  709. *
  710. * @param string $s
  711. * @return string
  712. */
  713. function qp($s) {
  714. return str_replace ("%","=",rawurlencode($s));
  715. }}
  716. if(! function_exists('contact_block')) {
  717. /**
  718. * Get html for contact block.
  719. *
  720. * @template contact_block.tpl
  721. * @hook contact_block_end (contacts=>array, output=>string)
  722. * @return string
  723. */
  724. function contact_block() {
  725. $o = '';
  726. $a = get_app();
  727. $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
  728. if($shown === false)
  729. $shown = 24;
  730. if($shown == 0)
  731. return;
  732. if((! is_array($a->profile)) || ($a->profile['hide-friends']))
  733. return $o;
  734. $r = q("SELECT COUNT(*) AS `total` FROM `contact`
  735. WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
  736. AND NOT `hidden` AND NOT `archive`
  737. AND `network` IN ('%s', '%s', '%s')",
  738. intval($a->profile['uid']),
  739. dbesc(NETWORK_DFRN),
  740. dbesc(NETWORK_OSTATUS),
  741. dbesc(NETWORK_DIASPORA)
  742. );
  743. if(count($r)) {
  744. $total = intval($r[0]['total']);
  745. }
  746. if(! $total) {
  747. $contacts = t('No contacts');
  748. $micropro = Null;
  749. } else {
  750. // Splitting the query in two parts makes it much faster
  751. $r = q("SELECT `id` FROM `contact`
  752. WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
  753. AND NOT `hidden` AND NOT `archive`
  754. AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
  755. intval($a->profile['uid']),
  756. dbesc(NETWORK_DFRN),
  757. dbesc(NETWORK_OSTATUS),
  758. dbesc(NETWORK_DIASPORA),
  759. intval($shown)
  760. );
  761. if ($r) {
  762. $contacts = "";
  763. foreach ($r AS $contact)
  764. $contacts[] = $contact["id"];
  765. $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `thumb`, `network` FROM `contact` WHERE `id` IN (%s)",
  766. dbesc(implode(",", $contacts)));
  767. if(count($r)) {
  768. $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
  769. $micropro = Array();
  770. foreach($r as $rr) {
  771. $micropro[] = micropro($rr,true,'mpfriend');
  772. }
  773. }
  774. }
  775. }
  776. $tpl = get_markup_template('contact_block.tpl');
  777. $o = replace_macros($tpl, array(
  778. '$contacts' => $contacts,
  779. '$nickname' => $a->profile['nickname'],
  780. '$viewcontacts' => t('View Contacts'),
  781. '$micropro' => $micropro,
  782. ));
  783. $arr = array('contacts' => $r, 'output' => $o);
  784. call_hooks('contact_block_end', $arr);
  785. return $o;
  786. }}
  787. /**
  788. * @brief Format contacts as picture links or as texxt links
  789. *
  790. * @param array $contact Array with contacts which contains an array with
  791. * int 'id' => The ID of the contact
  792. * int 'uid' => The user ID of the user who owns this data
  793. * string 'name' => The name of the contact
  794. * string 'url' => The url to the profile page of the contact
  795. * string 'addr' => The webbie of the contact (e.g.) username@friendica.com
  796. * string 'network' => The network to which the contact belongs to
  797. * string 'thumb' => The contact picture
  798. * string 'click' => js code which is performed when clicking on the contact
  799. * @param boolean $redirect If true try to use the redir url if it's possible
  800. * @param string $class CSS class for the
  801. * @param boolean $textmode If true display the contacts as text links
  802. * if false display the contacts as picture links
  803. * @return string Formatted html
  804. */
  805. function micropro($contact, $redirect = false, $class = '', $textmode = false) {
  806. // Use the contact URL if no address is available
  807. if ($contact["addr"] == "")
  808. $contact["addr"] = $contact["url"];
  809. $url = $contact['url'];
  810. $sparkle = '';
  811. $redir = false;
  812. if($redirect) {
  813. $a = get_app();
  814. $redirect_url = 'redir/' . $contact['id'];
  815. if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
  816. $redir = true;
  817. $url = $redirect_url;
  818. $sparkle = ' sparkle';
  819. }
  820. else
  821. $url = zrl($url);
  822. }
  823. // If there is some js available we don't need the url
  824. if(x($contact,'click'))
  825. $url = '';
  826. return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
  827. '$click' => (($contact['click']) ? $contact['click'] : ''),
  828. '$class' => $class,
  829. '$url' => $url,
  830. '$photo' => proxy_url($contact['thumb'], false, PROXY_SIZE_THUMB),
  831. '$name' => $contact['name'],
  832. 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
  833. '$parkle' => $sparkle,
  834. '$redir' => $redir,
  835. ));
  836. }
  837. if(! function_exists('search')) {
  838. /**
  839. * search box
  840. *
  841. * @param string $s search query
  842. * @param string $id html id
  843. * @param string $url search url
  844. * @param boolean $savedsearch show save search button
  845. */
  846. function search($s,$id='search-box',$url='search',$save = false, $aside = true) {
  847. $a = get_app();
  848. $values = array(
  849. '$s' => htmlspecialchars($s),
  850. '$id' => $id,
  851. '$action_url' => $url,
  852. '$search_label' => t('Search'),
  853. '$save_label' => t('Save'),
  854. '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
  855. '$search_hint' => t('@name, !forum, #tags, content'),
  856. );
  857. if (!$aside) {
  858. $values['$searchoption'] = array(
  859. t("Full Text"),
  860. t("Tags"),
  861. t("Contacts"));
  862. if (get_config('system','poco_local_search'))
  863. $values['$searchoption'][] = t("Forums");
  864. }
  865. return replace_macros(get_markup_template('searchbox.tpl'), $values);
  866. }}
  867. if(! function_exists('valid_email')) {
  868. /**
  869. * Check if $x is a valid email string
  870. *
  871. * @param string $x
  872. * @return boolean
  873. */
  874. function valid_email($x){
  875. // Removed because Fabio told me so.
  876. //if(get_config('system','disable_email_validation'))
  877. // return true;
  878. if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
  879. return true;
  880. return false;
  881. }}
  882. if(! function_exists('linkify')) {
  883. /**
  884. * Replace naked text hyperlink with HTML formatted hyperlink
  885. *
  886. * @param string $s
  887. */
  888. function linkify($s) {
  889. $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
  890. $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
  891. return($s);
  892. }}
  893. /**
  894. * Load poke verbs
  895. *
  896. * @return array index is present tense verb
  897. value is array containing past tense verb, translation of present, translation of past
  898. * @hook poke_verbs pokes array
  899. */
  900. function get_poke_verbs() {
  901. // index is present tense verb
  902. // value is array containing past tense verb, translation of present, translation of past
  903. $arr = array(
  904. 'poke' => array( 'poked', t('poke'), t('poked')),
  905. 'ping' => array( 'pinged', t('ping'), t('pinged')),
  906. 'prod' => array( 'prodded', t('prod'), t('prodded')),
  907. 'slap' => array( 'slapped', t('slap'), t('slapped')),
  908. 'finger' => array( 'fingered', t('finger'), t('fingered')),
  909. 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
  910. );
  911. call_hooks('poke_verbs', $arr);
  912. return $arr;
  913. }
  914. /**
  915. * Load moods
  916. * @return array index is mood, value is translated mood
  917. * @hook mood_verbs moods array
  918. */
  919. function get_mood_verbs() {
  920. $arr = array(
  921. 'happy' => t('happy'),
  922. 'sad' => t('sad'),
  923. 'mellow' => t('mellow'),
  924. 'tired' => t('tired'),
  925. 'perky' => t('perky'),
  926. 'angry' => t('angry'),
  927. 'stupefied' => t('stupified'),
  928. 'puzzled' => t('puzzled'),
  929. 'interested' => t('interested'),
  930. 'bitter' => t('bitter'),
  931. 'cheerful' => t('cheerful'),
  932. 'alive' => t('alive'),
  933. 'annoyed' => t('annoyed'),
  934. 'anxious' => t('anxious'),
  935. 'cranky' => t('cranky'),
  936. 'disturbed' => t('disturbed'),
  937. 'frustrated' => t('frustrated'),
  938. 'motivated' => t('motivated'),
  939. 'relaxed' => t('relaxed'),
  940. 'surprised' => t('surprised'),
  941. );
  942. call_hooks('mood_verbs', $arr);
  943. return $arr;
  944. }
  945. if(! function_exists('day_translate')) {
  946. /**
  947. * Translate days and months names
  948. *
  949. * @param string $s
  950. * @return string
  951. */
  952. function day_translate($s) {
  953. $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
  954. array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
  955. $s);
  956. $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
  957. array( t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')),
  958. $ret);
  959. return $ret;
  960. }}
  961. if(! function_exists('normalise_link')) {
  962. /**
  963. * Normalize url
  964. *
  965. * @param string $url
  966. * @return string
  967. */
  968. function normalise_link($url) {
  969. $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
  970. return(rtrim($ret,'/'));
  971. }}
  972. if(! function_exists('link_compare')) {
  973. /**
  974. * Compare two URLs to see if they are the same, but ignore
  975. * slight but hopefully insignificant differences such as if one
  976. * is https and the other isn't, or if one is www.something and
  977. * the other isn't - and also ignore case differences.
  978. *
  979. * @param string $a first url
  980. * @param string $b second url
  981. * @return boolean True if the URLs match, otherwise False
  982. *
  983. */
  984. function link_compare($a,$b) {
  985. if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
  986. return true;
  987. return false;
  988. }}
  989. /**
  990. * @brief Find any non-embedded images in private items and add redir links to them
  991. *
  992. * @param App $a
  993. * @param array &$item The field array of an item row
  994. */
  995. function redir_private_images($a, &$item)
  996. {
  997. $matches = false;
  998. $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
  999. if ($cnt) {
  1000. foreach ($matches as $mtch) {
  1001. if (strpos($mtch[1], '/redir') !== false) {
  1002. continue;
  1003. }
  1004. if ((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
  1005. $img_url = 'redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
  1006. $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
  1007. }
  1008. }
  1009. }
  1010. }
  1011. function put_item_in_cache(&$item, $update = false) {
  1012. if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
  1013. ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
  1014. // The function "redir_private_images" changes the body.
  1015. // I'm not sure if we should store it permanently, so we save the old value.
  1016. $body = $item["body"];
  1017. $a = get_app();
  1018. redir_private_images($a, $item);
  1019. $item["rendered-html"] = prepare_text($item["body"]);
  1020. $item["rendered-hash"] = hash("md5", $item["body"]);
  1021. $item["body"] = $body;
  1022. if ($update AND ($item["id"] != 0)) {
  1023. q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
  1024. dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
  1025. }
  1026. }
  1027. }
  1028. // Given an item array, convert the body element from bbcode to html and add smilie icons.
  1029. // If attach is true, also add icons for item attachments
  1030. if(! function_exists('prepare_body')) {
  1031. /**
  1032. * Given an item array, convert the body element from bbcode to html and add smilie icons.
  1033. * If attach is true, also add icons for item attachments
  1034. *
  1035. * @param array $item
  1036. * @param boolean $attach
  1037. * @return string item body html
  1038. * @hook prepare_body_init item array before any work
  1039. * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
  1040. * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
  1041. */
  1042. function prepare_body(&$item,$attach = false, $preview = false) {
  1043. $a = get_app();
  1044. call_hooks('prepare_body_init', $item);
  1045. $searchpath = z_root()."/search?tag=";
  1046. $tags=array();
  1047. $hashtags = array();
  1048. $mentions = array();
  1049. if (!get_config('system','suppress_tags')) {
  1050. $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
  1051. intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
  1052. foreach($taglist as $tag) {
  1053. if ($tag["url"] == "")
  1054. $tag["url"] = $searchpath.strtolower($tag["term"]);
  1055. if ($tag["type"] == TERM_HASHTAG) {
  1056. $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1057. $prefix = "#";
  1058. } elseif ($tag["type"] == TERM_MENTION) {
  1059. $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1060. $prefix = "@";
  1061. }
  1062. $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
  1063. }
  1064. }
  1065. $item['tags'] = $tags;
  1066. $item['hashtags'] = $hashtags;
  1067. $item['mentions'] = $mentions;
  1068. // Update the cached values if there is no "zrl=..." on the links
  1069. $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
  1070. // Or update it if the current viewer is the intented viewer
  1071. if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
  1072. $update = true;
  1073. put_item_in_cache($item, $update);
  1074. $s = $item["rendered-html"];
  1075. $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
  1076. call_hooks('prepare_body', $prep_arr);
  1077. $s = $prep_arr['html'];
  1078. if(! $attach) {
  1079. // Replace the blockquotes with quotes that are used in mails
  1080. $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
  1081. $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
  1082. return $s;
  1083. }
  1084. $as = '';
  1085. $vhead = false;
  1086. $arr = explode('[/attach],',$item['attach']);
  1087. if(count($arr)) {
  1088. $as .= '<div class="body-attach">';
  1089. foreach($arr as $r) {
  1090. $matches = false;
  1091. $icon = '';
  1092. $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
  1093. if($cnt) {
  1094. foreach($matches as $mtch) {
  1095. $mime = $mtch[3];
  1096. if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
  1097. $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
  1098. else
  1099. $the_url = $mtch[1];
  1100. if(strpos($mime, 'video') !== false) {
  1101. if(!$vhead) {
  1102. $vhead = true;
  1103. $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
  1104. '$baseurl' => z_root(),
  1105. ));
  1106. $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
  1107. '$baseurl' => z_root(),
  1108. ));
  1109. }
  1110. $id = end(explode('/', $the_url));
  1111. $as .= replace_macros(get_markup_template('video_top.tpl'), array(
  1112. '$video' => array(
  1113. 'id' => $id,
  1114. 'title' => t('View Video'),
  1115. 'src' => $the_url,
  1116. 'mime' => $mime,
  1117. ),
  1118. ));
  1119. }
  1120. $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
  1121. if($filetype) {
  1122. $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
  1123. $filesubtype = str_replace('.', '-', $filesubtype);
  1124. }
  1125. else {
  1126. $filetype = 'unkn';
  1127. $filesubtype = 'unkn';
  1128. }
  1129. $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
  1130. /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
  1131. switch($icontype) {
  1132. case 'video':
  1133. case 'audio':
  1134. case 'image':
  1135. case 'text':
  1136. $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
  1137. break;
  1138. default:
  1139. $icon = '<div class="attachtype icon s22 type-unkn"></div>';
  1140. break;
  1141. }*/
  1142. $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
  1143. $title .= ' ' . $mtch[2] . ' ' . t('bytes');
  1144. $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
  1145. }
  1146. }
  1147. }
  1148. $as .= '<div class="clear"></div></div>';
  1149. }
  1150. $s = $s . $as;
  1151. // map
  1152. if(strpos($s,'<div class="map">') !== false && $item['coord']) {
  1153. $x = generate_map(trim($item['coord']));
  1154. if($x) {
  1155. $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
  1156. }
  1157. }
  1158. // Look for spoiler
  1159. $spoilersearch = '<blockquote class="spoiler">';
  1160. // Remove line breaks before the spoiler
  1161. while ((strpos($s, "\n".$spoilersearch) !== false))
  1162. $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
  1163. while ((strpos($s, "<br />".$spoilersearch) !== false))
  1164. $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
  1165. while ((strpos($s, $spoilersearch) !== false)) {
  1166. $pos = strpos($s, $spoilersearch);
  1167. $rnd = random_string(8);
  1168. $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
  1169. '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
  1170. $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
  1171. }
  1172. // Look for quote with author
  1173. $authorsearch = '<blockquote class="author">';
  1174. while ((strpos($s, $authorsearch) !== false)) {
  1175. $pos = strpos($s, $authorsearch);
  1176. $rnd = random_string(8);
  1177. $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
  1178. '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
  1179. $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
  1180. }
  1181. // replace friendica image url size with theme preference
  1182. if (x($a->theme_info,'item_image_size')){
  1183. $ps = $a->theme_info['item_image_size'];
  1184. $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
  1185. }
  1186. $prep_arr = array('item' => $item, 'html' => $s);
  1187. call_hooks('prepare_body_final', $prep_arr);
  1188. return $prep_arr['html'];
  1189. }}
  1190. if(! function_exists('prepare_text')) {
  1191. /**
  1192. * Given a text string, convert from bbcode to html and add smilie icons.
  1193. *
  1194. * @param string $text
  1195. * @return string
  1196. */
  1197. function prepare_text($text) {
  1198. require_once('include/bbcode.php');
  1199. if(stristr($text,'[nosmile]'))
  1200. $s = bbcode($text);
  1201. else
  1202. $s = Smilies::replace(bbcode($text));
  1203. return trim($s);
  1204. }}
  1205. /**
  1206. * return array with details for categories and folders for an item
  1207. *
  1208. * @param array $item
  1209. * @return array
  1210. *
  1211. * [
  1212. * [ // categories array
  1213. * {
  1214. * 'name': 'category name',
  1215. * 'removeurl': 'url to remove this category',
  1216. * 'first': 'is the first in this array? true/false',
  1217. * 'last': 'is the last in this array? true/false',
  1218. * } ,
  1219. * ....
  1220. * ],
  1221. * [ //folders array
  1222. * {
  1223. * 'name': 'folder name',
  1224. * 'removeurl': 'url to remove this folder',
  1225. * 'first': 'is the first in this array? true/false',
  1226. * 'last': 'is the last in this array? true/false',
  1227. * } ,
  1228. * ....
  1229. * ]
  1230. * ]
  1231. */
  1232. function get_cats_and_terms($item) {
  1233. $a = get_app();
  1234. $categories = array();
  1235. $folders = array();
  1236. $matches = false; $first = true;
  1237. $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
  1238. if($cnt) {
  1239. foreach($matches as $mtch) {
  1240. $categories[] = array(
  1241. 'name' => xmlify(file_tag_decode($mtch[1])),
  1242. 'url' => "#",
  1243. 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
  1244. 'first' => $first,
  1245. 'last' => false
  1246. );
  1247. $first = false;
  1248. }
  1249. }
  1250. if (count($categories)) $categories[count($categories)-1]['last'] = true;
  1251. if(local_user() == $item['uid']) {
  1252. $matches = false; $first = true;
  1253. $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
  1254. if($cnt) {
  1255. foreach($matches as $mtch) {
  1256. $folders[] = array(
  1257. 'name' => xmlify(file_tag_decode($mtch[1])),
  1258. 'url' => "#",
  1259. 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
  1260. 'first' => $first,
  1261. 'last' => false
  1262. );
  1263. $first = false;
  1264. }
  1265. }
  1266. }
  1267. if (count($folders)) $folders[count($folders)-1]['last'] = true;
  1268. return array($categories, $folders);
  1269. }
  1270. if(! function_exists('get_plink')) {
  1271. /**
  1272. * get private link for item
  1273. * @param array $item
  1274. * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
  1275. */
  1276. function get_plink($item) {
  1277. $a = get_app();
  1278. if ($a->user['nickname'] != "") {
  1279. $ret = array(
  1280. //'href' => "display/".$a->user['nickname']."/".$item['id'],
  1281. 'href' => "display/".$item['guid'],
  1282. 'orig' => "display/".$item['guid'],
  1283. 'title' => t('View on separate page'),
  1284. 'orig_title' => t('view on separate page'),
  1285. );
  1286. if (x($item,'plink')) {
  1287. $ret["href"] = $a->remove_baseurl($item['plink']);
  1288. $ret["title"] = t('link to source');
  1289. }
  1290. } elseif (x($item,'plink') && ($item['private'] != 1))
  1291. $ret = array(
  1292. 'href' => $item['plink'],
  1293. 'orig' => $item['plink'],
  1294. 'title' => t('link to source'),
  1295. );
  1296. else
  1297. $ret = array();
  1298. //if (x($item,'plink') && ($item['private'] != 1))
  1299. return($ret);
  1300. }}
  1301. if(! function_exists('unamp')) {
  1302. /**
  1303. * replace html amp entity with amp char
  1304. * @param string $s
  1305. * @return string
  1306. */
  1307. function unamp($s) {
  1308. return str_replace('&amp;', '&', $s);
  1309. }}
  1310. if(! function_exists('return_bytes')) {
  1311. /**
  1312. * return number of bytes in size (K, M, G)
  1313. * @param string $size_str
  1314. * @return number
  1315. */
  1316. function return_bytes ($size_str) {
  1317. switch (substr ($size_str, -1)) {
  1318. case 'M': case 'm': return (int)$size_str * 1048576;
  1319. case 'K': case 'k': return (int)$size_str * 1024;
  1320. case 'G': case 'g': return (int)$size_str * 1073741824;
  1321. default: return $size_str;
  1322. }
  1323. }}
  1324. /**
  1325. * @return string
  1326. */
  1327. function generate_user_guid() {
  1328. $found = true;
  1329. do {
  1330. $guid = get_guid(32);
  1331. $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
  1332. dbesc($guid)
  1333. );
  1334. if(! count($x))
  1335. $found = false;
  1336. } while ($found == true );
  1337. return $guid;
  1338. }
  1339. /**
  1340. * @param string $s
  1341. * @param boolean $strip_padding
  1342. * @return string
  1343. */
  1344. function base64url_encode($s, $strip_padding = false) {
  1345. $s = strtr(base64_encode($s),'+/','-_');
  1346. if($strip_padding)
  1347. $s = str_replace('=','',$s);
  1348. return $s;
  1349. }
  1350. /**
  1351. * @param string $s
  1352. * @return string
  1353. */
  1354. function base64url_decode($s) {
  1355. if(is_array($s)) {
  1356. logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
  1357. return $s;
  1358. }
  1359. /*
  1360. * // Placeholder for new rev of salmon which strips base64 padding.
  1361. * // PHP base64_decode handles the un-padded input without requiring this step
  1362. * // Uncomment if you find you need it.
  1363. *
  1364. * $l = strlen($s);
  1365. * if(! strpos($s,'=')) {
  1366. * $m = $l % 4;
  1367. * if($m == 2)
  1368. * $s .= '==';
  1369. * if($m == 3)
  1370. * $s .= '=';
  1371. * }
  1372. *
  1373. */
  1374. return base64_decode(strtr($s,'-_','+/'));
  1375. }
  1376. if (!function_exists('str_getcsv')) {
  1377. /**
  1378. * Parse csv string
  1379. *
  1380. * @param string $input
  1381. * @param string $delimiter
  1382. * @param string $enclosure
  1383. * @param string $escape
  1384. * @param string $eol
  1385. * @return boolean|array False on error, otherwise array[row][column]
  1386. */
  1387. function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
  1388. if (is_string($input) && !empty($input)) {
  1389. $output = array();
  1390. $tmp = preg_split("/".$eol."/",$input);
  1391. if (is_array($tmp) && !empty($tmp)) {
  1392. while (list($line_num, $line) = each($tmp)) {
  1393. if (preg_match("/".$escape.$enclosure."/",$line)) {
  1394. while ($strlen = strlen($line)) {
  1395. $pos_delimiter = strpos($line,$delimiter);
  1396. $pos_enclosure_start = strpos($line,$enclosure);
  1397. if (
  1398. is_int($pos_delimiter) && is_int($pos_enclosure_start)
  1399. && ($pos_enclosure_start < $pos_delimiter)
  1400. ) {
  1401. $enclosed_str = substr($line,1);
  1402. $pos_enclosure_end = strpos($enclosed_str,$enclosure);
  1403. $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
  1404. $output[$line_num][] = $enclosed_str;
  1405. $offset = $pos_enclosure_end+3;
  1406. } else {
  1407. if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
  1408. $output[$line_num][] = substr($line,0);
  1409. $offset = strlen($line);
  1410. } else {
  1411. $output[$line_num][] = substr($line,0,$pos_delimiter);
  1412. $offset = (
  1413. !empty($pos_enclosure_start)
  1414. && ($pos_enclosure_start < $pos_delimiter)
  1415. )
  1416. ?$pos_enclosure_start
  1417. :$pos_delimiter+1;
  1418. }
  1419. }
  1420. $line = substr($line,$offset);
  1421. }
  1422. } else {
  1423. $line = preg_split("/".$delimiter."/",$line);
  1424. /*
  1425. * Validating against pesky extra line breaks creating false rows.
  1426. */
  1427. if (is_array($line) && !empty($line[0])) {
  1428. $output[$line_num] = $line;
  1429. }
  1430. }
  1431. }
  1432. return $output;
  1433. } else {
  1434. return false;
  1435. }
  1436. } else {
  1437. return false;
  1438. }
  1439. }
  1440. }
  1441. /**
  1442. * return div element with class 'clear'
  1443. * @return string
  1444. * @deprecated
  1445. */
  1446. function cleardiv() {
  1447. return '<div class="clear"></div>';
  1448. }
  1449. function bb_translate_video($s) {
  1450. $matches = null;
  1451. $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
  1452. if($r) {
  1453. foreach($matches as $mtch) {
  1454. if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
  1455. $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
  1456. elseif(stristr($mtch[1],'vimeo'))
  1457. $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
  1458. }
  1459. }
  1460. return $s;
  1461. }
  1462. function html2bb_video($s) {
  1463. $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
  1464. '[youtube]$2[/youtube]', $s);
  1465. $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
  1466. '[youtube]$2[/youtube]', $s);
  1467. $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
  1468. '[vimeo]$2[/vimeo]', $s);
  1469. return $s;
  1470. }
  1471. /**
  1472. * apply xmlify() to all values of array $val, recursively
  1473. * @param array $val
  1474. * @return array
  1475. */
  1476. function array_xmlify($val){
  1477. if (is_bool($val)) return $val?"true":"false";
  1478. if (is_array($val)) return array_map('array_xmlify', $val);
  1479. return xmlify((string) $val);
  1480. }
  1481. /**
  1482. * transorm link href and img src from relative to absolute
  1483. *
  1484. * @param string $text
  1485. * @param string $base base url
  1486. * @return string
  1487. */
  1488. function reltoabs($text, $base) {
  1489. if (empty($base))
  1490. return $text;
  1491. $base = rtrim($base,'/');
  1492. $base2 = $base . "/";
  1493. // Replace links
  1494. $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
  1495. $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
  1496. $text = preg_replace($pattern, $replace, $text);
  1497. $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
  1498. $replace = "<a\${1} href=\"" . $base . "\${2}\"";
  1499. $text = preg_replace($pattern, $replace, $text);
  1500. // Replace images
  1501. $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
  1502. $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
  1503. $text = preg_replace($pattern, $replace, $text);
  1504. $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
  1505. $replace = "<img\${1} src=\"" . $base . "\${2}\"";
  1506. $text = preg_replace($pattern, $replace, $text);
  1507. // Done
  1508. return $text;
  1509. }
  1510. /**
  1511. * get translated item type
  1512. *
  1513. * @param array $itme
  1514. * @return string
  1515. */
  1516. function item_post_type($item) {
  1517. if(intval($item['event-id']))
  1518. return t('event');
  1519. if(strlen($item['resource-id']))
  1520. return t('photo');
  1521. if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
  1522. return t('activity');
  1523. if($item['id'] != $item['parent'])
  1524. return t('comment');
  1525. return t('post');
  1526. }
  1527. // post categories and "save to file" use the same item.file table for storage.
  1528. // We will differentiate the different uses by wrapping categories in angle brackets
  1529. // and save to file categories in square brackets.
  1530. // To do this we need to escape these characters if they appear in our tag.
  1531. function file_tag_encode($s) {
  1532. return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
  1533. }
  1534. function file_tag_decode($s) {
  1535. return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
  1536. }
  1537. function file_tag_file_query($table,$s,$type = 'file') {
  1538. if($type == 'file')
  1539. $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
  1540. else
  1541. $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
  1542. return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
  1543. }
  1544. // ex. given music,video return <music><video> or [music][video]
  1545. function file_tag_list_to_file($list,$type = 'file') {
  1546. $tag_list = '';
  1547. if(strlen($list)) {
  1548. $list_array = explode(",",$list);
  1549. if($type == 'file') {
  1550. $lbracket = '[';
  1551. $rbracket = ']';
  1552. }
  1553. else {
  1554. $lbracket = '<';
  1555. $rbracket = '>';
  1556. }
  1557. foreach($list_array as $item) {
  1558. if(strlen($item)) {
  1559. $tag_list .= $lbracket . file_tag_encode(trim($item)) . $rbracket;
  1560. }
  1561. }
  1562. }
  1563. return $tag_list;
  1564. }
  1565. // ex. given <music><video>[friends], return music,video or friends
  1566. function file_tag_file_to_list($file,$type = 'file') {
  1567. $matches = false;
  1568. $list = '';
  1569. if($type == 'file') {
  1570. $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
  1571. }
  1572. else {
  1573. $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
  1574. }
  1575. if($cnt) {
  1576. foreach($matches as $mtch) {
  1577. if(strlen($list))
  1578. $list .= ',';
  1579. $list .= file_tag_decode($mtch[1]);
  1580. }
  1581. }
  1582. return $list;
  1583. }
  1584. function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
  1585. // $file_old - categories previously associated with an item
  1586. // $file_new - new list of categories for an item
  1587. if(! intval($uid))
  1588. return false;
  1589. if($file_old == $file_new)
  1590. return true;
  1591. $saved = get_pconfig($uid,'system','filetags');
  1592. if(strlen($saved)) {
  1593. if($type == 'file') {
  1594. $lbracket = '[';
  1595. $rbracket = ']';
  1596. $termtype = TERM_FILE;
  1597. }
  1598. else {
  1599. $lbracket = '<';
  1600. $rbracket = '>';
  1601. $termtype = TERM_CATEGORY;
  1602. }
  1603. $filetags_updated = $saved;
  1604. // check for new tags to be added as filetags in pconfig
  1605. $new_tags = array();
  1606. $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
  1607. foreach($check_new_tags as $tag) {
  1608. if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
  1609. $new_tags[] = $tag;
  1610. }
  1611. $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
  1612. // check for deleted tags to be removed from filetags in pconfig
  1613. $deleted_tags = array();
  1614. $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
  1615. foreach($check_deleted_tags as $tag) {
  1616. if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
  1617. $deleted_tags[] = $tag;
  1618. }
  1619. foreach($deleted_tags as $key => $tag) {
  1620. $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
  1621. dbesc($tag),
  1622. intval(TERM_OBJ_POST),
  1623. intval($termtype),
  1624. intval($uid));
  1625. //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
  1626. // intval($uid)
  1627. //);
  1628. if(count($r)) {
  1629. unset($deleted_tags[$key]);
  1630. }
  1631. else {
  1632. $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
  1633. }
  1634. }
  1635. if($saved != $filetags_updated) {
  1636. set_pconfig($uid,'system','filetags', $filetags_updated);
  1637. }
  1638. return true;
  1639. }
  1640. else
  1641. if(strlen($file_new)) {
  1642. set_pconfig($uid,'system','filetags', $file_new);
  1643. }
  1644. return true;
  1645. }
  1646. function file_tag_save_file($uid,$item,$file) {
  1647. require_once("include/files.php");
  1648. $result = false;
  1649. if(! intval($uid))
  1650. return false;
  1651. $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  1652. intval($item),
  1653. intval($uid)
  1654. );
  1655. if(count($r)) {
  1656. if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
  1657. q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
  1658. dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
  1659. intval($item),
  1660. intval($uid)
  1661. );
  1662. create_files_from_item($item);
  1663. $saved = get_pconfig($uid,'system','filetags');
  1664. if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
  1665. set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
  1666. info( t('Item filed') );
  1667. }
  1668. return true;
  1669. }
  1670. function file_tag_unsave_file($uid,$item,$file,$cat = false) {
  1671. require_once("include/files.php");
  1672. $result = false;
  1673. if(! intval($uid))
  1674. return false;
  1675. if($cat == true) {
  1676. $pattern = '<' . file_tag_encode($file) . '>' ;
  1677. $termtype = TERM_CATEGORY;
  1678. } else {
  1679. $pattern = '[' . file_tag_encode($file) . ']' ;
  1680. $termtype = TERM_FILE;
  1681. }
  1682. $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
  1683. intval($item),
  1684. intval($uid)
  1685. );
  1686. if(! count($r))
  1687. return false;
  1688. q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
  1689. dbesc(str_replace($pattern,'',$r[0]['file'])),
  1690. intval($item),
  1691. intval($uid)
  1692. );
  1693. create_files_from_item($item);
  1694. $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
  1695. dbesc($file),
  1696. intval(TERM_OBJ_POST),
  1697. intval($termtype),
  1698. intval($uid));
  1699. //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
  1700. //);
  1701. if(! count($r)) {
  1702. $saved = get_pconfig($uid,'system','filetags');
  1703. set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
  1704. }
  1705. return true;
  1706. }
  1707. function normalise_openid($s) {
  1708. return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
  1709. }
  1710. function undo_post_tagging($s) {
  1711. $matches = null;
  1712. $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
  1713. if($cnt) {
  1714. foreach($matches as $mtch) {
  1715. $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
  1716. }
  1717. }
  1718. return $s;
  1719. }
  1720. function fix_mce_lf($s) {
  1721. $s = str_replace("\r\n","\n",$s);
  1722. // $s = str_replace("\n\n","\n",$s);
  1723. return $s;
  1724. }
  1725. function protect_sprintf($s) {
  1726. return(str_replace('%','%%',$s));
  1727. }
  1728. function is_a_date_arg($s) {
  1729. $i = intval($s);
  1730. if($i > 1900) {
  1731. $y = date('Y');
  1732. if($i <= $y+1 && strpos($s,'-') == 4) {
  1733. $m = intval(substr($s,5));
  1734. if($m > 0 && $m <= 12)
  1735. return true;
  1736. }
  1737. }
  1738. return false;
  1739. }
  1740. /**
  1741. * remove intentation from a text
  1742. */
  1743. function deindent($text, $chr="[\t ]", $count=NULL) {
  1744. $text = fix_mce_lf($text);
  1745. $lines = explode("\n", $text);
  1746. if (is_null($count)) {
  1747. $m = array();
  1748. $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
  1749. preg_match("|^".$chr."*|", $lines[$k], $m);
  1750. $count = strlen($m[0]);
  1751. }
  1752. for ($k=0; $k<count($lines); $k++){
  1753. $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
  1754. }
  1755. return implode("\n", $lines);
  1756. }
  1757. function formatBytes($bytes, $precision = 2) {
  1758. $units = array('B', 'KB', 'MB', 'GB', 'TB');
  1759. $bytes = max($bytes, 0);
  1760. $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
  1761. $pow = min($pow, count($units) - 1);
  1762. $bytes /= pow(1024, $pow);
  1763. return round($bytes, $precision) . ' ' . $units[$pow];
  1764. }
  1765. /**
  1766. * @brief translate and format the networkname of a contact
  1767. *
  1768. * @param string $network
  1769. * Networkname of the contact (e.g. dfrn, rss and so on)
  1770. * @param sting $url
  1771. * The contact url
  1772. * @return string
  1773. */
  1774. function format_network_name($network, $url = 0) {
  1775. if ($network != "") {
  1776. require_once('include/contact_selectors.php');
  1777. if ($url != "")
  1778. $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
  1779. else
  1780. $network_name = network_to_name($network);
  1781. return $network_name;
  1782. }
  1783. }
  1784. /**
  1785. * @brief Syntax based code highlighting for popular languages.
  1786. * @param string $s Code block
  1787. * @param string $lang Programming language
  1788. * @return string Formated html
  1789. */
  1790. function text_highlight($s,$lang) {
  1791. if($lang === 'js')
  1792. $lang = 'javascript';
  1793. if(! strpos('Text_Highlighter',get_include_path())) {
  1794. set_include_path(get_include_path() . PATH_SEPARATOR . 'library/Text_Highlighter');
  1795. }
  1796. require_once('library/Text_Highlighter/Text/Highlighter.php');
  1797. require_once('library/Text_Highlighter/Text/Highlighter/Renderer/Html.php');
  1798. $options = array(
  1799. 'numbers' => HL_NUMBERS_LI,
  1800. 'tabsize' => 4,
  1801. );
  1802. $tag_added = false;
  1803. $s = trim(html_entity_decode($s,ENT_COMPAT));
  1804. $s = str_replace(" ","\t",$s);
  1805. // The highlighter library insists on an opening php tag for php code blocks. If
  1806. // it isn't present, nothing is highlighted. So we're going to see if it's present.
  1807. // If not, we'll add it, and then quietly remove it after we get the processed output back.
  1808. if($lang === 'php') {
  1809. if(strpos('<?php',$s) !== 0) {
  1810. $s = '<?php' . "\n" . $s;
  1811. $tag_added = true;
  1812. }
  1813. }
  1814. $renderer = new Text_Highlighter_Renderer_HTML($options);
  1815. $hl = Text_Highlighter::factory($lang);
  1816. $hl->setRenderer($renderer);
  1817. $o = $hl->highlight($s);
  1818. $o = str_replace([" ","\n"],["&nbsp;&nbsp;&nbsp;&nbsp;",''],$o);
  1819. if($tag_added) {
  1820. $b = substr($o,0,strpos($o,'<li>'));
  1821. $e = substr($o,strpos($o,'</li>'));
  1822. $o = $b . $e;
  1823. }
  1824. return('<code>' . $o . '</code>');
  1825. }