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