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