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