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