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