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