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