]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge pull request #2309 from annando/1602-new-guid
[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/map.php");
6 require_once("mod/proxy.php");
7
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'] = z_root();
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 if(! function_exists('paginate_data')) {
271 /**
272  * Automatica pagination data.
273  *
274  * @param App $a App instance
275  * @param int $count [optional] item count (used with alt pager)
276  * @return Array data for pagination template
277  */
278 function paginate_data(&$a, $count=null) {
279         $stripped = preg_replace('/([&?]page=[0-9]*)/','',$a->query_string);
280
281         $stripped = str_replace('q=','',$stripped);
282         $stripped = trim($stripped,'/');
283         $pagenum = $a->pager['page'];
284
285         if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped))
286                 $stripped .= "&offset=".urlencode($a->page_offset);
287
288         $url = z_root() . '/' . $stripped;
289
290         $data = array();
291         function _l(&$d, $name, $url, $text, $class="") {
292                 if (!strpos($url, "?")) {
293                         if ($pos = strpos($url, "&"))
294                                 $url = substr($url, 0, $pos)."?".substr($url, $pos + 1);
295                 }
296
297                 $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
298         }
299
300         if (!is_null($count)){
301                 // alt pager
302                 if($a->pager['page']>1)
303                         _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
304                 if($count>0)
305                         _l($data,  "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
306         } else {
307                 // full pager
308                 if($a->pager['total'] > $a->pager['itemspage']) {
309                         if($a->pager['page'] != 1)
310                                 _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
311
312                         _l($data, "first", $url."&page=1",  t('first'));
313
314
315                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
316
317                         $numstart = 1;
318                         $numstop = $numpages;
319
320                         if($numpages > 14) {
321                                 $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
322                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
323                         }
324
325                         $pages = array();
326
327                         for($i = $numstart; $i <= $numstop; $i++){
328                                 if($i == $a->pager['page'])
329                                         _l($pages, $i, "#",  $i, "current");
330                                 else
331                                         _l($pages, $i, $url."&page=$i", $i, "n");
332                         }
333
334                         if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
335                                 if($i == $a->pager['page'])
336                                         _l($pages, $i, "#",  $i, "current");
337                                 else
338                                         _l($pages, $i, $url."&page=$i", $i, "n");
339                         }
340
341                         $data['pages'] = $pages;
342
343                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
344                         _l($data, "last", $url."&page=$lastpage", t('last'));
345
346                         if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
347                                 _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
348
349                 }
350         }
351         return $data;
352
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(&$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(&$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(count($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(count($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$engine/$s")) {
584                 $stamp1 = microtime(true);
585                 $content = file_get_contents("view/$lang$engine/$s");
586                 $a->save_timestamp($stamp1, "file");
587                 return $content;
588         } elseif(file_exists("view/en$engine/$s")) {
589                 $stamp1 = microtime(true);
590                 $content = file_get_contents("view/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  * log levels:
686  * LOGGER_NORMAL (default)
687  * LOGGER_TRACE
688  * LOGGER_DEBUG
689  * LOGGER_DATA
690  * LOGGER_ALL
691  *
692  * @global App $a
693  * @global dba $db
694  * @param string $msg
695  * @param int $level
696  */
697 function logger($msg,$level = 0) {
698         // turn off logger in install mode
699         global $a;
700         global $db;
701         global $LOGGER_LEVELS;
702
703         if(($a->module == 'install') || (! ($db && $db->connected))) return;
704
705         if (count($LOGGER_LEVELS)==0){
706                 foreach (get_defined_constants() as $k=>$v){
707                         if (substr($k,0,7)=="LOGGER_")
708                                 $LOGGER_LEVELS[$v] = substr($k,7,7);
709                 }
710         }
711
712         $debugging = get_config('system','debugging');
713         $loglevel  = intval(get_config('system','loglevel'));
714         $logfile   = get_config('system','logfile');
715
716         if((! $debugging) || (! $logfile) || ($level > $loglevel))
717                 return;
718
719         $callers = debug_backtrace();
720         $logline =  sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
721                                  datetime_convert(),
722                                  session_id(),
723                                  $LOGGER_LEVELS[$level],
724                                  basename($callers[0]['file']),
725                                  $callers[0]['line'],
726                                  $callers[1]['function'],
727                                  $msg
728                                 );
729
730         $stamp1 = microtime(true);
731         @file_put_contents($logfile, $logline, FILE_APPEND);
732         $a->save_timestamp($stamp1, "file");
733         return;
734 }}
735
736
737 if(! function_exists('activity_match')) {
738 /**
739  * Compare activity uri. Knows about activity namespace.
740  *
741  * @param string $haystack
742  * @param string $needle
743  * @return boolean
744  */
745 function activity_match($haystack,$needle) {
746         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
747                 return true;
748         return false;
749 }}
750
751
752 if(! function_exists('get_tags')) {
753 /**
754  * Pull out all #hashtags and @person tags from $s;
755  * We also get @person@domain.com - which would make
756  * the regex quite complicated as tags can also
757  * end a sentence. So we'll run through our results
758  * and strip the period from any tags which end with one.
759  * Returns array of tags found, or empty array.
760  *
761  * @param string $s
762  * @return array
763  */
764 function get_tags($s) {
765         $ret = array();
766
767         // Convert hashtag links to hashtags
768         $s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
769
770         // ignore anything in a code block
771         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
772
773         // Force line feeds at bbtags
774         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
775
776         // ignore anything in a bbtag
777         $s = preg_replace('/\[(.*?)\]/sm','',$s);
778
779         // Match full names against @tags including the space between first and last
780         // We will look these up afterward to see if they are full names or not recognisable.
781
782         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
783                 foreach($match[1] as $mtch) {
784                         if(strstr($mtch,"]")) {
785                                 // we might be inside a bbcode color tag - leave it alone
786                                 continue;
787                         }
788                         if(substr($mtch,-1,1) === '.')
789                                 $ret[] = substr($mtch,0,-1);
790                         else
791                                 $ret[] = $mtch;
792                 }
793         }
794
795         // Otherwise pull out single word tags. These can be @nickname, @first_last
796         // and #hash tags.
797
798         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
799                 foreach($match[1] as $mtch) {
800                         if(strstr($mtch,"]")) {
801                                 // we might be inside a bbcode color tag - leave it alone
802                                 continue;
803                         }
804                         if(substr($mtch,-1,1) === '.')
805                                 $mtch = substr($mtch,0,-1);
806                         // ignore strictly numeric tags like #1
807                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
808                                 continue;
809                         // try not to catch url fragments
810                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
811                                 continue;
812                         $ret[] = $mtch;
813                 }
814         }
815         return $ret;
816 }}
817
818
819 //
820
821 if(! function_exists('qp')) {
822 /**
823  * quick and dirty quoted_printable encoding
824  *
825  * @param string $s
826  * @return string
827  */
828 function qp($s) {
829 return str_replace ("%","=",rawurlencode($s));
830 }}
831
832 if(! function_exists('contact_block')) {
833 /**
834  * Get html for contact block.
835  *
836  * @template contact_block.tpl
837  * @hook contact_block_end (contacts=>array, output=>string)
838  * @return string
839  */
840 function contact_block() {
841         $o = '';
842         $a = get_app();
843
844         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
845         if($shown === false)
846                 $shown = 24;
847         if($shown == 0)
848                 return;
849
850         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
851                 return $o;
852         $r = q("SELECT COUNT(*) AS `total` FROM `contact`
853                         WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
854                                 AND `hidden` = 0 AND `archive` = 0
855                                 AND `network` IN ('%s', '%s', '%s')",
856                         intval($a->profile['uid']),
857                         dbesc(NETWORK_DFRN),
858                         dbesc(NETWORK_OSTATUS),
859                         dbesc(NETWORK_DIASPORA)
860         );
861         if(count($r)) {
862                 $total = intval($r[0]['total']);
863         }
864         if(! $total) {
865                 $contacts = t('No contacts');
866                 $micropro = Null;
867
868         } else {
869                 $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `micro`, `network` FROM `contact`
870                                 WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
871                                         AND NOT `hidden` AND NOT `archive`
872                                 AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
873                                 intval($a->profile['uid']),
874                                 dbesc(NETWORK_DFRN),
875                                 dbesc(NETWORK_OSTATUS),
876                                 dbesc(NETWORK_DIASPORA),
877                                 intval($shown)
878                 );
879                 if(count($r)) {
880                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
881                         $micropro = Array();
882                         foreach($r as $rr) {
883                                 $micropro[] = micropro($rr,true,'mpfriend');
884                         }
885                 }
886         }
887
888         $tpl = get_markup_template('contact_block.tpl');
889         $o = replace_macros($tpl, array(
890                 '$contacts' => $contacts,
891                 '$nickname' => $a->profile['nickname'],
892                 '$viewcontacts' => t('View Contacts'),
893                 '$micropro' => $micropro,
894         ));
895
896         $arr = array('contacts' => $r, 'output' => $o);
897
898         call_hooks('contact_block_end', $arr);
899         return $o;
900
901 }}
902
903 if(! function_exists('micropro')) {
904 /**
905  *
906  * @param array $contact
907  * @param boolean $redirect
908  * @param string $class
909  * @param boolean $textmode
910  * @return string #FIXME: remove html
911  */
912 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
913
914         if($class)
915                 $class = ' ' . $class;
916
917         if ($contact["addr"] == "")
918                 $contact["addr"] = $contact["url"];
919
920         $url = $contact['url'];
921         $sparkle = '';
922         $redir = false;
923
924         if($redirect) {
925                 $a = get_app();
926                 $redirect_url = z_root() . '/redir/' . $contact['id'];
927                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
928                         $redir = true;
929                         $url = $redirect_url;
930                         $sparkle = ' sparkle';
931                 }
932                 else
933                         $url = zrl($url);
934         }
935         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
936         if($click)
937                 $url = '';
938         if($textmode) {
939                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle
940                         . (($click) ? ' fakelink' : '') . '" '
941                         . (($redir) ? ' target="redir" ' : '')
942                         . (($url) ? ' href="' . $url . '"' : '') . $click
943                         . '" title="' . $contact['name'] . ' [' . $contact['addr'] . ']" alt="' . $contact['name']
944                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
945         }
946         else {
947                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle
948                         . (($click) ? ' fakelink' : '') . '" '
949                         . (($redir) ? ' target="redir" ' : '')
950                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="'
951                         . proxy_url($contact['micro'], false, PROXY_SIZE_THUMB) . '" title="' . $contact['name'] . ' [' . $contact['addr'] . ']" alt="' . $contact['name']
952                         . '" /></a></div>' . "\r\n";
953         }
954 }}
955
956
957
958 if(! function_exists('search')) {
959 /**
960  * search box
961  *
962  * @param string $s search query
963  * @param string $id html id
964  * @param string $url search url
965  * @param boolean $savedsearch show save search button
966  */
967 function search($s,$id='search-box',$url='/search',$save = false, $aside = true) {
968         $a = get_app();
969
970         $values = array(
971                         '$s' => $s,
972                         '$id' => $id,
973                         '$action_url' => $a->get_baseurl((stristr($url,'network')) ? true : false) . $url,
974                         '$search_label' => t('Search'),
975                         '$save_label' => t('Save'),
976                         '$savedsearch' => feature_enabled(local_user(),'savedsearch'),
977                 );
978
979         if (!$aside) {
980                 $values['$searchoption'] = array(
981                                         t("Full Text"),
982                                         t("Tags"),
983                                         t("Contacts"));
984
985                 if (get_config('system','poco_local_search'))
986                         $values['$searchoption'][] = t("Forums");
987         }
988
989         return replace_macros(get_markup_template('searchbox.tpl'), $values);
990 }}
991
992 if(! function_exists('valid_email')) {
993 /**
994  * Check if $x is a valid email string
995  *
996  * @param string $x
997  * @return boolean
998  */
999 function valid_email($x){
1000
1001         // Removed because Fabio told me so.
1002         //if(get_config('system','disable_email_validation'))
1003         //      return true;
1004
1005         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1006                 return true;
1007         return false;
1008 }}
1009
1010
1011 if(! function_exists('linkify')) {
1012 /**
1013  * Replace naked text hyperlink with HTML formatted hyperlink
1014  *
1015  * @param string $s
1016  */
1017 function linkify($s) {
1018         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1019         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1020         return($s);
1021 }}
1022
1023
1024 /**
1025  * Load poke verbs
1026  *
1027  * @return array index is present tense verb
1028                                  value is array containing past tense verb, translation of present, translation of past
1029  * @hook poke_verbs pokes array
1030  */
1031 function get_poke_verbs() {
1032
1033         // index is present tense verb
1034         // value is array containing past tense verb, translation of present, translation of past
1035
1036         $arr = array(
1037                 'poke' => array( 'poked', t('poke'), t('poked')),
1038                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1039                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1040                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1041                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1042                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1043         );
1044         call_hooks('poke_verbs', $arr);
1045         return $arr;
1046 }
1047
1048 /**
1049  * Load moods
1050  * @return array index is mood, value is translated mood
1051  * @hook mood_verbs moods array
1052  */
1053 function get_mood_verbs() {
1054
1055         $arr = array(
1056                 'happy'      => t('happy'),
1057                 'sad'        => t('sad'),
1058                 'mellow'     => t('mellow'),
1059                 'tired'      => t('tired'),
1060                 'perky'      => t('perky'),
1061                 'angry'      => t('angry'),
1062                 'stupefied'  => t('stupified'),
1063                 'puzzled'    => t('puzzled'),
1064                 'interested' => t('interested'),
1065                 'bitter'     => t('bitter'),
1066                 'cheerful'   => t('cheerful'),
1067                 'alive'      => t('alive'),
1068                 'annoyed'    => t('annoyed'),
1069                 'anxious'    => t('anxious'),
1070                 'cranky'     => t('cranky'),
1071                 'disturbed'  => t('disturbed'),
1072                 'frustrated' => t('frustrated'),
1073                 'motivated'  => t('motivated'),
1074                 'relaxed'    => t('relaxed'),
1075                 'surprised'  => t('surprised'),
1076         );
1077
1078         call_hooks('mood_verbs', $arr);
1079         return $arr;
1080 }
1081
1082
1083
1084 if(! function_exists('smilies')) {
1085 /**
1086  * Replaces text emoticons with graphical images
1087  *
1088  * It is expected that this function will be called using HTML text.
1089  * We will escape text between HTML pre and code blocks from being
1090  * processed.
1091  *
1092  * At a higher level, the bbcode [nosmile] tag can be used to prevent this
1093  * function from being executed by the prepare_text() routine when preparing
1094  * bbcode source for HTML display
1095  *
1096  * @param string $s
1097  * @param boolean $sample
1098  * @return string
1099  * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
1100  */
1101 function smilies($s, $sample = false) {
1102         $a = get_app();
1103
1104         if(intval(get_config('system','no_smilies'))
1105                 || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
1106                 return $s;
1107
1108         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
1109         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
1110
1111         $texts =  array(
1112                 '&lt;3',
1113                 '&lt;/3',
1114                 '&lt;\\3',
1115                 ':-)',
1116                 ';-)',
1117                 ':-(',
1118                 ':-P',
1119                 ':-p',
1120                 ':-"',
1121                 ':-&quot;',
1122                 ':-x',
1123                 ':-X',
1124                 ':-D',
1125                 '8-|',
1126                 '8-O',
1127                 ':-O',
1128                 '\\o/',
1129                 'o.O',
1130                 'O.o',
1131                 'o_O',
1132                 'O_o',
1133                 ":'(",
1134                 ":-!",
1135                 ":-/",
1136                 ":-[",
1137                 "8-)",
1138                 ':beer',
1139                 ':homebrew',
1140                 ':coffee',
1141                 ':facepalm',
1142                 ':like',
1143                 ':dislike',
1144                 '~friendica',
1145                 'red#',
1146                 'red#matrix'
1147
1148         );
1149
1150         $icons = array(
1151                 '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="&lt;3" />',
1152                 '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="&lt;/3" />',
1153                 '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="&lt;\\3" />',
1154                 '<img class="smiley" src="' . z_root() . '/images/smiley-smile.gif" alt=":-)" />',
1155                 '<img class="smiley" src="' . z_root() . '/images/smiley-wink.gif" alt=";-)" />',
1156                 '<img class="smiley" src="' . z_root() . '/images/smiley-frown.gif" alt=":-(" />',
1157                 '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1158                 '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-p" />',
1159                 '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />',
1160                 '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />',
1161                 '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-x" />',
1162                 '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-X" />',
1163                 '<img class="smiley" src="' . z_root() . '/images/smiley-laughing.gif" alt=":-D" />',
1164                 '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-|" />',
1165                 '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-O" />',
1166                 '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt=":-O" />',
1167                 '<img class="smiley" src="' . z_root() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
1168                 '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o.O" />',
1169                 '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O.o" />',
1170                 '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o_O" />',
1171                 '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O_o" />',
1172                 '<img class="smiley" src="' . z_root() . '/images/smiley-cry.gif" alt=":\'(" />',
1173                 '<img class="smiley" src="' . z_root() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
1174                 '<img class="smiley" src="' . z_root() . '/images/smiley-undecided.gif" alt=":-/" />',
1175                 '<img class="smiley" src="' . z_root() . '/images/smiley-embarassed.gif" alt=":-[" />',
1176                 '<img class="smiley" src="' . z_root() . '/images/smiley-cool.gif" alt="8-)" />',
1177                 '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":beer" />',
1178                 '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":homebrew" />',
1179                 '<img class="smiley" src="' . z_root() . '/images/coffee.gif" alt=":coffee" />',
1180                 '<img class="smiley" src="' . z_root() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
1181                 '<img class="smiley" src="' . z_root() . '/images/like.gif" alt=":like" />',
1182                 '<img class="smiley" src="' . z_root() . '/images/dislike.gif" alt=":dislike" />',
1183                 '<a href="http://friendica.com">~friendica <img class="smiley" src="' . z_root() . '/images/friendica-16.png" alt="~friendica" /></a>',
1184                 '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red" />matrix</a>',
1185                 '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red" />matrix</a>'
1186         );
1187
1188         $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
1189         call_hooks('smilie', $params);
1190
1191         if($sample) {
1192                 $s = '<div class="smiley-sample">';
1193                 for($x = 0; $x < count($params['texts']); $x ++) {
1194                         $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
1195                 }
1196         }
1197         else {
1198                 $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
1199                 $s = str_replace($params['texts'],$params['icons'],$params['string']);
1200         }
1201
1202         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
1203         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
1204
1205         return $s;
1206
1207 }}
1208
1209 function smile_encode($m) {
1210         return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
1211 }
1212
1213 function smile_decode($m) {
1214         return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
1215 }
1216
1217
1218 /**
1219  * expand <3333 to the correct number of hearts
1220  *
1221  * @param string $x
1222  * @return string
1223  */
1224 function preg_heart($x) {
1225         $a = get_app();
1226         if(strlen($x[1]) == 1)
1227                 return $x[0];
1228         $t = '';
1229         for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
1230                 $t .= '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="&lt;3" />';
1231         $r =  str_replace($x[0],$t,$x[0]);
1232         return $r;
1233 }
1234
1235
1236 if(! function_exists('day_translate')) {
1237 /**
1238  * Translate days and months names
1239  *
1240  * @param string $s
1241  * @return string
1242  */
1243 function day_translate($s) {
1244         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1245                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1246                 $s);
1247
1248         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1249                 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')),
1250                 $ret);
1251
1252         return $ret;
1253 }}
1254
1255
1256 if(! function_exists('normalise_link')) {
1257 /**
1258  * Normalize url
1259  *
1260  * @param string $url
1261  * @return string
1262  */
1263 function normalise_link($url) {
1264         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1265         return(rtrim($ret,'/'));
1266 }}
1267
1268
1269
1270 if(! function_exists('link_compare')) {
1271 /**
1272  * Compare two URLs to see if they are the same, but ignore
1273  * slight but hopefully insignificant differences such as if one
1274  * is https and the other isn't, or if one is www.something and
1275  * the other isn't - and also ignore case differences.
1276  *
1277  * @param string $a first url
1278  * @param string $b second url
1279  * @return boolean True if the URLs match, otherwise False
1280  *
1281  */
1282 function link_compare($a,$b) {
1283         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1284                 return true;
1285         return false;
1286 }}
1287
1288
1289 if(! function_exists('redir_private_images')) {
1290 /**
1291  * Find any non-embedded images in private items and add redir links to them
1292  *
1293  * @param App $a
1294  * @param array $item
1295  */
1296 function redir_private_images($a, &$item) {
1297
1298         $matches = false;
1299         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1300         if($cnt) {
1301                 //logger("redir_private_images: matches = " . print_r($matches, true));
1302                 foreach($matches as $mtch) {
1303                         if(strpos($mtch[1], '/redir') !== false)
1304                                 continue;
1305
1306                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1307                                 //logger("redir_private_images: redir");
1308                                 $img_url = z_root() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1309                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1310                         }
1311                 }
1312         }
1313
1314 }}
1315
1316 function put_item_in_cache(&$item, $update = false) {
1317
1318         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1319                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1320
1321                 // The function "redir_private_images" changes the body.
1322                 // I'm not sure if we should store it permanently, so we save the old value.
1323                 $body = $item["body"];
1324
1325                 $a = get_app();
1326                 redir_private_images($a, $item);
1327
1328                 $item["rendered-html"] = prepare_text($item["body"]);
1329                 $item["rendered-hash"] = hash("md5", $item["body"]);
1330                 $item["body"] = $body;
1331
1332                 if ($update AND ($item["id"] != 0)) {
1333                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1334                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1335                 }
1336         }
1337 }
1338
1339 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1340 // If attach is true, also add icons for item attachments
1341
1342 if(! function_exists('prepare_body')) {
1343 /**
1344  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1345  * If attach is true, also add icons for item attachments
1346  *
1347  * @param array $item
1348  * @param boolean $attach
1349  * @return string item body html
1350  * @hook prepare_body_init item array before any work
1351  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1352  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1353  */
1354 function prepare_body(&$item,$attach = false, $preview = false) {
1355
1356         $a = get_app();
1357         call_hooks('prepare_body_init', $item);
1358
1359         $searchpath = z_root()."/search?tag=";
1360
1361         $tags=array();
1362         $hashtags = array();
1363         $mentions = array();
1364
1365         if (!get_config('system','suppress_tags')) {
1366                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1367                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1368
1369                 foreach($taglist as $tag) {
1370
1371                         if ($tag["url"] == "")
1372                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1373
1374                         if ($tag["type"] == TERM_HASHTAG) {
1375                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1376                                 $prefix = "#";
1377                         } elseif ($tag["type"] == TERM_MENTION) {
1378                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1379                                 $prefix = "@";
1380                         }
1381                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1382                 }
1383         }
1384
1385         $item['tags'] = $tags;
1386         $item['hashtags'] = $hashtags;
1387         $item['mentions'] = $mentions;
1388
1389         // Update the cached values if there is no "zrl=..." on the links
1390         $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
1391
1392         // Or update it if the current viewer is the intented viewer
1393         if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
1394                 $update = true;
1395
1396         put_item_in_cache($item, $update);
1397         $s = $item["rendered-html"];
1398
1399         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1400         call_hooks('prepare_body', $prep_arr);
1401         $s = $prep_arr['html'];
1402
1403         if(! $attach) {
1404                 // Replace the blockquotes with quotes that are used in mails
1405                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1406                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1407                 return $s;
1408         }
1409
1410         $as = '';
1411         $vhead = false;
1412         $arr = explode('[/attach],',$item['attach']);
1413         if(count($arr)) {
1414                 $as .= '<div class="body-attach">';
1415                 foreach($arr as $r) {
1416                         $matches = false;
1417                         $icon = '';
1418                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1419                         if($cnt) {
1420                                 foreach($matches as $mtch) {
1421                                         $mime = $mtch[3];
1422
1423                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1424                                                 $the_url = z_root() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1425                                         else
1426                                                 $the_url = $mtch[1];
1427
1428                                         if(strpos($mime, 'video') !== false) {
1429                                                 if(!$vhead) {
1430                                                         $vhead = true;
1431                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1432                                                                 '$baseurl' => z_root(),
1433                                                         ));
1434                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1435                                                                 '$baseurl' => z_root(),
1436                                                         ));
1437                                                 }
1438
1439                                                 $id = end(explode('/', $the_url));
1440                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1441                                                         '$video'        => array(
1442                                                                 'id'       => $id,
1443                                                                 'title'         => t('View Video'),
1444                                                                 'src'           => $the_url,
1445                                                                 'mime'          => $mime,
1446                                                         ),
1447                                                 ));
1448                                         }
1449
1450                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1451                                         if($filetype) {
1452                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1453                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1454                                         }
1455                                         else {
1456                                                 $filetype = 'unkn';
1457                                                 $filesubtype = 'unkn';
1458                                         }
1459
1460                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1461                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1462                                         switch($icontype) {
1463                                                 case 'video':
1464                                                 case 'audio':
1465                                                 case 'image':
1466                                                 case 'text':
1467                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1468                                                         break;
1469                                                 default:
1470                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1471                                                         break;
1472                                         }*/
1473
1474                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1475                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1476
1477                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1478                                 }
1479                         }
1480                 }
1481                 $as .= '<div class="clear"></div></div>';
1482         }
1483         $s = $s . $as;
1484
1485         // map
1486         if(strpos($s,'<div class="map">') !== false && $item['coord']) {
1487                 $x = generate_map(trim($item['coord']));
1488                 if($x) {
1489                         $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
1490                 }
1491         }
1492
1493
1494         // Look for spoiler
1495         $spoilersearch = '<blockquote class="spoiler">';
1496
1497         // Remove line breaks before the spoiler
1498         while ((strpos($s, "\n".$spoilersearch) !== false))
1499                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1500         while ((strpos($s, "<br />".$spoilersearch) !== false))
1501                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1502
1503         while ((strpos($s, $spoilersearch) !== false)) {
1504
1505                 $pos = strpos($s, $spoilersearch);
1506                 $rnd = random_string(8);
1507                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1508                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1509                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1510         }
1511
1512         // Look for quote with author
1513         $authorsearch = '<blockquote class="author">';
1514
1515         while ((strpos($s, $authorsearch) !== false)) {
1516
1517                 $pos = strpos($s, $authorsearch);
1518                 $rnd = random_string(8);
1519                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1520                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1521                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1522         }
1523
1524         // replace friendica image url size with theme preference
1525         if (x($a->theme_info,'item_image_size')){
1526             $ps = $a->theme_info['item_image_size'];
1527
1528             $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1529         }
1530
1531         $prep_arr = array('item' => $item, 'html' => $s);
1532         call_hooks('prepare_body_final', $prep_arr);
1533
1534         return $prep_arr['html'];
1535 }}
1536
1537
1538 if(! function_exists('prepare_text')) {
1539 /**
1540  * Given a text string, convert from bbcode to html and add smilie icons.
1541  *
1542  * @param string $text
1543  * @return string
1544  */
1545 function prepare_text($text) {
1546
1547         require_once('include/bbcode.php');
1548
1549         if(stristr($text,'[nosmile]'))
1550                 $s = bbcode($text);
1551         else
1552                 $s = smilies(bbcode($text));
1553
1554         return trim($s);
1555 }}
1556
1557
1558
1559 /**
1560  * return array with details for categories and folders for an item
1561  *
1562  * @param array $item
1563  * @return array
1564  *
1565   * [
1566  *      [ // categories array
1567  *          {
1568  *               'name': 'category name',
1569  *               'removeurl': 'url to remove this category',
1570  *               'first': 'is the first in this array? true/false',
1571  *               'last': 'is the last in this array? true/false',
1572  *           } ,
1573  *           ....
1574  *       ],
1575  *       [ //folders array
1576  *                      {
1577  *               'name': 'folder name',
1578  *               'removeurl': 'url to remove this folder',
1579  *               'first': 'is the first in this array? true/false',
1580  *               'last': 'is the last in this array? true/false',
1581  *           } ,
1582  *           ....
1583  *       ]
1584  *  ]
1585  */
1586 function get_cats_and_terms($item) {
1587
1588         $a = get_app();
1589         $categories = array();
1590         $folders = array();
1591
1592         $matches = false; $first = true;
1593         $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1594         if($cnt) {
1595                 foreach($matches as $mtch) {
1596                         $categories[] = array(
1597                                 'name' => xmlify(file_tag_decode($mtch[1])),
1598                                 'url' =>  "#",
1599                                 'removeurl' => ((local_user() == $item['uid'])?z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1600                                 'first' => $first,
1601                                 'last' => false
1602                         );
1603                         $first = false;
1604                 }
1605         }
1606         if (count($categories)) $categories[count($categories)-1]['last'] = true;
1607
1608
1609         if(local_user() == $item['uid']) {
1610                 $matches = false; $first = true;
1611                 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1612                 if($cnt) {
1613                         foreach($matches as $mtch) {
1614                                 $folders[] = array(
1615                                         'name' => xmlify(file_tag_decode($mtch[1])),
1616                                         'url' =>  "#",
1617                                         'removeurl' => ((local_user() == $item['uid'])?z_root() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1618                                         'first' => $first,
1619                                         'last' => false
1620                                 );
1621                                 $first = false;
1622                         }
1623                 }
1624         }
1625
1626         if (count($folders)) $folders[count($folders)-1]['last'] = true;
1627
1628         return array($categories, $folders);
1629 }
1630
1631 if(! function_exists('get_plink')) {
1632 /**
1633  * get private link for item
1634  * @param array $item
1635  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1636  */
1637 function get_plink($item) {
1638         $a = get_app();
1639
1640         if ($a->user['nickname'] != "") {
1641                 $ret = array(
1642                                 //'href' => z_root()."/display/".$a->user['nickname']."/".$item['id'],
1643                                 'href' => z_root()."/display/".$item['guid'],
1644                                 'orig' => z_root()."/display/".$item['guid'],
1645                                 'title' => t('View on separate page'),
1646                                 'orig_title' => t('view on separate page'),
1647                         );
1648
1649                 if (x($item,'plink')) {
1650                         $ret["href"] = $item['plink'];
1651                         $ret["title"] = t('link to source');
1652                 }
1653
1654         } elseif (x($item,'plink') && ($item['private'] != 1))
1655                 $ret = array(
1656                                 'href' => $item['plink'],
1657                                 'orig' => $item['plink'],
1658                                 'title' => t('link to source'),
1659                         );
1660         else
1661                 $ret = array();
1662
1663         //if (x($item,'plink') && ($item['private'] != 1))
1664
1665         return($ret);
1666 }}
1667
1668 if(! function_exists('unamp')) {
1669 /**
1670  * replace html amp entity with amp char
1671  * @param string $s
1672  * @return string
1673  */
1674 function unamp($s) {
1675         return str_replace('&amp;', '&', $s);
1676 }}
1677
1678
1679 if(! function_exists('return_bytes')) {
1680 /**
1681  * return number of bytes in size (K, M, G)
1682  * @param string $size_str
1683  * @return number
1684  */
1685 function return_bytes ($size_str) {
1686         switch (substr ($size_str, -1)) {
1687                 case 'M': case 'm': return (int)$size_str * 1048576;
1688                 case 'K': case 'k': return (int)$size_str * 1024;
1689                 case 'G': case 'g': return (int)$size_str * 1073741824;
1690                 default: return $size_str;
1691         }
1692 }}
1693
1694 /**
1695  * @return string
1696  */
1697 function generate_user_guid() {
1698         $found = true;
1699         do {
1700                 $guid = get_guid(32);
1701                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1702                         dbesc($guid)
1703                 );
1704                 if(! count($x))
1705                         $found = false;
1706         } while ($found == true );
1707         return $guid;
1708 }
1709
1710
1711 /**
1712  * @param string $s
1713  * @param boolean $strip_padding
1714  * @return string
1715  */
1716 function base64url_encode($s, $strip_padding = false) {
1717
1718         $s = strtr(base64_encode($s),'+/','-_');
1719
1720         if($strip_padding)
1721                 $s = str_replace('=','',$s);
1722
1723         return $s;
1724 }
1725
1726 /**
1727  * @param string $s
1728  * @return string
1729  */
1730 function base64url_decode($s) {
1731
1732         if(is_array($s)) {
1733                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1734                 return $s;
1735         }
1736
1737 /*
1738  *  // Placeholder for new rev of salmon which strips base64 padding.
1739  *  // PHP base64_decode handles the un-padded input without requiring this step
1740  *  // Uncomment if you find you need it.
1741  *
1742  *      $l = strlen($s);
1743  *      if(! strpos($s,'=')) {
1744  *              $m = $l % 4;
1745  *              if($m == 2)
1746  *                      $s .= '==';
1747  *              if($m == 3)
1748  *                      $s .= '=';
1749  *      }
1750  *
1751  */
1752
1753         return base64_decode(strtr($s,'-_','+/'));
1754 }
1755
1756
1757 if (!function_exists('str_getcsv')) {
1758         /**
1759          * Parse csv string
1760          *
1761          * @param string $input
1762          * @param string $delimiter
1763          * @param string $enclosure
1764          * @param string $escape
1765          * @param string $eol
1766          * @return boolean|array False on error, otherwise array[row][column]
1767          */
1768 function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1769         if (is_string($input) && !empty($input)) {
1770                 $output = array();
1771                 $tmp    = preg_split("/".$eol."/",$input);
1772                 if (is_array($tmp) && !empty($tmp)) {
1773                         while (list($line_num, $line) = each($tmp)) {
1774                                 if (preg_match("/".$escape.$enclosure."/",$line)) {
1775                                         while ($strlen = strlen($line)) {
1776                                                 $pos_delimiter       = strpos($line,$delimiter);
1777                                                 $pos_enclosure_start = strpos($line,$enclosure);
1778                                                 if (
1779                                                         is_int($pos_delimiter) && is_int($pos_enclosure_start)
1780                                                         && ($pos_enclosure_start < $pos_delimiter)
1781                                                         ) {
1782                                                         $enclosed_str = substr($line,1);
1783                                                         $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1784                                                         $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1785                                                         $output[$line_num][] = $enclosed_str;
1786                                                         $offset = $pos_enclosure_end+3;
1787                                                 } else {
1788                                                         if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1789                                                                 $output[$line_num][] = substr($line,0);
1790                                                                 $offset = strlen($line);
1791                                                         } else {
1792                                                                 $output[$line_num][] = substr($line,0,$pos_delimiter);
1793                                                                 $offset = (
1794                                                                         !empty($pos_enclosure_start)
1795                                                                         && ($pos_enclosure_start < $pos_delimiter)
1796                                                                         )
1797                                                                         ?$pos_enclosure_start
1798                                                                         :$pos_delimiter+1;
1799                                                         }
1800                                                 }
1801                                                 $line = substr($line,$offset);
1802                                         }
1803                                 } else {
1804                                         $line = preg_split("/".$delimiter."/",$line);
1805
1806                                         /*
1807                                          * Validating against pesky extra line breaks creating false rows.
1808                                          */
1809                                         if (is_array($line) && !empty($line[0])) {
1810                                                 $output[$line_num] = $line;
1811                                 }
1812                                 }
1813                         }
1814                         return $output;
1815                 } else {
1816                 return false;
1817                 }
1818         } else {
1819                 return false;
1820         }
1821 }
1822 }
1823
1824 /**
1825  * return div element with class 'clear'
1826  * @return string
1827  * @deprecated
1828  */
1829 function cleardiv() {
1830         return '<div class="clear"></div>';
1831 }
1832
1833
1834 function bb_translate_video($s) {
1835
1836         $matches = null;
1837         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1838         if($r) {
1839                 foreach($matches as $mtch) {
1840                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1841                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1842                         elseif(stristr($mtch[1],'vimeo'))
1843                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1844                 }
1845         }
1846         return $s;
1847 }
1848
1849 function html2bb_video($s) {
1850
1851         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1852                         '[youtube]$2[/youtube]', $s);
1853
1854         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1855                         '[youtube]$2[/youtube]', $s);
1856
1857         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1858                         '[vimeo]$2[/vimeo]', $s);
1859
1860         return $s;
1861 }
1862
1863 /**
1864  * apply xmlify() to all values of array $val, recursively
1865  * @param array $val
1866  * @return array
1867  */
1868 function array_xmlify($val){
1869         if (is_bool($val)) return $val?"true":"false";
1870         if (is_array($val)) return array_map('array_xmlify', $val);
1871         return xmlify((string) $val);
1872 }
1873
1874
1875 /**
1876  * transorm link href and img src from relative to absolute
1877  *
1878  * @param string $text
1879  * @param string $base base url
1880  * @return string
1881  */
1882 function reltoabs($text, $base) {
1883         if (empty($base))
1884             return $text;
1885
1886         $base = rtrim($base,'/');
1887
1888         $base2 = $base . "/";
1889
1890         // Replace links
1891         $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1892         $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1893         $text = preg_replace($pattern, $replace, $text);
1894
1895         $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1896         $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1897         $text = preg_replace($pattern, $replace, $text);
1898
1899         // Replace images
1900         $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1901         $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1902         $text = preg_replace($pattern, $replace, $text);
1903
1904         $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1905         $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1906         $text = preg_replace($pattern, $replace, $text);
1907
1908
1909         // Done
1910         return $text;
1911 }
1912
1913 /**
1914  * get translated item type
1915  *
1916  * @param array $itme
1917  * @return string
1918  */
1919 function item_post_type($item) {
1920         if(intval($item['event-id']))
1921                 return t('event');
1922         if(strlen($item['resource-id']))
1923                 return t('photo');
1924         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1925                 return t('activity');
1926         if($item['id'] != $item['parent'])
1927                 return t('comment');
1928         return t('post');
1929 }
1930
1931 // post categories and "save to file" use the same item.file table for storage.
1932 // We will differentiate the different uses by wrapping categories in angle brackets
1933 // and save to file categories in square brackets.
1934 // To do this we need to escape these characters if they appear in our tag.
1935
1936 function file_tag_encode($s) {
1937         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1938 }
1939
1940 function file_tag_decode($s) {
1941         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1942 }
1943
1944 function file_tag_file_query($table,$s,$type = 'file') {
1945
1946         if($type == 'file')
1947                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1948         else
1949                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1950         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1951 }
1952
1953 // ex. given music,video return <music><video> or [music][video]
1954 function file_tag_list_to_file($list,$type = 'file') {
1955         $tag_list = '';
1956         if(strlen($list)) {
1957                 $list_array = explode(",",$list);
1958                 if($type == 'file') {
1959                         $lbracket = '[';
1960                         $rbracket = ']';
1961                 }
1962                 else {
1963                         $lbracket = '<';
1964                         $rbracket = '>';
1965                 }
1966
1967                 foreach($list_array as $item) {
1968                   if(strlen($item)) {
1969                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1970                         }
1971                 }
1972         }
1973         return $tag_list;
1974 }
1975
1976 // ex. given <music><video>[friends], return music,video or friends
1977 function file_tag_file_to_list($file,$type = 'file') {
1978         $matches = false;
1979         $list = '';
1980         if($type == 'file') {
1981                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
1982         }
1983         else {
1984                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
1985         }
1986         if($cnt) {
1987                 foreach($matches as $mtch) {
1988                         if(strlen($list))
1989                                 $list .= ',';
1990                         $list .= file_tag_decode($mtch[1]);
1991                 }
1992         }
1993
1994         return $list;
1995 }
1996
1997 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
1998         // $file_old - categories previously associated with an item
1999         // $file_new - new list of categories for an item
2000
2001         if(! intval($uid))
2002                 return false;
2003
2004         if($file_old == $file_new)
2005                 return true;
2006
2007         $saved = get_pconfig($uid,'system','filetags');
2008         if(strlen($saved)) {
2009                 if($type == 'file') {
2010                         $lbracket = '[';
2011                         $rbracket = ']';
2012                         $termtype = TERM_FILE;
2013                 }
2014                 else {
2015                         $lbracket = '<';
2016                         $rbracket = '>';
2017                         $termtype = TERM_CATEGORY;
2018                 }
2019
2020                 $filetags_updated = $saved;
2021
2022                 // check for new tags to be added as filetags in pconfig
2023                 $new_tags = array();
2024                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2025
2026                 foreach($check_new_tags as $tag) {
2027                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2028                                 $new_tags[] = $tag;
2029                 }
2030
2031                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2032
2033                 // check for deleted tags to be removed from filetags in pconfig
2034                 $deleted_tags = array();
2035                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2036
2037                 foreach($check_deleted_tags as $tag) {
2038                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2039                                 $deleted_tags[] = $tag;
2040                 }
2041
2042                 foreach($deleted_tags as $key => $tag) {
2043                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2044                                 dbesc($tag),
2045                                 intval(TERM_OBJ_POST),
2046                                 intval($termtype),
2047                                 intval($uid));
2048
2049                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2050                         //      intval($uid)
2051                         //);
2052
2053                         if(count($r)) {
2054                                 unset($deleted_tags[$key]);
2055                         }
2056                         else {
2057                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2058                         }
2059                 }
2060
2061                 if($saved != $filetags_updated) {
2062                         set_pconfig($uid,'system','filetags', $filetags_updated);
2063                 }
2064                 return true;
2065         }
2066         else
2067                 if(strlen($file_new)) {
2068                         set_pconfig($uid,'system','filetags', $file_new);
2069                 }
2070                 return true;
2071 }
2072
2073 function file_tag_save_file($uid,$item,$file) {
2074         require_once("include/files.php");
2075
2076         $result = false;
2077         if(! intval($uid))
2078                 return false;
2079         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2080                 intval($item),
2081                 intval($uid)
2082         );
2083         if(count($r)) {
2084                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2085                         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2086                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2087                                 intval($item),
2088                                 intval($uid)
2089                         );
2090
2091                 create_files_from_item($item);
2092
2093                 $saved = get_pconfig($uid,'system','filetags');
2094                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2095                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2096                 info( t('Item filed') );
2097         }
2098         return true;
2099 }
2100
2101 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2102         require_once("include/files.php");
2103
2104         $result = false;
2105         if(! intval($uid))
2106                 return false;
2107
2108         if($cat == true) {
2109                 $pattern = '<' . file_tag_encode($file) . '>' ;
2110                 $termtype = TERM_CATEGORY;
2111         } else {
2112                 $pattern = '[' . file_tag_encode($file) . ']' ;
2113                 $termtype = TERM_FILE;
2114         }
2115
2116
2117         $r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
2118                 intval($item),
2119                 intval($uid)
2120         );
2121         if(! count($r))
2122                 return false;
2123
2124         q("UPDATE `item` SET `file` = '%s' WHERE `id` = %d AND `uid` = %d",
2125                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2126                 intval($item),
2127                 intval($uid)
2128         );
2129
2130         create_files_from_item($item);
2131
2132         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2133                 dbesc($file),
2134                 intval(TERM_OBJ_POST),
2135                 intval($termtype),
2136                 intval($uid));
2137
2138         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2139         //);
2140
2141         if(! count($r)) {
2142                 $saved = get_pconfig($uid,'system','filetags');
2143                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2144
2145         }
2146         return true;
2147 }
2148
2149 function normalise_openid($s) {
2150         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2151 }
2152
2153
2154 function undo_post_tagging($s) {
2155         $matches = null;
2156         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2157         if($cnt) {
2158                 foreach($matches as $mtch) {
2159                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2160                 }
2161         }
2162         return $s;
2163 }
2164
2165 function fix_mce_lf($s) {
2166         $s = str_replace("\r\n","\n",$s);
2167 //      $s = str_replace("\n\n","\n",$s);
2168         return $s;
2169 }
2170
2171
2172 function protect_sprintf($s) {
2173         return(str_replace('%','%%',$s));
2174 }
2175
2176
2177 function is_a_date_arg($s) {
2178         $i = intval($s);
2179         if($i > 1900) {
2180                 $y = date('Y');
2181                 if($i <= $y+1 && strpos($s,'-') == 4) {
2182                         $m = intval(substr($s,5));
2183                         if($m > 0 && $m <= 12)
2184                                 return true;
2185                 }
2186         }
2187         return false;
2188 }
2189
2190 /**
2191  * remove intentation from a text
2192  */
2193 function deindent($text, $chr="[\t ]", $count=NULL) {
2194         $text = fix_mce_lf($text);
2195         $lines = explode("\n", $text);
2196         if (is_null($count)) {
2197                 $m = array();
2198                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2199                 preg_match("|^".$chr."*|", $lines[$k], $m);
2200                 $count = strlen($m[0]);
2201         }
2202         for ($k=0; $k<count($lines); $k++){
2203                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2204         }
2205
2206         return implode("\n", $lines);
2207 }
2208
2209 function formatBytes($bytes, $precision = 2) {
2210          $units = array('B', 'KB', 'MB', 'GB', 'TB');
2211
2212         $bytes = max($bytes, 0);
2213         $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
2214         $pow = min($pow, count($units) - 1);
2215
2216         $bytes /= pow(1024, $pow);
2217
2218         return round($bytes, $precision) . ' ' . $units[$pow];
2219 }
2220
2221 /**
2222  * @brief translate and format the networkname of a contact
2223  * 
2224  * @param string $network
2225  *      Networkname of the contact (e.g. dfrn, rss and so on)
2226  * @param sting $url
2227  *      The contact url
2228  * @return string
2229  */
2230 function format_network_name($network, $url = 0) {
2231         if ($network != "") {
2232                 require_once('include/contact_selectors.php');
2233                 if ($url != "")
2234                         $network_name = '<a href="'.$url.'">'.network_to_name($network, $url)."</a>";
2235                 else
2236                         $network_name = network_to_name($network);
2237
2238                 return $network_name;
2239         }
2240
2241 }