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