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