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