]> git.mxchange.org Git - friendica.git/blob - include/text.php
9cd861fa55bee80bfe6ec3328f92ff8bfd8b42e1
[friendica.git] / include / text.php
1 <?php
2
3 use Friendica\App;
4 use Friendica\Core\System;
5
6 require_once "include/template_processor.php";
7 require_once "include/friendica_smarty.php";
8 require_once "include/Smilies.php";
9 require_once "include/map.php";
10 require_once "mod/proxy.php";
11
12 if (! function_exists('replace_macros')) {
13 /**
14  * This is our template processor
15  *
16  * @param string|FriendicaSmarty $s the string requiring macro substitution,
17  *                              or an instance of FriendicaSmarty
18  * @param array $r key value pairs (search => replace)
19  * @return string substituted string
20  */
21 function replace_macros($s, $r) {
22
23         $stamp1 = microtime(true);
24
25         $a = get_app();
26
27         // pass $baseurl to all templates
28         $r['$baseurl'] = System::baseUrl();
29
30         $t = $a->template_engine();
31         try {
32                 $output = $t->replace_macros($s, $r);
33         } catch (Exception $e) {
34                 echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
35                 killme();
36         }
37
38         $a->save_timestamp($stamp1, "rendering");
39
40         return $output;
41 }}
42
43
44 // random string, there are 86 characters max in text mode, 128 for hex
45 // output is urlsafe
46
47 define('RANDOM_STRING_HEX',  0x00 );
48 define('RANDOM_STRING_TEXT', 0x01 );
49
50 if (! function_exists('random_string')) {
51 function random_string($size = 64, $type = RANDOM_STRING_HEX) {
52         // generate a bit of entropy and run it through the whirlpool
53         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(), (($type == RANDOM_STRING_TEXT) ? true : false));
54         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n", "", base64url_encode($s,true)) : $s);
55         return(substr($s,0,$size));
56 }}
57
58 if (! function_exists('notags')) {
59 /**
60  * This is our primary input filter.
61  *
62  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
63  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
64  * after cleansing, and angle chars with the high bit set could get through as markup.
65  *
66  * This is now disabled because it was interfering with some legitimate unicode sequences
67  * and hopefully there aren't a lot of those browsers left.
68  *
69  * Use this on any text input where angle chars are not valid or permitted
70  * They will be replaced with safer brackets. This may be filtered further
71  * if these are not allowed either.
72  *
73  * @param string $string Input string
74  * @return string Filtered string
75  */
76 function notags($string) {
77         return str_replace(array("<", ">"), array('[', ']'), $string);
78
79 //  High-bit filter no longer used
80 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
81 }}
82
83
84
85 if (! function_exists('escape_tags')) {
86 /**
87  * use this on "body" or "content" input where angle chars shouldn't be removed,
88  * and allow them to be safely displayed.
89  * @param string $string
90  * @return string
91  */
92 function escape_tags($string) {
93         return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
94 }}
95
96
97 // generate a string that's random, but usually pronounceable.
98 // used to generate initial passwords
99
100 if (! function_exists('autoname')) {
101 /**
102  * generate a string that's random, but usually pronounceable.
103  * used to generate initial passwords
104  * @param int $len
105  * @return string
106  */
107 function autoname($len) {
108
109         if ($len <= 0) {
110                 return '';
111         }
112
113         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
114         if (mt_rand(0, 5) == 4) {
115                 $vowels[] = 'y';
116         }
117
118         $cons = array(
119                         'b','bl','br',
120                         'c','ch','cl','cr',
121                         'd','dr',
122                         'f','fl','fr',
123                         'g','gh','gl','gr',
124                         'h',
125                         'j',
126                         'k','kh','kl','kr',
127                         'l',
128                         'm',
129                         'n',
130                         'p','ph','pl','pr',
131                         'qu',
132                         'r','rh',
133                         's','sc','sh','sm','sp','st',
134                         't','th','tr',
135                         'v',
136                         'w','wh',
137                         'x',
138                         'z','zh'
139                         );
140
141         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
142                                 'nd','ng','nk','nt','rn','rp','rt');
143
144         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
145                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
146
147         $start = mt_rand(0,2);
148         if ($start == 0) {
149                 $table = $vowels;
150         } else {
151                 $table = $cons;
152         }
153
154         $word = '';
155
156         for ($x = 0; $x < $len; $x ++) {
157                 $r = mt_rand(0,count($table) - 1);
158                 $word .= $table[$r];
159
160                 if ($table == $vowels) {
161                         $table = array_merge($cons,$midcons);
162                 } else {
163                         $table = $vowels;
164                 }
165
166         }
167
168         $word = substr($word,0,$len);
169
170         foreach ($noend as $noe) {
171                 if ((strlen($word) > 2) && (substr($word, -2) == $noe)) {
172                         $word = substr($word, 0, -1);
173                         break;
174                 }
175         }
176         if (substr($word, -1) == 'q') {
177                 $word = substr($word, 0, -1);
178         }
179         return $word;
180 }}
181
182
183 // escape text ($str) for XML transport
184 // returns escaped text.
185
186 if (! function_exists('xmlify')) {
187 /**
188  * escape text ($str) for XML transport
189  * @param string $str
190  * @return string Escaped text.
191  */
192 function xmlify($str) {
193         /// @TODO deprecated code found?
194 /*      $buffer = '';
195
196         $len = mb_strlen($str);
197         for ($x = 0; $x < $len; $x ++) {
198                 $char = mb_substr($str,$x,1);
199
200                 switch( $char ) {
201
202                         case "\r" :
203                                 break;
204                         case "&" :
205                                 $buffer .= '&amp;';
206                                 break;
207                         case "'" :
208                                 $buffer .= '&apos;';
209                                 break;
210                         case "\"" :
211                                 $buffer .= '&quot;';
212                                 break;
213                         case '<' :
214                                 $buffer .= '&lt;';
215                                 break;
216                         case '>' :
217                                 $buffer .= '&gt;';
218                                 break;
219                         case "\n" :
220                                 $buffer .= "\n";
221                                 break;
222                         default :
223                                 $buffer .= $char;
224                                 break;
225                 }
226         }*/
227         /*
228         $buffer = mb_ereg_replace("&", "&amp;", $str);
229         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
230         $buffer = mb_ereg_replace('"', "&quot;", $buffer);
231         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
232         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
233         */
234         $buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
235         $buffer = trim($buffer);
236
237         return($buffer);
238 }}
239
240 if (! function_exists('unxmlify')) {
241 /**
242  * undo an xmlify
243  * @param string $s xml escaped text
244  * @return string unescaped text
245  */
246 function unxmlify($s) {
247         /// @TODO deprecated code found?
248 //      $ret = str_replace('&amp;','&', $s);
249 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
250         /*$ret = mb_ereg_replace('&amp;', '&', $s);
251         $ret = mb_ereg_replace('&apos;', "'", $ret);
252         $ret = mb_ereg_replace('&quot;', '"', $ret);
253         $ret = mb_ereg_replace('&lt;', "<", $ret);
254         $ret = mb_ereg_replace('&gt;', ">", $ret);
255         */
256         $ret = htmlspecialchars_decode($s, ENT_QUOTES);
257         return $ret;
258 }}
259
260 if (! function_exists('hex2bin')) {
261 /**
262  * convenience wrapper, reverse the operation "bin2hex"
263  * @param string $s
264  * @return number
265  */
266 function hex2bin($s) {
267         if (! (is_string($s) && strlen($s))) {
268                 return '';
269         }
270
271         if (! ctype_xdigit($s)) {
272                 return $s;
273         }
274
275         return pack("H*",$s);
276 }}
277
278
279 /**
280  * @brief Paginator function. Pushes relevant links in a pager array structure.
281  *
282  * Links are generated depending on the current page and the total number of items.
283  * Inactive links (like "first" and "prev" on page 1) are given the "disabled" class.
284  * Current page link is given the "active" CSS class
285  *
286  * @param App $a App instance
287  * @param int $count [optional] item count (used with minimal pager)
288  * @return Array data for pagination template
289  */
290 function paginate_data(App $a, $count = null) {
291         $stripped = preg_replace('/([&?]page=[0-9]*)/', '', $a->query_string);
292
293         $stripped = str_replace('q=', '', $stripped);
294         $stripped = trim($stripped, '/');
295         $pagenum = $a->pager['page'];
296
297         if (($a->page_offset != '') && !preg_match('/[?&].offset=/', $stripped)) {
298                 $stripped .= '&offset=' . urlencode($a->page_offset);
299         }
300
301         $url = $stripped;
302         $data = array();
303
304         function _l(&$d, $name, $url, $text, $class = '') {
305                 if (strpos($url, '?') === false && ($pos = strpos($url, '&')) !== false) {
306                         $url = substr($url, 0, $pos) . '?' . substr($url, $pos + 1);
307                 }
308
309                 $d[$name] = array('url' => $url, 'text' => $text, 'class' => $class);
310         }
311
312         if (!is_null($count)) {
313                 // minimal pager (newer / older)
314                 $data['class'] = 'pager';
315                 _l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), t('newer'), 'previous' . ($a->pager['page'] == 1 ? ' disabled' : ''));
316                 _l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), t('older'), 'next' . ($count <= 0 ? ' disabled' : ''));
317         } else {
318                 // full pager (first / prev / 1 / 2 / ... / 14 / 15 / next / last)
319                 $data['class'] = 'pagination';
320                 if ($a->pager['total'] > $a->pager['itemspage']) {
321                         _l($data, 'first', $url . '&page=1',  t('first'), $a->pager['page'] == 1 ? 'disabled' : '');
322                         _l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), t('prev'), $a->pager['page'] == 1 ? 'disabled' : '');
323
324                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
325
326                         $numstart = 1;
327                         $numstop = $numpages;
328
329                         // Limit the number of displayed page number buttons.
330                         if ($numpages > 8) {
331                                 $numstart = (($pagenum > 4) ? ($pagenum - 4) : 1);
332                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 8));
333                         }
334
335                         $pages = array();
336
337                         for ($i = $numstart; $i <= $numstop; $i++) {
338                                 if ($i == $a->pager['page']) {
339                                         _l($pages, $i, '#',  $i, 'current active');
340                                 } else {
341                                         _l($pages, $i, $url . '&page='. $i, $i, 'n');
342                                 }
343                         }
344
345                         if (($a->pager['total'] % $a->pager['itemspage']) != 0) {
346                                 if ($i == $a->pager['page']) {
347                                         _l($pages, $i, '#',  $i, 'current active');
348                                 } else {
349                                         _l($pages, $i, $url . '&page=' . $i, $i, 'n');
350                                 }
351                         }
352
353                         $data['pages'] = $pages;
354
355                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
356                         _l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), t('next'), $a->pager['page'] == $lastpage ? 'disabled' : '');
357                         _l($data, 'last', $url . '&page=' . $lastpage, t('last'), $a->pager['page'] == $lastpage ? 'disabled' : '');
358                 }
359         }
360
361         return $data;
362 }
363
364 if (! function_exists('paginate')) {
365 /**
366  * Automatic pagination.
367  *
368  *  To use, get the count of total items.
369  * Then call $a->set_pager_total($number_items);
370  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
371  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
372  * (assuming there are enough items to paginate).
373  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
374  * will limit the results to the correct items for the current page.
375  * The actual page handling is then accomplished at the application layer.
376  *
377  * @param App $a App instance
378  * @return string html for pagination #FIXME remove html
379  */
380 function paginate(App $a) {
381
382         $data = paginate_data($a);
383         $tpl = get_markup_template("paginate.tpl");
384         return replace_macros($tpl, array("pager" => $data));
385
386 }}
387
388 if (! function_exists('alt_pager')) {
389 /**
390  * Alternative pager
391  * @param App $a App instance
392  * @param int $i
393  * @return string html for pagination #FIXME remove html
394  */
395 function alt_pager(App $a, $i) {
396
397         $data = paginate_data($a, $i);
398         $tpl = get_markup_template("paginate.tpl");
399         return replace_macros($tpl, array('pager' => $data));
400
401 }}
402
403 if (! function_exists('scroll_loader')) {
404 /**
405  * Loader for infinite scrolling
406  * @return string html for loader
407  */
408 function scroll_loader() {
409         $tpl = get_markup_template("scroll_loader.tpl");
410         return replace_macros($tpl, array(
411                 'wait' => t('Loading more entries...'),
412                 'end' => t('The end')
413         ));
414 }}
415
416 if (! function_exists('expand_acl')) {
417 /**
418  * Turn user/group ACLs stored as angle bracketed text into arrays
419  *
420  * @param string $s
421  * @return array
422  */
423 function expand_acl($s) {
424         // turn string array of angle-bracketed elements into numeric array
425         // e.g. "<1><2><3>" => array(1,2,3);
426         $ret = array();
427
428         if (strlen($s)) {
429                 $t = str_replace('<', '', $s);
430                 $a = explode('>', $t);
431                 foreach ($a as $aa) {
432                         if (intval($aa)) {
433                                 $ret[] = intval($aa);
434                         }
435                 }
436         }
437         return $ret;
438 }}
439
440 if (! function_exists('sanitise_acl')) {
441 /**
442  * Wrap ACL elements in angle brackets for storage
443  * @param string $item
444  */
445 function sanitise_acl(&$item) {
446         if (intval($item)) {
447                 $item = '<' . intval(notags(trim($item))) . '>';
448         } else {
449                 unset($item);
450         }
451 }}
452
453
454 if (! function_exists('perms2str')) {
455 /**
456  * Convert an ACL array to a storable string
457  *
458  * Normally ACL permissions will be an array.
459  * We'll also allow a comma-separated string.
460  *
461  * @param string|array $p
462  * @return string
463  */
464 function perms2str($p) {
465         $ret = '';
466         if (is_array($p)) {
467                 $tmp = $p;
468         } else {
469                 $tmp = explode(',',$p);
470         }
471
472         if (is_array($tmp)) {
473                 array_walk($tmp, 'sanitise_acl');
474                 $ret = implode('', $tmp);
475         }
476         return $ret;
477 }}
478
479
480 if (! function_exists('item_new_uri')) {
481 /**
482  * generate a guaranteed unique (for this domain) item ID for ATOM
483  * safe from birthday paradox
484  *
485  * @param string $hostname
486  * @param int $uid
487  * @return string
488  */
489 function item_new_uri($hostname, $uid, $guid = "") {
490
491         do {
492                 if ($guid == "") {
493                         $hash = get_guid(32);
494                 } else {
495                         $hash = $guid;
496                         $guid = "";
497                 }
498
499                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
500
501                 $dups = dba::exists('item', array('uri' => $uri));
502         } while ($dups == true);
503
504         return $uri;
505 }}
506
507 // Generate a guaranteed unique photo ID.
508 // safe from birthday paradox
509
510 if (! function_exists('photo_new_resource')) {
511 /**
512  * Generate a guaranteed unique photo ID.
513  * safe from birthday paradox
514  *
515  * @return string
516  */
517 function photo_new_resource() {
518
519         do {
520                 $found = false;
521                 $resource = hash('md5',uniqid(mt_rand(),true));
522                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
523                         dbesc($resource)
524                 );
525
526                 if (dbm::is_result($r)) {
527                         $found = true;
528                 }
529         } while ($found == true);
530
531         return $resource;
532 }}
533
534
535 if (! function_exists('load_view_file')) {
536 /**
537  * @deprecated
538  * wrapper to load a view template, checking for alternate
539  * languages before falling back to the default
540  *
541  * @global string $lang
542  * @global App $a
543  * @param string $s view name
544  * @return string
545  */
546 function load_view_file($s) {
547         global $lang, $a;
548         if (! isset($lang)) {
549                 $lang = 'en';
550         }
551         $b = basename($s);
552         $d = dirname($s);
553         if (file_exists("$d/$lang/$b")) {
554                 $stamp1 = microtime(true);
555                 $content = file_get_contents("$d/$lang/$b");
556                 $a->save_timestamp($stamp1, "file");
557                 return $content;
558         }
559
560         $theme = current_theme();
561
562         if (file_exists("$d/theme/$theme/$b")) {
563                 $stamp1 = microtime(true);
564                 $content = file_get_contents("$d/theme/$theme/$b");
565                 $a->save_timestamp($stamp1, "file");
566                 return $content;
567         }
568
569         $stamp1 = microtime(true);
570         $content = file_get_contents($s);
571         $a->save_timestamp($stamp1, "file");
572         return $content;
573 }}
574
575 if (! function_exists('get_intltext_template')) {
576 /**
577  * load a view template, checking for alternate
578  * languages before falling back to the default
579  *
580  * @global string $lang
581  * @param string $s view path
582  * @return string
583  */
584 function get_intltext_template($s) {
585         global $lang;
586
587         $a = get_app();
588         $engine = '';
589         if ($a->theme['template_engine'] === 'smarty3') {
590                 $engine = "/smarty3";
591         }
592
593         if (! isset($lang)) {
594                 $lang = 'en';
595         }
596
597         if (file_exists("view/lang/$lang$engine/$s")) {
598                 $stamp1 = microtime(true);
599                 $content = file_get_contents("view/lang/$lang$engine/$s");
600                 $a->save_timestamp($stamp1, "file");
601                 return $content;
602         } elseif (file_exists("view/lang/en$engine/$s")) {
603                 $stamp1 = microtime(true);
604                 $content = file_get_contents("view/lang/en$engine/$s");
605                 $a->save_timestamp($stamp1, "file");
606                 return $content;
607         } else {
608                 $stamp1 = microtime(true);
609                 $content = file_get_contents("view$engine/$s");
610                 $a->save_timestamp($stamp1, "file");
611                 return $content;
612         }
613 }}
614
615 if (! function_exists('get_markup_template')) {
616 /**
617  * load template $s
618  *
619  * @param string $s
620  * @param string $root
621  * @return string
622  */
623 function get_markup_template($s, $root = '') {
624         $stamp1 = microtime(true);
625
626         $a = get_app();
627         $t = $a->template_engine();
628         try {
629                 $template = $t->get_template_file($s, $root);
630         } catch (Exception $e) {
631                 echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
632                 killme();
633         }
634
635         $a->save_timestamp($stamp1, "file");
636
637         return $template;
638 }}
639
640 if (! function_exists("get_template_file")) {
641 /**
642  *
643  * @param App $a
644  * @param string $filename
645  * @param string $root
646  * @return string
647  */
648 function get_template_file($a, $filename, $root = '') {
649         $theme = current_theme();
650
651         // Make sure $root ends with a slash /
652         if ($root !== '' && substr($root, -1, 1) !== '/') {
653                 $root = $root . '/';
654         }
655
656         if (file_exists("{$root}view/theme/$theme/$filename")) {
657                 $template_file = "{$root}view/theme/$theme/$filename";
658         } elseif (x($a->theme_info, "extends") && file_exists(sprintf('%sview/theme/%s}/%s', $root, $a->theme_info["extends"], $filename))) {
659                 $template_file = sprintf('%sview/theme/%s}/%s', $root, $a->theme_info["extends"], $filename);
660         } elseif (file_exists("{$root}/$filename")) {
661                 $template_file = "{$root}/$filename";
662         } else {
663                 $template_file = "{$root}view/$filename";
664         }
665
666         return $template_file;
667 }}
668
669
670 if (! function_exists('attribute_contains')) {
671 /**
672  *  for html,xml parsing - let's say you've got
673  *  an attribute foobar="class1 class2 class3"
674  *  and you want to find out if it contains 'class3'.
675  *  you can't use a normal sub string search because you
676  *  might match 'notclass3' and a regex to do the job is
677  *  possible but a bit complicated.
678  *  pass the attribute string as $attr and the attribute you
679  *  are looking for as $s - returns true if found, otherwise false
680  *
681  * @param string $attr attribute value
682  * @param string $s string to search
683  * @return boolean True if found, False otherwise
684  */
685 function attribute_contains($attr, $s) {
686         $a = explode(' ', $attr);
687         return (count($a) && in_array($s,$a));
688 }}
689
690 if (! function_exists('logger')) {
691 /* setup int->string log level map */
692 $LOGGER_LEVELS = array();
693
694 /**
695  * @brief Logs the given message at the given log level
696  *
697  * log levels:
698  * LOGGER_NORMAL (default)
699  * LOGGER_TRACE
700  * LOGGER_DEBUG
701  * LOGGER_DATA
702  * LOGGER_ALL
703  *
704  * @global App $a
705  * @global dba $db
706  * @global array $LOGGER_LEVELS
707  * @param string $msg
708  * @param int $level
709  */
710 function logger($msg, $level = 0) {
711         $a = get_app();
712         global $db;
713         global $LOGGER_LEVELS;
714
715         // turn off logger in install mode
716         if (
717                 $a->module == 'install'
718                 || ! ($db && $db->connected)
719         ) {
720                 return;
721         }
722
723         $debugging = get_config('system','debugging');
724         $logfile   = get_config('system','logfile');
725         $loglevel = intval(get_config('system','loglevel'));
726
727         if (
728                 ! $debugging
729                 || ! $logfile
730                 || $level > $loglevel
731         ) {
732                 return;
733         }
734
735         if (count($LOGGER_LEVELS) == 0) {
736                 foreach (get_defined_constants() as $k => $v) {
737                         if (substr($k, 0, 7) == "LOGGER_") {
738                                 $LOGGER_LEVELS[$v] = substr($k, 7, 7);
739                         }
740                 }
741         }
742
743         $process_id = session_id();
744
745         if ($process_id == '') {
746                 $process_id = get_app()->process_id;
747         }
748
749         $callers = debug_backtrace();
750         $logline = sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
751                         datetime_convert(),
752                         $process_id,
753                         $LOGGER_LEVELS[$level],
754                         basename($callers[0]['file']),
755                         $callers[0]['line'],
756                         $callers[1]['function'],
757                         $msg
758                 );
759
760         $stamp1 = microtime(true);
761         @file_put_contents($logfile, $logline, FILE_APPEND);
762         $a->save_timestamp($stamp1, "file");
763 }}
764
765 /**
766  * @brief An alternative logger for development.
767  * Works largely as logger() but allows developers
768  * to isolate particular elements they are targetting
769  * personally without background noise
770  *
771  * log levels:
772  * LOGGER_NORMAL (default)
773  * LOGGER_TRACE
774  * LOGGER_DEBUG
775  * LOGGER_DATA
776  * LOGGER_ALL
777  *
778  * @global App $a
779  * @global dba $db
780  * @global array $LOGGER_LEVELS
781  * @param string $msg
782  * @param int $level
783  */
784
785 function dlogger($msg, $level = 0) {
786         $a = get_app();
787         global $db;
788
789         // turn off logger in install mode
790         if (
791                 $a->module == 'install'
792                 || ! ($db && $db->connected)
793         ) {
794                 return;
795         }
796
797         $logfile = get_config('system','dlogfile');
798
799         if (! $logfile) {
800                 return;
801         }
802
803         if (count($LOGGER_LEVELS) == 0) {
804                 foreach (get_defined_constants() as $k => $v) {
805                         if (substr($k, 0, 7) == "LOGGER_") {
806                                 $LOGGER_LEVELS[$v] = substr($k, 7, 7);
807                         }
808                 }
809         }
810
811         $process_id = session_id();
812
813         if ($process_id == '') {
814                 $process_id = get_app()->process_id;
815         }
816
817         $callers = debug_backtrace();
818         $logline = sprintf("%s@\t%s:\t%s:\t%s\t%s\t%s\n",
819                         datetime_convert(),
820                         $process_id,
821                         basename($callers[0]['file']),
822                         $callers[0]['line'],
823                         $callers[1]['function'],
824                         $msg
825                 );
826
827         $stamp1 = microtime(true);
828         @file_put_contents($logfile, $logline, FILE_APPEND);
829         $a->save_timestamp($stamp1, "file");
830 }
831
832 if (! function_exists('activity_match')) {
833 /**
834  * Compare activity uri. Knows about activity namespace.
835  *
836  * @param string $haystack
837  * @param string $needle
838  * @return boolean
839  */
840 function activity_match($haystack,$needle) {
841         return (($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle, NAMESPACE_ACTIVITY_SCHEMA)));
842 }}
843
844
845 /**
846  * @brief Pull out all #hashtags and @person tags from $string.
847  *
848  * We also get @person@domain.com - which would make
849  * the regex quite complicated as tags can also
850  * end a sentence. So we'll run through our results
851  * and strip the period from any tags which end with one.
852  * Returns array of tags found, or empty array.
853  *
854  * @param string $string Post content
855  * @return array List of tag and person names
856  */
857 function get_tags($string) {
858         $ret = array();
859
860         // Convert hashtag links to hashtags
861         $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
862
863         // ignore anything in a code block
864         $string = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $string);
865
866         // Force line feeds at bbtags
867         $string = str_replace(array('[', ']'), array("\n[", "]\n"), $string);
868
869         // ignore anything in a bbtag
870         $string = preg_replace('/\[(.*?)\]/sm', '', $string);
871
872         // Match full names against @tags including the space between first and last
873         // We will look these up afterward to see if they are full names or not recognisable.
874
875         if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
876                 foreach ($matches[1] as $match) {
877                         if (strstr($match, ']')) {
878                                 // we might be inside a bbcode color tag - leave it alone
879                                 continue;
880                         }
881                         if (substr($match, -1, 1) === '.') {
882                                 $ret[] = substr($match, 0, -1);
883                         } else {
884                                 $ret[] = $match;
885                         }
886                 }
887         }
888
889         // Otherwise pull out single word tags. These can be @nickname, @first_last
890         // and #hash tags.
891
892         if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/', $string, $matches)) {
893                 foreach ($matches[1] as $match) {
894                         if (strstr($match, ']')) {
895                                 // we might be inside a bbcode color tag - leave it alone
896                                 continue;
897                         }
898                         if (substr($match, -1, 1) === '.') {
899                                 $match = substr($match,0,-1);
900                         }
901                         // ignore strictly numeric tags like #1
902                         if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
903                                 continue;
904                         }
905                         // try not to catch url fragments
906                         if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
907                                 continue;
908                         }
909                         $ret[] = $match;
910                 }
911         }
912         return $ret;
913 }
914
915
916 //
917
918 if (! function_exists('qp')) {
919 /**
920  * quick and dirty quoted_printable encoding
921  *
922  * @param string $s
923  * @return string
924  */
925 function qp($s) {
926         return str_replace("%", "=", rawurlencode($s));
927 }}
928
929 if (! function_exists('contact_block')) {
930 /**
931  * Get html for contact block.
932  *
933  * @template contact_block.tpl
934  * @hook contact_block_end (contacts=>array, output=>string)
935  * @return string
936  */
937 function contact_block() {
938         $o = '';
939         $a = get_app();
940
941         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
942         if ($shown === false) {
943                 $shown = 24;
944         }
945         if ($shown == 0) {
946                 return;
947         }
948
949         if ((! is_array($a->profile)) || ($a->profile['hide-friends'])) {
950                 return $o;
951         }
952         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
953                         WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
954                                 AND NOT `pending` AND NOT `hidden` AND NOT `archive`
955                                 AND `network` IN ('%s', '%s', '%s')",
956                         intval($a->profile['uid']),
957                         dbesc(NETWORK_DFRN),
958                         dbesc(NETWORK_OSTATUS),
959                         dbesc(NETWORK_DIASPORA)
960         );
961         if (dbm::is_result($r)) {
962                 $total = intval($r[0]['total']);
963         }
964         if (! $total) {
965                 $contacts = t('No contacts');
966                 $micropro = null;
967         } else {
968                 // Splitting the query in two parts makes it much faster
969                 $r = q("SELECT `id` FROM `contact`
970                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked`
971                                         AND NOT `pending` AND NOT `hidden` AND NOT `archive`
972                                         AND `network` IN ('%s', '%s', '%s')
973                                 ORDER BY RAND() LIMIT %d",
974                                 intval($a->profile['uid']),
975                                 dbesc(NETWORK_DFRN),
976                                 dbesc(NETWORK_OSTATUS),
977                                 dbesc(NETWORK_DIASPORA),
978                                 intval($shown)
979                 );
980                 if (dbm::is_result($r)) {
981                         $contacts = array();
982                         foreach ($r AS $contact) {
983                                 $contacts[] = $contact["id"];
984                         }
985                         $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `thumb`, `network` FROM `contact` WHERE `id` IN (%s)",
986                                 dbesc(implode(",", $contacts)));
987
988                         if (dbm::is_result($r)) {
989                                 $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
990                                 $micropro = Array();
991                                 foreach ($r as $rr) {
992                                         $micropro[] = micropro($rr,true,'mpfriend');
993                                 }
994                         }
995                 }
996         }
997
998         $tpl = get_markup_template('contact_block.tpl');
999         $o = replace_macros($tpl, array(
1000                 '$contacts' => $contacts,
1001                 '$nickname' => $a->profile['nickname'],
1002                 '$viewcontacts' => t('View Contacts'),
1003                 '$micropro' => $micropro,
1004         ));
1005
1006         $arr = array('contacts' => $r, 'output' => $o);
1007
1008         call_hooks('contact_block_end', $arr);
1009         return $o;
1010
1011 }}
1012
1013 /**
1014  * @brief Format contacts as picture links or as texxt links
1015  *
1016  * @param array $contact Array with contacts which contains an array with
1017  *      int 'id' => The ID of the contact
1018  *      int 'uid' => The user ID of the user who owns this data
1019  *      string 'name' => The name of the contact
1020  *      string 'url' => The url to the profile page of the contact
1021  *      string 'addr' => The webbie of the contact (e.g.) username@friendica.com
1022  *      string 'network' => The network to which the contact belongs to
1023  *      string 'thumb' => The contact picture
1024  *      string 'click' => js code which is performed when clicking on the contact
1025  * @param boolean $redirect If true try to use the redir url if it's possible
1026  * @param string $class CSS class for the
1027  * @param boolean $textmode If true display the contacts as text links
1028  *      if false display the contacts as picture links
1029
1030  * @return string Formatted html
1031  */
1032 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
1033
1034         // Use the contact URL if no address is available
1035         if ($contact["addr"] == "") {
1036                 $contact["addr"] = $contact["url"];
1037         }
1038
1039         $url = $contact['url'];
1040         $sparkle = '';
1041         $redir = false;
1042
1043         if ($redirect) {
1044                 $a = get_app();
1045                 $redirect_url = 'redir/' . $contact['id'];
1046                 if (local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
1047                         $redir = true;
1048                         $url = $redirect_url;
1049                         $sparkle = ' sparkle';
1050                 } else {
1051                         $url = zrl($url);
1052                 }
1053         }
1054
1055         // If there is some js available we don't need the url
1056         if (x($contact, 'click')) {
1057                 $url = '';
1058         }
1059
1060         return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array(
1061                 '$click' => (($contact['click']) ? $contact['click'] : ''),
1062                 '$class' => $class,
1063                 '$url' => $url,
1064                 '$photo' => proxy_url($contact['thumb'], false, PROXY_SIZE_THUMB),
1065                 '$name' => $contact['name'],
1066                 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
1067                 '$parkle' => $sparkle,
1068                 '$redir' => $redir,
1069
1070         ));
1071 }
1072
1073
1074
1075 if (! function_exists('search')) {
1076 /**
1077  * search box
1078  *
1079  * @param string $s search query
1080  * @param string $id html id
1081  * @param string $url search url
1082  * @param boolean $savedsearch show save search button
1083  */
1084 function search($s, $id = 'search-box', $url = 'search', $save = false, $aside = true) {
1085         $a = get_app();
1086
1087         $values = array(
1088                         '$s' => htmlspecialchars($s),
1089                         '$id' => $id,
1090                         '$action_url' => $url,
1091                         '$search_label' => t('Search'),
1092                         '$save_label' => t('Save'),
1093                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
1094                         '$search_hint' => t('@name, !forum, #tags, content'),
1095                 );
1096
1097         if (!$aside) {
1098                 $values['$searchoption'] = array(
1099                                         t("Full Text"),
1100                                         t("Tags"),
1101                                         t("Contacts"));
1102
1103                 if (get_config('system','poco_local_search')) {
1104                         $values['$searchoption'][] = t("Forums");
1105                 }
1106         }
1107
1108         return replace_macros(get_markup_template('searchbox.tpl'), $values);
1109 }}
1110
1111 if (! function_exists('valid_email')) {
1112 /**
1113  * Check if $x is a valid email string
1114  *
1115  * @param string $x
1116  * @return boolean
1117  */
1118 function valid_email($x){
1119
1120         /// @TODO Removed because Fabio told me so.
1121         //if (get_config('system','disable_email_validation'))
1122         //      return true;
1123         return preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/', $x);
1124 }}
1125
1126
1127 if (! function_exists('linkify')) {
1128 /**
1129  * Replace naked text hyperlink with HTML formatted hyperlink
1130  *
1131  * @param string $s
1132  */
1133 function linkify($s) {
1134         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1135         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1136         return $s;
1137 }}
1138
1139
1140 /**
1141  * Load poke verbs
1142  *
1143  * @return array index is present tense verb
1144                                  value is array containing past tense verb, translation of present, translation of past
1145  * @hook poke_verbs pokes array
1146  */
1147 function get_poke_verbs() {
1148
1149         // index is present tense verb
1150         // value is array containing past tense verb, translation of present, translation of past
1151
1152         $arr = array(
1153                 'poke' => array( 'poked', t('poke'), t('poked')),
1154                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1155                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1156                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1157                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1158                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1159         );
1160         call_hooks('poke_verbs', $arr);
1161         return $arr;
1162 }
1163
1164 /**
1165  * Load moods
1166  * @return array index is mood, value is translated mood
1167  * @hook mood_verbs moods array
1168  */
1169 function get_mood_verbs() {
1170
1171         $arr = array(
1172                 'happy'      => t('happy'),
1173                 'sad'        => t('sad'),
1174                 'mellow'     => t('mellow'),
1175                 'tired'      => t('tired'),
1176                 'perky'      => t('perky'),
1177                 'angry'      => t('angry'),
1178                 'stupefied'  => t('stupified'),
1179                 'puzzled'    => t('puzzled'),
1180                 'interested' => t('interested'),
1181                 'bitter'     => t('bitter'),
1182                 'cheerful'   => t('cheerful'),
1183                 'alive'      => t('alive'),
1184                 'annoyed'    => t('annoyed'),
1185                 'anxious'    => t('anxious'),
1186                 'cranky'     => t('cranky'),
1187                 'disturbed'  => t('disturbed'),
1188                 'frustrated' => t('frustrated'),
1189                 'motivated'  => t('motivated'),
1190                 'relaxed'    => t('relaxed'),
1191                 'surprised'  => t('surprised'),
1192         );
1193
1194         call_hooks('mood_verbs', $arr);
1195         return $arr;
1196 }
1197
1198 if (! function_exists('day_translate')) {
1199 /**
1200  * Translate days and months names
1201  *
1202  * @param string $s
1203  * @return string
1204  */
1205 function day_translate($s) {
1206         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1207                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1208                 $s);
1209
1210         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1211                 array( t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')),
1212                 $ret);
1213
1214         return $ret;
1215 }}
1216
1217
1218 if (! function_exists('normalise_link')) {
1219 /**
1220  * Normalize url
1221  *
1222  * @param string $url
1223  * @return string
1224  */
1225 function normalise_link($url) {
1226         $ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
1227         return rtrim($ret,'/');
1228 }}
1229
1230
1231
1232 if (! function_exists('link_compare')) {
1233 /**
1234  * Compare two URLs to see if they are the same, but ignore
1235  * slight but hopefully insignificant differences such as if one
1236  * is https and the other isn't, or if one is www.something and
1237  * the other isn't - and also ignore case differences.
1238  *
1239  * @param string $a first url
1240  * @param string $b second url
1241  * @return boolean True if the URLs match, otherwise False
1242  *
1243  */
1244 function link_compare($a, $b) {
1245         return (strcasecmp(normalise_link($a), normalise_link($b)) === 0);
1246 }}
1247
1248 /**
1249  * @brief Find any non-embedded images in private items and add redir links to them
1250  *
1251  * @param App $a
1252  * @param array &$item The field array of an item row
1253  */
1254 function redir_private_images($a, &$item)
1255 {
1256         $matches = false;
1257         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1258         if ($cnt) {
1259                 foreach ($matches as $mtch) {
1260                         if (strpos($mtch[1], '/redir') !== false) {
1261                                 continue;
1262                         }
1263
1264                         if ((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1265                                 $img_url = 'redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
1266                                 $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
1267                         }
1268                 }
1269         }
1270 }
1271
1272 function put_item_in_cache(&$item, $update = false) {
1273
1274         if (($item["rendered-hash"] != hash("md5", $item["body"])) || ($item["rendered-hash"] == "") ||
1275                 ($item["rendered-html"] == "") || get_config("system", "ignore_cache")) {
1276
1277                 // The function "redir_private_images" changes the body.
1278                 // I'm not sure if we should store it permanently, so we save the old value.
1279                 $body = $item["body"];
1280
1281                 $a = get_app();
1282                 redir_private_images($a, $item);
1283
1284                 $item["rendered-html"] = prepare_text($item["body"]);
1285                 $item["rendered-hash"] = hash("md5", $item["body"]);
1286                 $item["body"] = $body;
1287
1288                 if ($update && ($item["id"] > 0)) {
1289                         dba::update('item', array('rendered-html' => $item["rendered-html"], 'rendered-hash' => $item["rendered-hash"]),
1290                                         array('id' => $item["id"]), false);
1291                 }
1292         }
1293 }
1294
1295 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1296 // If attach is true, also add icons for item attachments
1297
1298 if (! function_exists('prepare_body')) {
1299 /**
1300  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1301  * If attach is true, also add icons for item attachments
1302  *
1303  * @param array $item
1304  * @param boolean $attach
1305  * @return string item body html
1306  * @hook prepare_body_init item array before any work
1307  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1308  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1309  */
1310 function prepare_body(&$item, $attach = false, $preview = false) {
1311
1312         $a = get_app();
1313         call_hooks('prepare_body_init', $item);
1314
1315         $searchpath = System::baseUrl() . "/search?tag=";
1316
1317         $tags = array();
1318         $hashtags = array();
1319         $mentions = array();
1320
1321         if (!get_config('system','suppress_tags')) {
1322                 $taglist = dba::p("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = ? AND `oid` = ? AND `type` IN (?, ?) ORDER BY `tid`",
1323                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1324
1325                 while ($tag = dba::fetch($taglist)) {
1326                         if ($tag["url"] == "") {
1327                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1328                         }
1329
1330                         $orig_tag = $tag["url"];
1331
1332                         $tag["url"] = best_link_url($item, $sp, $tag["url"]);
1333
1334                         if ($tag["type"] == TERM_HASHTAG) {
1335                                 if ($orig_tag != $tag["url"]) {
1336                                         $item['body'] = str_replace($orig_tag, $tag["url"], $item['body']);
1337                                 }
1338                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1339                                 $prefix = "#";
1340                         } elseif ($tag["type"] == TERM_MENTION) {
1341                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1342                                 $prefix = "@";
1343                         }
1344                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1345                 }
1346                 dba::close($taglist);
1347         }
1348
1349         $item['tags'] = $tags;
1350         $item['hashtags'] = $hashtags;
1351         $item['mentions'] = $mentions;
1352
1353         // Update the cached values if there is no "zrl=..." on the links
1354         $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
1355
1356         // Or update it if the current viewer is the intented viewer
1357         if (($item["uid"] == local_user()) && ($item["uid"] != 0)) {
1358                 $update = true;
1359         }
1360
1361         put_item_in_cache($item, $update);
1362         $s = $item["rendered-html"];
1363
1364         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1365         call_hooks('prepare_body', $prep_arr);
1366         $s = $prep_arr['html'];
1367
1368         if (! $attach) {
1369                 // Replace the blockquotes with quotes that are used in mails
1370                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1371                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1372                 return $s;
1373         }
1374
1375         $as = '';
1376         $vhead = false;
1377         $arr = explode('[/attach],', $item['attach']);
1378         if (count($arr)) {
1379                 foreach ($arr as $r) {
1380                         $matches = false;
1381                         $icon = '';
1382                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1383                         if ($cnt) {
1384                                 foreach ($matches as $mtch) {
1385                                         $mime = $mtch[3];
1386
1387                                         if ((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1388                                                 $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1389                                         } else {
1390                                                 $the_url = $mtch[1];
1391                                         }
1392
1393                                         if (strpos($mime, 'video') !== false) {
1394                                                 if (!$vhead) {
1395                                                         $vhead = true;
1396                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1397                                                                 '$baseurl' => System::baseUrl(),
1398                                                         ));
1399                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1400                                                                 '$baseurl' => System::baseUrl(),
1401                                                         ));
1402                                                 }
1403
1404                                                 $id = end(explode('/', $the_url));
1405                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1406                                                         '$video' => array(
1407                                                                 'id'     => $id,
1408                                                                 'title'  => t('View Video'),
1409                                                                 'src'    => $the_url,
1410                                                                 'mime'   => $mime,
1411                                                         ),
1412                                                 ));
1413                                         }
1414
1415                                         $filetype = strtolower(substr($mime, 0, strpos($mime,'/')));
1416                                         if ($filetype) {
1417                                                 $filesubtype = strtolower(substr($mime, strpos($mime,'/') + 1));
1418                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1419                                         } else {
1420                                                 $filetype = 'unkn';
1421                                                 $filesubtype = 'unkn';
1422                                         }
1423
1424                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1425                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1426
1427                                         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 }