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