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