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