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