]> git.mxchange.org Git - friendica.git/blob - include/text.php
Fix Friendica private image URL replacing
[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 `self` = 0 AND `blocked` = 0 and `pending` = 0
878                                 AND `hidden` = 0 AND `archive` = 0
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` AND NOT `pending`
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  * @brief Find any non-embedded images in private items and add redir links to them
1175  *
1176  * @param App $a
1177  * @param array &$item The field array of an item row
1178  */
1179 function redir_private_images($a, &$item)
1180 {
1181         $matches = false;
1182         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1183         if ($cnt) {
1184                 foreach ($matches as $mtch) {
1185                         if(strpos($mtch[1], '/redir') !== false) {
1186                                 continue;
1187                         }
1188
1189                         if ((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1190                                 $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
1191                                 $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
1192                         }
1193                 }
1194         }
1195 }
1196
1197 function put_item_in_cache(&$item, $update = false) {
1198
1199         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1200                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1201
1202                 // The function "redir_private_images" changes the body.
1203                 // I'm not sure if we should store it permanently, so we save the old value.
1204                 $body = $item["body"];
1205
1206                 $a = get_app();
1207                 redir_private_images($a, $item);
1208
1209                 $item["rendered-html"] = prepare_text($item["body"]);
1210                 $item["rendered-hash"] = hash("md5", $item["body"]);
1211                 $item["body"] = $body;
1212
1213                 if ($update AND ($item["id"] != 0)) {
1214                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1215                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1216                 }
1217         }
1218 }
1219
1220 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1221 // If attach is true, also add icons for item attachments
1222
1223 if(! function_exists('prepare_body')) {
1224 /**
1225  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1226  * If attach is true, also add icons for item attachments
1227  *
1228  * @param array $item
1229  * @param boolean $attach
1230  * @return string item body html
1231  * @hook prepare_body_init item array before any work
1232  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1233  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1234  */
1235 function prepare_body(&$item,$attach = false, $preview = false) {
1236
1237         $a = get_app();
1238         call_hooks('prepare_body_init', $item);
1239
1240         $searchpath = z_root()."/search?tag=";
1241
1242         $tags=array();
1243         $hashtags = array();
1244         $mentions = array();
1245
1246         if (!get_config('system','suppress_tags')) {
1247                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1248                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1249
1250                 foreach($taglist as $tag) {
1251
1252                         if ($tag["url"] == "")
1253                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1254
1255                         if ($tag["type"] == TERM_HASHTAG) {
1256                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1257                                 $prefix = "#";
1258                         } elseif ($tag["type"] == TERM_MENTION) {
1259                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1260                                 $prefix = "@";
1261                         }
1262                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1263                 }
1264         }
1265
1266         $item['tags'] = $tags;
1267         $item['hashtags'] = $hashtags;
1268         $item['mentions'] = $mentions;
1269
1270         // Update the cached values if there is no "zrl=..." on the links
1271         $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
1272
1273         // Or update it if the current viewer is the intented viewer
1274         if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
1275                 $update = true;
1276
1277         put_item_in_cache($item, $update);
1278         $s = $item["rendered-html"];
1279
1280         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1281         call_hooks('prepare_body', $prep_arr);
1282         $s = $prep_arr['html'];
1283
1284         if(! $attach) {
1285                 // Replace the blockquotes with quotes that are used in mails
1286                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1287                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1288                 return $s;
1289         }
1290
1291         $as = '';
1292         $vhead = false;
1293         $arr = explode('[/attach],',$item['attach']);
1294         if(count($arr)) {
1295                 $as .= '<div class="body-attach">';
1296                 foreach($arr as $r) {
1297                         $matches = false;
1298                         $icon = '';
1299                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1300                         if($cnt) {
1301                                 foreach($matches as $mtch) {
1302                                         $mime = $mtch[3];
1303
1304                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1305                                                 $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1306                                         else
1307                                                 $the_url = $mtch[1];
1308
1309                                         if(strpos($mime, 'video') !== false) {
1310                                                 if(!$vhead) {
1311                                                         $vhead = true;
1312                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1313                                                                 '$baseurl' => z_root(),
1314                                                         ));
1315                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1316                                                                 '$baseurl' => z_root(),
1317                                                         ));
1318                                                 }
1319
1320                                                 $id = end(explode('/', $the_url));
1321                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1322                                                         '$video'        => array(
1323                                                                 'id'       => $id,
1324                                                                 'title'         => t('View Video'),
1325                                                                 'src'           => $the_url,
1326                                                                 'mime'          => $mime,
1327                                                         ),
1328                                                 ));
1329                                         }
1330
1331                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1332                                         if($filetype) {
1333                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1334                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1335                                         }
1336                                         else {
1337                                                 $filetype = 'unkn';
1338                                                 $filesubtype = 'unkn';
1339                                         }
1340
1341                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1342                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1343                                         switch($icontype) {
1344                                                 case 'video':
1345                                                 case 'audio':
1346                                                 case 'image':
1347                                                 case 'text':
1348                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1349                                                         break;
1350                                                 default:
1351                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1352                                                         break;
1353                                         }*/
1354
1355                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1356                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1357
1358                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1359                                 }
1360                         }
1361                 }
1362                 $as .= '<div class="clear"></div></div>';
1363         }
1364         $s = $s . $as;
1365
1366         // map
1367         if(strpos($s,'<div class="map">') !== false && $item['coord']) {
1368                 $x = generate_map(trim($item['coord']));
1369                 if($x) {
1370                         $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
1371                 }
1372         }
1373
1374
1375         // Look for spoiler
1376         $spoilersearch = '<blockquote class="spoiler">';
1377
1378         // Remove line breaks before the spoiler
1379         while ((strpos($s, "\n".$spoilersearch) !== false))
1380                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1381         while ((strpos($s, "<br />".$spoilersearch) !== false))
1382                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1383
1384         while ((strpos($s, $spoilersearch) !== false)) {
1385
1386                 $pos = strpos($s, $spoilersearch);
1387                 $rnd = random_string(8);
1388                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1389                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1390                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1391         }
1392
1393         // Look for quote with author
1394         $authorsearch = '<blockquote class="author">';
1395
1396         while ((strpos($s, $authorsearch) !== false)) {
1397
1398                 $pos = strpos($s, $authorsearch);
1399                 $rnd = random_string(8);
1400                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1401                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1402                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1403         }
1404
1405         // replace friendica image url size with theme preference
1406         if (x($a->theme_info,'item_image_size')){
1407             $ps = $a->theme_info['item_image_size'];
1408
1409             $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1410         }
1411
1412         $prep_arr = array('item' => $item, 'html' => $s);
1413         call_hooks('prepare_body_final', $prep_arr);
1414
1415         return $prep_arr['html'];
1416 }}
1417
1418
1419 if(! function_exists('prepare_text')) {
1420 /**
1421  * Given a text string, convert from bbcode to html and add smilie icons.
1422  *
1423  * @param string $text
1424  * @return string
1425  */
1426 function prepare_text($text) {
1427
1428         require_once('include/bbcode.php');
1429
1430         if(stristr($text,'[nosmile]'))
1431                 $s = bbcode($text);
1432         else
1433                 $s = Smilies::replace(bbcode($text));
1434
1435         return trim($s);
1436 }}
1437
1438
1439
1440 /**
1441  * return array with details for categories and folders for an item
1442  *
1443  * @param array $item
1444  * @return array
1445  *
1446   * [
1447  *      [ // categories array
1448  *          {
1449  *               'name': 'category name',
1450  *               'removeurl': 'url to remove this category',
1451  *               'first': 'is the first in this array? true/false',
1452  *               'last': 'is the last in this array? true/false',
1453  *           } ,
1454  *           ....
1455  *       ],
1456  *       [ //folders array
1457  *                      {
1458  *               'name': 'folder name',
1459  *               'removeurl': 'url to remove this folder',
1460  *               'first': 'is the first in this array? true/false',
1461  *               'last': 'is the last in this array? true/false',
1462  *           } ,
1463  *           ....
1464  *       ]
1465  *  ]
1466  */
1467 function get_cats_and_terms($item) {
1468
1469         $a = get_app();
1470         $categories = array();
1471         $folders = array();
1472
1473         $matches = false; $first = true;
1474         $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1475         if($cnt) {
1476                 foreach($matches as $mtch) {
1477                         $categories[] = array(
1478                                 'name' => xmlify(file_tag_decode($mtch[1])),
1479                                 'url' =>  "#",
1480                                 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1481                                 'first' => $first,
1482                                 'last' => false
1483                         );
1484                         $first = false;
1485                 }
1486         }
1487         if (count($categories)) $categories[count($categories)-1]['last'] = true;
1488
1489
1490         if(local_user() == $item['uid']) {
1491                 $matches = false; $first = true;
1492                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1493                 if($cnt) {
1494                         foreach($matches as $mtch) {
1495                                 $folders[] = array(
1496                                         'name' => xmlify(file_tag_decode($mtch[1])),
1497                                         'url' =>  "#",
1498                                         'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1499                                         'first' => $first,
1500                                         'last' => false
1501                                 );
1502                                 $first = false;
1503                         }
1504                 }
1505         }
1506
1507         if (count($folders)) $folders[count($folders)-1]['last'] = true;
1508
1509         return array($categories, $folders);
1510 }
1511
1512 if(! function_exists('get_plink')) {
1513 /**
1514  * get private link for item
1515  * @param array $item
1516  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1517  */
1518 function get_plink($item) {
1519         $a = get_app();
1520
1521         if ($a->user['nickname'] != "") {
1522                 $ret = array(
1523                                 //'href' => "display/".$a->user['nickname']."/".$item['id'],
1524                                 'href' => "display/".$item['guid'],
1525                                 'orig' => "display/".$item['guid'],
1526                                 'title' => t('View on separate page'),
1527                                 'orig_title' => t('view on separate page'),
1528                         );
1529
1530                 if (x($item,'plink')) {
1531                         $ret["href"] = $a->remove_baseurl($item['plink']);
1532                         $ret["title"] = t('link to source');
1533                 }
1534
1535         } elseif (x($item,'plink') && ($item['private'] != 1))
1536                 $ret = array(
1537                                 'href' => $item['plink'],
1538                                 'orig' => $item['plink'],
1539                                 'title' => t('link to source'),
1540                         );
1541         else
1542                 $ret = array();
1543
1544         //if (x($item,'plink') && ($item['private'] != 1))
1545
1546         return($ret);
1547 }}
1548
1549 if(! function_exists('unamp')) {
1550 /**
1551  * replace html amp entity with amp char
1552  * @param string $s
1553  * @return string
1554  */
1555 function unamp($s) {
1556         return str_replace('&amp;', '&', $s);
1557 }}
1558
1559
1560 if(! function_exists('return_bytes')) {
1561 /**
1562  * return number of bytes in size (K, M, G)
1563  * @param string $size_str
1564  * @return number
1565  */
1566 function return_bytes ($size_str) {
1567         switch (substr ($size_str, -1)) {
1568                 case 'M': case 'm': return (int)$size_str * 1048576;
1569                 case 'K': case 'k': return (int)$size_str * 1024;
1570                 case 'G': case 'g': return (int)$size_str * 1073741824;
1571                 default: return $size_str;
1572         }
1573 }}
1574
1575 /**
1576  * @return string
1577  */
1578 function generate_user_guid() {
1579         $found = true;
1580         do {
1581                 $guid = get_guid(32);
1582                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1583                         dbesc($guid)
1584                 );
1585                 if(! count($x))
1586                         $found = false;
1587         } while ($found == true );
1588         return $guid;
1589 }
1590
1591
1592 /**
1593  * @param string $s
1594  * @param boolean $strip_padding
1595  * @return string
1596  */
1597 function base64url_encode($s, $strip_padding = false) {
1598
1599         $s = strtr(base64_encode($s),'+/','-_');
1600
1601         if($strip_padding)
1602                 $s = str_replace('=','',$s);
1603
1604         return $s;
1605 }
1606
1607 /**
1608  * @param string $s
1609  * @return string
1610  */
1611 function base64url_decode($s) {
1612
1613         if(is_array($s)) {
1614                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1615                 return $s;
1616         }
1617
1618 /*
1619  *  // Placeholder for new rev of salmon which strips base64 padding.
1620  *  // PHP base64_decode handles the un-padded input without requiring this step
1621  *  // Uncomment if you find you need it.
1622  *
1623  *      $l = strlen($s);
1624  *      if(! strpos($s,'=')) {
1625  *              $m = $l % 4;
1626  *              if($m == 2)
1627  *                      $s .= '==';
1628  *              if($m == 3)
1629  *                      $s .= '=';
1630  *      }
1631  *
1632  */
1633
1634         return base64_decode(strtr($s,'-_','+/'));
1635 }
1636
1637
1638 if (!function_exists('str_getcsv')) {
1639         /**
1640          * Parse csv string
1641          *
1642          * @param string $input
1643          * @param string $delimiter
1644          * @param string $enclosure
1645          * @param string $escape
1646          * @param string $eol
1647          * @return boolean|array False on error, otherwise array[row][column]
1648          */
1649 function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1650         if (is_string($input) && !empty($input)) {
1651                 $output = array();
1652                 $tmp    = preg_split("/".$eol."/",$input);
1653                 if (is_array($tmp) && !empty($tmp)) {
1654                         while (list($line_num, $line) = each($tmp)) {
1655                                 if (preg_match("/".$escape.$enclosure."/",$line)) {
1656                                         while ($strlen = strlen($line)) {
1657                                                 $pos_delimiter       = strpos($line,$delimiter);
1658                                                 $pos_enclosure_start = strpos($line,$enclosure);
1659                                                 if (
1660                                                         is_int($pos_delimiter) && is_int($pos_enclosure_start)
1661                                                         && ($pos_enclosure_start < $pos_delimiter)
1662                                                         ) {
1663                                                         $enclosed_str = substr($line,1);
1664                                                         $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1665                                                         $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1666                                                         $output[$line_num][] = $enclosed_str;
1667                                                         $offset = $pos_enclosure_end+3;
1668                                                 } else {
1669                                                         if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1670                                                                 $output[$line_num][] = substr($line,0);
1671                                                                 $offset = strlen($line);
1672                                                         } else {
1673                                                                 $output[$line_num][] = substr($line,0,$pos_delimiter);
1674                                                                 $offset = (
1675                                                                         !empty($pos_enclosure_start)
1676                                                                         && ($pos_enclosure_start < $pos_delimiter)
1677                                                                         )
1678                                                                         ?$pos_enclosure_start
1679                                                                         :$pos_delimiter+1;
1680                                                         }
1681                                                 }
1682                                                 $line = substr($line,$offset);
1683                                         }
1684                                 } else {
1685                                         $line = preg_split("/".$delimiter."/",$line);
1686
1687                                         /*
1688                                          * Validating against pesky extra line breaks creating false rows.
1689                                          */
1690                                         if (is_array($line) && !empty($line[0])) {
1691                                                 $output[$line_num] = $line;
1692                                 }
1693                                 }
1694                         }
1695                         return $output;
1696                 } else {
1697                 return false;
1698                 }
1699         } else {
1700                 return false;
1701         }
1702 }
1703 }
1704
1705 /**
1706  * return div element with class 'clear'
1707  * @return string
1708  * @deprecated
1709  */
1710 function cleardiv() {
1711         return '<div class="clear"></div>';
1712 }
1713
1714
1715 function bb_translate_video($s) {
1716
1717         $matches = null;
1718         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1719         if($r) {
1720                 foreach($matches as $mtch) {
1721                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1722                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1723                         elseif(stristr($mtch[1],'vimeo'))
1724                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1725                 }
1726         }
1727         return $s;
1728 }
1729
1730 function html2bb_video($s) {
1731
1732         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1733                         '[youtube]$2[/youtube]', $s);
1734
1735         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1736                         '[youtube]$2[/youtube]', $s);
1737
1738         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1739                         '[vimeo]$2[/vimeo]', $s);
1740
1741         return $s;
1742 }
1743
1744 /**
1745  * apply xmlify() to all values of array $val, recursively
1746  * @param array $val
1747  * @return array
1748  */
1749 function array_xmlify($val){
1750         if (is_bool($val)) return $val?"true":"false";
1751         if (is_array($val)) return array_map('array_xmlify', $val);
1752         return xmlify((string) $val);
1753 }
1754
1755
1756 /**
1757  * transorm link href and img src from relative to absolute
1758  *
1759  * @param string $text
1760  * @param string $base base url
1761  * @return string
1762  */
1763 function reltoabs($text, $base) {
1764         if (empty($base))
1765             return $text;
1766
1767         $base = rtrim($base,'/');
1768
1769         $base2 = $base . "/";
1770
1771         // Replace links
1772         $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1773         $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1774         $text = preg_replace($pattern, $replace, $text);
1775
1776         $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1777         $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1778         $text = preg_replace($pattern, $replace, $text);
1779
1780         // Replace images
1781         $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1782         $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1783         $text = preg_replace($pattern, $replace, $text);
1784
1785         $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1786         $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1787         $text = preg_replace($pattern, $replace, $text);
1788
1789
1790         // Done
1791         return $text;
1792 }
1793
1794 /**
1795  * get translated item type
1796  *
1797  * @param array $itme
1798  * @return string
1799  */
1800 function item_post_type($item) {
1801         if(intval($item['event-id']))
1802                 return t('event');
1803         if(strlen($item['resource-id']))
1804                 return t('photo');
1805         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1806                 return t('activity');
1807         if($item['id'] != $item['parent'])
1808                 return t('comment');
1809         return t('post');
1810 }
1811
1812 // post categories and "save to file" use the same item.file table for storage.
1813 // We will differentiate the different uses by wrapping categories in angle brackets
1814 // and save to file categories in square brackets.
1815 // To do this we need to escape these characters if they appear in our tag.
1816
1817 function file_tag_encode($s) {
1818         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1819 }
1820
1821 function file_tag_decode($s) {
1822         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1823 }
1824
1825 function file_tag_file_query($table,$s,$type = 'file') {
1826
1827         if($type == 'file')
1828                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1829         else
1830                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1831         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1832 }
1833
1834 // ex. given music,video return <music><video> or [music][video]
1835 function file_tag_list_to_file($list,$type = 'file') {
1836         $tag_list = '';
1837         if(strlen($list)) {
1838                 $list_array = explode(",",$list);
1839                 if($type == 'file') {
1840                         $lbracket = '[';
1841                         $rbracket = ']';
1842                 }
1843                 else {
1844                         $lbracket = '<';
1845                         $rbracket = '>';
1846                 }
1847
1848                 foreach($list_array as $item) {
1849                   if(strlen($item)) {
1850                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1851                         }
1852                 }
1853         }
1854         return $tag_list;
1855 }
1856
1857 // ex. given <music><video>[friends], return music,video or friends
1858 function file_tag_file_to_list($file,$type = 'file') {
1859         $matches = false;
1860         $list = '';
1861         if($type == 'file') {
1862                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
1863         }
1864         else {
1865                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
1866         }
1867         if($cnt) {
1868                 foreach($matches as $mtch) {
1869                         if(strlen($list))
1870                                 $list .= ',';
1871                         $list .= file_tag_decode($mtch[1]);
1872                 }
1873         }
1874
1875         return $list;
1876 }
1877
1878 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
1879         // $file_old - categories previously associated with an item
1880         // $file_new - new list of categories for an item
1881
1882         if(! intval($uid))
1883                 return false;
1884
1885         if($file_old == $file_new)
1886                 return true;
1887
1888         $saved = get_pconfig($uid,'system','filetags');
1889         if(strlen($saved)) {
1890                 if($type == 'file') {
1891                         $lbracket = '[';
1892                         $rbracket = ']';
1893                         $termtype = TERM_FILE;
1894                 }
1895                 else {
1896                         $lbracket = '<';
1897                         $rbracket = '>';
1898                         $termtype = TERM_CATEGORY;
1899                 }
1900
1901                 $filetags_updated = $saved;
1902
1903                 // check for new tags to be added as filetags in pconfig
1904                 $new_tags = array();
1905                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
1906
1907                 foreach($check_new_tags as $tag) {
1908                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
1909                                 $new_tags[] = $tag;
1910                 }
1911
1912                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
1913
1914                 // check for deleted tags to be removed from filetags in pconfig
1915                 $deleted_tags = array();
1916                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
1917
1918                 foreach($check_deleted_tags as $tag) {
1919                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
1920                                 $deleted_tags[] = $tag;
1921                 }
1922
1923                 foreach($deleted_tags as $key => $tag) {
1924                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
1925                                 dbesc($tag),
1926                                 intval(TERM_OBJ_POST),
1927                                 intval($termtype),
1928                                 intval($uid));
1929
1930                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
1931                         //      intval($uid)
1932                         //);
1933
1934                         if(count($r)) {
1935                                 unset($deleted_tags[$key]);
1936                         }
1937                         else {
1938                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
1939                         }
1940                 }
1941
1942                 if($saved != $filetags_updated) {
1943                         set_pconfig($uid,'system','filetags', $filetags_updated);
1944                 }
1945                 return true;
1946         }
1947         else
1948                 if(strlen($file_new)) {
1949                         set_pconfig($uid,'system','filetags', $file_new);
1950                 }
1951                 return true;
1952 }
1953
1954 function file_tag_save_file($uid,$item,$file) {
1955         require_once("include/files.php");
1956
1957         $result = false;
1958         if(! intval($uid))
1959                 return false;
1960         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1961                 intval($item),
1962                 intval($uid)
1963         );
1964         if(count($r)) {
1965                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
1966                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
1967                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
1968                                 intval($item),
1969                                 intval($uid)
1970                         );
1971
1972                 create_files_from_item($item);
1973
1974                 $saved = get_pconfig($uid,'system','filetags');
1975                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
1976                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
1977                 info( t('Item filed') );
1978         }
1979         return true;
1980 }
1981
1982 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
1983         require_once("include/files.php");
1984
1985         $result = false;
1986         if(! intval($uid))
1987                 return false;
1988
1989         if($cat == true) {
1990                 $pattern = '<' . file_tag_encode($file) . '>' ;
1991                 $termtype = TERM_CATEGORY;
1992         } else {
1993                 $pattern = '[' . file_tag_encode($file) . ']' ;
1994                 $termtype = TERM_FILE;
1995         }
1996
1997
1998         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1999                 intval($item),
2000                 intval($uid)
2001         );
2002         if(! count($r))
2003                 return false;
2004
2005         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2006                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2007                 intval($item),
2008                 intval($uid)
2009         );
2010
2011         create_files_from_item($item);
2012
2013         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2014                 dbesc($file),
2015                 intval(TERM_OBJ_POST),
2016                 intval($termtype),
2017                 intval($uid));
2018
2019         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2020         //);
2021
2022         if(! count($r)) {
2023                 $saved = get_pconfig($uid,'system','filetags');
2024                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2025
2026         }
2027         return true;
2028 }
2029
2030 function normalise_openid($s) {
2031         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2032 }
2033
2034
2035 function undo_post_tagging($s) {
2036         $matches = null;
2037         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2038         if($cnt) {
2039                 foreach($matches as $mtch) {
2040                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2041                 }
2042         }
2043         return $s;
2044 }
2045
2046 function fix_mce_lf($s) {
2047         $s = str_replace("\r\n","\n",$s);
2048 //      $s = str_replace("\n\n","\n",$s);
2049         return $s;
2050 }
2051
2052
2053 function protect_sprintf($s) {
2054         return(str_replace('%','%%',$s));
2055 }
2056
2057
2058 function is_a_date_arg($s) {
2059         $i = intval($s);
2060         if($i > 1900) {
2061                 $y = date('Y');
2062                 if($i <= $y+1 && strpos($s,'-') == 4) {
2063                         $m = intval(substr($s,5));
2064                         if($m > 0 && $m <= 12)
2065                                 return true;
2066                 }
2067         }
2068         return false;
2069 }
2070
2071 /**
2072  * remove intentation from a text
2073  */
2074 function deindent($text, $chr="[\t ]", $count=NULL) {
2075         $text = fix_mce_lf($text);
2076         $lines = explode("\n", $text);
2077         if (is_null($count)) {
2078                 $m = array();
2079                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2080                 preg_match("|^".$chr."*|", $lines[$k], $m);
2081                 $count = strlen($m[0]);
2082         }
2083         for ($k=0; $k<count($lines); $k++){
2084                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2085         }
2086
2087         return implode("\n", $lines);
2088 }
2089
2090 function formatBytes($bytes, $precision = 2) {
2091          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2092
2093         $bytes = max($bytes, 0);
2094         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2095         $pow = min($pow, count($units) - 1);
2096
2097         $bytes /= pow(1024, $pow);
2098
2099         return round($bytes, $precision) . ' ' . $units[$pow];
2100 }
2101
2102 /**
2103  * @brief translate and format the networkname of a contact
2104  *
2105  * @param string $network
2106  *      Networkname of the contact (e.g. dfrn, rss and so on)
2107  * @param sting $url
2108  *      The contact url
2109  * @return string
2110  */
2111 function format_network_name($network, $url = 0) {
2112         if ($network != "") {
2113                 require_once('include/contact_selectors.php');
2114                 if ($url != "")
2115                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2116                 else
2117                         $network_name = network_to_name($network);
2118
2119                 return $network_name;
2120         }
2121
2122 }
2123
2124 /**
2125  * @brief Syntax based code highlighting for popular languages.
2126  * @param string $s Code block
2127  * @param string $lang Programming language
2128  * @return string Formated html
2129  */
2130 function text_highlight($s,$lang) {
2131         if($lang === 'js')
2132                 $lang = 'javascript';
2133
2134         if(! strpos('Text_Highlighter',get_include_path())) {
2135                 set_include_path(get_include_path() . PATH_SEPARATOR . 'library/Text_Highlighter');
2136         }
2137
2138         require_once('library/Text_Highlighter/Text/Highlighter.php');
2139         require_once('library/Text_Highlighter/Text/Highlighter/Renderer/Html.php');
2140         $options = array(
2141                 'numbers' => HL_NUMBERS_LI,
2142                 'tabsize' => 4,
2143                 );
2144
2145         $tag_added = false;
2146         $s = trim(html_entity_decode($s,ENT_COMPAT));
2147         $s = str_replace("    ","\t",$s);
2148
2149         // The highlighter library insists on an opening php tag for php code blocks. If
2150         // it isn't present, nothing is highlighted. So we're going to see if it's present.
2151         // If not, we'll add it, and then quietly remove it after we get the processed output back.
2152
2153         if($lang === 'php') {
2154                 if(strpos('<?php',$s) !== 0) {
2155                         $s = '<?php' . "\n" . $s;
2156                         $tag_added = true;
2157                 }
2158         }
2159
2160         $renderer = new Text_Highlighter_Renderer_HTML($options);
2161         $hl = Text_Highlighter::factory($lang);
2162         $hl->setRenderer($renderer);
2163         $o = $hl->highlight($s);
2164         $o = str_replace(["    ","\n"],["&nbsp;&nbsp;&nbsp;&nbsp;",''],$o);
2165
2166         if($tag_added) {
2167                 $b = substr($o,0,strpos($o,'<li>'));
2168                 $e = substr($o,strpos($o,'</li>'));
2169                 $o = $b . $e;
2170         }
2171
2172         return('<code>' . $o . '</code>');
2173 }