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