]> git.mxchange.org Git - friendica.git/blob - include/text.php
The friend contact block wasn't displayed in the profile
[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', 24);
941         if ($shown == 0) {
942                 return;
943         }
944
945         if ((! is_array($a->profile)) || ($a->profile['hide-friends'])) {
946                 return $o;
947         }
948         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
949                         WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
950                                 AND NOT `pending` AND NOT `hidden` AND NOT `archive`
951                                 AND `network` IN ('%s', '%s', '%s')",
952                         intval($a->profile['uid']),
953                         dbesc(NETWORK_DFRN),
954                         dbesc(NETWORK_OSTATUS),
955                         dbesc(NETWORK_DIASPORA)
956         );
957         if (DBM::is_result($r)) {
958                 $total = intval($r[0]['total']);
959         }
960         if (! $total) {
961                 $contacts = t('No contacts');
962                 $micropro = null;
963         } else {
964                 // Splitting the query in two parts makes it much faster
965                 $r = q("SELECT `id` FROM `contact`
966                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
967                                         AND NOT `pending` AND NOT `hidden` AND NOT `archive`
968                                         AND `network` IN ('%s', '%s', '%s')
969                                 ORDER BY RAND() LIMIT %d",
970                                 intval($a->profile['uid']),
971                                 dbesc(NETWORK_DFRN),
972                                 dbesc(NETWORK_OSTATUS),
973                                 dbesc(NETWORK_DIASPORA),
974                                 intval($shown)
975                 );
976                 if (DBM::is_result($r)) {
977                         $contacts = array();
978                         foreach ($r AS $contact) {
979                                 $contacts[] = $contact["id"];
980                         }
981                         $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `thumb`, `network` FROM `contact` WHERE `id` IN (%s)",
982                                 dbesc(implode(",", $contacts)));
983
984                         if (DBM::is_result($r)) {
985                                 $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
986                                 $micropro = Array();
987                                 foreach ($r as $rr) {
988                                         $micropro[] = micropro($rr,true,'mpfriend');
989                                 }
990                         }
991                 }
992         }
993
994         $tpl = get_markup_template('contact_block.tpl');
995         $o = replace_macros($tpl, array(
996                 '$contacts' => $contacts,
997                 '$nickname' => $a->profile['nickname'],
998                 '$viewcontacts' => t('View Contacts'),
999                 '$micropro' => $micropro,
1000         ));
1001
1002         $arr = array('contacts' => $r, 'output' => $o);
1003
1004         call_hooks('contact_block_end', $arr);
1005         return $o;
1006
1007 }}
1008
1009 /**
1010  * @brief Format contacts as picture links or as texxt links
1011  *
1012  * @param array $contact Array with contacts which contains an array with
1013  *      int 'id' => The ID of the contact
1014  *      int 'uid' => The user ID of the user who owns this data
1015  *      string 'name' => The name of the contact
1016  *      string 'url' => The url to the profile page of the contact
1017  *      string 'addr' => The webbie of the contact (e.g.) username@friendica.com
1018  *      string 'network' => The network to which the contact belongs to
1019  *      string 'thumb' => The contact picture
1020  *      string 'click' => js code which is performed when clicking on the contact
1021  * @param boolean $redirect If true try to use the redir url if it's possible
1022  * @param string $class CSS class for the
1023  * @param boolean $textmode If true display the contacts as text links
1024  *      if false display the contacts as picture links
1025
1026  * @return string Formatted html
1027  */
1028 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
1029
1030         // Use the contact URL if no address is available
1031         if ($contact["addr"] == "") {
1032                 $contact["addr"] = $contact["url"];
1033         }
1034
1035         $url = $contact['url'];
1036         $sparkle = '';
1037         $redir = false;
1038
1039         if ($redirect) {
1040                 $a = get_app();
1041                 $redirect_url = 'redir/' . $contact['id'];
1042                 if (local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
1043                         $redir = true;
1044                         $url = $redirect_url;
1045                         $sparkle = ' sparkle';
1046                 } else {
1047                         $url = zrl($url);
1048                 }
1049         }
1050
1051         // If there is some js available we don't need the url
1052         if (x($contact, 'click')) {
1053                 $url = '';
1054         }
1055
1056         return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
1057                 '$click' => (($contact['click']) ? $contact['click'] : ''),
1058                 '$class' => $class,
1059                 '$url' => $url,
1060                 '$photo' => proxy_url($contact['thumb'], false, PROXY_SIZE_THUMB),
1061                 '$name' => $contact['name'],
1062                 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
1063                 '$parkle' => $sparkle,
1064                 '$redir' => $redir,
1065
1066         ));
1067 }
1068
1069
1070
1071 if (! function_exists('search')) {
1072 /**
1073  * search box
1074  *
1075  * @param string $s search query
1076  * @param string $id html id
1077  * @param string $url search url
1078  * @param boolean $savedsearch show save search button
1079  */
1080 function search($s, $id = 'search-box', $url = 'search', $save = false, $aside = true) {
1081         $a = get_app();
1082
1083         $values = array(
1084                         '$s' => htmlspecialchars($s),
1085                         '$id' => $id,
1086                         '$action_url' => $url,
1087                         '$search_label' => t('Search'),
1088                         '$save_label' => t('Save'),
1089                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
1090                         '$search_hint' => t('@name, !forum, #tags, content'),
1091                 );
1092
1093         if (!$aside) {
1094                 $values['$searchoption'] = array(
1095                                         t("Full Text"),
1096                                         t("Tags"),
1097                                         t("Contacts"));
1098
1099                 if (Config::get('system','poco_local_search')) {
1100                         $values['$searchoption'][] = t("Forums");
1101                 }
1102         }
1103
1104         return replace_macros(get_markup_template('searchbox.tpl'), $values);
1105 }}
1106
1107 if (! function_exists('valid_email')) {
1108 /**
1109  * Check if $x is a valid email string
1110  *
1111  * @param string $x
1112  * @return boolean
1113  */
1114 function valid_email($x){
1115
1116         /// @TODO Removed because Fabio told me so.
1117         //if (Config::get('system','disable_email_validation'))
1118         //      return true;
1119         return preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/', $x);
1120 }}
1121
1122
1123 if (! function_exists('linkify')) {
1124 /**
1125  * Replace naked text hyperlink with HTML formatted hyperlink
1126  *
1127  * @param string $s
1128  */
1129 function linkify($s) {
1130         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1131         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1132         return $s;
1133 }}
1134
1135
1136 /**
1137  * Load poke verbs
1138  *
1139  * @return array index is present tense verb
1140                                  value is array containing past tense verb, translation of present, translation of past
1141  * @hook poke_verbs pokes array
1142  */
1143 function get_poke_verbs() {
1144
1145         // index is present tense verb
1146         // value is array containing past tense verb, translation of present, translation of past
1147
1148         $arr = array(
1149                 'poke' => array( 'poked', t('poke'), t('poked')),
1150                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1151                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1152                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1153                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1154                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1155         );
1156         call_hooks('poke_verbs', $arr);
1157         return $arr;
1158 }
1159
1160 /**
1161  * Load moods
1162  * @return array index is mood, value is translated mood
1163  * @hook mood_verbs moods array
1164  */
1165 function get_mood_verbs() {
1166
1167         $arr = array(
1168                 'happy'      => t('happy'),
1169                 'sad'        => t('sad'),
1170                 'mellow'     => t('mellow'),
1171                 'tired'      => t('tired'),
1172                 'perky'      => t('perky'),
1173                 'angry'      => t('angry'),
1174                 'stupefied'  => t('stupified'),
1175                 'puzzled'    => t('puzzled'),
1176                 'interested' => t('interested'),
1177                 'bitter'     => t('bitter'),
1178                 'cheerful'   => t('cheerful'),
1179                 'alive'      => t('alive'),
1180                 'annoyed'    => t('annoyed'),
1181                 'anxious'    => t('anxious'),
1182                 'cranky'     => t('cranky'),
1183                 'disturbed'  => t('disturbed'),
1184                 'frustrated' => t('frustrated'),
1185                 'motivated'  => t('motivated'),
1186                 'relaxed'    => t('relaxed'),
1187                 'surprised'  => t('surprised'),
1188         );
1189
1190         call_hooks('mood_verbs', $arr);
1191         return $arr;
1192 }
1193
1194 /**
1195  * @brief Translate days and months names.
1196  * 
1197  * @param string $s String with day or month name.
1198  * @return string Translated string.
1199  */
1200 function day_translate($s) {
1201         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1202                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1203                 $s);
1204
1205         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1206                 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')),
1207                 $ret);
1208
1209         return $ret;
1210 }
1211
1212 /**
1213  * @brief Translate short days and months names.
1214  * 
1215  * @param string $s String with short day or month name.
1216  * @return string Translated string.
1217  */
1218 function day_short_translate($s) {
1219         $ret = str_replace(array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'),
1220                 array(t('Mon'), t('Tue'), t('Wed'), t('Thu'), t('Fri'), t('Sat'), t('Sund')),
1221                 $s);
1222         $ret = str_replace(array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov','Dec'),
1223                 array(t('Jan'), t('Feb'), t('Mar'), t('Apr'), t('May'), ('Jun'), t('Jul'), t('Aug'), t('Sep'), t('Oct'), t('Nov'), t('Dec')),
1224                 $ret);
1225         return $ret;
1226 }
1227
1228 if (! function_exists('normalise_link')) {
1229 /**
1230  * Normalize url
1231  *
1232  * @param string $url
1233  * @return string
1234  */
1235 function normalise_link($url) {
1236         $ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
1237         return rtrim($ret,'/');
1238 }}
1239
1240
1241
1242 if (! function_exists('link_compare')) {
1243 /**
1244  * Compare two URLs to see if they are the same, but ignore
1245  * slight but hopefully insignificant differences such as if one
1246  * is https and the other isn't, or if one is www.something and
1247  * the other isn't - and also ignore case differences.
1248  *
1249  * @param string $a first url
1250  * @param string $b second url
1251  * @return boolean True if the URLs match, otherwise False
1252  *
1253  */
1254 function link_compare($a, $b) {
1255         return (strcasecmp(normalise_link($a), normalise_link($b)) === 0);
1256 }}
1257
1258 /**
1259  * @brief Find any non-embedded images in private items and add redir links to them
1260  *
1261  * @param App $a
1262  * @param array &$item The field array of an item row
1263  */
1264 function redir_private_images($a, &$item)
1265 {
1266         $matches = false;
1267         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1268         if ($cnt) {
1269                 foreach ($matches as $mtch) {
1270                         if (strpos($mtch[1], '/redir') !== false) {
1271                                 continue;
1272                         }
1273
1274                         if ((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1275                                 $img_url = 'redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
1276                                 $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
1277                         }
1278                 }
1279         }
1280 }
1281
1282 function put_item_in_cache(&$item, $update = false) {
1283
1284         if (($item["rendered-hash"] != hash("md5", $item["body"])) || ($item["rendered-hash"] == "") ||
1285                 ($item["rendered-html"] == "") || Config::get("system", "ignore_cache")) {
1286
1287                 // The function "redir_private_images" changes the body.
1288                 // I'm not sure if we should store it permanently, so we save the old value.
1289                 $body = $item["body"];
1290
1291                 $a = get_app();
1292                 redir_private_images($a, $item);
1293
1294                 $item["rendered-html"] = prepare_text($item["body"]);
1295                 $item["rendered-hash"] = hash("md5", $item["body"]);
1296                 $item["body"] = $body;
1297
1298                 if ($update && ($item["id"] > 0)) {
1299                         dba::update('item', array('rendered-html' => $item["rendered-html"], 'rendered-hash' => $item["rendered-hash"]),
1300                                         array('id' => $item["id"]), false);
1301                 }
1302         }
1303 }
1304
1305 /**
1306  * @brief Given an item array, convert the body element from bbcode to html and add smilie icons.
1307  * If attach is true, also add icons for item attachments.
1308  *
1309  * @param array $item
1310  * @param boolean $attach
1311  * @return string item body html
1312  * @hook prepare_body_init item array before any work
1313  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1314  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1315  */
1316 function prepare_body(&$item, $attach = false, $preview = false) {
1317
1318         $a = get_app();
1319         call_hooks('prepare_body_init', $item);
1320
1321         $searchpath = System::baseUrl() . "/search?tag=";
1322
1323         $tags = array();
1324         $hashtags = array();
1325         $mentions = array();
1326
1327         // In order to provide theme developers more possibilities, event items
1328         // are treated differently.
1329         if ($item['object-type'] === ACTIVITY_OBJ_EVENT && isset($item['event-id'])) {
1330                 $ev = format_event_item($item);
1331                 return $ev;
1332         }
1333
1334         if (!Config::get('system','suppress_tags')) {
1335                 $taglist = dba::p("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = ? AND `oid` = ? AND `type` IN (?, ?) ORDER BY `tid`",
1336                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1337
1338                 while ($tag = dba::fetch($taglist)) {
1339                         if ($tag["url"] == "") {
1340                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1341                         }
1342
1343                         $orig_tag = $tag["url"];
1344
1345                         $tag["url"] = best_link_url($item, $sp, $tag["url"]);
1346
1347                         if ($tag["type"] == TERM_HASHTAG) {
1348                                 if ($orig_tag != $tag["url"]) {
1349                                         $item['body'] = str_replace($orig_tag, $tag["url"], $item['body']);
1350                                 }
1351                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1352                                 $prefix = "#";
1353                         } elseif ($tag["type"] == TERM_MENTION) {
1354                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1355                                 $prefix = "@";
1356                         }
1357                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1358                 }
1359                 dba::close($taglist);
1360         }
1361
1362         $item['tags'] = $tags;
1363         $item['hashtags'] = $hashtags;
1364         $item['mentions'] = $mentions;
1365
1366         // Update the cached values if there is no "zrl=..." on the links.
1367         $update = (!local_user() && !remote_user() && ($item["uid"] == 0));
1368
1369         // Or update it if the current viewer is the intented viewer.
1370         if (($item["uid"] == local_user()) && ($item["uid"] != 0)) {
1371                 $update = true;
1372         }
1373
1374         put_item_in_cache($item, $update);
1375         $s = $item["rendered-html"];
1376
1377         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1378         call_hooks('prepare_body', $prep_arr);
1379         $s = $prep_arr['html'];
1380
1381         if (! $attach) {
1382                 // Replace the blockquotes with quotes that are used in mails.
1383                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1384                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1385                 return $s;
1386         }
1387
1388         $as = '';
1389         $vhead = false;
1390         $arr = explode('[/attach],', $item['attach']);
1391         if (count($arr)) {
1392                 foreach ($arr as $r) {
1393                         $matches = false;
1394                         $icon = '';
1395                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r ,$matches, PREG_SET_ORDER);
1396                         if ($cnt) {
1397                                 foreach ($matches as $mtch) {
1398                                         $mime = $mtch[3];
1399
1400                                         if ((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1401                                                 $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1402                                         } else {
1403                                                 $the_url = $mtch[1];
1404                                         }
1405
1406                                         if (strpos($mime, 'video') !== false) {
1407                                                 if (!$vhead) {
1408                                                         $vhead = true;
1409                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1410                                                                 '$baseurl' => System::baseUrl(),
1411                                                         ));
1412                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1413                                                                 '$baseurl' => System::baseUrl(),
1414                                                         ));
1415                                                 }
1416
1417                                                 $id = end(explode('/', $the_url));
1418                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1419                                                         '$video' => array(
1420                                                                 'id'     => $id,
1421                                                                 'title'  => t('View Video'),
1422                                                                 'src'    => $the_url,
1423                                                                 'mime'   => $mime,
1424                                                         ),
1425                                                 ));
1426                                         }
1427
1428                                         $filetype = strtolower(substr($mime, 0, strpos($mime, '/')));
1429                                         if ($filetype) {
1430                                                 $filesubtype = strtolower(substr($mime, strpos($mime, '/') + 1));
1431                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1432                                         } else {
1433                                                 $filetype = 'unkn';
1434                                                 $filesubtype = 'unkn';
1435                                         }
1436
1437                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1438                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1439
1440                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1441                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1442                                 }
1443                         }
1444                 }
1445         }
1446         if ($as != '') {
1447                 $s .= '<div class="body-attach">'.$as.'<div class="clear"></div></div>';
1448         }
1449
1450         // Map.
1451         if (strpos($s, '<div class="map">') !== false && x($item, 'coord')) {
1452                 $x = generate_map(trim($item['coord']));
1453                 if ($x) {
1454                         $s = preg_replace('/\<div class\=\"map\"\>/', '$0' . $x, $s);
1455                 }
1456         }
1457
1458
1459         // Look for spoiler.
1460         $spoilersearch = '<blockquote class="spoiler">';
1461
1462         // Remove line breaks before the spoiler.
1463         while ((strpos($s, "\n" . $spoilersearch) !== false)) {
1464                 $s = str_replace("\n" . $spoilersearch, $spoilersearch, $s);
1465         }
1466         while ((strpos($s, "<br />" . $spoilersearch) !== false)) {
1467                 $s = str_replace("<br />" . $spoilersearch, $spoilersearch, $s);
1468         }
1469
1470         while ((strpos($s, $spoilersearch) !== false)) {
1471                 $pos = strpos($s, $spoilersearch);
1472                 $rnd = random_string(8);
1473                 $spoilerreplace = '<br /> <span id="spoiler-wrap-' . $rnd . '" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-' . $rnd . '\');">' . sprintf(t('Click to open/close')) . '</span>'.
1474                                         '<blockquote class="spoiler" id="spoiler-' . $rnd . '" style="display: none;">';
1475                 $s = substr($s, 0, $pos) . $spoilerreplace . substr($s, $pos + strlen($spoilersearch));
1476         }
1477
1478         // Look for quote with author.
1479         $authorsearch = '<blockquote class="author">';
1480
1481         while ((strpos($s, $authorsearch) !== false)) {
1482                 $pos = strpos($s, $authorsearch);
1483                 $rnd = random_string(8);
1484                 $authorreplace = '<br /> <span id="author-wrap-' . $rnd . '" class="author-wrap fakelink" onclick="openClose(\'author-' . $rnd . '\');">' . sprintf(t('Click to open/close')) . '</span>'.
1485                                         '<blockquote class="author" id="author-' . $rnd . '" style="display: block;">';
1486                 $s = substr($s, 0, $pos) . $authorreplace . substr($s, $pos + strlen($authorsearch));
1487         }
1488
1489         // Replace friendica image url size with theme preference.
1490         if (x($a->theme_info, 'item_image_size')){
1491                 $ps = $a->theme_info['item_image_size'];
1492                 $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|', "$1-" . $ps, $s);
1493         }
1494
1495         $prep_arr = array('item' => $item, 'html' => $s);
1496         call_hooks('prepare_body_final', $prep_arr);
1497
1498         return $prep_arr['html'];
1499 }
1500
1501 /**
1502  * @brief Given a text string, convert from bbcode to html and add smilie icons.
1503  *
1504  * @param string $text String with bbcode.
1505  * @return string Formattet HTML.
1506  */
1507 function prepare_text($text) {
1508
1509         require_once 'include/bbcode.php';
1510
1511         if (stristr($text, '[nosmile]')) {
1512                 $s = bbcode($text);
1513         } else {
1514                 $s = Smilies::replace(bbcode($text));
1515         }
1516
1517         return trim($s);
1518 }
1519
1520 /**
1521  * return array with details for categories and folders for an item
1522  *
1523  * @param array $item
1524  * @return array
1525  *
1526   * [
1527  *      [ // categories array
1528  *          {
1529  *               'name': 'category name',
1530  *               'removeurl': 'url to remove this category',
1531  *               'first': 'is the first in this array? true/false',
1532  *               'last': 'is the last in this array? true/false',
1533  *           } ,
1534  *           ....
1535  *       ],
1536  *       [ //folders array
1537  *                      {
1538  *               'name': 'folder name',
1539  *               'removeurl': 'url to remove this folder',
1540  *               'first': 'is the first in this array? true/false',
1541  *               'last': 'is the last in this array? true/false',
1542  *           } ,
1543  *           ....
1544  *       ]
1545  *  ]
1546  */
1547 function get_cats_and_terms($item) {
1548
1549         $a = get_app();
1550         $categories = array();
1551         $folders = array();
1552
1553         $matches = false;
1554         $first = true;
1555         $cnt = preg_match_all('/<(.*?)>/', $item['file'], $matches, PREG_SET_ORDER);
1556         if ($cnt) {
1557                 foreach ($matches as $mtch) {
1558                         $categories[] = array(
1559                                 'name' => xmlify(file_tag_decode($mtch[1])),
1560                                 'url' =>  "#",
1561                                 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1562                                 'first' => $first,
1563                                 'last' => false
1564                         );
1565                         $first = false;
1566                 }
1567         }
1568
1569         if (count($categories)) {
1570                 $categories[count($categories) - 1]['last'] = true;
1571         }
1572
1573         if (local_user() == $item['uid']) {
1574                 $matches = false;
1575                 $first = true;
1576                 $cnt = preg_match_all('/\[(.*?)\]/', $item['file'], $matches, PREG_SET_ORDER);
1577                 if ($cnt) {
1578                         foreach ($matches as $mtch) {
1579                                 $folders[] = array(
1580                                         'name' => xmlify(file_tag_decode($mtch[1])),
1581                                         'url' =>  "#",
1582                                         'removeurl' => ((local_user() == $item['uid']) ? 'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])) : ""),
1583                                         'first' => $first,
1584                                         'last' => false
1585                                 );
1586                                 $first = false;
1587                         }
1588                 }
1589         }
1590
1591         if (count($folders)) {
1592                 $folders[count($folders) - 1]['last'] = true;
1593         }
1594
1595         return array($categories, $folders);
1596 }
1597
1598 if (! function_exists('get_plink')) {
1599 /**
1600  * get private link for item
1601  * @param array $item
1602  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1603  */
1604 function get_plink($item) {
1605         $a = get_app();
1606
1607         if ($a->user['nickname'] != "") {
1608                 $ret = array(
1609                                 //'href' => "display/" . $a->user['nickname'] . "/" . $item['id'],
1610                                 'href' => "display/" . $item['guid'],
1611                                 'orig' => "display/" . $item['guid'],
1612                                 'title' => t('View on separate page'),
1613                                 'orig_title' => t('view on separate page'),
1614                         );
1615
1616                 if (x($item, 'plink')) {
1617                         $ret["href"] = $a->remove_baseurl($item['plink']);
1618                         $ret["title"] = t('link to source');
1619                 }
1620
1621         } elseif (x($item, 'plink') && ($item['private'] != 1)) {
1622                 $ret = array(
1623                                 'href' => $item['plink'],
1624                                 'orig' => $item['plink'],
1625                                 'title' => t('link to source'),
1626                         );
1627         } else {
1628                 $ret = array();
1629         }
1630
1631         return $ret;
1632 }}
1633
1634 if (! function_exists('unamp')) {
1635 /**
1636  * replace html amp entity with amp char
1637  * @param string $s
1638  * @return string
1639  */
1640 function unamp($s) {
1641         return str_replace('&amp;', '&', $s);
1642 }}
1643
1644
1645 if (! function_exists('return_bytes')) {
1646 /**
1647  * return number of bytes in size (K, M, G)
1648  * @param string $size_str
1649  * @return number
1650  */
1651 function return_bytes ($size_str) {
1652         switch (substr ($size_str, -1)) {
1653                 case 'M': case 'm': return (int)$size_str * 1048576;
1654                 case 'K': case 'k': return (int)$size_str * 1024;
1655                 case 'G': case 'g': return (int)$size_str * 1073741824;
1656                 default: return $size_str;
1657         }
1658 }}
1659
1660 /**
1661  * @return string
1662  */
1663 function generate_user_guid() {
1664         $found = true;
1665         do {
1666                 $guid = get_guid(32);
1667                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1668                         dbesc($guid)
1669                 );
1670                 if (! DBM::is_result($x)) {
1671                         $found = false;
1672                 }
1673         } while ($found == true);
1674
1675         return $guid;
1676 }
1677
1678
1679 /**
1680  * @param string $s
1681  * @param boolean $strip_padding
1682  * @return string
1683  */
1684 function base64url_encode($s, $strip_padding = false) {
1685
1686         $s = strtr(base64_encode($s), '+/', '-_');
1687
1688         if ($strip_padding) {
1689                 $s = str_replace('=','',$s);
1690         }
1691
1692         return $s;
1693 }
1694
1695 /**
1696  * @param string $s
1697  * @return string
1698  */
1699 function base64url_decode($s) {
1700
1701         if (is_array($s)) {
1702                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1703                 return $s;
1704         }
1705
1706 /*
1707  *  // Placeholder for new rev of salmon which strips base64 padding.
1708  *  // PHP base64_decode handles the un-padded input without requiring this step
1709  *  // Uncomment if you find you need it.
1710  *
1711  *      $l = strlen($s);
1712  *      if (! strpos($s,'=')) {
1713  *              $m = $l % 4;
1714  *              if ($m == 2)
1715  *                      $s .= '==';
1716  *              if ($m == 3)
1717  *                      $s .= '=';
1718  *      }
1719  *
1720  */
1721
1722         return base64_decode(strtr($s,'-_','+/'));
1723 }
1724
1725
1726 if (!function_exists('str_getcsv')) {
1727         /**
1728          * Parse csv string
1729          *
1730          * @param string $input
1731          * @param string $delimiter
1732          * @param string $enclosure
1733          * @param string $escape
1734          * @param string $eol
1735          * @return boolean|array False on error, otherwise array[row][column]
1736          */
1737 function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1738         if (is_string($input) && !empty($input)) {
1739                 $output = array();
1740                 $tmp    = preg_split("/".$eol."/",$input);
1741                 if (is_array($tmp) && !empty($tmp)) {
1742                         while (list($line_num, $line) = each($tmp)) {
1743                                 if (preg_match("/".$escape.$enclosure."/",$line)) {
1744                                         while ($strlen = strlen($line)) {
1745                                                 $pos_delimiter       = strpos($line,$delimiter);
1746                                                 $pos_enclosure_start = strpos($line,$enclosure);
1747                                                 if (
1748                                                         is_int($pos_delimiter) && is_int($pos_enclosure_start)
1749                                                         && ($pos_enclosure_start < $pos_delimiter)
1750                                                         ) {
1751                                                         $enclosed_str = substr($line,1);
1752                                                         $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1753                                                         $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1754                                                         $output[$line_num][] = $enclosed_str;
1755                                                         $offset = $pos_enclosure_end+3;
1756                                                 } else {
1757                                                         if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1758                                                                 $output[$line_num][] = substr($line,0);
1759                                                                 $offset = strlen($line);
1760                                                         } else {
1761                                                                 $output[$line_num][] = substr($line,0,$pos_delimiter);
1762                                                                 $offset = (
1763                                                                         !empty($pos_enclosure_start)
1764                                                                         && ($pos_enclosure_start < $pos_delimiter)
1765                                                                         )
1766                                                                         ?$pos_enclosure_start
1767                                                                         :$pos_delimiter+1;
1768                                                         }
1769                                                 }
1770                                                 $line = substr($line,$offset);
1771                                         }
1772                                 } else {
1773                                         $line = preg_split("/".$delimiter."/",$line);
1774
1775                                         /*
1776                                          * Validating against pesky extra line breaks creating false rows.
1777                                          */
1778                                         if (is_array($line) && !empty($line[0])) {
1779                                                 $output[$line_num] = $line;
1780                                 }
1781                                 }
1782                         }
1783                         return $output;
1784                 } else {
1785                 return false;
1786                 }
1787         } else {
1788                 return false;
1789         }
1790 }
1791 }
1792
1793 /**
1794  * return div element with class 'clear'
1795  * @return string
1796  * @deprecated
1797  */
1798 function cleardiv() {
1799         return '<div class="clear"></div>';
1800 }
1801
1802
1803 function bb_translate_video($s) {
1804
1805         $matches = null;
1806         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1807         if ($r) {
1808                 foreach ($matches as $mtch) {
1809                         if ((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1810                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1811                         elseif (stristr($mtch[1],'vimeo'))
1812                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1813                 }
1814         }
1815         return $s;
1816 }
1817
1818 function html2bb_video($s) {
1819
1820         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1821                         '[youtube]$2[/youtube]', $s);
1822
1823         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1824                         '[youtube]$2[/youtube]', $s);
1825
1826         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1827                         '[vimeo]$2[/vimeo]', $s);
1828
1829         return $s;
1830 }
1831
1832 /**
1833  * apply xmlify() to all values of array $val, recursively
1834  * @param array $val
1835  * @return array
1836  */
1837 function array_xmlify($val){
1838         if (is_bool($val)) {
1839                 return $val?"true":"false";
1840         } elseif (is_array($val)) {
1841                 return array_map('array_xmlify', $val);
1842         }
1843         return xmlify((string) $val);
1844 }
1845
1846
1847 /**
1848  * transorm link href and img src from relative to absolute
1849  *
1850  * @param string $text
1851  * @param string $base base url
1852  * @return string
1853  */
1854 function reltoabs($text, $base) {
1855         if (empty($base)) {
1856                 return $text;
1857         }
1858
1859         $base = rtrim($base,'/');
1860
1861         $base2 = $base . "/";
1862
1863         // Replace links
1864         $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1865         $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1866         $text = preg_replace($pattern, $replace, $text);
1867
1868         $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1869         $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1870         $text = preg_replace($pattern, $replace, $text);
1871
1872         // Replace images
1873         $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1874         $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1875         $text = preg_replace($pattern, $replace, $text);
1876
1877         $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1878         $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1879         $text = preg_replace($pattern, $replace, $text);
1880
1881
1882         // Done
1883         return $text;
1884 }
1885
1886 /**
1887  * get translated item type
1888  *
1889  * @param array $itme
1890  * @return string
1891  */
1892 function item_post_type($item) {
1893         if (intval($item['event-id'])) {
1894                 return t('event');
1895         } elseif (strlen($item['resource-id'])) {
1896                 return t('photo');
1897         } elseif (strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST) {
1898                 return t('activity');
1899         } elseif ($item['id'] != $item['parent']) {
1900                 return t('comment');
1901         }
1902
1903         return t('post');
1904 }
1905
1906 // post categories and "save to file" use the same item.file table for storage.
1907 // We will differentiate the different uses by wrapping categories in angle brackets
1908 // and save to file categories in square brackets.
1909 // To do this we need to escape these characters if they appear in our tag.
1910
1911 function file_tag_encode($s) {
1912         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1913 }
1914
1915 function file_tag_decode($s) {
1916         return str_replace(array('%3c', '%3e', '%5b', '%5d'), array('<', '>', '[', ']'), $s);
1917 }
1918
1919 function file_tag_file_query($table,$s,$type = 'file') {
1920
1921         if ($type == 'file') {
1922                 $str = preg_quote( '[' . str_replace('%', '%%', file_tag_encode($s)) . ']' );
1923         } else {
1924                 $str = preg_quote( '<' . str_replace('%', '%%', file_tag_encode($s)) . '>' );
1925         }
1926         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1927 }
1928
1929 // ex. given music,video return <music><video> or [music][video]
1930 function file_tag_list_to_file($list,$type = 'file') {
1931         $tag_list = '';
1932         if (strlen($list)) {
1933                 $list_array = explode(",",$list);
1934                 if ($type == 'file') {
1935                         $lbracket = '[';
1936                         $rbracket = ']';
1937                 } else {
1938                         $lbracket = '<';
1939                         $rbracket = '>';
1940                 }
1941
1942                 foreach ($list_array as $item) {
1943                         if (strlen($item)) {
1944                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1945                         }
1946                 }
1947         }
1948         return $tag_list;
1949 }
1950
1951 // ex. given <music><video>[friends], return music,video or friends
1952 function file_tag_file_to_list($file,$type = 'file') {
1953         $matches = false;
1954         $list = '';
1955         if ($type == 'file') {
1956                 $cnt = preg_match_all('/\[(.*?)\]/', $file, $matches, PREG_SET_ORDER);
1957         } else {
1958                 $cnt = preg_match_all('/<(.*?)>/', $file, $matches, PREG_SET_ORDER);
1959         }
1960         if ($cnt) {
1961                 foreach ($matches as $mtch) {
1962                         if (strlen($list)) {
1963                                 $list .= ',';
1964                         }
1965                         $list .= file_tag_decode($mtch[1]);
1966                 }
1967         }
1968
1969         return $list;
1970 }
1971
1972 function file_tag_update_pconfig($uid, $file_old, $file_new, $type = 'file') {
1973         // $file_old - categories previously associated with an item
1974         // $file_new - new list of categories for an item
1975
1976         if (!intval($uid)) {
1977                 return false;
1978         }
1979         if ($file_old == $file_new) {
1980                 return true;
1981         }
1982
1983         $saved = PConfig::get($uid,'system','filetags');
1984         if (strlen($saved)) {
1985                 if ($type == 'file') {
1986                         $lbracket = '[';
1987                         $rbracket = ']';
1988                         $termtype = TERM_FILE;
1989                 } else {
1990                         $lbracket = '<';
1991                         $rbracket = '>';
1992                         $termtype = TERM_CATEGORY;
1993                 }
1994
1995                 $filetags_updated = $saved;
1996
1997                 // check for new tags to be added as filetags in pconfig
1998                 $new_tags = array();
1999                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2000
2001                 foreach ($check_new_tags as $tag) {
2002                         if (! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2003                                 $new_tags[] = $tag;
2004                 }
2005
2006                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2007
2008                 // check for deleted tags to be removed from filetags in pconfig
2009                 $deleted_tags = array();
2010                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2011
2012                 foreach ($check_deleted_tags as $tag) {
2013                         if (! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2014                                 $deleted_tags[] = $tag;
2015                 }
2016
2017                 foreach ($deleted_tags as $key => $tag) {
2018                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2019                                 dbesc($tag),
2020                                 intval(TERM_OBJ_POST),
2021                                 intval($termtype),
2022                                 intval($uid));
2023
2024                         if (DBM::is_result($r)) {
2025                                 unset($deleted_tags[$key]);
2026                         } else {
2027                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2028                         }
2029                 }
2030
2031                 if ($saved != $filetags_updated) {
2032                         PConfig::set($uid, 'system', 'filetags', $filetags_updated);
2033                 }
2034                 return true;
2035         } elseif (strlen($file_new)) {
2036                 PConfig::set($uid, 'system', 'filetags', $file_new);
2037         }
2038         return true;
2039 }
2040
2041 function file_tag_save_file($uid, $item, $file) {
2042         require_once "include/files.php";
2043
2044         $result = false;
2045         if (! intval($uid))
2046                 return false;
2047         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2048                 intval($item),
2049                 intval($uid)
2050         );
2051         if (DBM::is_result($r)) {
2052                 if (! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']')) {
2053                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2054                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2055                                 intval($item),
2056                                 intval($uid)
2057                         );
2058                 }
2059
2060                 create_files_from_item($item);
2061
2062                 $saved = PConfig::get($uid,'system','filetags');
2063                 if ((! strlen($saved)) || (! stristr($saved, '[' . file_tag_encode($file) . ']'))) {
2064                         PConfig::set($uid, 'system', 'filetags', $saved . '[' . file_tag_encode($file) . ']');
2065                 }
2066                 info( t('Item filed') );
2067         }
2068         return true;
2069 }
2070
2071 function file_tag_unsave_file($uid, $item, $file, $cat = false) {
2072         require_once "include/files.php";
2073
2074         $result = false;
2075         if (! intval($uid))
2076                 return false;
2077
2078         if ($cat == true) {
2079                 $pattern = '<' . file_tag_encode($file) . '>' ;
2080                 $termtype = TERM_CATEGORY;
2081         } else {
2082                 $pattern = '[' . file_tag_encode($file) . ']' ;
2083                 $termtype = TERM_FILE;
2084         }
2085
2086
2087         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2088                 intval($item),
2089                 intval($uid)
2090         );
2091         if (! DBM::is_result($r)) {
2092                 return false;
2093         }
2094
2095         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2096                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2097                 intval($item),
2098                 intval($uid)
2099         );
2100
2101         create_files_from_item($item);
2102
2103         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2104                 dbesc($file),
2105                 intval(TERM_OBJ_POST),
2106                 intval($termtype),
2107                 intval($uid));
2108
2109         if (! DBM::is_result($r)) {
2110                 $saved = PConfig::get($uid,'system','filetags');
2111                 PConfig::set($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
2112         }
2113
2114         return true;
2115 }
2116
2117 function normalise_openid($s) {
2118         return trim(str_replace(array('http://', 'https://'), array('', ''), $s), '/');
2119 }
2120
2121
2122 function undo_post_tagging($s) {
2123         $matches = null;
2124         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism', $s, $matches, PREG_SET_ORDER);
2125         if ($cnt) {
2126                 foreach ($matches as $mtch) {
2127                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2128                 }
2129         }
2130         return $s;
2131 }
2132
2133 function protect_sprintf($s) {
2134         return str_replace('%', '%%', $s);
2135 }
2136
2137
2138 function is_a_date_arg($s) {
2139         $i = intval($s);
2140         if ($i > 1900) {
2141                 $y = date('Y');
2142                 if ($i <= $y + 1 && strpos($s, '-') == 4) {
2143                         $m = intval(substr($s,5));
2144                         if ($m > 0 && $m <= 12)
2145                                 return true;
2146                 }
2147         }
2148         return false;
2149 }
2150
2151 /**
2152  * remove intentation from a text
2153  */
2154 function deindent($text, $chr = "[\t ]", $count = NULL) {
2155         $lines = explode("\n", $text);
2156         if (is_null($count)) {
2157                 $m = array();
2158                 $k = 0;
2159                 while ($k < count($lines) && strlen($lines[$k]) == 0) {
2160                         $k++;
2161                 }
2162                 preg_match("|^" . $chr . "*|", $lines[$k], $m);
2163                 $count = strlen($m[0]);
2164         }
2165         for ($k = 0; $k < count($lines); $k++) {
2166                 $lines[$k] = preg_replace("|^" . $chr . "{" . $count . "}|", "", $lines[$k]);
2167         }
2168
2169         return implode("\n", $lines);
2170 }
2171
2172 function formatBytes($bytes, $precision = 2) {
2173          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2174
2175         $bytes = max($bytes, 0);
2176         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2177         $pow = min($pow, count($units) - 1);
2178
2179         $bytes /= pow(1024, $pow);
2180
2181         return round($bytes, $precision) . ' ' . $units[$pow];
2182 }
2183
2184 /**
2185  * @brief translate and format the networkname of a contact
2186  *
2187  * @param string $network
2188  *      Networkname of the contact (e.g. dfrn, rss and so on)
2189  * @param sting $url
2190  *      The contact url
2191  * @return string
2192  */
2193 function format_network_name($network, $url = 0) {
2194         if ($network != "") {
2195                 require_once 'include/contact_selectors.php';
2196                 if ($url != "") {
2197                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2198                 } else {
2199                         $network_name = network_to_name($network);
2200                 }
2201
2202                 return $network_name;
2203         }
2204
2205 }
2206
2207 /**
2208  * @brief Syntax based code highlighting for popular languages.
2209  * @param string $s Code block
2210  * @param string $lang Programming language
2211  * @return string Formated html
2212  */
2213 function text_highlight($s, $lang) {
2214         if ($lang === 'js') {
2215                 $lang = 'javascript';
2216         }
2217
2218         // @TODO: Replace Text_Highlighter_Renderer_Html by scrivo/highlight.php
2219
2220         // Autoload the library to make constants available
2221         class_exists('Text_Highlighter_Renderer_Html');
2222
2223         $options = array(
2224                 'numbers' => HL_NUMBERS_LI,
2225                 'tabsize' => 4,
2226         );
2227
2228         $tag_added = false;
2229         $s = trim(html_entity_decode($s, ENT_COMPAT));
2230         $s = str_replace('    ', "\t", $s);
2231
2232         /*
2233          * The highlighter library insists on an opening php tag for php code blocks. If
2234          * it isn't present, nothing is highlighted. So we're going to see if it's present.
2235          * If not, we'll add it, and then quietly remove it after we get the processed output back.
2236          */
2237         if ($lang === 'php' && strpos($s, '<?php') !== 0) {
2238                 $s = '<?php' . "\n" . $s;
2239                 $tag_added = true;
2240         }
2241
2242         $renderer = new Text_Highlighter_Renderer_Html($options);
2243         $hl = Text_Highlighter::factory($lang);
2244         $hl->setRenderer($renderer);
2245         $o = $hl->highlight($s);
2246         $o = str_replace("\n", '', $o);
2247
2248         if ($tag_added) {
2249                 $b = substr($o, 0, strpos($o, '<li>'));
2250                 $e = substr($o, strpos($o, '</li>'));
2251                 $o = $b . $e;
2252         }
2253
2254         return '<code>' . $o . '</code>';
2255 }