]> git.mxchange.org Git - friendica.git/blob - include/text.php
8f0ff6e73e584153fce9fa13a318181bb55495e8
[friendica.git] / include / text.php
1 <?php
2
3 use Friendica\App;
4 use Friendica\Core\System;
5
6 require_once "include/template_processor.php";
7 require_once "include/friendica_smarty.php";
8 require_once "include/Smilies.php";
9 require_once "include/map.php";
10 require_once "mod/proxy.php";
11
12 if (! function_exists('replace_macros')) {
13 /**
14  * This is our template processor
15  *
16  * @param string|FriendicaSmarty $s the string requiring macro substitution,
17  *                              or an instance of FriendicaSmarty
18  * @param array $r key value pairs (search => replace)
19  * @return string substituted string
20  */
21 function replace_macros($s, $r) {
22
23         $stamp1 = microtime(true);
24
25         $a = get_app();
26
27         // pass $baseurl to all templates
28         $r['$baseurl'] = System::baseUrl();
29
30         $t = $a->template_engine();
31         try {
32                 $output = $t->replace_macros($s, $r);
33         } catch (Exception $e) {
34                 echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
35                 killme();
36         }
37
38         $a->save_timestamp($stamp1, "rendering");
39
40         return $output;
41 }}
42
43
44 // random string, there are 86 characters max in text mode, 128 for hex
45 // output is urlsafe
46
47 define('RANDOM_STRING_HEX',  0x00 );
48 define('RANDOM_STRING_TEXT', 0x01 );
49
50 if (! function_exists('random_string')) {
51 function random_string($size = 64, $type = RANDOM_STRING_HEX) {
52         // generate a bit of entropy and run it through the whirlpool
53         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(), (($type == RANDOM_STRING_TEXT) ? true : false));
54         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n", "", base64url_encode($s,true)) : $s);
55         return(substr($s,0,$size));
56 }}
57
58 if (! function_exists('notags')) {
59 /**
60  * This is our primary input filter.
61  *
62  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
63  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
64  * after cleansing, and angle chars with the high bit set could get through as markup.
65  *
66  * This is now disabled because it was interfering with some legitimate unicode sequences
67  * and hopefully there aren't a lot of those browsers left.
68  *
69  * Use this on any text input where angle chars are not valid or permitted
70  * They will be replaced with safer brackets. This may be filtered further
71  * if these are not allowed either.
72  *
73  * @param string $string Input string
74  * @return string Filtered string
75  */
76 function notags($string) {
77         return str_replace(array("<", ">"), array('[', ']'), $string);
78
79 //  High-bit filter no longer used
80 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
81 }}
82
83
84
85 if (! function_exists('escape_tags')) {
86 /**
87  * use this on "body" or "content" input where angle chars shouldn't be removed,
88  * and allow them to be safely displayed.
89  * @param string $string
90  * @return string
91  */
92 function escape_tags($string) {
93         return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
94 }}
95
96
97 // generate a string that's random, but usually pronounceable.
98 // used to generate initial passwords
99
100 if (! function_exists('autoname')) {
101 /**
102  * generate a string that's random, but usually pronounceable.
103  * used to generate initial passwords
104  * @param int $len
105  * @return string
106  */
107 function autoname($len) {
108
109         if ($len <= 0) {
110                 return '';
111         }
112
113         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
114         if (mt_rand(0, 5) == 4) {
115                 $vowels[] = 'y';
116         }
117
118         $cons = array(
119                         'b','bl','br',
120                         'c','ch','cl','cr',
121                         'd','dr',
122                         'f','fl','fr',
123                         'g','gh','gl','gr',
124                         'h',
125                         'j',
126                         'k','kh','kl','kr',
127                         'l',
128                         'm',
129                         'n',
130                         'p','ph','pl','pr',
131                         'qu',
132                         'r','rh',
133                         's','sc','sh','sm','sp','st',
134                         't','th','tr',
135                         'v',
136                         'w','wh',
137                         'x',
138                         'z','zh'
139                         );
140
141         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
142                                 'nd','ng','nk','nt','rn','rp','rt');
143
144         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
145                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
146
147         $start = mt_rand(0,2);
148         if ($start == 0) {
149                 $table = $vowels;
150         } else {
151                 $table = $cons;
152         }
153
154         $word = '';
155
156         for ($x = 0; $x < $len; $x ++) {
157                 $r = mt_rand(0,count($table) - 1);
158                 $word .= $table[$r];
159
160                 if ($table == $vowels) {
161                         $table = array_merge($cons,$midcons);
162                 } else {
163                         $table = $vowels;
164                 }
165
166         }
167
168         $word = substr($word,0,$len);
169
170         foreach ($noend as $noe) {
171                 if ((strlen($word) > 2) && (substr($word, -2) == $noe)) {
172                         $word = substr($word, 0, -1);
173                         break;
174                 }
175         }
176         if (substr($word, -1) == 'q') {
177                 $word = substr($word, 0, -1);
178         }
179         return $word;
180 }}
181
182
183 // escape text ($str) for XML transport
184 // returns escaped text.
185
186 if (! function_exists('xmlify')) {
187 /**
188  * escape text ($str) for XML transport
189  * @param string $str
190  * @return string Escaped text.
191  */
192 function xmlify($str) {
193         /// @TODO deprecated code found?
194 /*      $buffer = '';
195
196         $len = mb_strlen($str);
197         for ($x = 0; $x < $len; $x ++) {
198                 $char = mb_substr($str,$x,1);
199
200                 switch( $char ) {
201
202                         case "\r" :
203                                 break;
204                         case "&" :
205                                 $buffer .= '&amp;';
206                                 break;
207                         case "'" :
208                                 $buffer .= '&apos;';
209                                 break;
210                         case "\"" :
211                                 $buffer .= '&quot;';
212                                 break;
213                         case '<' :
214                                 $buffer .= '&lt;';
215                                 break;
216                         case '>' :
217                                 $buffer .= '&gt;';
218                                 break;
219                         case "\n" :
220                                 $buffer .= "\n";
221                                 break;
222                         default :
223                                 $buffer .= $char;
224                                 break;
225                 }
226         }*/
227         /*
228         $buffer = mb_ereg_replace("&", "&amp;", $str);
229         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
230         $buffer = mb_ereg_replace('"', "&quot;", $buffer);
231         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
232         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
233         */
234         $buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
235         $buffer = trim($buffer);
236
237         return($buffer);
238 }}
239
240 if (! function_exists('unxmlify')) {
241 /**
242  * undo an xmlify
243  * @param string $s xml escaped text
244  * @return string unescaped text
245  */
246 function unxmlify($s) {
247         /// @TODO deprecated code found?
248 //      $ret = str_replace('&amp;','&', $s);
249 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
250         /*$ret = mb_ereg_replace('&amp;', '&', $s);
251         $ret = mb_ereg_replace('&apos;', "'", $ret);
252         $ret = mb_ereg_replace('&quot;', '"', $ret);
253         $ret = mb_ereg_replace('&lt;', "<", $ret);
254         $ret = mb_ereg_replace('&gt;', ">", $ret);
255         */
256         $ret = htmlspecialchars_decode($s, ENT_QUOTES);
257         return $ret;
258 }}
259
260 if (! function_exists('hex2bin')) {
261 /**
262  * convenience wrapper, reverse the operation "bin2hex"
263  * @param string $s
264  * @return number
265  */
266 function hex2bin($s) {
267         if (! (is_string($s) && strlen($s))) {
268                 return '';
269         }
270
271         if (! ctype_xdigit($s)) {
272                 return $s;
273         }
274
275         return pack("H*",$s);
276 }}
277
278
279 /**
280  * @brief Paginator function. Pushes relevant links in a pager array structure.
281  *
282  * Links are generated depending on the current page and the total number of items.
283  * Inactive links (like "first" and "prev" on page 1) are given the "disabled" class.
284  * Current page link is given the "active" CSS class
285  *
286  * @param App $a App instance
287  * @param int $count [optional] item count (used with minimal pager)
288  * @return Array data for pagination template
289  */
290 function paginate_data(App $a, $count = null) {
291         $stripped = preg_replace('/([&?]page=[0-9]*)/', '', $a->query_string);
292
293         $stripped = str_replace('q=', '', $stripped);
294         $stripped = trim($stripped, '/');
295         $pagenum = $a->pager['page'];
296
297         if (($a->page_offset != '') && !preg_match('/[?&].offset=/', $stripped)) {
298                 $stripped .= '&offset=' . urlencode($a->page_offset);
299         }
300
301         $url = $stripped;
302         $data = array();
303
304         function _l(&$d, $name, $url, $text, $class = '') {
305                 if (strpos($url, '?') === false && ($pos = strpos($url, '&')) !== false) {
306                         $url = substr($url, 0, $pos) . '?' . substr($url, $pos + 1);
307                 }
308
309                 $d[$name] = array('url' => $url, 'text' => $text, 'class' => $class);
310         }
311
312         if (!is_null($count)) {
313                 // minimal pager (newer / older)
314                 $data['class'] = 'pager';
315                 _l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), t('newer'), 'previous' . ($a->pager['page'] == 1 ? ' disabled' : ''));
316                 _l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), t('older'), 'next' . ($count <= 0 ? ' disabled' : ''));
317         } else {
318                 // full pager (first / prev / 1 / 2 / ... / 14 / 15 / next / last)
319                 $data['class'] = 'pagination';
320                 if ($a->pager['total'] > $a->pager['itemspage']) {
321                         _l($data, 'first', $url . '&page=1',  t('first'), $a->pager['page'] == 1 ? 'disabled' : '');
322                         _l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), t('prev'), $a->pager['page'] == 1 ? 'disabled' : '');
323
324                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
325
326                         $numstart = 1;
327                         $numstop = $numpages;
328
329                         // Limit the number of displayed page number buttons.
330                         if ($numpages > 8) {
331                                 $numstart = (($pagenum > 4) ? ($pagenum - 4) : 1);
332                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 8));
333                         }
334
335                         $pages = array();
336
337                         for ($i = $numstart; $i <= $numstop; $i++) {
338                                 if ($i == $a->pager['page']) {
339                                         _l($pages, $i, '#',  $i, 'current active');
340                                 } else {
341                                         _l($pages, $i, $url . '&page='. $i, $i, 'n');
342                                 }
343                         }
344
345                         if (($a->pager['total'] % $a->pager['itemspage']) != 0) {
346                                 if ($i == $a->pager['page']) {
347                                         _l($pages, $i, '#',  $i, 'current active');
348                                 } else {
349                                         _l($pages, $i, $url . '&page=' . $i, $i, 'n');
350                                 }
351                         }
352
353                         $data['pages'] = $pages;
354
355                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
356                         _l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), t('next'), $a->pager['page'] == $lastpage ? 'disabled' : '');
357                         _l($data, 'last', $url . '&page=' . $lastpage, t('last'), $a->pager['page'] == $lastpage ? 'disabled' : '');
358                 }
359         }
360
361         return $data;
362 }
363
364 if (! function_exists('paginate')) {
365 /**
366  * Automatic pagination.
367  *
368  *  To use, get the count of total items.
369  * Then call $a->set_pager_total($number_items);
370  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
371  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
372  * (assuming there are enough items to paginate).
373  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
374  * will limit the results to the correct items for the current page.
375  * The actual page handling is then accomplished at the application layer.
376  *
377  * @param App $a App instance
378  * @return string html for pagination #FIXME remove html
379  */
380 function paginate(App $a) {
381
382         $data = paginate_data($a);
383         $tpl = get_markup_template("paginate.tpl");
384         return replace_macros($tpl, array("pager" => $data));
385
386 }}
387
388 if (! function_exists('alt_pager')) {
389 /**
390  * Alternative pager
391  * @param App $a App instance
392  * @param int $i
393  * @return string html for pagination #FIXME remove html
394  */
395 function alt_pager(App $a, $i) {
396
397         $data = paginate_data($a, $i);
398         $tpl = get_markup_template("paginate.tpl");
399         return replace_macros($tpl, array('pager' => $data));
400
401 }}
402
403 if (! function_exists('scroll_loader')) {
404 /**
405  * Loader for infinite scrolling
406  * @return string html for loader
407  */
408 function scroll_loader() {
409         $tpl = get_markup_template("scroll_loader.tpl");
410         return replace_macros($tpl, array(
411                 'wait' => t('Loading more entries...'),
412                 'end' => t('The end')
413         ));
414 }}
415
416 if (! function_exists('expand_acl')) {
417 /**
418  * Turn user/group ACLs stored as angle bracketed text into arrays
419  *
420  * @param string $s
421  * @return array
422  */
423 function expand_acl($s) {
424         // turn string array of angle-bracketed elements into numeric array
425         // e.g. "<1><2><3>" => array(1,2,3);
426         $ret = array();
427
428         if (strlen($s)) {
429                 $t = str_replace('<', '', $s);
430                 $a = explode('>', $t);
431                 foreach ($a as $aa) {
432                         if (intval($aa)) {
433                                 $ret[] = intval($aa);
434                         }
435                 }
436         }
437         return $ret;
438 }}
439
440 if (! function_exists('sanitise_acl')) {
441 /**
442  * Wrap ACL elements in angle brackets for storage
443  * @param string $item
444  */
445 function sanitise_acl(&$item) {
446         if (intval($item)) {
447                 $item = '<' . intval(notags(trim($item))) . '>';
448         } else {
449                 unset($item);
450         }
451 }}
452
453
454 if (! function_exists('perms2str')) {
455 /**
456  * Convert an ACL array to a storable string
457  *
458  * Normally ACL permissions will be an array.
459  * We'll also allow a comma-separated string.
460  *
461  * @param string|array $p
462  * @return string
463  */
464 function perms2str($p) {
465         $ret = '';
466         if (is_array($p)) {
467                 $tmp = $p;
468         } else {
469                 $tmp = explode(',',$p);
470         }
471
472         if (is_array($tmp)) {
473                 array_walk($tmp, 'sanitise_acl');
474                 $ret = implode('', $tmp);
475         }
476         return $ret;
477 }}
478
479
480 if (! function_exists('item_new_uri')) {
481 /**
482  * generate a guaranteed unique (for this domain) item ID for ATOM
483  * safe from birthday paradox
484  *
485  * @param string $hostname
486  * @param int $uid
487  * @return string
488  */
489 function item_new_uri($hostname, $uid, $guid = "") {
490
491         do {
492                 if ($guid == "") {
493                         $hash = get_guid(32);
494                 } else {
495                         $hash = $guid;
496                         $guid = "";
497                 }
498
499                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
500
501                 $dups = dba::exists('item', array('uri' => $uri));
502         } while ($dups == true);
503
504         return $uri;
505 }}
506
507 // Generate a guaranteed unique photo ID.
508 // safe from birthday paradox
509
510 if (! function_exists('photo_new_resource')) {
511 /**
512  * Generate a guaranteed unique photo ID.
513  * safe from birthday paradox
514  *
515  * @return string
516  */
517 function photo_new_resource() {
518
519         do {
520                 $found = false;
521                 $resource = hash('md5',uniqid(mt_rand(),true));
522                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
523                         dbesc($resource)
524                 );
525
526                 if (dbm::is_result($r)) {
527                         $found = true;
528                 }
529         } while ($found == true);
530
531         return $resource;
532 }}
533
534
535 if (! function_exists('load_view_file')) {
536 /**
537  * @deprecated
538  * wrapper to load a view template, checking for alternate
539  * languages before falling back to the default
540  *
541  * @global string $lang
542  * @global App $a
543  * @param string $s view name
544  * @return string
545  */
546 function load_view_file($s) {
547         global $lang, $a;
548         if (! isset($lang)) {
549                 $lang = 'en';
550         }
551         $b = basename($s);
552         $d = dirname($s);
553         if (file_exists("$d/$lang/$b")) {
554                 $stamp1 = microtime(true);
555                 $content = file_get_contents("$d/$lang/$b");
556                 $a->save_timestamp($stamp1, "file");
557                 return $content;
558         }
559
560         $theme = current_theme();
561
562         if (file_exists("$d/theme/$theme/$b")) {
563                 $stamp1 = microtime(true);
564                 $content = file_get_contents("$d/theme/$theme/$b");
565                 $a->save_timestamp($stamp1, "file");
566                 return $content;
567         }
568
569         $stamp1 = microtime(true);
570         $content = file_get_contents($s);
571         $a->save_timestamp($stamp1, "file");
572         return $content;
573 }}
574
575 if (! function_exists('get_intltext_template')) {
576 /**
577  * load a view template, checking for alternate
578  * languages before falling back to the default
579  *
580  * @global string $lang
581  * @param string $s view path
582  * @return string
583  */
584 function get_intltext_template($s) {
585         global $lang;
586
587         $a = get_app();
588         $engine = '';
589         if ($a->theme['template_engine'] === 'smarty3') {
590                 $engine = "/smarty3";
591         }
592
593         if (! isset($lang)) {
594                 $lang = 'en';
595         }
596
597         if (file_exists("view/lang/$lang$engine/$s")) {
598                 $stamp1 = microtime(true);
599                 $content = file_get_contents("view/lang/$lang$engine/$s");
600                 $a->save_timestamp($stamp1, "file");
601                 return $content;
602         } elseif (file_exists("view/lang/en$engine/$s")) {
603                 $stamp1 = microtime(true);
604                 $content = file_get_contents("view/lang/en$engine/$s");
605                 $a->save_timestamp($stamp1, "file");
606                 return $content;
607         } else {
608                 $stamp1 = microtime(true);
609                 $content = file_get_contents("view$engine/$s");
610                 $a->save_timestamp($stamp1, "file");
611                 return $content;
612         }
613 }}
614
615 if (! function_exists('get_markup_template')) {
616 /**
617  * load template $s
618  *
619  * @param string $s
620  * @param string $root
621  * @return string
622  */
623 function get_markup_template($s, $root = '') {
624         $stamp1 = microtime(true);
625
626         $a = get_app();
627         $t = $a->template_engine();
628         try {
629                 $template = $t->get_template_file($s, $root);
630         } catch (Exception $e) {
631                 echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
632                 killme();
633         }
634
635         $a->save_timestamp($stamp1, "file");
636
637         return $template;
638 }}
639
640 if (! function_exists("get_template_file")) {
641 /**
642  *
643  * @param App $a
644  * @param string $filename
645  * @param string $root
646  * @return string
647  */
648 function get_template_file($a, $filename, $root = '') {
649         $theme = current_theme();
650
651         // Make sure $root ends with a slash /
652         if ($root !== '' && substr($root, -1, 1) !== '/') {
653                 $root = $root . '/';
654         }
655
656         if (file_exists("{$root}view/theme/$theme/$filename")) {
657                 $template_file = "{$root}view/theme/$theme/$filename";
658         } elseif (x($a->theme_info, "extends") && file_exists(sprintf('%sview/theme/%s}/%s', $root, $a->theme_info["extends"], $filename))) {
659                 $template_file = sprintf('%sview/theme/%s}/%s', $root, $a->theme_info["extends"], $filename);
660         } elseif (file_exists("{$root}/$filename")) {
661                 $template_file = "{$root}/$filename";
662         } else {
663                 $template_file = "{$root}view/$filename";
664         }
665
666         return $template_file;
667 }}
668
669
670 if (! function_exists('attribute_contains')) {
671 /**
672  *  for html,xml parsing - let's say you've got
673  *  an attribute foobar="class1 class2 class3"
674  *  and you want to find out if it contains 'class3'.
675  *  you can't use a normal sub string search because you
676  *  might match 'notclass3' and a regex to do the job is
677  *  possible but a bit complicated.
678  *  pass the attribute string as $attr and the attribute you
679  *  are looking for as $s - returns true if found, otherwise false
680  *
681  * @param string $attr attribute value
682  * @param string $s string to search
683  * @return boolean True if found, False otherwise
684  */
685 function attribute_contains($attr, $s) {
686         $a = explode(' ', $attr);
687         return (count($a) && in_array($s,$a));
688 }}
689
690 if (! function_exists('logger')) {
691 /* setup int->string log level map */
692 $LOGGER_LEVELS = array();
693
694 /**
695  * @brief Logs the given message at the given log level
696  *
697  * log levels:
698  * LOGGER_NORMAL (default)
699  * LOGGER_TRACE
700  * LOGGER_DEBUG
701  * LOGGER_DATA
702  * LOGGER_ALL
703  *
704  * @global App $a
705  * @global array $LOGGER_LEVELS
706  * @param string $msg
707  * @param int $level
708  */
709 function logger($msg, $level = 0) {
710         $a = get_app();
711         global $LOGGER_LEVELS;
712
713         // turn off logger in install mode
714         if (
715                 $a->module == 'install'
716                 || !dba::$connected
717         ) {
718                 return;
719         }
720
721         $debugging = get_config('system','debugging');
722         $logfile   = get_config('system','logfile');
723         $loglevel = intval(get_config('system','loglevel'));
724
725         if (
726                 ! $debugging
727                 || ! $logfile
728                 || $level > $loglevel
729         ) {
730                 return;
731         }
732
733         if (count($LOGGER_LEVELS) == 0) {
734                 foreach (get_defined_constants() as $k => $v) {
735                         if (substr($k, 0, 7) == "LOGGER_") {
736                                 $LOGGER_LEVELS[$v] = substr($k, 7, 7);
737                         }
738                 }
739         }
740
741         $process_id = session_id();
742
743         if ($process_id == '') {
744                 $process_id = get_app()->process_id;
745         }
746
747         $callers = debug_backtrace();
748         $logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
749                         datetime_convert('UTC', 'UTC', 'now', 'Y-m-d\TH:i:s\Z'),
750                         $process_id,
751                         $LOGGER_LEVELS[$level],
752                         basename($callers[0]['file']),
753                         $callers[0]['line'],
754                         $callers[1]['function'],
755                         $msg
756                 );
757
758         $stamp1 = microtime(true);
759         @file_put_contents($logfile, $logline, FILE_APPEND);
760         $a->save_timestamp($stamp1, "file");
761 }}
762
763 /**
764  * @brief An alternative logger for development.
765  * Works largely as logger() but allows developers
766  * to isolate particular elements they are targetting
767  * personally without background noise
768  *
769  * log levels:
770  * LOGGER_NORMAL (default)
771  * LOGGER_TRACE
772  * LOGGER_DEBUG
773  * LOGGER_DATA
774  * LOGGER_ALL
775  *
776  * @global App $a
777  * @global array $LOGGER_LEVELS
778  * @param string $msg
779  * @param int $level
780  */
781
782 function dlogger($msg, $level = 0) {
783         $a = get_app();
784
785         // turn off logger in install mode
786         if (
787                 $a->module == 'install'
788                 || !dba::$connected
789         ) {
790                 return;
791         }
792
793         $logfile = get_config('system','dlogfile');
794
795         if (! $logfile) {
796                 return;
797         }
798
799         if (count($LOGGER_LEVELS) == 0) {
800                 foreach (get_defined_constants() as $k => $v) {
801                         if (substr($k, 0, 7) == "LOGGER_") {
802                                 $LOGGER_LEVELS[$v] = substr($k, 7, 7);
803                         }
804                 }
805         }
806
807         $process_id = session_id();
808
809         if ($process_id == '') {
810                 $process_id = get_app()->process_id;
811         }
812
813         $callers = debug_backtrace();
814         $logline = sprintf("%s@\t%s:\t%s:\t%s\t%s\t%s\n",
815                         datetime_convert(),
816                         $process_id,
817                         basename($callers[0]['file']),
818                         $callers[0]['line'],
819                         $callers[1]['function'],
820                         $msg
821                 );
822
823         $stamp1 = microtime(true);
824         @file_put_contents($logfile, $logline, FILE_APPEND);
825         $a->save_timestamp($stamp1, "file");
826 }
827
828 if (! function_exists('activity_match')) {
829 /**
830  * Compare activity uri. Knows about activity namespace.
831  *
832  * @param string $haystack
833  * @param string $needle
834  * @return boolean
835  */
836 function activity_match($haystack,$needle) {
837         return (($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle, NAMESPACE_ACTIVITY_SCHEMA)));
838 }}
839
840
841 /**
842  * @brief Pull out all #hashtags and @person tags from $string.
843  *
844  * We also get @person@domain.com - which would make
845  * the regex quite complicated as tags can also
846  * end a sentence. So we'll run through our results
847  * and strip the period from any tags which end with one.
848  * Returns array of tags found, or empty array.
849  *
850  * @param string $string Post content
851  * @return array List of tag and person names
852  */
853 function get_tags($string) {
854         $ret = array();
855
856         // Convert hashtag links to hashtags
857         $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
858
859         // ignore anything in a code block
860         $string = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $string);
861
862         // Force line feeds at bbtags
863         $string = str_replace(array('[', ']'), array("\n[", "]\n"), $string);
864
865         // ignore anything in a bbtag
866         $string = preg_replace('/\[(.*?)\]/sm', '', $string);
867
868         // Match full names against @tags including the space between first and last
869         // We will look these up afterward to see if they are full names or not recognisable.
870
871         if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
872                 foreach ($matches[1] as $match) {
873                         if (strstr($match, ']')) {
874                                 // we might be inside a bbcode color tag - leave it alone
875                                 continue;
876                         }
877                         if (substr($match, -1, 1) === '.') {
878                                 $ret[] = substr($match, 0, -1);
879                         } else {
880                                 $ret[] = $match;
881                         }
882                 }
883         }
884
885         // Otherwise pull out single word tags. These can be @nickname, @first_last
886         // and #hash tags.
887
888         if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/', $string, $matches)) {
889                 foreach ($matches[1] as $match) {
890                         if (strstr($match, ']')) {
891                                 // we might be inside a bbcode color tag - leave it alone
892                                 continue;
893                         }
894                         if (substr($match, -1, 1) === '.') {
895                                 $match = substr($match,0,-1);
896                         }
897                         // ignore strictly numeric tags like #1
898                         if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
899                                 continue;
900                         }
901                         // try not to catch url fragments
902                         if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
903                                 continue;
904                         }
905                         $ret[] = $match;
906                 }
907         }
908         return $ret;
909 }
910
911
912 //
913
914 if (! function_exists('qp')) {
915 /**
916  * quick and dirty quoted_printable encoding
917  *
918  * @param string $s
919  * @return string
920  */
921 function qp($s) {
922         return str_replace("%", "=", rawurlencode($s));
923 }}
924
925 if (! function_exists('contact_block')) {
926 /**
927  * Get html for contact block.
928  *
929  * @template contact_block.tpl
930  * @hook contact_block_end (contacts=>array, output=>string)
931  * @return string
932  */
933 function contact_block() {
934         $o = '';
935         $a = get_app();
936
937         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
938         if ($shown === false) {
939                 $shown = 24;
940         }
941         if ($shown == 0) {
942                 return;
943         }
944
945         if ((! is_array($a->profile)) || ($a->profile['hide-friends'])) {
946                 return $o;
947         }
948         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
949                         WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
950                                 AND NOT `pending` AND NOT `hidden` AND NOT `archive`
951                                 AND `network` IN ('%s', '%s', '%s')",
952                         intval($a->profile['uid']),
953                         dbesc(NETWORK_DFRN),
954                         dbesc(NETWORK_OSTATUS),
955                         dbesc(NETWORK_DIASPORA)
956         );
957         if (dbm::is_result($r)) {
958                 $total = intval($r[0]['total']);
959         }
960         if (! $total) {
961                 $contacts = t('No contacts');
962                 $micropro = null;
963         } else {
964                 // Splitting the query in two parts makes it much faster
965                 $r = q("SELECT `id` FROM `contact`
966                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
967                                         AND NOT `pending` AND NOT `hidden` AND NOT `archive`
968                                         AND `network` IN ('%s', '%s', '%s')
969                                 ORDER BY RAND() LIMIT %d",
970                                 intval($a->profile['uid']),
971                                 dbesc(NETWORK_DFRN),
972                                 dbesc(NETWORK_OSTATUS),
973                                 dbesc(NETWORK_DIASPORA),
974                                 intval($shown)
975                 );
976                 if (dbm::is_result($r)) {
977                         $contacts = array();
978                         foreach ($r AS $contact) {
979                                 $contacts[] = $contact["id"];
980                         }
981                         $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `thumb`, `network` FROM `contact` WHERE `id` IN (%s)",
982                                 dbesc(implode(",", $contacts)));
983
984                         if (dbm::is_result($r)) {
985                                 $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
986                                 $micropro = Array();
987                                 foreach ($r as $rr) {
988                                         $micropro[] = micropro($rr,true,'mpfriend');
989                                 }
990                         }
991                 }
992         }
993
994         $tpl = get_markup_template('contact_block.tpl');
995         $o = replace_macros($tpl, array(
996                 '$contacts' => $contacts,
997                 '$nickname' => $a->profile['nickname'],
998                 '$viewcontacts' => t('View Contacts'),
999                 '$micropro' => $micropro,
1000         ));
1001
1002         $arr = array('contacts' => $r, 'output' => $o);
1003
1004         call_hooks('contact_block_end', $arr);
1005         return $o;
1006
1007 }}
1008
1009 /**
1010  * @brief Format contacts as picture links or as texxt links
1011  *
1012  * @param array $contact Array with contacts which contains an array with
1013  *      int 'id' => The ID of the contact
1014  *      int 'uid' => The user ID of the user who owns this data
1015  *      string 'name' => The name of the contact
1016  *      string 'url' => The url to the profile page of the contact
1017  *      string 'addr' => The webbie of the contact (e.g.) username@friendica.com
1018  *      string 'network' => The network to which the contact belongs to
1019  *      string 'thumb' => The contact picture
1020  *      string 'click' => js code which is performed when clicking on the contact
1021  * @param boolean $redirect If true try to use the redir url if it's possible
1022  * @param string $class CSS class for the
1023  * @param boolean $textmode If true display the contacts as text links
1024  *      if false display the contacts as picture links
1025
1026  * @return string Formatted html
1027  */
1028 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
1029
1030         // Use the contact URL if no address is available
1031         if ($contact["addr"] == "") {
1032                 $contact["addr"] = $contact["url"];
1033         }
1034
1035         $url = $contact['url'];
1036         $sparkle = '';
1037         $redir = false;
1038
1039         if ($redirect) {
1040                 $a = get_app();
1041                 $redirect_url = 'redir/' . $contact['id'];
1042                 if (local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
1043                         $redir = true;
1044                         $url = $redirect_url;
1045                         $sparkle = ' sparkle';
1046                 } else {
1047                         $url = zrl($url);
1048                 }
1049         }
1050
1051         // If there is some js available we don't need the url
1052         if (x($contact, 'click')) {
1053                 $url = '';
1054         }
1055
1056         return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
1057                 '$click' => (($contact['click']) ? $contact['click'] : ''),
1058                 '$class' => $class,
1059                 '$url' => $url,
1060                 '$photo' => proxy_url($contact['thumb'], false, PROXY_SIZE_THUMB),
1061                 '$name' => $contact['name'],
1062                 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
1063                 '$parkle' => $sparkle,
1064                 '$redir' => $redir,
1065
1066         ));
1067 }
1068
1069
1070
1071 if (! function_exists('search')) {
1072 /**
1073  * search box
1074  *
1075  * @param string $s search query
1076  * @param string $id html id
1077  * @param string $url search url
1078  * @param boolean $savedsearch show save search button
1079  */
1080 function search($s, $id = 'search-box', $url = 'search', $save = false, $aside = true) {
1081         $a = get_app();
1082
1083         $values = array(
1084                         '$s' => htmlspecialchars($s),
1085                         '$id' => $id,
1086                         '$action_url' => $url,
1087                         '$search_label' => t('Search'),
1088                         '$save_label' => t('Save'),
1089                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
1090                         '$search_hint' => t('@name, !forum, #tags, content'),
1091                 );
1092
1093         if (!$aside) {
1094                 $values['$searchoption'] = array(
1095                                         t("Full Text"),
1096                                         t("Tags"),
1097                                         t("Contacts"));
1098
1099                 if (get_config('system','poco_local_search')) {
1100                         $values['$searchoption'][] = t("Forums");
1101                 }
1102         }
1103
1104         return replace_macros(get_markup_template('searchbox.tpl'), $values);
1105 }}
1106
1107 if (! function_exists('valid_email')) {
1108 /**
1109  * Check if $x is a valid email string
1110  *
1111  * @param string $x
1112  * @return boolean
1113  */
1114 function valid_email($x){
1115
1116         /// @TODO Removed because Fabio told me so.
1117         //if (get_config('system','disable_email_validation'))
1118         //      return true;
1119         return preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/', $x);
1120 }}
1121
1122
1123 if (! function_exists('linkify')) {
1124 /**
1125  * Replace naked text hyperlink with HTML formatted hyperlink
1126  *
1127  * @param string $s
1128  */
1129 function linkify($s) {
1130         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1131         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1132         return $s;
1133 }}
1134
1135
1136 /**
1137  * Load poke verbs
1138  *
1139  * @return array index is present tense verb
1140                                  value is array containing past tense verb, translation of present, translation of past
1141  * @hook poke_verbs pokes array
1142  */
1143 function get_poke_verbs() {
1144
1145         // index is present tense verb
1146         // value is array containing past tense verb, translation of present, translation of past
1147
1148         $arr = array(
1149                 'poke' => array( 'poked', t('poke'), t('poked')),
1150                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1151                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1152                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1153                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1154                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1155         );
1156         call_hooks('poke_verbs', $arr);
1157         return $arr;
1158 }
1159
1160 /**
1161  * Load moods
1162  * @return array index is mood, value is translated mood
1163  * @hook mood_verbs moods array
1164  */
1165 function get_mood_verbs() {
1166
1167         $arr = array(
1168                 'happy'      => t('happy'),
1169                 'sad'        => t('sad'),
1170                 'mellow'     => t('mellow'),
1171                 'tired'      => t('tired'),
1172                 'perky'      => t('perky'),
1173                 'angry'      => t('angry'),
1174                 'stupefied'  => t('stupified'),
1175                 'puzzled'    => t('puzzled'),
1176                 'interested' => t('interested'),
1177                 'bitter'     => t('bitter'),
1178                 'cheerful'   => t('cheerful'),
1179                 'alive'      => t('alive'),
1180                 'annoyed'    => t('annoyed'),
1181                 'anxious'    => t('anxious'),
1182                 'cranky'     => t('cranky'),
1183                 'disturbed'  => t('disturbed'),
1184                 'frustrated' => t('frustrated'),
1185                 'motivated'  => t('motivated'),
1186                 'relaxed'    => t('relaxed'),
1187                 'surprised'  => t('surprised'),
1188         );
1189
1190         call_hooks('mood_verbs', $arr);
1191         return $arr;
1192 }
1193
1194 /**
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"] == "") || get_config("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 (!get_config('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         $saved = get_pconfig($uid,'system','filetags');
1983         if (strlen($saved)) {
1984                 if ($type == 'file') {
1985                         $lbracket = '[';
1986                         $rbracket = ']';
1987                         $termtype = TERM_FILE;
1988                 }
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                         }
2027                         else {
2028                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2029                         }
2030                 }
2031
2032                 if ($saved != $filetags_updated) {
2033                         set_pconfig($uid, 'system', 'filetags', $filetags_updated);
2034                 }
2035                 return true;
2036         }
2037         else
2038                 if (strlen($file_new)) {
2039                         set_pconfig($uid, 'system', 'filetags', $file_new);
2040                 }
2041                 return true;
2042 }
2043
2044 function file_tag_save_file($uid, $item, $file) {
2045         require_once "include/files.php";
2046
2047         $result = false;
2048         if (! intval($uid))
2049                 return false;
2050         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2051                 intval($item),
2052                 intval($uid)
2053         );
2054         if (dbm::is_result($r)) {
2055                 if (! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']')) {
2056                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2057                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2058                                 intval($item),
2059                                 intval($uid)
2060                         );
2061                 }
2062
2063                 create_files_from_item($item);
2064
2065                 $saved = get_pconfig($uid,'system','filetags');
2066                 if ((! strlen($saved)) || (! stristr($saved, '[' . file_tag_encode($file) . ']'))) {
2067                         set_pconfig($uid, 'system', 'filetags', $saved . '[' . file_tag_encode($file) . ']');
2068                 }
2069                 info( t('Item filed') );
2070         }
2071         return true;
2072 }
2073
2074 function file_tag_unsave_file($uid, $item, $file, $cat = false) {
2075         require_once "include/files.php";
2076
2077         $result = false;
2078         if (! intval($uid))
2079                 return false;
2080
2081         if ($cat == true) {
2082                 $pattern = '<' . file_tag_encode($file) . '>' ;
2083                 $termtype = TERM_CATEGORY;
2084         } else {
2085                 $pattern = '[' . file_tag_encode($file) . ']' ;
2086                 $termtype = TERM_FILE;
2087         }
2088
2089
2090         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2091                 intval($item),
2092                 intval($uid)
2093         );
2094         if (! dbm::is_result($r)) {
2095                 return false;
2096         }
2097
2098         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2099                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2100                 intval($item),
2101                 intval($uid)
2102         );
2103
2104         create_files_from_item($item);
2105
2106         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2107                 dbesc($file),
2108                 intval(TERM_OBJ_POST),
2109                 intval($termtype),
2110                 intval($uid));
2111
2112         if (! dbm::is_result($r)) {
2113                 $saved = get_pconfig($uid,'system','filetags');
2114                 set_pconfig($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
2115         }
2116
2117         return true;
2118 }
2119
2120 function normalise_openid($s) {
2121         return trim(str_replace(array('http://', 'https://'), array('', ''), $s), '/');
2122 }
2123
2124
2125 function undo_post_tagging($s) {
2126         $matches = null;
2127         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism', $s, $matches, PREG_SET_ORDER);
2128         if ($cnt) {
2129                 foreach ($matches as $mtch) {
2130                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2131                 }
2132         }
2133         return $s;
2134 }
2135
2136 function protect_sprintf($s) {
2137         return str_replace('%', '%%', $s);
2138 }
2139
2140
2141 function is_a_date_arg($s) {
2142         $i = intval($s);
2143         if ($i > 1900) {
2144                 $y = date('Y');
2145                 if ($i <= $y + 1 && strpos($s, '-') == 4) {
2146                         $m = intval(substr($s,5));
2147                         if ($m > 0 && $m <= 12)
2148                                 return true;
2149                 }
2150         }
2151         return false;
2152 }
2153
2154 /**
2155  * remove intentation from a text
2156  */
2157 function deindent($text, $chr = "[\t ]", $count = NULL) {
2158         $lines = explode("\n", $text);
2159         if (is_null($count)) {
2160                 $m = array();
2161                 $k = 0;
2162                 while ($k < count($lines) && strlen($lines[$k]) == 0) {
2163                         $k++;
2164                 }
2165                 preg_match("|^" . $chr . "*|", $lines[$k], $m);
2166                 $count = strlen($m[0]);
2167         }
2168         for ($k = 0; $k < count($lines); $k++) {
2169                 $lines[$k] = preg_replace("|^" . $chr . "{" . $count . "}|", "", $lines[$k]);
2170         }
2171
2172         return implode("\n", $lines);
2173 }
2174
2175 function formatBytes($bytes, $precision = 2) {
2176          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2177
2178         $bytes = max($bytes, 0);
2179         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2180         $pow = min($pow, count($units) - 1);
2181
2182         $bytes /= pow(1024, $pow);
2183
2184         return round($bytes, $precision) . ' ' . $units[$pow];
2185 }
2186
2187 /**
2188  * @brief translate and format the networkname of a contact
2189  *
2190  * @param string $network
2191  *      Networkname of the contact (e.g. dfrn, rss and so on)
2192  * @param sting $url
2193  *      The contact url
2194  * @return string
2195  */
2196 function format_network_name($network, $url = 0) {
2197         if ($network != "") {
2198                 require_once 'include/contact_selectors.php';
2199                 if ($url != "") {
2200                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2201                 } else {
2202                         $network_name = network_to_name($network);
2203                 }
2204
2205                 return $network_name;
2206         }
2207
2208 }
2209
2210 /**
2211  * @brief Syntax based code highlighting for popular languages.
2212  * @param string $s Code block
2213  * @param string $lang Programming language
2214  * @return string Formated html
2215  */
2216 function text_highlight($s, $lang) {
2217         if ($lang === 'js') {
2218                 $lang = 'javascript';
2219         }
2220
2221         // @TODO: Replace Text_Highlighter_Renderer_Html by scrivo/highlight.php
2222
2223         // Autoload the library to make constants available
2224         class_exists('Text_Highlighter_Renderer_Html');
2225
2226         $options = array(
2227                 'numbers' => HL_NUMBERS_LI,
2228                 'tabsize' => 4,
2229         );
2230
2231         $tag_added = false;
2232         $s = trim(html_entity_decode($s, ENT_COMPAT));
2233         $s = str_replace('    ', "\t", $s);
2234
2235         /*
2236          * The highlighter library insists on an opening php tag for php code blocks. If
2237          * it isn't present, nothing is highlighted. So we're going to see if it's present.
2238          * If not, we'll add it, and then quietly remove it after we get the processed output back.
2239          */
2240         if ($lang === 'php' && strpos($s, '<?php') !== 0) {
2241                 $s = '<?php' . "\n" . $s;
2242                 $tag_added = true;
2243         }
2244
2245         $renderer = new Text_Highlighter_Renderer_Html($options);
2246         $hl = Text_Highlighter::factory($lang);
2247         $hl->setRenderer($renderer);
2248         $o = $hl->highlight($s);
2249         $o = str_replace("\n", '', $o);
2250
2251         if ($tag_added) {
2252                 $b = substr($o, 0, strpos($o, '<li>'));
2253                 $e = substr($o, strpos($o, '</li>'));
2254                 $o = $b . $e;
2255         }
2256
2257         return '<code>' . $o . '</code>';
2258 }