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