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