]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge pull request #938 from annando/master
[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="_blank">$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://redmatrix.me/">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=\"_blank\">".$tag["term"]."</a>";
1305                                 $prefix = "#";
1306                         } elseif ($tag["type"] == TERM_MENTION) {
1307                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1308                                 $prefix = "@";
1309                         }
1310                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$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="_blank" >' . $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
1617         if ($a->user['nickname'] != "") {
1618                 $ret = array(
1619                                 'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
1620                                 'title' => t('link to source'),
1621                         );
1622                 $ret["orig"] = $ret["href"];
1623
1624                 if (x($item,'plink'))
1625                         $ret["href"] = $item['plink'];
1626
1627         } elseif (x($item,'plink') && ($item['private'] != 1))
1628                 $ret = array(
1629                                 'href' => $item['plink'],
1630                                 'orig' => $item['plink'],
1631                                 'title' => t('link to source'),
1632                         );
1633         else
1634                 $ret = array();
1635
1636         //if (x($item,'plink') && ($item['private'] != 1))
1637
1638         return($ret);
1639 }}
1640
1641 if(! function_exists('unamp')) {
1642 /**
1643  * replace html amp entity with amp char
1644  * @param string $s
1645  * @return string
1646  */
1647 function unamp($s) {
1648         return str_replace('&amp;', '&', $s);
1649 }}
1650
1651
1652
1653
1654 if(! function_exists('lang_selector')) {
1655 /**
1656  * get html for language selector
1657  * @global string $lang
1658  * @return string 
1659  * @template lang_selector.tpl
1660  */
1661 function lang_selector() {
1662         global $lang;
1663         
1664         $langs = glob('view/*/strings.php');
1665         
1666         $lang_options = array();
1667         $selected = "";
1668         
1669         if(is_array($langs) && count($langs)) {
1670                 $langs[] = '';
1671                 if(! in_array('view/en/strings.php',$langs))
1672                         $langs[] = 'view/en/';
1673                 asort($langs);
1674                 foreach($langs as $l) {
1675                         if($l == '') {
1676                                 $lang_options[""] = t('default');
1677                                 continue;
1678                         }
1679                         $ll = substr($l,5);
1680                         $ll = substr($ll,0,strrpos($ll,'/'));
1681                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1682                         $lang_options[$ll]=$ll;
1683                 }
1684         }
1685
1686         $tpl = get_markup_template("lang_selector.tpl");        
1687         $o = replace_macros($tpl, array(
1688                 '$title' => t('Select an alternate language'),
1689                 '$langs' => array($lang_options, $selected),
1690                 
1691         ));
1692         return $o;
1693 }}
1694
1695
1696 if(! function_exists('return_bytes')) {
1697 /**
1698  * return number of bytes in size (K, M, G)
1699  * @param string $size_str
1700  * @return number
1701  */
1702 function return_bytes ($size_str) {
1703     switch (substr ($size_str, -1))
1704     {
1705         case 'M': case 'm': return (int)$size_str * 1048576;
1706         case 'K': case 'k': return (int)$size_str * 1024;
1707         case 'G': case 'g': return (int)$size_str * 1073741824;
1708         default: return $size_str;
1709     }
1710 }}
1711
1712 /**
1713  * @return string
1714  */
1715 function generate_user_guid() {
1716         $found = true;
1717         do {
1718                 $guid = random_string(16);
1719                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1720                         dbesc($guid)
1721                 );
1722                 if(! count($x))
1723                         $found = false;
1724         } while ($found == true );
1725         return $guid;
1726 }
1727
1728
1729 /**
1730  * @param string $s
1731  * @param boolean $strip_padding
1732  * @return string
1733  */
1734 function base64url_encode($s, $strip_padding = false) {
1735
1736         $s = strtr(base64_encode($s),'+/','-_');
1737
1738         if($strip_padding)
1739                 $s = str_replace('=','',$s);
1740
1741         return $s;
1742 }
1743
1744 /**
1745  * @param string $s
1746  * @return string
1747  */
1748 function base64url_decode($s) {
1749
1750         if(is_array($s)) {
1751                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1752                 return $s;
1753         }
1754
1755 /*
1756  *  // Placeholder for new rev of salmon which strips base64 padding.
1757  *  // PHP base64_decode handles the un-padded input without requiring this step
1758  *  // Uncomment if you find you need it.
1759  *
1760  *      $l = strlen($s);
1761  *      if(! strpos($s,'=')) {
1762  *              $m = $l % 4;
1763  *              if($m == 2)
1764  *                      $s .= '==';
1765  *              if($m == 3)
1766  *                      $s .= '=';
1767  *      }
1768  *
1769  */
1770
1771         return base64_decode(strtr($s,'-_','+/'));
1772 }
1773
1774
1775 if (!function_exists('str_getcsv')) {
1776         /**
1777          * Parse csv string
1778          * 
1779          * @param string $input
1780          * @param string $delimiter
1781          * @param string $enclosure
1782          * @param string $escape
1783          * @param string $eol
1784          * @return boolean|array False on error, otherwise array[row][column]
1785          */
1786     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1787         if (is_string($input) && !empty($input)) {
1788             $output = array();
1789             $tmp    = preg_split("/".$eol."/",$input);
1790             if (is_array($tmp) && !empty($tmp)) {
1791                 while (list($line_num, $line) = each($tmp)) {
1792                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1793                         while ($strlen = strlen($line)) {
1794                             $pos_delimiter       = strpos($line,$delimiter);
1795                             $pos_enclosure_start = strpos($line,$enclosure);
1796                             if (
1797                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1798                                 && ($pos_enclosure_start < $pos_delimiter)
1799                                 ) {
1800                                 $enclosed_str = substr($line,1);
1801                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1802                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1803                                 $output[$line_num][] = $enclosed_str;
1804                                 $offset = $pos_enclosure_end+3;
1805                             } else {
1806                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1807                                     $output[$line_num][] = substr($line,0);
1808                                     $offset = strlen($line);
1809                                 } else {
1810                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1811                                     $offset = (
1812                                                 !empty($pos_enclosure_start)
1813                                                 && ($pos_enclosure_start < $pos_delimiter)
1814                                                 )
1815                                                 ?$pos_enclosure_start
1816                                                 :$pos_delimiter+1;
1817                                 }
1818                             }
1819                             $line = substr($line,$offset);
1820                         }
1821                     } else {
1822                         $line = preg_split("/".$delimiter."/",$line);
1823    
1824                         /*
1825                          * Validating against pesky extra line breaks creating false rows.
1826                          */
1827                         if (is_array($line) && !empty($line[0])) {
1828                             $output[$line_num] = $line;
1829                         } 
1830                     }
1831                 }
1832                 return $output;
1833             } else {
1834                 return false;
1835             }
1836         } else {
1837             return false;
1838         }
1839     }
1840
1841
1842 /**
1843  * return div element with class 'clear'
1844  * @return string
1845  * @deprecated
1846  */
1847 function cleardiv() {
1848         return '<div class="clear"></div>';
1849 }
1850
1851
1852 function bb_translate_video($s) {
1853
1854         $matches = null;
1855         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1856         if($r) {
1857                 foreach($matches as $mtch) {
1858                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1859                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1860                         elseif(stristr($mtch[1],'vimeo'))
1861                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1862                 }
1863         }
1864         return $s;      
1865 }
1866
1867 function html2bb_video($s) {
1868
1869         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1870                         '[youtube]$2[/youtube]', $s);
1871
1872         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1873                         '[youtube]$2[/youtube]', $s);
1874
1875         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1876                         '[vimeo]$2[/vimeo]', $s);
1877
1878         return $s;
1879 }
1880
1881 /**
1882  * apply xmlify() to all values of array $val, recursively
1883  * @param array $val
1884  * @return array
1885  */
1886 function array_xmlify($val){
1887         if (is_bool($val)) return $val?"true":"false";
1888         if (is_array($val)) return array_map('array_xmlify', $val);
1889         return xmlify((string) $val);
1890 }
1891
1892
1893 /**
1894  * transorm link href and img src from relative to absolute
1895  * 
1896  * @param string $text
1897  * @param string $base base url
1898  * @return string
1899  */
1900 function reltoabs($text, $base)
1901 {
1902   if (empty($base))
1903     return $text;
1904
1905   $base = rtrim($base,'/');
1906
1907   $base2 = $base . "/";
1908         
1909   // Replace links
1910   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1911   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1912   $text = preg_replace($pattern, $replace, $text);
1913
1914   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1915   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1916   $text = preg_replace($pattern, $replace, $text);
1917
1918   // Replace images
1919   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1920   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1921   $text = preg_replace($pattern, $replace, $text); 
1922
1923   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1924   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1925   $text = preg_replace($pattern, $replace, $text); 
1926
1927
1928   // Done
1929   return $text;
1930 }
1931
1932 /**
1933  * get translated item type
1934  * 
1935  * @param array $itme
1936  * @return string
1937  */
1938 function item_post_type($item) {
1939         if(intval($item['event-id']))
1940                 return t('event');
1941         if(strlen($item['resource-id']))
1942                 return t('photo');
1943         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1944                 return t('activity');
1945         if($item['id'] != $item['parent'])
1946                 return t('comment');
1947         return t('post');
1948 }
1949
1950 // post categories and "save to file" use the same item.file table for storage.
1951 // We will differentiate the different uses by wrapping categories in angle brackets
1952 // and save to file categories in square brackets.
1953 // To do this we need to escape these characters if they appear in our tag. 
1954
1955 function file_tag_encode($s) {
1956         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1957 }
1958
1959 function file_tag_decode($s) {
1960         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1961 }
1962
1963 function file_tag_file_query($table,$s,$type = 'file') {
1964
1965         if($type == 'file')
1966                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1967         else
1968                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1969         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1970 }
1971
1972 // ex. given music,video return <music><video> or [music][video]
1973 function file_tag_list_to_file($list,$type = 'file') {
1974         $tag_list = '';
1975         if(strlen($list)) {
1976                 $list_array = explode(",",$list);
1977                 if($type == 'file') {
1978                         $lbracket = '[';
1979                         $rbracket = ']';
1980                 }
1981                 else {
1982                         $lbracket = '<';
1983                         $rbracket = '>';
1984                 }
1985
1986                 foreach($list_array as $item) {
1987                   if(strlen($item)) {
1988                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1989                         }
1990                 }
1991         }
1992         return $tag_list;
1993 }
1994
1995 // ex. given <music><video>[friends], return music,video or friends
1996 function file_tag_file_to_list($file,$type = 'file') {
1997         $matches = false;
1998         $list = '';
1999         if($type == 'file') {
2000                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2001         }
2002         else {
2003                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2004         }
2005         if($cnt) {
2006                 foreach($matches as $mtch) {
2007                         if(strlen($list))
2008                                 $list .= ',';
2009                         $list .= file_tag_decode($mtch[1]);
2010                 }
2011         }
2012
2013         return $list;
2014 }
2015
2016 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2017         // $file_old - categories previously associated with an item
2018         // $file_new - new list of categories for an item
2019
2020         if(! intval($uid))
2021                 return false;
2022
2023         if($file_old == $file_new)
2024                 return true;
2025
2026         $saved = get_pconfig($uid,'system','filetags');
2027         if(strlen($saved)) {
2028                 if($type == 'file') {
2029                         $lbracket = '[';
2030                         $rbracket = ']';
2031                         $termtype = TERM_FILE;
2032                 }
2033                 else {
2034                         $lbracket = '<';
2035                         $rbracket = '>';
2036                         $termtype = TERM_CATEGORY;
2037                 }
2038
2039                 $filetags_updated = $saved;
2040
2041                 // check for new tags to be added as filetags in pconfig
2042                 $new_tags = array();
2043                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2044
2045                 foreach($check_new_tags as $tag) {
2046                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2047                                 $new_tags[] = $tag;
2048                 }
2049
2050                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2051
2052                 // check for deleted tags to be removed from filetags in pconfig
2053                 $deleted_tags = array();
2054                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2055
2056                 foreach($check_deleted_tags as $tag) {
2057                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2058                                 $deleted_tags[] = $tag;
2059                 }
2060
2061                 foreach($deleted_tags as $key => $tag) {
2062                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2063                                 dbesc($tag),
2064                                 intval(TERM_OBJ_POST),
2065                                 intval($termtype),
2066                                 intval($uid));
2067
2068                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2069                         //      intval($uid)
2070                         //);
2071
2072                         if(count($r)) {
2073                                 unset($deleted_tags[$key]);
2074                         }
2075                         else {
2076                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2077                         }
2078                 }
2079
2080                 if($saved != $filetags_updated) {
2081                         set_pconfig($uid,'system','filetags', $filetags_updated);
2082                 }
2083                 return true;
2084         }
2085         else
2086                 if(strlen($file_new)) {
2087                         set_pconfig($uid,'system','filetags', $file_new);
2088                 }
2089                 return true;
2090 }
2091
2092 function file_tag_save_file($uid,$item,$file) {
2093         require_once("include/files.php");
2094
2095         $result = false;
2096         if(! intval($uid))
2097                 return false;
2098         $r = q("select file from item where id = %d and uid = %d limit 1",
2099                 intval($item),
2100                 intval($uid)
2101         );
2102         if(count($r)) {
2103                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2104                         q("update item set file = '%s' where id = %d and uid = %d",
2105                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2106                                 intval($item),
2107                                 intval($uid)
2108                         );
2109
2110                 create_files_from_item($item);
2111
2112                 $saved = get_pconfig($uid,'system','filetags');
2113                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2114                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2115                 info( t('Item filed') );
2116         }
2117         return true;
2118 }
2119
2120 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2121         require_once("include/files.php");
2122
2123         $result = false;
2124         if(! intval($uid))
2125                 return false;
2126
2127         if($cat == true) {
2128                 $pattern = '<' . file_tag_encode($file) . '>' ;
2129                 $termtype = TERM_CATEGORY;
2130         } else {
2131                 $pattern = '[' . file_tag_encode($file) . ']' ;
2132                 $termtype = TERM_FILE;
2133         }
2134
2135
2136         $r = q("select file from item where id = %d and uid = %d limit 1",
2137                 intval($item),
2138                 intval($uid)
2139         );
2140         if(! count($r))
2141                 return false;
2142
2143         q("update item set file = '%s' where id = %d and uid = %d",
2144                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2145                 intval($item),
2146                 intval($uid)
2147         );
2148
2149         create_files_from_item($item);
2150
2151         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2152                 dbesc($file),
2153                 intval(TERM_OBJ_POST),
2154                 intval($termtype),
2155                 intval($uid));
2156
2157         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2158         //);
2159
2160         if(! count($r)) {
2161                 $saved = get_pconfig($uid,'system','filetags');
2162                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2163
2164         }
2165         return true;
2166 }
2167
2168 function normalise_openid($s) {
2169         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2170 }
2171
2172
2173 function undo_post_tagging($s) {
2174         $matches = null;
2175         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2176         if($cnt) {
2177                 foreach($matches as $mtch) {
2178                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2179                 }
2180         }
2181         return $s;
2182 }
2183
2184 function fix_mce_lf($s) {
2185         $s = str_replace("\r\n","\n",$s);
2186 //      $s = str_replace("\n\n","\n",$s);
2187         return $s;
2188 }
2189
2190
2191 function protect_sprintf($s) {
2192         return(str_replace('%','%%',$s));
2193 }
2194
2195
2196 function is_a_date_arg($s) {
2197         $i = intval($s);
2198         if($i > 1900) {
2199                 $y = date('Y');
2200                 if($i <= $y+1 && strpos($s,'-') == 4) {
2201                         $m = intval(substr($s,5));
2202                         if($m > 0 && $m <= 12)
2203                                 return true;
2204                 }
2205         }
2206         return false;
2207 }