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