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