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