]> git.mxchange.org Git - friendica.git/blob - include/text.php
ea36a2a01699d6b9794a611a6a2743721ddc49de
[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         $searchpath = $a->get_baseurl()."/search?tag=";
1278
1279         $tags=array();
1280         $hashtags = array();
1281         $mentions = array();
1282
1283         if (!get_config('system','suppress_tags')) {
1284                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1285                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1286
1287                 foreach($taglist as $tag) {
1288
1289                         if ($tag["url"] == "")
1290                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1291
1292                         if ($tag["type"] == TERM_HASHTAG) {
1293                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"external-link\">".$tag["term"]."</a>";
1294                                 $prefix = "#";
1295                         } elseif ($tag["type"] == TERM_MENTION) {
1296                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"external-link\">".$tag["term"]."</a>";
1297                                 $prefix = "@";
1298                         }
1299                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"external-link\">".$tag["term"]."</a>";
1300                 }
1301         }
1302
1303         $item['tags'] = $tags;
1304         $item['hashtags'] = $hashtags;
1305         $item['mentions'] = $mentions;
1306
1307
1308         //$cachefile = get_cachefile($item["guid"]."-".strtotime($item["edited"])."-".hash("crc32", $item['body']));
1309         $cachefile = get_cachefile($item["guid"]."-".hash("md5", $item['body']));
1310
1311         if (($cachefile != '')) {
1312                 if (file_exists($cachefile)) {
1313                         $stamp1 = microtime(true);
1314                         $s = file_get_contents($cachefile);
1315                         $a->save_timestamp($stamp1, "file");
1316                 } else {
1317                         redir_private_images($a, $item);
1318                         $s = prepare_text($item['body']);
1319
1320                         $stamp1 = microtime(true);
1321                         file_put_contents($cachefile, $s);
1322                         $a->save_timestamp($stamp1, "file");
1323
1324                         logger('prepare_body: put item '.$item["id"].' into cachefile '.$cachefile);
1325                 }
1326         } else {
1327                 redir_private_images($a, $item);
1328                 $s = prepare_text($item['body']);
1329         }
1330
1331
1332         $prep_arr = array('item' => $item, 'html' => $s);
1333         call_hooks('prepare_body', $prep_arr);
1334         $s = $prep_arr['html'];
1335
1336         if(! $attach) {
1337                 // Replace the blockquotes with quotes that are used in mails
1338                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1339                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1340                 return $s;
1341         }
1342
1343         $as = '';
1344         $vhead = false;
1345         $arr = explode('[/attach],',$item['attach']);
1346         if(count($arr)) {
1347                 $as .= '<div class="body-attach">';
1348                 foreach($arr as $r) {
1349                         $matches = false;
1350                         $icon = '';
1351                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1352                         if($cnt) {
1353                                 foreach($matches as $mtch) {
1354                                         $mime = $mtch[3];
1355
1356                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1357                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1358                                         else
1359                                                 $the_url = $mtch[1];
1360
1361                                         if(strpos($mime, 'video') !== false) {
1362                                                 if(!$vhead) {
1363                                                         $vhead = true;
1364                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1365                                                                 '$baseurl' => $a->get_baseurl(),
1366                                                         ));
1367                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1368                                                                 '$baseurl' => $a->get_baseurl(),
1369                                                         ));
1370                                                 }
1371
1372                                                 $id = end(explode('/', $the_url));
1373                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1374                                                         '$video'        => array(
1375                                                                 'id'       => $id,
1376                                                                 'title'         => t('View Video'),
1377                                                                 'src'           => $the_url,
1378                                                                 'mime'          => $mime,
1379                                                         ),
1380                                                 ));
1381                                         }
1382
1383                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1384                                         if($filetype) {
1385                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1386                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1387                                         }
1388                                         else {
1389                                                 $filetype = 'unkn';
1390                                                 $filesubtype = 'unkn';
1391                                         }
1392
1393                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1394                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1395                                         switch($icontype) {
1396                                                 case 'video':
1397                                                 case 'audio':
1398                                                 case 'image':
1399                                                 case 'text':
1400                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1401                                                         break;
1402                                                 default:
1403                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1404                                                         break;
1405                                         }*/
1406
1407                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1408                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1409
1410                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="external-link" >' . $icon . '</a>';
1411                                 }
1412                         }
1413                 }
1414                 $as .= '<div class="clear"></div></div>';
1415         }
1416         $s = $s . $as;
1417
1418
1419         // Look for spoiler
1420         $spoilersearch = '<blockquote class="spoiler">';
1421
1422         // Remove line breaks before the spoiler
1423         while ((strpos($s, "\n".$spoilersearch) !== false))
1424                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1425         while ((strpos($s, "<br />".$spoilersearch) !== false))
1426                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1427
1428         while ((strpos($s, $spoilersearch) !== false)) {
1429
1430                 $pos = strpos($s, $spoilersearch);
1431                 $rnd = random_string(8);
1432                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1433                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1434                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1435         }
1436
1437         // Look for quote with author
1438         $authorsearch = '<blockquote class="author">';
1439
1440         while ((strpos($s, $authorsearch) !== false)) {
1441
1442                 $pos = strpos($s, $authorsearch);
1443                 $rnd = random_string(8);
1444                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1445                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1446                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1447         }
1448
1449         $prep_arr = array('item' => $item, 'html' => $s);
1450         call_hooks('prepare_body_final', $prep_arr);
1451
1452         return $prep_arr['html'];
1453 }}
1454
1455
1456 if(! function_exists('prepare_text')) {
1457 /**
1458  * Given a text string, convert from bbcode to html and add smilie icons.
1459  * 
1460  * @param string $text
1461  * @return string
1462  */
1463 function prepare_text($text) {
1464
1465         require_once('include/bbcode.php');
1466
1467         if(stristr($text,'[nosmile]'))
1468                 $s = bbcode($text);
1469         else
1470                 $s = smilies(bbcode($text));
1471
1472         return $s;
1473 }}
1474
1475
1476
1477 /**
1478  * return array with details for categories and folders for an item
1479  * 
1480  * @param array $item
1481  * @return array
1482  * 
1483   * [
1484  *      [ // categories array
1485  *          {
1486  *               'name': 'category name',
1487  *               'removeurl': 'url to remove this category',
1488  *               'first': 'is the first in this array? true/false',
1489  *               'last': 'is the last in this array? true/false',
1490  *           } ,
1491  *           ....
1492  *       ],
1493  *       [ //folders array
1494  *                      {
1495  *               'name': 'folder name',
1496  *               'removeurl': 'url to remove this folder',
1497  *               'first': 'is the first in this array? true/false',
1498  *               'last': 'is the last in this array? true/false',
1499  *           } ,
1500  *           ....       
1501  *       ]
1502  *  ]
1503  */
1504 function get_cats_and_terms($item) {
1505
1506     $a = get_app();
1507     $categories = array();
1508     $folders = array();
1509
1510     $matches = false; $first = true;
1511     $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1512     if($cnt) {
1513         foreach($matches as $mtch) {
1514             $categories[] = array(
1515                 'name' => xmlify(file_tag_decode($mtch[1])),
1516                 'url' =>  "#",
1517                 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1518                 'first' => $first,
1519                 'last' => false
1520             );
1521             $first = false;
1522         }
1523     }
1524     if (count($categories)) $categories[count($categories)-1]['last'] = true;
1525     
1526
1527         if(local_user() == $item['uid']) {
1528             $matches = false; $first = true;
1529         $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1530             if($cnt) {
1531             foreach($matches as $mtch) {
1532                     $folders[] = array(
1533                     'name' => xmlify(file_tag_decode($mtch[1])),
1534                          'url' =>  "#",
1535                         'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1536                     'first' => $first,
1537                         'last' => false
1538                 );
1539                     $first = false;
1540                         }
1541         }
1542     }
1543
1544     if (count($folders)) $folders[count($folders)-1]['last'] = true;
1545
1546     return array($categories, $folders);
1547 }
1548
1549
1550
1551 if(! function_exists('feed_hublinks')) {
1552 /**
1553  * return atom link elements for all of our hubs
1554  * @return string hub link xml elements
1555  */
1556 function feed_hublinks() {
1557         $a = get_app();
1558         $hub = get_config('system','huburl');
1559
1560         $hubxml = '';
1561         if(strlen($hub)) {
1562                 $hubs = explode(',', $hub);
1563                 if(count($hubs)) {
1564                         foreach($hubs as $h) {
1565                                 $h = trim($h);
1566                                 if(! strlen($h))
1567                                         continue;
1568                                 if ($h === '[internal]')
1569                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1570                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
1571                         }
1572                 }
1573         }
1574         return $hubxml;
1575 }}
1576
1577
1578 if(! function_exists('feed_salmonlinks')) {
1579 /**
1580  * return atom link elements for salmon endpoints
1581  * @param string $nick user nickname
1582  * @return string salmon link xml elements
1583  */
1584 function feed_salmonlinks($nick) {
1585
1586         $a = get_app();
1587
1588         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1589
1590         // old style links that status.net still needed as of 12/2010 
1591
1592         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
1593         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
1594         return $salmon;
1595 }}
1596
1597 if(! function_exists('get_plink')) {
1598 /**
1599  * get private link for item
1600  * @param array $item
1601  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1602  */
1603 function get_plink($item) {
1604         $a = get_app(); 
1605         if (x($item,'plink') && ($item['private'] != 1)) {
1606                 return array(
1607                         'href' => $item['plink'],
1608                         'title' => t('link to source'),
1609                 );
1610         } 
1611         else {
1612                 return false;
1613         }
1614 }}
1615
1616 if(! function_exists('unamp')) {
1617 /**
1618  * replace html amp entity with amp char
1619  * @param string $s
1620  * @return string
1621  */
1622 function unamp($s) {
1623         return str_replace('&amp;', '&', $s);
1624 }}
1625
1626
1627
1628
1629 if(! function_exists('lang_selector')) {
1630 /**
1631  * get html for language selector
1632  * @global string $lang
1633  * @return string 
1634  * @template lang_selector.tpl
1635  */
1636 function lang_selector() {
1637         global $lang;
1638         
1639         $langs = glob('view/*/strings.php');
1640         
1641         $lang_options = array();
1642         $selected = "";
1643         
1644         if(is_array($langs) && count($langs)) {
1645                 $langs[] = '';
1646                 if(! in_array('view/en/strings.php',$langs))
1647                         $langs[] = 'view/en/';
1648                 asort($langs);
1649                 foreach($langs as $l) {
1650                         if($l == '') {
1651                                 $lang_options[""] = t('default');
1652                                 continue;
1653                         }
1654                         $ll = substr($l,5);
1655                         $ll = substr($ll,0,strrpos($ll,'/'));
1656                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1657                         $lang_options[$ll]=$ll;
1658                 }
1659         }
1660
1661         $tpl = get_markup_template("lang_selector.tpl");        
1662         $o = replace_macros($tpl, array(
1663                 '$title' => t('Select an alternate language'),
1664                 '$langs' => array($lang_options, $selected),
1665                 
1666         ));
1667         return $o;
1668 }}
1669
1670
1671 if(! function_exists('return_bytes')) {
1672 /**
1673  * return number of bytes in size (K, M, G)
1674  * @param string $size_str
1675  * @return number
1676  */
1677 function return_bytes ($size_str) {
1678     switch (substr ($size_str, -1))
1679     {
1680         case 'M': case 'm': return (int)$size_str * 1048576;
1681         case 'K': case 'k': return (int)$size_str * 1024;
1682         case 'G': case 'g': return (int)$size_str * 1073741824;
1683         default: return $size_str;
1684     }
1685 }}
1686
1687 /**
1688  * @return string
1689  */
1690 function generate_user_guid() {
1691         $found = true;
1692         do {
1693                 $guid = random_string(16);
1694                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1695                         dbesc($guid)
1696                 );
1697                 if(! count($x))
1698                         $found = false;
1699         } while ($found == true );
1700         return $guid;
1701 }
1702
1703
1704 /**
1705  * @param string $s
1706  * @param boolean $strip_padding
1707  * @return string
1708  */
1709 function base64url_encode($s, $strip_padding = false) {
1710
1711         $s = strtr(base64_encode($s),'+/','-_');
1712
1713         if($strip_padding)
1714                 $s = str_replace('=','',$s);
1715
1716         return $s;
1717 }
1718
1719 /**
1720  * @param string $s
1721  * @return string
1722  */
1723 function base64url_decode($s) {
1724
1725         if(is_array($s)) {
1726                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1727                 return $s;
1728         }
1729
1730 /*
1731  *  // Placeholder for new rev of salmon which strips base64 padding.
1732  *  // PHP base64_decode handles the un-padded input without requiring this step
1733  *  // Uncomment if you find you need it.
1734  *
1735  *      $l = strlen($s);
1736  *      if(! strpos($s,'=')) {
1737  *              $m = $l % 4;
1738  *              if($m == 2)
1739  *                      $s .= '==';
1740  *              if($m == 3)
1741  *                      $s .= '=';
1742  *      }
1743  *
1744  */
1745
1746         return base64_decode(strtr($s,'-_','+/'));
1747 }
1748
1749
1750 if (!function_exists('str_getcsv')) {
1751         /**
1752          * Parse csv string
1753          * 
1754          * @param string $input
1755          * @param string $delimiter
1756          * @param string $enclosure
1757          * @param string $escape
1758          * @param string $eol
1759          * @return boolean|array False on error, otherwise array[row][column]
1760          */
1761     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1762         if (is_string($input) && !empty($input)) {
1763             $output = array();
1764             $tmp    = preg_split("/".$eol."/",$input);
1765             if (is_array($tmp) && !empty($tmp)) {
1766                 while (list($line_num, $line) = each($tmp)) {
1767                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1768                         while ($strlen = strlen($line)) {
1769                             $pos_delimiter       = strpos($line,$delimiter);
1770                             $pos_enclosure_start = strpos($line,$enclosure);
1771                             if (
1772                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1773                                 && ($pos_enclosure_start < $pos_delimiter)
1774                                 ) {
1775                                 $enclosed_str = substr($line,1);
1776                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1777                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1778                                 $output[$line_num][] = $enclosed_str;
1779                                 $offset = $pos_enclosure_end+3;
1780                             } else {
1781                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1782                                     $output[$line_num][] = substr($line,0);
1783                                     $offset = strlen($line);
1784                                 } else {
1785                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1786                                     $offset = (
1787                                                 !empty($pos_enclosure_start)
1788                                                 && ($pos_enclosure_start < $pos_delimiter)
1789                                                 )
1790                                                 ?$pos_enclosure_start
1791                                                 :$pos_delimiter+1;
1792                                 }
1793                             }
1794                             $line = substr($line,$offset);
1795                         }
1796                     } else {
1797                         $line = preg_split("/".$delimiter."/",$line);
1798    
1799                         /*
1800                          * Validating against pesky extra line breaks creating false rows.
1801                          */
1802                         if (is_array($line) && !empty($line[0])) {
1803                             $output[$line_num] = $line;
1804                         } 
1805                     }
1806                 }
1807                 return $output;
1808             } else {
1809                 return false;
1810             }
1811         } else {
1812             return false;
1813         }
1814     }
1815
1816
1817 /**
1818  * return div element with class 'clear'
1819  * @return string
1820  * @deprecated
1821  */
1822 function cleardiv() {
1823         return '<div class="clear"></div>';
1824 }
1825
1826
1827 function bb_translate_video($s) {
1828
1829         $matches = null;
1830         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1831         if($r) {
1832                 foreach($matches as $mtch) {
1833                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1834                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1835                         elseif(stristr($mtch[1],'vimeo'))
1836                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1837                 }
1838         }
1839         return $s;      
1840 }
1841
1842 function html2bb_video($s) {
1843
1844         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1845                         '[youtube]$2[/youtube]', $s);
1846
1847         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1848                         '[youtube]$2[/youtube]', $s);
1849
1850         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1851                         '[vimeo]$2[/vimeo]', $s);
1852
1853         return $s;
1854 }
1855
1856 /**
1857  * apply xmlify() to all values of array $val, recursively
1858  * @param array $val
1859  * @return array
1860  */
1861 function array_xmlify($val){
1862         if (is_bool($val)) return $val?"true":"false";
1863         if (is_array($val)) return array_map('array_xmlify', $val);
1864         return xmlify((string) $val);
1865 }
1866
1867
1868 /**
1869  * transorm link href and img src from relative to absolute
1870  * 
1871  * @param string $text
1872  * @param string $base base url
1873  * @return string
1874  */
1875 function reltoabs($text, $base)
1876 {
1877   if (empty($base))
1878     return $text;
1879
1880   $base = rtrim($base,'/');
1881
1882   $base2 = $base . "/";
1883         
1884   // Replace links
1885   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1886   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1887   $text = preg_replace($pattern, $replace, $text);
1888
1889   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1890   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1891   $text = preg_replace($pattern, $replace, $text);
1892
1893   // Replace images
1894   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1895   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1896   $text = preg_replace($pattern, $replace, $text); 
1897
1898   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1899   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1900   $text = preg_replace($pattern, $replace, $text); 
1901
1902
1903   // Done
1904   return $text;
1905 }
1906
1907 /**
1908  * get translated item type
1909  * 
1910  * @param array $itme
1911  * @return string
1912  */
1913 function item_post_type($item) {
1914         if(intval($item['event-id']))
1915                 return t('event');
1916         if(strlen($item['resource-id']))
1917                 return t('photo');
1918         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1919                 return t('activity');
1920         if($item['id'] != $item['parent'])
1921                 return t('comment');
1922         return t('post');
1923 }
1924
1925 // post categories and "save to file" use the same item.file table for storage.
1926 // We will differentiate the different uses by wrapping categories in angle brackets
1927 // and save to file categories in square brackets.
1928 // To do this we need to escape these characters if they appear in our tag. 
1929
1930 function file_tag_encode($s) {
1931         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1932 }
1933
1934 function file_tag_decode($s) {
1935         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1936 }
1937
1938 function file_tag_file_query($table,$s,$type = 'file') {
1939
1940         if($type == 'file')
1941                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1942         else
1943                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1944         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1945 }
1946
1947 // ex. given music,video return <music><video> or [music][video]
1948 function file_tag_list_to_file($list,$type = 'file') {
1949         $tag_list = '';
1950         if(strlen($list)) {
1951                 $list_array = explode(",",$list);
1952                 if($type == 'file') {
1953                         $lbracket = '[';
1954                         $rbracket = ']';
1955                 }
1956                 else {
1957                         $lbracket = '<';
1958                         $rbracket = '>';
1959                 }
1960
1961                 foreach($list_array as $item) {
1962                   if(strlen($item)) {
1963                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
1964                         }
1965                 }
1966         }
1967         return $tag_list;
1968 }
1969
1970 // ex. given <music><video>[friends], return music,video or friends
1971 function file_tag_file_to_list($file,$type = 'file') {
1972         $matches = false;
1973         $list = '';
1974         if($type == 'file') {
1975                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
1976         }
1977         else {
1978                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
1979         }
1980         if($cnt) {
1981                 foreach($matches as $mtch) {
1982                         if(strlen($list))
1983                                 $list .= ',';
1984                         $list .= file_tag_decode($mtch[1]);
1985                 }
1986         }
1987
1988         return $list;
1989 }
1990
1991 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
1992         // $file_old - categories previously associated with an item
1993         // $file_new - new list of categories for an item
1994
1995         if(! intval($uid))
1996                 return false;
1997
1998         if($file_old == $file_new)
1999                 return true;
2000
2001         $saved = get_pconfig($uid,'system','filetags');
2002         if(strlen($saved)) {
2003                 if($type == 'file') {
2004                         $lbracket = '[';
2005                         $rbracket = ']';
2006                 }
2007                 else {
2008                         $lbracket = '<';
2009                         $rbracket = '>';
2010                 }
2011
2012                 $filetags_updated = $saved;
2013
2014                 // check for new tags to be added as filetags in pconfig
2015                 $new_tags = array();
2016                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2017
2018                 foreach($check_new_tags as $tag) {
2019                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2020                                 $new_tags[] = $tag;
2021                 }
2022
2023                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2024
2025                 // check for deleted tags to be removed from filetags in pconfig
2026                 $deleted_tags = array();
2027                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2028
2029                 foreach($check_deleted_tags as $tag) {
2030                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2031                                 $deleted_tags[] = $tag;
2032                 }
2033
2034                 foreach($deleted_tags as $key => $tag) {
2035                         $r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2036                                 intval($uid)
2037                         );
2038
2039                         if(count($r)) {
2040                                 unset($deleted_tags[$key]);
2041                         }
2042                         else {
2043                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2044                         }
2045                 }
2046
2047                 if($saved != $filetags_updated) {
2048                         set_pconfig($uid,'system','filetags', $filetags_updated);
2049                 }
2050                 return true;
2051         }
2052         else
2053                 if(strlen($file_new)) {
2054                         set_pconfig($uid,'system','filetags', $file_new);
2055                 }
2056                 return true;
2057 }
2058
2059 function file_tag_save_file($uid,$item,$file) {
2060         $result = false;
2061         if(! intval($uid))
2062                 return false;
2063         $r = q("select file from item where id = %d and uid = %d limit 1",
2064                 intval($item),
2065                 intval($uid)
2066         );
2067         if(count($r)) {
2068                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2069                         q("update item set file = '%s' where id = %d and uid = %d limit 1",
2070                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2071                                 intval($item),
2072                                 intval($uid)
2073                         );
2074                 $saved = get_pconfig($uid,'system','filetags');
2075                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2076                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2077                 info( t('Item filed') );
2078         }
2079         return true;
2080 }
2081
2082 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2083         $result = false;
2084         if(! intval($uid))
2085                 return false;
2086
2087         if($cat == true)
2088                 $pattern = '<' . file_tag_encode($file) . '>' ;
2089         else
2090                 $pattern = '[' . file_tag_encode($file) . ']' ;
2091
2092
2093         $r = q("select file from item where id = %d and uid = %d limit 1",
2094                 intval($item),
2095                 intval($uid)
2096         );
2097         if(! count($r))
2098                 return false;
2099
2100         q("update item set file = '%s' where id = %d and uid = %d limit 1",
2101                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2102                 intval($item),
2103                 intval($uid)
2104         );
2105
2106         $r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2107                 intval($uid)
2108         );
2109
2110         if(! count($r)) {
2111                 $saved = get_pconfig($uid,'system','filetags');
2112                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2113
2114         }
2115         return true;
2116 }
2117
2118 function normalise_openid($s) {
2119         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2120 }
2121
2122
2123 function undo_post_tagging($s) {
2124         $matches = null;
2125         $cnt = preg_match_all('/([@#])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2126         if($cnt) {
2127                 foreach($matches as $mtch) {
2128                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2129                 }
2130         }
2131         return $s;
2132 }
2133
2134 function fix_mce_lf($s) {
2135         $s = str_replace("\r\n","\n",$s);
2136 //      $s = str_replace("\n\n","\n",$s);
2137         return $s;
2138 }
2139
2140
2141 function protect_sprintf($s) {
2142         return(str_replace('%','%%',$s));
2143 }
2144
2145
2146 function is_a_date_arg($s) {
2147         $i = intval($s);
2148         if($i > 1900) {
2149                 $y = date('Y');
2150                 if($i <= $y+1 && strpos($s,'-') == 4) {
2151                         $m = intval(substr($s,5));
2152                         if($m > 0 && $m <= 12)
2153                                 return true;
2154                 }
2155         }
2156         return false;
2157 }