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