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