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