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