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