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