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