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