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