]> git.mxchange.org Git - friendica.git/blob - include/text.php
9b34b0884a6b47f26e2fa52b53eb080dd6056522
[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         $callers = debug_backtrace();
721         $logline =  sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
722                                  datetime_convert(),
723                                  session_id(),
724                                  $LOGGER_LEVELS[$level],
725                                  basename($callers[0]['file']),
726                                  $callers[0]['line'],
727                                  $callers[1]['function'],
728                                  $msg
729                                 );
730
731         $stamp1 = microtime(true);
732         @file_put_contents($logfile, $logline, FILE_APPEND);
733         $a->save_timestamp($stamp1, "file");
734         return;
735 }}
736
737
738 if(! function_exists('activity_match')) {
739 /**
740  * Compare activity uri. Knows about activity namespace.
741  *
742  * @param string $haystack
743  * @param string $needle
744  * @return boolean
745  */
746 function activity_match($haystack,$needle) {
747         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
748                 return true;
749         return false;
750 }}
751
752
753 if(! function_exists('get_tags')) {
754 /**
755  * Pull out all #hashtags and @person tags from $s;
756  * We also get @person@domain.com - which would make
757  * the regex quite complicated as tags can also
758  * end a sentence. So we'll run through our results
759  * and strip the period from any tags which end with one.
760  * Returns array of tags found, or empty array.
761  *
762  * @param string $s
763  * @return array
764  */
765 function get_tags($s) {
766         $ret = array();
767
768         // Convert hashtag links to hashtags
769         $s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
770
771         // ignore anything in a code block
772         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
773
774         // Force line feeds at bbtags
775         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
776
777         // ignore anything in a bbtag
778         $s = preg_replace('/\[(.*?)\]/sm','',$s);
779
780         // Match full names against @tags including the space between first and last
781         // We will look these up afterward to see if they are full names or not recognisable.
782
783         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
784                 foreach($match[1] as $mtch) {
785                         if(strstr($mtch,"]")) {
786                                 // we might be inside a bbcode color tag - leave it alone
787                                 continue;
788                         }
789                         if(substr($mtch,-1,1) === '.')
790                                 $ret[] = substr($mtch,0,-1);
791                         else
792                                 $ret[] = $mtch;
793                 }
794         }
795
796         // Otherwise pull out single word tags. These can be @nickname, @first_last
797         // and #hash tags.
798
799         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
800                 foreach($match[1] as $mtch) {
801                         if(strstr($mtch,"]")) {
802                                 // we might be inside a bbcode color tag - leave it alone
803                                 continue;
804                         }
805                         if(substr($mtch,-1,1) === '.')
806                                 $mtch = substr($mtch,0,-1);
807                         // ignore strictly numeric tags like #1
808                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
809                                 continue;
810                         // try not to catch url fragments
811                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
812                                 continue;
813                         $ret[] = $mtch;
814                 }
815         }
816         return $ret;
817 }}
818
819
820 //
821
822 if(! function_exists('qp')) {
823 /**
824  * quick and dirty quoted_printable encoding
825  *
826  * @param string $s
827  * @return string
828  */
829 function qp($s) {
830 return str_replace ("%","=",rawurlencode($s));
831 }}
832
833 if(! function_exists('contact_block')) {
834 /**
835  * Get html for contact block.
836  *
837  * @template contact_block.tpl
838  * @hook contact_block_end (contacts=>array, output=>string)
839  * @return string
840  */
841 function contact_block() {
842         $o = '';
843         $a = get_app();
844
845         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
846         if($shown === false)
847                 $shown = 24;
848         if($shown == 0)
849                 return;
850
851         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
852                 return $o;
853         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
854                         WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
855                                 AND `hidden` = 0 AND `archive` = 0
856                                 AND `network` IN ('%s', '%s', '%s')",
857                         intval($a->profile['uid']),
858                         dbesc(NETWORK_DFRN),
859                         dbesc(NETWORK_OSTATUS),
860                         dbesc(NETWORK_DIASPORA)
861         );
862         if(count($r)) {
863                 $total = intval($r[0]['total']);
864         }
865         if(! $total) {
866                 $contacts = t('No contacts');
867                 $micropro = Null;
868
869         } else {
870                 $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `micro`, `network` FROM `contact`
871                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
872                                         AND NOT `hidden` AND NOT `archive`
873                                 AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
874                                 intval($a->profile['uid']),
875                                 dbesc(NETWORK_DFRN),
876                                 dbesc(NETWORK_OSTATUS),
877                                 dbesc(NETWORK_DIASPORA),
878                                 intval($shown)
879                 );
880                 if(count($r)) {
881                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
882                         $micropro = Array();
883                         foreach($r as $rr) {
884                                 $micropro[] = micropro($rr,true,'mpfriend');
885                         }
886                 }
887         }
888
889         $tpl = get_markup_template('contact_block.tpl');
890         $o = replace_macros($tpl, array(
891                 '$contacts' => $contacts,
892                 '$nickname' => $a->profile['nickname'],
893                 '$viewcontacts' => t('View Contacts'),
894                 '$micropro' => $micropro,
895         ));
896
897         $arr = array('contacts' => $r, 'output' => $o);
898
899         call_hooks('contact_block_end', $arr);
900         return $o;
901
902 }}
903
904 if(! function_exists('micropro')) {
905 /**
906  *
907  * @param array $contact
908  * @param boolean $redirect
909  * @param string $class
910  * @param boolean $textmode
911  * @return string #FIXME: remove html
912  */
913 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
914
915         if($class)
916                 $class = ' ' . $class;
917
918         if ($contact["addr"] == "")
919                 $contact["addr"] = $contact["url"];
920
921         $url = $contact['url'];
922         $sparkle = '';
923         $redir = false;
924
925         if($redirect) {
926                 $a = get_app();
927                 $redirect_url = 'redir/' . $contact['id'];
928                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
929                         $redir = true;
930                         $url = $redirect_url;
931                         $sparkle = ' sparkle';
932                 }
933                 else
934                         $url = zrl($url);
935         }
936         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
937         if($click)
938                 $url = '';
939         if($textmode) {
940                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle
941                         . (($click) ? ' fakelink' : '') . '" '
942                         . (($redir) ? ' target="redir" ' : '')
943                         . (($url) ? ' href="' . $url . '"' : '') . $click
944                         . '" title="' . $contact['name'] . ' [' . $contact['addr'] . ']" alt="' . $contact['name']
945                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
946         }
947         else {
948                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle
949                         . (($click) ? ' fakelink' : '') . '" '
950                         . (($redir) ? ' target="redir" ' : '')
951                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="'
952                         . proxy_url($contact['micro'], false, PROXY_SIZE_THUMB) . '" title="' . $contact['name'] . ' [' . $contact['addr'] . ']" alt="' . $contact['name']
953                         . '" /></a></div>' . "\r\n";
954         }
955 }}
956
957
958
959 if(! function_exists('search')) {
960 /**
961  * search box
962  *
963  * @param string $s search query
964  * @param string $id html id
965  * @param string $url search url
966  * @param boolean $savedsearch show save search button
967  */
968 function search($s,$id='search-box',$url='search',$save = false, $aside = true) {
969         $a = get_app();
970
971         $values = array(
972                         '$s' => $s,
973                         '$id' => $id,
974                         '$action_url' => $url,
975                         '$search_label' => t('Search'),
976                         '$save_label' => t('Save'),
977                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
978                         '$search_hint' => t('@name, !forum, #tags, content'),
979                 );
980
981         if (!$aside) {
982                 $values['$searchoption'] = array(
983                                         t("Full Text"),
984                                         t("Tags"),
985                                         t("Contacts"));
986
987                 if (get_config('system','poco_local_search'))
988                         $values['$searchoption'][] = t("Forums");
989         }
990
991         return replace_macros(get_markup_template('searchbox.tpl'), $values);
992 }}
993
994 if(! function_exists('valid_email')) {
995 /**
996  * Check if $x is a valid email string
997  *
998  * @param string $x
999  * @return boolean
1000  */
1001 function valid_email($x){
1002
1003         // Removed because Fabio told me so.
1004         //if(get_config('system','disable_email_validation'))
1005         //      return true;
1006
1007         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1008                 return true;
1009         return false;
1010 }}
1011
1012
1013 if(! function_exists('linkify')) {
1014 /**
1015  * Replace naked text hyperlink with HTML formatted hyperlink
1016  *
1017  * @param string $s
1018  */
1019 function linkify($s) {
1020         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1021         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1022         return($s);
1023 }}
1024
1025
1026 /**
1027  * Load poke verbs
1028  *
1029  * @return array index is present tense verb
1030                                  value is array containing past tense verb, translation of present, translation of past
1031  * @hook poke_verbs pokes array
1032  */
1033 function get_poke_verbs() {
1034
1035         // index is present tense verb
1036         // value is array containing past tense verb, translation of present, translation of past
1037
1038         $arr = array(
1039                 'poke' => array( 'poked', t('poke'), t('poked')),
1040                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1041                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1042                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1043                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1044                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1045         );
1046         call_hooks('poke_verbs', $arr);
1047         return $arr;
1048 }
1049
1050 /**
1051  * Load moods
1052  * @return array index is mood, value is translated mood
1053  * @hook mood_verbs moods array
1054  */
1055 function get_mood_verbs() {
1056
1057         $arr = array(
1058                 'happy'      => t('happy'),
1059                 'sad'        => t('sad'),
1060                 'mellow'     => t('mellow'),
1061                 'tired'      => t('tired'),
1062                 'perky'      => t('perky'),
1063                 'angry'      => t('angry'),
1064                 'stupefied'  => t('stupified'),
1065                 'puzzled'    => t('puzzled'),
1066                 'interested' => t('interested'),
1067                 'bitter'     => t('bitter'),
1068                 'cheerful'   => t('cheerful'),
1069                 'alive'      => t('alive'),
1070                 'annoyed'    => t('annoyed'),
1071                 'anxious'    => t('anxious'),
1072                 'cranky'     => t('cranky'),
1073                 'disturbed'  => t('disturbed'),
1074                 'frustrated' => t('frustrated'),
1075                 'motivated'  => t('motivated'),
1076                 'relaxed'    => t('relaxed'),
1077                 'surprised'  => t('surprised'),
1078         );
1079
1080         call_hooks('mood_verbs', $arr);
1081         return $arr;
1082 }
1083
1084 if(! function_exists('day_translate')) {
1085 /**
1086  * Translate days and months names
1087  *
1088  * @param string $s
1089  * @return string
1090  */
1091 function day_translate($s) {
1092         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1093                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1094                 $s);
1095
1096         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1097                 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')),
1098                 $ret);
1099
1100         return $ret;
1101 }}
1102
1103
1104 if(! function_exists('normalise_link')) {
1105 /**
1106  * Normalize url
1107  *
1108  * @param string $url
1109  * @return string
1110  */
1111 function normalise_link($url) {
1112         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1113         return(rtrim($ret,'/'));
1114 }}
1115
1116
1117
1118 if(! function_exists('link_compare')) {
1119 /**
1120  * Compare two URLs to see if they are the same, but ignore
1121  * slight but hopefully insignificant differences such as if one
1122  * is https and the other isn't, or if one is www.something and
1123  * the other isn't - and also ignore case differences.
1124  *
1125  * @param string $a first url
1126  * @param string $b second url
1127  * @return boolean True if the URLs match, otherwise False
1128  *
1129  */
1130 function link_compare($a,$b) {
1131         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1132                 return true;
1133         return false;
1134 }}
1135
1136
1137 if(! function_exists('redir_private_images')) {
1138 /**
1139  * Find any non-embedded images in private items and add redir links to them
1140  *
1141  * @param App $a
1142  * @param array $item
1143  */
1144 function redir_private_images($a, &$item) {
1145
1146         $matches = false;
1147         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1148         if($cnt) {
1149                 //logger("redir_private_images: matches = " . print_r($matches, true));
1150                 foreach($matches as $mtch) {
1151                         if(strpos($mtch[1], '/redir') !== false)
1152                                 continue;
1153
1154                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1155                                 //logger("redir_private_images: redir");
1156                                 $img_url = 'redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1157                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1158                         }
1159                 }
1160         }
1161
1162 }}
1163
1164 function put_item_in_cache(&$item, $update = false) {
1165
1166         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1167                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1168
1169                 // The function "redir_private_images" changes the body.
1170                 // I'm not sure if we should store it permanently, so we save the old value.
1171                 $body = $item["body"];
1172
1173                 $a = get_app();
1174                 redir_private_images($a, $item);
1175
1176                 $item["rendered-html"] = prepare_text($item["body"]);
1177                 $item["rendered-hash"] = hash("md5", $item["body"]);
1178                 $item["body"] = $body;
1179
1180                 if ($update AND ($item["id"] != 0)) {
1181                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1182                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1183                 }
1184         }
1185 }
1186
1187 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1188 // If attach is true, also add icons for item attachments
1189
1190 if(! function_exists('prepare_body')) {
1191 /**
1192  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1193  * If attach is true, also add icons for item attachments
1194  *
1195  * @param array $item
1196  * @param boolean $attach
1197  * @return string item body html
1198  * @hook prepare_body_init item array before any work
1199  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1200  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1201  */
1202 function prepare_body(&$item,$attach = false, $preview = false) {
1203
1204         $a = get_app();
1205         call_hooks('prepare_body_init', $item);
1206
1207         $searchpath = z_root()."/search?tag=";
1208
1209         $tags=array();
1210         $hashtags = array();
1211         $mentions = array();
1212
1213         if (!get_config('system','suppress_tags')) {
1214                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1215                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1216
1217                 foreach($taglist as $tag) {
1218
1219                         if ($tag["url"] == "")
1220                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1221
1222                         if ($tag["type"] == TERM_HASHTAG) {
1223                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1224                                 $prefix = "#";
1225                         } elseif ($tag["type"] == TERM_MENTION) {
1226                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1227                                 $prefix = "@";
1228                         }
1229                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1230                 }
1231         }
1232
1233         $item['tags'] = $tags;
1234         $item['hashtags'] = $hashtags;
1235         $item['mentions'] = $mentions;
1236
1237         // Update the cached values if there is no "zrl=..." on the links
1238         $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
1239
1240         // Or update it if the current viewer is the intented viewer
1241         if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
1242                 $update = true;
1243
1244         put_item_in_cache($item, $update);
1245         $s = $item["rendered-html"];
1246
1247         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1248         call_hooks('prepare_body', $prep_arr);
1249         $s = $prep_arr['html'];
1250
1251         if(! $attach) {
1252                 // Replace the blockquotes with quotes that are used in mails
1253                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1254                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1255                 return $s;
1256         }
1257
1258         $as = '';
1259         $vhead = false;
1260         $arr = explode('[/attach],',$item['attach']);
1261         if(count($arr)) {
1262                 $as .= '<div class="body-attach">';
1263                 foreach($arr as $r) {
1264                         $matches = false;
1265                         $icon = '';
1266                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1267                         if($cnt) {
1268                                 foreach($matches as $mtch) {
1269                                         $mime = $mtch[3];
1270
1271                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1272                                                 $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1273                                         else
1274                                                 $the_url = $mtch[1];
1275
1276                                         if(strpos($mime, 'video') !== false) {
1277                                                 if(!$vhead) {
1278                                                         $vhead = true;
1279                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1280                                                                 '$baseurl' => z_root(),
1281                                                         ));
1282                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1283                                                                 '$baseurl' => z_root(),
1284                                                         ));
1285                                                 }
1286
1287                                                 $id = end(explode('/', $the_url));
1288                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1289                                                         '$video'        => array(
1290                                                                 'id'       => $id,
1291                                                                 'title'         => t('View Video'),
1292                                                                 'src'           => $the_url,
1293                                                                 'mime'          => $mime,
1294                                                         ),
1295                                                 ));
1296                                         }
1297
1298                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1299                                         if($filetype) {
1300                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1301                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1302                                         }
1303                                         else {
1304                                                 $filetype = 'unkn';
1305                                                 $filesubtype = 'unkn';
1306                                         }
1307
1308                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1309                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1310                                         switch($icontype) {
1311                                                 case 'video':
1312                                                 case 'audio':
1313                                                 case 'image':
1314                                                 case 'text':
1315                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1316                                                         break;
1317                                                 default:
1318                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1319                                                         break;
1320                                         }*/
1321
1322                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1323                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1324
1325                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1326                                 }
1327                         }
1328                 }
1329                 $as .= '<div class="clear"></div></div>';
1330         }
1331         $s = $s . $as;
1332
1333         // map
1334         if(strpos($s,'<div class="map">') !== false && $item['coord']) {
1335                 $x = generate_map(trim($item['coord']));
1336                 if($x) {
1337                         $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
1338                 }
1339         }
1340
1341
1342         // Look for spoiler
1343         $spoilersearch = '<blockquote class="spoiler">';
1344
1345         // Remove line breaks before the spoiler
1346         while ((strpos($s, "\n".$spoilersearch) !== false))
1347                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1348         while ((strpos($s, "<br />".$spoilersearch) !== false))
1349                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1350
1351         while ((strpos($s, $spoilersearch) !== false)) {
1352
1353                 $pos = strpos($s, $spoilersearch);
1354                 $rnd = random_string(8);
1355                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1356                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1357                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1358         }
1359
1360         // Look for quote with author
1361         $authorsearch = '<blockquote class="author">';
1362
1363         while ((strpos($s, $authorsearch) !== false)) {
1364
1365                 $pos = strpos($s, $authorsearch);
1366                 $rnd = random_string(8);
1367                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1368                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1369                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1370         }
1371
1372         // replace friendica image url size with theme preference
1373         if (x($a->theme_info,'item_image_size')){
1374             $ps = $a->theme_info['item_image_size'];
1375
1376             $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1377         }
1378
1379         $prep_arr = array('item' => $item, 'html' => $s);
1380         call_hooks('prepare_body_final', $prep_arr);
1381
1382         return $prep_arr['html'];
1383 }}
1384
1385
1386 if(! function_exists('prepare_text')) {
1387 /**
1388  * Given a text string, convert from bbcode to html and add smilie icons.
1389  *
1390  * @param string $text
1391  * @return string
1392  */
1393 function prepare_text($text) {
1394
1395         require_once('include/bbcode.php');
1396
1397         if(stristr($text,'[nosmile]'))
1398                 $s = bbcode($text);
1399         else
1400                 $s = Smilies::replace(bbcode($text));
1401
1402         return trim($s);
1403 }}
1404
1405
1406
1407 /**
1408  * return array with details for categories and folders for an item
1409  *
1410  * @param array $item
1411  * @return array
1412  *
1413   * [
1414  *      [ // categories array
1415  *          {
1416  *               'name': 'category name',
1417  *               'removeurl': 'url to remove this category',
1418  *               'first': 'is the first in this array? true/false',
1419  *               'last': 'is the last in this array? true/false',
1420  *           } ,
1421  *           ....
1422  *       ],
1423  *       [ //folders array
1424  *                      {
1425  *               'name': 'folder name',
1426  *               'removeurl': 'url to remove this folder',
1427  *               'first': 'is the first in this array? true/false',
1428  *               'last': 'is the last in this array? true/false',
1429  *           } ,
1430  *           ....
1431  *       ]
1432  *  ]
1433  */
1434 function get_cats_and_terms($item) {
1435
1436         $a = get_app();
1437         $categories = array();
1438         $folders = array();
1439
1440         $matches = false; $first = true;
1441         $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1442         if($cnt) {
1443                 foreach($matches as $mtch) {
1444                         $categories[] = array(
1445                                 'name' => xmlify(file_tag_decode($mtch[1])),
1446                                 'url' =>  "#",
1447                                 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1448                                 'first' => $first,
1449                                 'last' => false
1450                         );
1451                         $first = false;
1452                 }
1453         }
1454         if (count($categories)) $categories[count($categories)-1]['last'] = true;
1455
1456
1457         if(local_user() == $item['uid']) {
1458                 $matches = false; $first = true;
1459                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1460                 if($cnt) {
1461                         foreach($matches as $mtch) {
1462                                 $folders[] = array(
1463                                         'name' => xmlify(file_tag_decode($mtch[1])),
1464                                         'url' =>  "#",
1465                                         'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1466                                         'first' => $first,
1467                                         'last' => false
1468                                 );
1469                                 $first = false;
1470                         }
1471                 }
1472         }
1473
1474         if (count($folders)) $folders[count($folders)-1]['last'] = true;
1475
1476         return array($categories, $folders);
1477 }
1478
1479 if(! function_exists('get_plink')) {
1480 /**
1481  * get private link for item
1482  * @param array $item
1483  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1484  */
1485 function get_plink($item) {
1486         $a = get_app();
1487
1488         if ($a->user['nickname'] != "") {
1489                 $ret = array(
1490                                 //'href' => "display/".$a->user['nickname']."/".$item['id'],
1491                                 'href' => "display/".$item['guid'],
1492                                 'orig' => "display/".$item['guid'],
1493                                 'title' => t('View on separate page'),
1494                                 'orig_title' => t('view on separate page'),
1495                         );
1496
1497                 if (x($item,'plink')) {
1498                         $ret["href"] = $a->remove_baseurl($item['plink']);
1499                         $ret["title"] = t('link to source');
1500                 }
1501
1502         } elseif (x($item,'plink') && ($item['private'] != 1))
1503                 $ret = array(
1504                                 'href' => $item['plink'],
1505                                 'orig' => $item['plink'],
1506                                 'title' => t('link to source'),
1507                         );
1508         else
1509                 $ret = array();
1510
1511         //if (x($item,'plink') && ($item['private'] != 1))
1512
1513         return($ret);
1514 }}
1515
1516 if(! function_exists('unamp')) {
1517 /**
1518  * replace html amp entity with amp char
1519  * @param string $s
1520  * @return string
1521  */
1522 function unamp($s) {
1523         return str_replace('&amp;', '&', $s);
1524 }}
1525
1526
1527 if(! function_exists('return_bytes')) {
1528 /**
1529  * return number of bytes in size (K, M, G)
1530  * @param string $size_str
1531  * @return number
1532  */
1533 function return_bytes ($size_str) {
1534         switch (substr ($size_str, -1)) {
1535                 case 'M': case 'm': return (int)$size_str * 1048576;
1536                 case 'K': case 'k': return (int)$size_str * 1024;
1537                 case 'G': case 'g': return (int)$size_str * 1073741824;
1538                 default: return $size_str;
1539         }
1540 }}
1541
1542 /**
1543  * @return string
1544  */
1545 function generate_user_guid() {
1546         $found = true;
1547         do {
1548                 $guid = get_guid(32);
1549                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1550                         dbesc($guid)
1551                 );
1552                 if(! count($x))
1553                         $found = false;
1554         } while ($found == true );
1555         return $guid;
1556 }
1557
1558
1559 /**
1560  * @param string $s
1561  * @param boolean $strip_padding
1562  * @return string
1563  */
1564 function base64url_encode($s, $strip_padding = false) {
1565
1566         $s = strtr(base64_encode($s),'+/','-_');
1567
1568         if($strip_padding)
1569                 $s = str_replace('=','',$s);
1570
1571         return $s;
1572 }
1573
1574 /**
1575  * @param string $s
1576  * @return string
1577  */
1578 function base64url_decode($s) {
1579
1580         if(is_array($s)) {
1581                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1582                 return $s;
1583         }
1584
1585 /*
1586  *  // Placeholder for new rev of salmon which strips base64 padding.
1587  *  // PHP base64_decode handles the un-padded input without requiring this step
1588  *  // Uncomment if you find you need it.
1589  *
1590  *      $l = strlen($s);
1591  *      if(! strpos($s,'=')) {
1592  *              $m = $l % 4;
1593  *              if($m == 2)
1594  *                      $s .= '==';
1595  *              if($m == 3)
1596  *                      $s .= '=';
1597  *      }
1598  *
1599  */
1600
1601         return base64_decode(strtr($s,'-_','+/'));
1602 }
1603
1604
1605 if (!function_exists('str_getcsv')) {
1606         /**
1607          * Parse csv string
1608          *
1609          * @param string $input
1610          * @param string $delimiter
1611          * @param string $enclosure
1612          * @param string $escape
1613          * @param string $eol
1614          * @return boolean|array False on error, otherwise array[row][column]
1615          */
1616 function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1617         if (is_string($input) && !empty($input)) {
1618                 $output = array();
1619                 $tmp    = preg_split("/".$eol."/",$input);
1620                 if (is_array($tmp) && !empty($tmp)) {
1621                         while (list($line_num, $line) = each($tmp)) {
1622                                 if (preg_match("/".$escape.$enclosure."/",$line)) {
1623                                         while ($strlen = strlen($line)) {
1624                                                 $pos_delimiter       = strpos($line,$delimiter);
1625                                                 $pos_enclosure_start = strpos($line,$enclosure);
1626                                                 if (
1627                                                         is_int($pos_delimiter) && is_int($pos_enclosure_start)
1628                                                         && ($pos_enclosure_start < $pos_delimiter)
1629                                                         ) {
1630                                                         $enclosed_str = substr($line,1);
1631                                                         $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1632                                                         $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1633                                                         $output[$line_num][] = $enclosed_str;
1634                                                         $offset = $pos_enclosure_end+3;
1635                                                 } else {
1636                                                         if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1637                                                                 $output[$line_num][] = substr($line,0);
1638                                                                 $offset = strlen($line);
1639                                                         } else {
1640                                                                 $output[$line_num][] = substr($line,0,$pos_delimiter);
1641                                                                 $offset = (
1642                                                                         !empty($pos_enclosure_start)
1643                                                                         && ($pos_enclosure_start < $pos_delimiter)
1644                                                                         )
1645                                                                         ?$pos_enclosure_start
1646                                                                         :$pos_delimiter+1;
1647                                                         }
1648                                                 }
1649                                                 $line = substr($line,$offset);
1650                                         }
1651                                 } else {
1652                                         $line = preg_split("/".$delimiter."/",$line);
1653
1654                                         /*
1655                                          * Validating against pesky extra line breaks creating false rows.
1656                                          */
1657                                         if (is_array($line) && !empty($line[0])) {
1658                                                 $output[$line_num] = $line;
1659                                 }
1660                                 }
1661                         }
1662                         return $output;
1663                 } else {
1664                 return false;
1665                 }
1666         } else {
1667                 return false;
1668         }
1669 }
1670 }
1671
1672 /**
1673  * return div element with class 'clear'
1674  * @return string
1675  * @deprecated
1676  */
1677 function cleardiv() {
1678         return '<div class="clear"></div>';
1679 }
1680
1681
1682 function bb_translate_video($s) {
1683
1684         $matches = null;
1685         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1686         if($r) {
1687                 foreach($matches as $mtch) {
1688                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1689                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1690                         elseif(stristr($mtch[1],'vimeo'))
1691                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1692                 }
1693         }
1694         return $s;
1695 }
1696
1697 function html2bb_video($s) {
1698
1699         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1700                         '[youtube]$2[/youtube]', $s);
1701
1702         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1703                         '[youtube]$2[/youtube]', $s);
1704
1705         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1706                         '[vimeo]$2[/vimeo]', $s);
1707
1708         return $s;
1709 }
1710
1711 /**
1712  * apply xmlify() to all values of array $val, recursively
1713  * @param array $val
1714  * @return array
1715  */
1716 function array_xmlify($val){
1717         if (is_bool($val)) return $val?"true":"false";
1718         if (is_array($val)) return array_map('array_xmlify', $val);
1719         return xmlify((string) $val);
1720 }
1721
1722
1723 /**
1724  * transorm link href and img src from relative to absolute
1725  *
1726  * @param string $text
1727  * @param string $base base url
1728  * @return string
1729  */
1730 function reltoabs($text, $base) {
1731         if (empty($base))
1732             return $text;
1733
1734         $base = rtrim($base,'/');
1735
1736         $base2 = $base . "/";
1737
1738         // Replace links
1739         $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1740         $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1741         $text = preg_replace($pattern, $replace, $text);
1742
1743         $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1744         $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1745         $text = preg_replace($pattern, $replace, $text);
1746
1747         // Replace images
1748         $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1749         $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1750         $text = preg_replace($pattern, $replace, $text);
1751
1752         $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1753         $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1754         $text = preg_replace($pattern, $replace, $text);
1755
1756
1757         // Done
1758         return $text;
1759 }
1760
1761 /**
1762  * get translated item type
1763  *
1764  * @param array $itme
1765  * @return string
1766  */
1767 function item_post_type($item) {
1768         if(intval($item['event-id']))
1769                 return t('event');
1770         if(strlen($item['resource-id']))
1771                 return t('photo');
1772         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1773                 return t('activity');
1774         if($item['id'] != $item['parent'])
1775                 return t('comment');
1776         return t('post');
1777 }
1778
1779 // post categories and "save to file" use the same item.file table for storage.
1780 // We will differentiate the different uses by wrapping categories in angle brackets
1781 // and save to file categories in square brackets.
1782 // To do this we need to escape these characters if they appear in our tag.
1783
1784 function file_tag_encode($s) {
1785         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1786 }
1787
1788 function file_tag_decode($s) {
1789         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1790 }
1791
1792 function file_tag_file_query($table,$s,$type = 'file') {
1793
1794         if($type == 'file')
1795                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1796         else
1797                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1798         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1799 }
1800
1801 // ex. given music,video return <music><video> or [music][video]
1802 function file_tag_list_to_file($list,$type = 'file') {
1803         $tag_list = '';
1804         if(strlen($list)) {
1805                 $list_array = explode(",",$list);
1806                 if($type == 'file') {
1807                         $lbracket = '[';
1808                         $rbracket = ']';
1809                 }
1810                 else {
1811                         $lbracket = '<';
1812                         $rbracket = '>';
1813                 }
1814
1815                 foreach($list_array as $item) {
1816                   if(strlen($item)) {
1817                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1818                         }
1819                 }
1820         }
1821         return $tag_list;
1822 }
1823
1824 // ex. given <music><video>[friends], return music,video or friends
1825 function file_tag_file_to_list($file,$type = 'file') {
1826         $matches = false;
1827         $list = '';
1828         if($type == 'file') {
1829                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
1830         }
1831         else {
1832                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
1833         }
1834         if($cnt) {
1835                 foreach($matches as $mtch) {
1836                         if(strlen($list))
1837                                 $list .= ',';
1838                         $list .= file_tag_decode($mtch[1]);
1839                 }
1840         }
1841
1842         return $list;
1843 }
1844
1845 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
1846         // $file_old - categories previously associated with an item
1847         // $file_new - new list of categories for an item
1848
1849         if(! intval($uid))
1850                 return false;
1851
1852         if($file_old == $file_new)
1853                 return true;
1854
1855         $saved = get_pconfig($uid,'system','filetags');
1856         if(strlen($saved)) {
1857                 if($type == 'file') {
1858                         $lbracket = '[';
1859                         $rbracket = ']';
1860                         $termtype = TERM_FILE;
1861                 }
1862                 else {
1863                         $lbracket = '<';
1864                         $rbracket = '>';
1865                         $termtype = TERM_CATEGORY;
1866                 }
1867
1868                 $filetags_updated = $saved;
1869
1870                 // check for new tags to be added as filetags in pconfig
1871                 $new_tags = array();
1872                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
1873
1874                 foreach($check_new_tags as $tag) {
1875                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
1876                                 $new_tags[] = $tag;
1877                 }
1878
1879                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
1880
1881                 // check for deleted tags to be removed from filetags in pconfig
1882                 $deleted_tags = array();
1883                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
1884
1885                 foreach($check_deleted_tags as $tag) {
1886                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
1887                                 $deleted_tags[] = $tag;
1888                 }
1889
1890                 foreach($deleted_tags as $key => $tag) {
1891                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
1892                                 dbesc($tag),
1893                                 intval(TERM_OBJ_POST),
1894                                 intval($termtype),
1895                                 intval($uid));
1896
1897                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
1898                         //      intval($uid)
1899                         //);
1900
1901                         if(count($r)) {
1902                                 unset($deleted_tags[$key]);
1903                         }
1904                         else {
1905                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
1906                         }
1907                 }
1908
1909                 if($saved != $filetags_updated) {
1910                         set_pconfig($uid,'system','filetags', $filetags_updated);
1911                 }
1912                 return true;
1913         }
1914         else
1915                 if(strlen($file_new)) {
1916                         set_pconfig($uid,'system','filetags', $file_new);
1917                 }
1918                 return true;
1919 }
1920
1921 function file_tag_save_file($uid,$item,$file) {
1922         require_once("include/files.php");
1923
1924         $result = false;
1925         if(! intval($uid))
1926                 return false;
1927         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1928                 intval($item),
1929                 intval($uid)
1930         );
1931         if(count($r)) {
1932                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
1933                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
1934                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
1935                                 intval($item),
1936                                 intval($uid)
1937                         );
1938
1939                 create_files_from_item($item);
1940
1941                 $saved = get_pconfig($uid,'system','filetags');
1942                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
1943                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
1944                 info( t('Item filed') );
1945         }
1946         return true;
1947 }
1948
1949 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
1950         require_once("include/files.php");
1951
1952         $result = false;
1953         if(! intval($uid))
1954                 return false;
1955
1956         if($cat == true) {
1957                 $pattern = '<' . file_tag_encode($file) . '>' ;
1958                 $termtype = TERM_CATEGORY;
1959         } else {
1960                 $pattern = '[' . file_tag_encode($file) . ']' ;
1961                 $termtype = TERM_FILE;
1962         }
1963
1964
1965         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1966                 intval($item),
1967                 intval($uid)
1968         );
1969         if(! count($r))
1970                 return false;
1971
1972         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
1973                 dbesc(str_replace($pattern,'',$r[0]['file'])),
1974                 intval($item),
1975                 intval($uid)
1976         );
1977
1978         create_files_from_item($item);
1979
1980         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
1981                 dbesc($file),
1982                 intval(TERM_OBJ_POST),
1983                 intval($termtype),
1984                 intval($uid));
1985
1986         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
1987         //);
1988
1989         if(! count($r)) {
1990                 $saved = get_pconfig($uid,'system','filetags');
1991                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
1992
1993         }
1994         return true;
1995 }
1996
1997 function normalise_openid($s) {
1998         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
1999 }
2000
2001
2002 function undo_post_tagging($s) {
2003         $matches = null;
2004         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2005         if($cnt) {
2006                 foreach($matches as $mtch) {
2007                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2008                 }
2009         }
2010         return $s;
2011 }
2012
2013 function fix_mce_lf($s) {
2014         $s = str_replace("\r\n","\n",$s);
2015 //      $s = str_replace("\n\n","\n",$s);
2016         return $s;
2017 }
2018
2019
2020 function protect_sprintf($s) {
2021         return(str_replace('%','%%',$s));
2022 }
2023
2024
2025 function is_a_date_arg($s) {
2026         $i = intval($s);
2027         if($i > 1900) {
2028                 $y = date('Y');
2029                 if($i <= $y+1 && strpos($s,'-') == 4) {
2030                         $m = intval(substr($s,5));
2031                         if($m > 0 && $m <= 12)
2032                                 return true;
2033                 }
2034         }
2035         return false;
2036 }
2037
2038 /**
2039  * remove intentation from a text
2040  */
2041 function deindent($text, $chr="[\t ]", $count=NULL) {
2042         $text = fix_mce_lf($text);
2043         $lines = explode("\n", $text);
2044         if (is_null($count)) {
2045                 $m = array();
2046                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2047                 preg_match("|^".$chr."*|", $lines[$k], $m);
2048                 $count = strlen($m[0]);
2049         }
2050         for ($k=0; $k<count($lines); $k++){
2051                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2052         }
2053
2054         return implode("\n", $lines);
2055 }
2056
2057 function formatBytes($bytes, $precision = 2) {
2058          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2059
2060         $bytes = max($bytes, 0);
2061         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2062         $pow = min($pow, count($units) - 1);
2063
2064         $bytes /= pow(1024, $pow);
2065
2066         return round($bytes, $precision) . ' ' . $units[$pow];
2067 }
2068
2069 /**
2070  * @brief translate and format the networkname of a contact
2071  * 
2072  * @param string $network
2073  *      Networkname of the contact (e.g. dfrn, rss and so on)
2074  * @param sting $url
2075  *      The contact url
2076  * @return string
2077  */
2078 function format_network_name($network, $url = 0) {
2079         if ($network != "") {
2080                 require_once('include/contact_selectors.php');
2081                 if ($url != "")
2082                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2083                 else
2084                         $network_name = network_to_name($network);
2085
2086                 return $network_name;
2087         }
2088
2089 }
2090
2091 /**
2092  * @brief Syntax based code highlighting for popular languages.
2093  * @param string $s Code block
2094  * @param string $lang Programming language
2095  * @return string Formated html
2096  */
2097 function text_highlight($s,$lang) {
2098         if($lang === 'js')
2099                 $lang = 'javascript';
2100
2101         if(! strpos('Text_Highlighter',get_include_path())) {
2102                 set_include_path(get_include_path() . PATH_SEPARATOR . 'library/Text_Highlighter');
2103         }
2104
2105         require_once('library/Text_Highlighter/Text/Highlighter.php');
2106         require_once('library/Text_Highlighter/Text/Highlighter/Renderer/Html.php');
2107         $options = array(
2108                 'numbers' => HL_NUMBERS_LI,
2109                 'tabsize' => 4,
2110                 );
2111
2112         $tag_added = false;
2113         $s = trim(html_entity_decode($s,ENT_COMPAT));
2114         $s = str_replace("    ","\t",$s);
2115
2116         // The highlighter library insists on an opening php tag for php code blocks. If 
2117         // it isn't present, nothing is highlighted. So we're going to see if it's present.
2118         // If not, we'll add it, and then quietly remove it after we get the processed output back.
2119
2120         if($lang === 'php') {
2121                 if(strpos('<?php',$s) !== 0) {
2122                         $s = '<?php' . "\n" . $s;
2123                         $tag_added = true;
2124                 }
2125         } 
2126
2127         $renderer = new Text_Highlighter_Renderer_HTML($options);
2128         $hl = Text_Highlighter::factory($lang);
2129         $hl->setRenderer($renderer);
2130         $o = $hl->highlight($s);
2131         $o = str_replace(["    ","\n"],["&nbsp;&nbsp;&nbsp;&nbsp;",''],$o);
2132
2133         if($tag_added) {
2134                 $b = substr($o,0,strpos($o,'<li>'));
2135                 $e = substr($o,strpos($o,'</li>'));
2136                 $o = $b . $e;
2137         }
2138
2139         return('<code>' . $o . '</code>');
2140 }