]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge remote-tracking branch 'upstream/master'
[friendica.git] / include / text.php
1 <?php
2
3 // This is our template processor.
4 // $s is the string requiring macro substitution.
5 // $r is an array of key value pairs (search => replace)
6 // returns substituted string.
7 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
8 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
9 // depending on the order in which they were declared in the array.
10
11 require_once("include/template_processor.php");
12 require_once("include/friendica_smarty.php");
13
14 if(! function_exists('replace_macros')) {
15 /**
16  * This is our template processor
17  * 
18  * @param string|FriendicaSmarty $s the string requiring macro substitution, 
19  *                                                                      or an instance of FriendicaSmarty
20  * @param array $r key value pairs (search => replace)
21  * @return string substituted string
22  */
23 function replace_macros($s,$r) {
24         
25         $stamp1 = microtime(true);
26
27         $a = get_app();
28
29         $t = $a->template_engine();
30         $output = $t->replace_macros($s,$r);
31
32         $a->save_timestamp($stamp1, "rendering");
33
34         return $output;
35 }}
36
37
38 // random string, there are 86 characters max in text mode, 128 for hex
39 // output is urlsafe
40
41 define('RANDOM_STRING_HEX',  0x00 );
42 define('RANDOM_STRING_TEXT', 0x01 );
43
44 if(! function_exists('random_string')) {
45 function random_string($size = 64,$type = RANDOM_STRING_HEX) {
46         // generate a bit of entropy and run it through the whirlpool
47         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
48         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
49         return(substr($s,0,$size));
50 }}
51
52 if(! function_exists('notags')) {
53 /**
54  * This is our primary input filter. 
55  *
56  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
57  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
58  * after cleansing, and angle chars with the high bit set could get through as markup.
59  * 
60  * This is now disabled because it was interfering with some legitimate unicode sequences 
61  * and hopefully there aren't a lot of those browsers left. 
62  *
63  * Use this on any text input where angle chars are not valid or permitted
64  * They will be replaced with safer brackets. This may be filtered further
65  * if these are not allowed either.   
66  *
67  * @param string $string Input string
68  * @return string Filtered string
69  */
70 function notags($string) {
71
72         return(str_replace(array("<",">"), array('[',']'), $string));
73
74 //  High-bit filter no longer used
75 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
76 }}
77
78
79
80 if(! function_exists('escape_tags')) {
81 /**
82  * use this on "body" or "content" input where angle chars shouldn't be removed,
83  * and allow them to be safely displayed.
84  * @param string $string
85  * @return string
86  */
87 function escape_tags($string) {
88
89         return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
90 }}
91
92
93 // generate a string that's random, but usually pronounceable. 
94 // used to generate initial passwords
95
96 if(! function_exists('autoname')) {
97 /**
98  * generate a string that's random, but usually pronounceable. 
99  * used to generate initial passwords
100  * @param int $len
101  * @return string
102  */
103 function autoname($len) {
104
105         if($len <= 0)
106                 return '';
107
108         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
109         if(mt_rand(0,5) == 4)
110                 $vowels[] = 'y';
111
112         $cons = array(
113                         'b','bl','br',
114                         'c','ch','cl','cr',
115                         'd','dr',
116                         'f','fl','fr',
117                         'g','gh','gl','gr',
118                         'h',
119                         'j',
120                         'k','kh','kl','kr',
121                         'l',
122                         'm',
123                         'n',
124                         'p','ph','pl','pr',
125                         'qu',
126                         'r','rh',
127                         's','sc','sh','sm','sp','st',
128                         't','th','tr',
129                         'v',
130                         'w','wh',
131                         'x',
132                         'z','zh'
133                         );
134
135         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
136                                 'nd','ng','nk','nt','rn','rp','rt');
137
138         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
139                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
140
141         $start = mt_rand(0,2);
142         if($start == 0)
143                 $table = $vowels;
144         else
145                 $table = $cons;
146
147         $word = '';
148
149         for ($x = 0; $x < $len; $x ++) {
150                 $r = mt_rand(0,count($table) - 1);
151                 $word .= $table[$r];
152   
153                 if($table == $vowels)
154                         $table = array_merge($cons,$midcons);
155                 else
156                         $table = $vowels;
157
158         }
159
160         $word = substr($word,0,$len);
161
162         foreach($noend as $noe) {
163                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
164                         $word = substr($word,0,-1);
165                         break;
166                 }
167         }
168         if(substr($word,-1) == 'q')
169                 $word = substr($word,0,-1);    
170         return $word;
171 }}
172
173
174 // escape text ($str) for XML transport
175 // returns escaped text.
176
177 if(! function_exists('xmlify')) {
178 /**
179  * escape text ($str) for XML transport
180  * @param string $str
181  * @return string Escaped text.
182  */
183 function xmlify($str) {
184 /*      $buffer = '';
185         
186         $len = mb_strlen($str);
187         for($x = 0; $x < $len; $x ++) {
188                 $char = mb_substr($str,$x,1);
189         
190                 switch( $char ) {
191
192                         case "\r" :
193                                 break;
194                         case "&" :
195                                 $buffer .= '&amp;';
196                                 break;
197                         case "'" :
198                                 $buffer .= '&apos;';
199                                 break;
200                         case "\"" :
201                                 $buffer .= '&quot;';
202                                 break;
203                         case '<' :
204                                 $buffer .= '&lt;';
205                                 break;
206                         case '>' :
207                                 $buffer .= '&gt;';
208                                 break;
209                         case "\n" :
210                                 $buffer .= "\n";
211                                 break;
212                         default :
213                                 $buffer .= $char;
214                                 break;
215                 }       
216         }*/
217
218         $buffer = mb_ereg_replace("&", "&amp;", $str);
219         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
220         $buffer = mb_ereg_replace("\"", "&quot;", $buffer);
221         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
222         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
223
224         $buffer = trim($buffer);
225         return($buffer);
226 }}
227
228 if(! function_exists('unxmlify')) {
229 /**
230  * undo an xmlify
231  * @param string $s xml escaped text
232  * @return string unescaped text
233  */
234 function unxmlify($s) {
235 //      $ret = str_replace('&amp;','&', $s);
236 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
237         $ret = mb_ereg_replace('&amp;', '&', $s);
238         $ret = mb_ereg_replace('&apos;', "'", $ret);
239         $ret = mb_ereg_replace('&quot;', '"', $ret);
240         $ret = mb_ereg_replace('&lt;', "<", $ret);
241         $ret = mb_ereg_replace('&gt;', ">", $ret);
242         return $ret;    
243 }}
244
245 if(! function_exists('hex2bin')) {
246 /**
247  * convenience wrapper, reverse the operation "bin2hex"
248  * @param string $s
249  * @return number
250  */
251 function hex2bin($s) {
252         if(! (is_string($s) && strlen($s)))
253                 return '';
254
255         if(! ctype_xdigit($s)) {
256                 return($s);
257         }
258
259         return(pack("H*",$s));
260 }}
261
262
263 if(! function_exists('paginate')) {
264 /**
265  * Automatic pagination.
266  *
267  *  To use, get the count of total items.
268  * Then call $a->set_pager_total($number_items);
269  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
270  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
271  * (assuming there are enough items to paginate).
272  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
273  * will limit the results to the correct items for the current page. 
274  * The actual page handling is then accomplished at the application layer. 
275  * 
276  * @param App $a App instance
277  * @return string html for pagination #FIXME remove html
278  */
279 function paginate(&$a) {
280         $o = '';
281         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
282
283 //      $stripped = preg_replace('/&zrl=(.*?)([\?&]|$)/ism','',$stripped);
284
285         $stripped = str_replace('q=','',$stripped);
286         $stripped = trim($stripped,'/');
287         $pagenum = $a->pager['page'];
288         $url = $a->get_baseurl() . '/' . $stripped;
289
290
291           if($a->pager['total'] > $a->pager['itemspage']) {
292                 $o .= '<div class="pager">';
293                 if($a->pager['page'] != 1)
294                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
295
296                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
297
298                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
299
300                         $numstart = 1;
301                 $numstop = $numpages;
302
303                 if($numpages > 14) {
304                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
305                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
306                 }
307    
308                 for($i = $numstart; $i <= $numstop; $i++){
309                         if($i == $a->pager['page'])
310                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
311                         else
312                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
313                         $o .= '</span> ';
314                 }
315
316                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
317                         if($i == $a->pager['page'])
318                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
319                         else
320                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
321                         $o .= '</span> ';
322                 }
323
324                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
325                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
326
327                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
328                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
329                 $o .= '</div>'."\r\n";
330         }
331         return $o;
332 }}
333
334 if(! function_exists('alt_pager')) {
335 /**
336  * Alternative pager
337  * @param App $a App instance
338  * @param int $i
339  * @return string html for pagination #FIXME remove html
340  */
341 function alt_pager(&$a, $i) {
342         $o = '';
343         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
344         $stripped = str_replace('q=','',$stripped);
345         $stripped = trim($stripped,'/');
346         $pagenum = $a->pager['page'];
347         $url = $a->get_baseurl() . '/' . $stripped;
348
349         $o .= '<div class="pager">';
350
351         if($a->pager['page']>1)
352           $o .= "<a href=\"$url"."&page=".($a->pager['page'] - 1).'" class="pager_newer">' . t('newer') . '</a>';
353         if($i>0) {
354           if($a->pager['page']>1)
355                   $o .= "&nbsp;-&nbsp;";
356           $o .= "<a href=\"$url"."&page=".($a->pager['page'] + 1).'" class="pager_older">' . t('older') . '</a>';
357         }
358
359
360         $o .= '</div>'."\r\n";
361
362         return $o;
363 }}
364
365
366 if(! function_exists('expand_acl')) {
367 /**
368  * Turn user/group ACLs stored as angle bracketed text into arrays
369  * 
370  * @param string $s
371  * @return array
372  */
373 function expand_acl($s) {
374         // turn string array of angle-bracketed elements into numeric array
375         // e.g. "<1><2><3>" => array(1,2,3);
376         $ret = array();
377
378         if(strlen($s)) {
379                 $t = str_replace('<','',$s);
380                 $a = explode('>',$t);
381                 foreach($a as $aa) {
382                         if(intval($aa))
383                                 $ret[] = intval($aa);
384                 }
385         }
386         return $ret;
387 }}              
388
389 if(! function_exists('sanitise_acl')) {
390 /**
391  * Wrap ACL elements in angle brackets for storage 
392  * @param string $item
393  */
394 function sanitise_acl(&$item) {
395         if(intval($item))
396                 $item = '<' . intval(notags(trim($item))) . '>';
397         else
398                 unset($item);
399 }}
400
401
402 if(! function_exists('perms2str')) {
403 /**
404  * Convert an ACL array to a storable string
405  * 
406  * Normally ACL permissions will be an array.
407  * We'll also allow a comma-separated string.
408  * 
409  * @param string|array $p
410  * @return string
411  */
412 function perms2str($p) {
413         $ret = '';
414         if(is_array($p))
415                 $tmp = $p;
416         else
417                 $tmp = explode(',',$p);
418
419         if(is_array($tmp)) {
420                 array_walk($tmp,'sanitise_acl');
421                 $ret = implode('',$tmp);
422         }
423         return $ret;
424 }}
425
426
427 if(! function_exists('item_new_uri')) {
428 /**
429  * generate a guaranteed unique (for this domain) item ID for ATOM
430  * safe from birthday paradox
431  * 
432  * @param string $hostname
433  * @param int $uid
434  * @return string
435  */
436 function item_new_uri($hostname,$uid) {
437
438         do {
439                 $dups = false;
440                 $hash = random_string();
441
442                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
443
444                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
445                         dbesc($uri));
446                 if(count($r))
447                         $dups = true;
448         } while($dups == true);
449         return $uri;
450 }}
451
452 // Generate a guaranteed unique photo ID.
453 // safe from birthday paradox
454
455 if(! function_exists('photo_new_resource')) {
456 /**
457  * Generate a guaranteed unique photo ID.
458  * safe from birthday paradox
459  * 
460  * @return string
461  */     
462 function photo_new_resource() {
463
464         do {
465                 $found = false;
466                 $resource = hash('md5',uniqid(mt_rand(),true));
467                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
468                         dbesc($resource)
469                 );
470                 if(count($r))
471                         $found = true;
472         } while($found == true);
473         return $resource;
474 }}
475
476
477 if(! function_exists('load_view_file')) {
478 /**
479  * @deprecated
480  * wrapper to load a view template, checking for alternate
481  * languages before falling back to the default
482  * 
483  * @global string $lang
484  * @global App $a
485  * @param string $s view name
486  * @return string
487  */     
488 function load_view_file($s) {
489         global $lang, $a;
490         if(! isset($lang))
491                 $lang = 'en';
492         $b = basename($s);
493         $d = dirname($s);
494         if(file_exists("$d/$lang/$b")) {
495                 $stamp1 = microtime(true);
496                 $content = file_get_contents("$d/$lang/$b");
497                 $a->save_timestamp($stamp1, "file");
498                 return $content;
499         }
500
501         $theme = current_theme();
502
503         if(file_exists("$d/theme/$theme/$b")) {
504                 $stamp1 = microtime(true);
505                 $content = file_get_contents("$d/theme/$theme/$b");
506                 $a->save_timestamp($stamp1, "file");
507                 return $content;
508         }
509
510         $stamp1 = microtime(true);
511         $content = file_get_contents($s);
512         $a->save_timestamp($stamp1, "file");
513         return $content;
514 }}
515
516 if(! function_exists('get_intltext_template')) {
517 /**
518  * load a view template, checking for alternate
519  * languages before falling back to the default
520  * 
521  * @global string $lang
522  * @param string $s view path
523  * @return string
524  */
525 function get_intltext_template($s) {
526         global $lang;
527
528         $a = get_app();
529         $engine = '';
530         if($a->theme['template_engine'] === 'smarty3')
531                 $engine = "/smarty3";
532
533         if(! isset($lang))
534                 $lang = 'en';
535
536         if(file_exists("view/$lang$engine/$s")) {
537                 $stamp1 = microtime(true);
538                 $content = file_get_contents("view/$lang$engine/$s");
539                 $a->save_timestamp($stamp1, "file");
540                 return $content;
541         } elseif(file_exists("view/en$engine/$s")) {
542                 $stamp1 = microtime(true);
543                 $content = file_get_contents("view/en$engine/$s");
544                 $a->save_timestamp($stamp1, "file");
545                 return $content;
546         } else {
547                 $stamp1 = microtime(true);
548                 $content = file_get_contents("view$engine/$s");
549                 $a->save_timestamp($stamp1, "file");
550                 return $content;
551         }
552 }}
553
554 if(! function_exists('get_markup_template')) {
555 /**
556  * load template $s
557  * 
558  * @param string $s
559  * @param string $root
560  * @return string
561  */
562 function get_markup_template($s, $root = '') {
563         $stamp1 = microtime(true);
564
565         $a = get_app();
566         $t = $a->template_engine();
567         
568         $template = $t->get_template_file($s, $root);
569         
570         $a->save_timestamp($stamp1, "file");
571         
572         return $template;
573 }}
574
575 if(! function_exists("get_template_file")) {
576 /**
577  * 
578  * @param App $a
579  * @param string $filename
580  * @param string $root
581  * @return string
582  */
583 function get_template_file($a, $filename, $root = '') {
584         $theme = current_theme();
585
586         // Make sure $root ends with a slash /
587         if($root !== '' && $root[strlen($root)-1] !== '/')
588                 $root = $root . '/';
589
590         if(file_exists("{$root}view/theme/$theme/$filename"))
591                 $template_file = "{$root}view/theme/$theme/$filename";
592         elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
593                 $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
594         elseif (file_exists("{$root}/$filename"))
595                 $template_file = "{$root}/$filename";
596         else
597                 $template_file = "{$root}view/$filename";
598
599         return $template_file;
600 }}
601
602
603
604
605
606
607
608 if(! function_exists('attribute_contains')) {
609 /**
610  *  for html,xml parsing - let's say you've got
611  *  an attribute foobar="class1 class2 class3"
612  *  and you want to find out if it contains 'class3'.
613  *  you can't use a normal sub string search because you
614  *  might match 'notclass3' and a regex to do the job is 
615  *  possible but a bit complicated. 
616  *  pass the attribute string as $attr and the attribute you 
617  *  are looking for as $s - returns true if found, otherwise false
618  * 
619  * @param string $attr attribute value
620  * @param string $s string to search
621  * @return boolean True if found, False otherwise
622  */
623 function attribute_contains($attr,$s) {
624         $a = explode(' ', $attr);
625         if(count($a) && in_array($s,$a))
626                 return true;
627         return false;
628 }}
629
630 if(! function_exists('logger')) {
631 /**
632  * log levels:
633  * LOGGER_NORMAL (default)
634  * LOGGER_TRACE
635  * LOGGER_DEBUG
636  * LOGGER_DATA
637  * LOGGER_ALL
638  * 
639  * @global App $a
640  * @global dba $db
641  * @param string $msg
642  * @param int $level
643  */
644 function logger($msg,$level = 0) {
645         // turn off logger in install mode
646         global $a;
647         global $db;
648
649         if(($a->module == 'install') || (! ($db && $db->connected))) return;
650
651         $debugging = get_config('system','debugging');
652         $loglevel  = intval(get_config('system','loglevel'));
653         $logfile   = get_config('system','logfile');
654
655         if((! $debugging) || (! $logfile) || ($level > $loglevel))
656                 return;
657
658         $stamp1 = microtime(true);
659         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
660         $a->save_timestamp($stamp1, "file");
661         return;
662 }}
663
664
665 if(! function_exists('activity_match')) {
666 /**
667  * Compare activity uri. Knows about activity namespace.
668  * 
669  * @param string $haystack
670  * @param string $needle
671  * @return boolean
672  */
673 function activity_match($haystack,$needle) {
674         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
675                 return true;
676         return false;
677 }}
678
679
680 if(! function_exists('get_tags')) {
681 /**
682  * Pull out all #hashtags and @person tags from $s;
683  * We also get @person@domain.com - which would make 
684  * the regex quite complicated as tags can also
685  * end a sentence. So we'll run through our results
686  * and strip the period from any tags which end with one.
687  * Returns array of tags found, or empty array.
688  * 
689  * @param string $s
690  * @return array
691  */
692 function get_tags($s) {
693         $ret = array();
694
695         // ignore anything in a code block
696         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
697
698         // Force line feeds at bbtags
699         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
700
701         // ignore anything in a bbtag
702         $s = preg_replace('/\[(.*?)\]/sm','',$s);
703
704         // Match full names against @tags including the space between first and last
705         // We will look these up afterward to see if they are full names or not recognisable.
706
707         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
708                 foreach($match[1] as $mtch) {
709                         if(strstr($mtch,"]")) {
710                                 // we might be inside a bbcode color tag - leave it alone
711                                 continue;
712                         }
713                         if(substr($mtch,-1,1) === '.')
714                                 $ret[] = substr($mtch,0,-1);
715                         else
716                                 $ret[] = $mtch;
717                 }
718         }
719
720         // Otherwise pull out single word tags. These can be @nickname, @first_last
721         // and #hash tags.
722
723         if(preg_match_all('/([@#][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
724                 foreach($match[1] as $mtch) {
725                         if(strstr($mtch,"]")) {
726                                 // we might be inside a bbcode color tag - leave it alone
727                                 continue;
728                         }
729                         if(substr($mtch,-1,1) === '.')
730                                 $mtch = substr($mtch,0,-1);
731                         // ignore strictly numeric tags like #1
732                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
733                                 continue;
734                         // try not to catch url fragments
735                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
736                                 continue;
737                         $ret[] = $mtch;
738                 }
739         }
740         return $ret;
741 }}
742
743
744 // 
745
746 if(! function_exists('qp')) {
747 /**
748  * quick and dirty quoted_printable encoding
749  * 
750  * @param string $s
751  * @return string
752  */     
753 function qp($s) {
754 return str_replace ("%","=",rawurlencode($s));
755 }} 
756
757
758
759 if(! function_exists('get_mentions')) {
760 /**
761  * @param array $item
762  * @return string html for mentions #FIXME: remove html
763  */
764 function get_mentions($item) {
765         $o = '';
766         if(! strlen($item['tag']))
767                 return $o;
768
769         $arr = explode(',',$item['tag']);
770         foreach($arr as $x) {
771                 $matches = null;
772                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
773                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
774                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
775                 }
776         }
777         return $o;
778 }}
779
780 if(! function_exists('contact_block')) {
781 /**
782  * Get html for contact block.
783  * 
784  * @template contact_block.tpl
785  * @hook contact_block_end (contacts=>array, output=>string)
786  * @return string
787  */
788 function contact_block() {
789         $o = '';
790         $a = get_app();
791
792         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
793         if($shown === false)
794                 $shown = 24;
795         if($shown == 0)
796                 return;
797
798         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
799                 return $o;
800         $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",
801                         intval($a->profile['uid'])
802         );
803         if(count($r)) {
804                 $total = intval($r[0]['total']);
805         }
806         if(! $total) {
807                 $contacts = t('No contacts');
808                 $micropro = Null;
809                 
810         } else {
811                 $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",
812                                 intval($a->profile['uid']),
813                                 intval($shown)
814                 );
815                 if(count($r)) {
816                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
817                         $micropro = Array();
818                         foreach($r as $rr) {
819                                 $micropro[] = micropro($rr,true,'mpfriend');
820                         }
821                 }
822         }
823         
824         $tpl = get_markup_template('contact_block.tpl');
825         $o = replace_macros($tpl, array(
826                 '$contacts' => $contacts,
827                 '$nickname' => $a->profile['nickname'],
828                 '$viewcontacts' => t('View Contacts'),
829                 '$micropro' => $micropro,
830         ));
831
832         $arr = array('contacts' => $r, 'output' => $o);
833
834         call_hooks('contact_block_end', $arr);
835         return $o;
836
837 }}
838
839 if(! function_exists('micropro')) {
840 /**
841  * 
842  * @param array $contact
843  * @param boolean $redirect
844  * @param string $class
845  * @param boolean $textmode
846  * @return string #FIXME: remove html
847  */
848 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
849
850         if($class)
851                 $class = ' ' . $class;
852
853         $url = $contact['url'];
854         $sparkle = '';
855         $redir = false;
856
857         if($redirect) {
858                 $a = get_app();
859                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
860                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
861                         $redir = true;
862                         $url = $redirect_url;
863                         $sparkle = ' sparkle';
864                 }
865                 else
866                         $url = zrl($url);
867         }
868         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
869         if($click)
870                 $url = '';
871         if($textmode) {
872                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle 
873                         . (($click) ? ' fakelink' : '') . '" '
874                         . (($redir) ? ' target="redir" ' : '')
875                         . (($url) ? ' href="' . $url . '"' : '') . $click
876                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
877                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
878         }
879         else {
880                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle 
881                         . (($click) ? ' fakelink' : '') . '" '
882                         . (($redir) ? ' target="redir" ' : '')
883                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="' 
884                         . $contact['micro'] . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
885                         . '" /></a></div>' . "\r\n";
886         }
887 }}
888
889
890
891 if(! function_exists('search')) {
892 /**
893  * search box
894  * 
895  * @param string $s search query
896  * @param string $id html id
897  * @param string $url search url
898  * @param boolean $save show save search button
899  * @return string html for search box #FIXME: remove html
900  */
901 function search($s,$id='search-box',$url='/search',$save = false) {
902         $a = get_app();
903         $o  = '<div id="' . $id . '">';
904         $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >';
905         $o .= '<input type="text" name="search" id="search-text" placeholder="' . t('Search') . '" value="' . $s .'" />';
906         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
907         if($save)
908                 $o .= '<input type="submit" name="save" id="search-save" value="' . t('Save') . '" />'; 
909         $o .= '</form></div>';
910         return $o;
911 }}
912
913 if(! function_exists('valid_email')) {
914 /**
915  * Check if $x is a valid email string
916  * 
917  * @param string $x
918  * @return boolean
919  */
920 function valid_email($x){
921
922         if(get_config('system','disable_email_validation'))
923                 return true;
924
925         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
926                 return true;
927         return false;
928 }}
929
930
931 if(! function_exists('linkify')) {
932 /**
933  * Replace naked text hyperlink with HTML formatted hyperlink
934  *
935  * @param string $s
936  */
937 function linkify($s) {
938         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
939         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
940         return($s);
941 }}
942
943
944 /**
945  * Load poke verbs
946  *
947  * @return array index is present tense verb
948                                  value is array containing past tense verb, translation of present, translation of past
949  * @hook poke_verbs pokes array
950  */
951 function get_poke_verbs() {
952         
953         // index is present tense verb
954         // value is array containing past tense verb, translation of present, translation of past
955
956         $arr = array(
957                 'poke' => array( 'poked', t('poke'), t('poked')),
958                 'ping' => array( 'pinged', t('ping'), t('pinged')),
959                 'prod' => array( 'prodded', t('prod'), t('prodded')),
960                 'slap' => array( 'slapped', t('slap'), t('slapped')),
961                 'finger' => array( 'fingered', t('finger'), t('fingered')),
962                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
963         );
964         call_hooks('poke_verbs', $arr);
965         return $arr;
966 }
967
968 /**
969  * Load moods
970  * @return array index is mood, value is translated mood
971  * @hook mood_verbs moods array
972  */
973 function get_mood_verbs() {
974         
975         $arr = array(
976                 'happy'      => t('happy'),
977                 'sad'        => t('sad'),
978                 'mellow'     => t('mellow'),
979                 'tired'      => t('tired'),
980                 'perky'      => t('perky'),
981                 'angry'      => t('angry'),
982                 'stupefied'  => t('stupified'),
983                 'puzzled'    => t('puzzled'),
984                 'interested' => t('interested'),
985                 'bitter'     => t('bitter'),
986                 'cheerful'   => t('cheerful'),
987                 'alive'      => t('alive'),
988                 'annoyed'    => t('annoyed'),
989                 'anxious'    => t('anxious'),
990                 'cranky'     => t('cranky'),
991                 'disturbed'  => t('disturbed'),
992                 'frustrated' => t('frustrated'),
993                 'motivated'  => t('motivated'),
994                 'relaxed'    => t('relaxed'),
995                 'surprised'  => t('surprised'),
996         );
997
998         call_hooks('mood_verbs', $arr);
999         return $arr;
1000 }
1001
1002
1003
1004 if(! function_exists('smilies')) {
1005 /**
1006  * Replaces text emoticons with graphical images
1007  *
1008  * It is expected that this function will be called using HTML text.
1009  * We will escape text between HTML pre and code blocks from being 
1010  * processed. 
1011  * 
1012  * At a higher level, the bbcode [nosmile] tag can be used to prevent this 
1013  * function from being executed by the prepare_text() routine when preparing
1014  * bbcode source for HTML display
1015  *
1016  * @param string $s
1017  * @param boolean $sample
1018  * @return string
1019  * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
1020  */
1021 function smilies($s, $sample = false) {
1022         $a = get_app();
1023
1024         if(intval(get_config('system','no_smilies')) 
1025                 || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
1026                 return $s;
1027
1028         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
1029         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
1030
1031         $texts =  array( 
1032                 '&lt;3', 
1033                 '&lt;/3', 
1034                 '&lt;\\3', 
1035                 ':-)', 
1036                 ';-)', 
1037                 ':-(', 
1038                 ':-P', 
1039                 ':-p', 
1040                 ':-"', 
1041                 ':-&quot;', 
1042                 ':-x', 
1043                 ':-X', 
1044                 ':-D', 
1045                 '8-|', 
1046                 '8-O', 
1047                 ':-O', 
1048                 '\\o/', 
1049                 'o.O', 
1050                 'O.o', 
1051                 'o_O', 
1052                 'O_o', 
1053                 ":'(", 
1054                 ":-!", 
1055                 ":-/", 
1056                 ":-[", 
1057                 "8-)",
1058                 ':beer', 
1059                 ':homebrew', 
1060                 ':coffee', 
1061                 ':facepalm',
1062                 ':like',
1063                 ':dislike',
1064                 '~friendica',
1065                 'red#'
1066
1067         );
1068
1069         $icons = array(
1070                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
1071                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
1072                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
1073                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1074                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1075                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1076                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1077                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-p" />',
1078                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1079                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1080                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1081                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1082                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1083                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1084                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />',
1085                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt=":-O" />',                
1086                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
1087                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o.O" />',
1088                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O.o" />',
1089                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o_O" />',
1090                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O_o" />',
1091                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cry.gif" alt=":\'(" />',
1092                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
1093                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-undecided.gif" alt=":-/" />',
1094                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-embarassed.gif" alt=":-[" />',
1095                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cool.gif" alt="8-)" />',
1096                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":beer" />',
1097                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":homebrew" />',
1098                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/coffee.gif" alt=":coffee" />',
1099                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
1100                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />',
1101                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />',
1102                 '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>',
1103                 '<a href="http://friendica.com">red <img class="smiley" src="' . $a->get_baseurl() . '/images/rhash-16.png" alt="red" /></a>'
1104         );
1105
1106         $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
1107         call_hooks('smilie', $params);
1108
1109         if($sample) {
1110                 $s = '<div class="smiley-sample">';
1111                 for($x = 0; $x < count($params['texts']); $x ++) {
1112                         $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
1113                 }
1114         }
1115         else {
1116                 $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
1117                 $s = str_replace($params['texts'],$params['icons'],$params['string']);
1118         }
1119
1120         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
1121         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
1122
1123         return $s;
1124
1125 }}
1126
1127 function smile_encode($m) {
1128         return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
1129 }
1130
1131 function smile_decode($m) {
1132         return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
1133 }
1134
1135
1136 /**
1137  * expand <3333 to the correct number of hearts
1138  * 
1139  * @param string $x
1140  * @return string
1141  */
1142 function preg_heart($x) {
1143         $a = get_app();
1144         if(strlen($x[1]) == 1)
1145                 return $x[0];
1146         $t = '';
1147         for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
1148                 $t .= '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />';
1149         $r =  str_replace($x[0],$t,$x[0]);
1150         return $r;
1151 }
1152
1153
1154 if(! function_exists('day_translate')) {
1155 /**
1156  * Translate days and months names
1157  * 
1158  * @param string $s
1159  * @return string
1160  */
1161 function day_translate($s) {
1162         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1163                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1164                 $s);
1165
1166         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1167                 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')),
1168                 $ret);
1169
1170         return $ret;
1171 }}
1172
1173
1174 if(! function_exists('normalise_link')) {
1175 /**
1176  * Normalize url
1177  * 
1178  * @param string $url
1179  * @return string
1180  */
1181 function normalise_link($url) {
1182         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1183         return(rtrim($ret,'/'));
1184 }}
1185
1186
1187
1188 if(! function_exists('link_compare')) {
1189 /**
1190  * Compare two URLs to see if they are the same, but ignore
1191  * slight but hopefully insignificant differences such as if one 
1192  * is https and the other isn't, or if one is www.something and 
1193  * the other isn't - and also ignore case differences.
1194  *
1195  * @param string $a first url
1196  * @param string $b second url
1197  * @return boolean True if the URLs match, otherwise False
1198  *
1199  */     
1200 function link_compare($a,$b) {
1201         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1202                 return true;
1203         return false;
1204 }}
1205
1206
1207 if(! function_exists('redir_private_images')) {
1208 /**
1209  * Find any non-embedded images in private items and add redir links to them
1210  * 
1211  * @param App $a
1212  * @param array $item
1213  */
1214 function redir_private_images($a, &$item) {
1215
1216         $matches = false;
1217         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1218         if($cnt) {
1219                 //logger("redir_private_images: matches = " . print_r($matches, true));
1220                 foreach($matches as $mtch) {
1221                         if(strpos($mtch[1], '/redir') !== false)
1222                                 continue;
1223
1224                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1225                                 //logger("redir_private_images: redir");
1226                                 $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1227                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1228                         }
1229                 }
1230         }
1231
1232 }}
1233
1234
1235 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1236 // If attach is true, also add icons for item attachments
1237
1238 if(! function_exists('prepare_body')) {
1239 /**
1240  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1241  * If attach is true, also add icons for item attachments
1242  * 
1243  * @param array $item
1244  * @param boolean $attach
1245  * @return string item body html
1246  * @hook prepare_body_init item array before any work
1247  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1248  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1249  */
1250 function prepare_body($item,$attach = false) {
1251
1252         $a = get_app();
1253         call_hooks('prepare_body_init', $item);
1254
1255         //$cachefile = get_cachefile($item["guid"]."-".strtotime($item["edited"])."-".hash("crc32", $item['body']));
1256         $cachefile = get_cachefile($item["guid"]."-".hash("md5", $item['body']));
1257
1258         if (($cachefile != '')) {
1259                 if (file_exists($cachefile)) {
1260                         $stamp1 = microtime(true);
1261                         $s = file_get_contents($cachefile);
1262                         $a->save_timestamp($stamp1, "file");
1263                 } else {
1264                         redir_private_images($a, $item);
1265                         $s = prepare_text($item['body']);
1266
1267                         $stamp1 = microtime(true);
1268                         file_put_contents($cachefile, $s);
1269                         $a->save_timestamp($stamp1, "file");
1270
1271                         logger('prepare_body: put item '.$item["id"].' into cachefile '.$cachefile);
1272                 }
1273         } else {
1274                 redir_private_images($a, $item);
1275                 $s = prepare_text($item['body']);
1276         }
1277
1278
1279         $prep_arr = array('item' => $item, 'html' => $s);
1280         call_hooks('prepare_body', $prep_arr);
1281         $s = $prep_arr['html'];
1282
1283         if(! $attach) {
1284                 // Replace the blockquotes with quotes that are used in mails
1285                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1286                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1287                 return $s;
1288         }
1289
1290         $as = '';
1291         $vhead = false;
1292         $arr = explode('[/attach],',$item['attach']);
1293         if(count($arr)) {
1294                 $as .= '<div class="body-attach">';
1295                 foreach($arr as $r) {
1296                         $matches = false;
1297                         $icon = '';
1298                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1299                         if($cnt) {
1300                                 foreach($matches as $mtch) {
1301                                         $mime = $mtch[3];
1302
1303                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1304                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1305                                         else
1306                                                 $the_url = $mtch[1];
1307
1308                                         if(strpos($mime, 'video') !== false) {
1309                                                 if(!$vhead) {
1310                                                         $vhead = true;
1311                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1312                                                                 '$baseurl' => $a->get_baseurl(),
1313                                                         ));
1314                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1315                                                                 '$baseurl' => $a->get_baseurl(),
1316                                                         ));
1317                                                 }
1318
1319                                                 $id = end(explode('/', $the_url));
1320                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1321                                                         '$video'        => array(
1322                                                                 'id'       => $id,
1323                                                                 'title'         => t('View Video'),
1324                                                                 'src'           => $the_url,
1325                                                                 'mime'          => $mime,
1326                                                         ),
1327                                                 ));
1328                                         }
1329
1330                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1331                                         if($filetype) {
1332                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1333                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1334                                         }
1335                                         else {
1336                                                 $filetype = 'unkn';
1337                                                 $filesubtype = 'unkn';
1338                                         }
1339
1340                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1341                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1342                                         switch($icontype) {
1343                                                 case 'video':
1344                                                 case 'audio':
1345                                                 case 'image':
1346                                                 case 'text':
1347                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1348                                                         break;
1349                                                 default:
1350                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1351                                                         break;
1352                                         }*/
1353
1354                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1355                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1356
1357                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="external-link" >' . $icon . '</a>';
1358                                 }
1359                         }
1360                 }
1361                 $as .= '<div class="clear"></div></div>';
1362         }
1363         $s = $s . $as;
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 }