]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge pull request #999 from annando/master
[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_LEVEL)==0){
681         foreach (get_defined_constants() as $k=>$v){
682             if (substr($k,0,7)=="LOGGER_") $LOGGER_LEVELS[$v] = substr($k,7,7);
683         }        
684     }
685     
686         $debugging = get_config('system','debugging');
687         $loglevel  = intval(get_config('system','loglevel'));
688         $logfile   = get_config('system','logfile');
689
690         if((! $debugging) || (! $logfile) || ($level > $loglevel))
691                 return;
692
693         $callers = debug_backtrace(); 
694         $logline =  sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n", 
695                                  datetime_convert(), 
696                                  session_id(),
697                                  $LOGGER_LEVELS[$level],
698                                  basename($callers[0]['file']),
699                                  $callers[0]['line'],
700                                  $callers[1]['function'],
701                                  $msg
702                                 );
703         
704         $stamp1 = microtime(true);
705         @file_put_contents($logfile, $logline, FILE_APPEND);
706         $a->save_timestamp($stamp1, "file");
707         return;
708 }}
709
710
711 if(! function_exists('activity_match')) {
712 /**
713  * Compare activity uri. Knows about activity namespace.
714  * 
715  * @param string $haystack
716  * @param string $needle
717  * @return boolean
718  */
719 function activity_match($haystack,$needle) {
720         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
721                 return true;
722         return false;
723 }}
724
725
726 if(! function_exists('get_tags')) {
727 /**
728  * Pull out all #hashtags and @person tags from $s;
729  * We also get @person@domain.com - which would make 
730  * the regex quite complicated as tags can also
731  * end a sentence. So we'll run through our results
732  * and strip the period from any tags which end with one.
733  * Returns array of tags found, or empty array.
734  * 
735  * @param string $s
736  * @return array
737  */
738 function get_tags($s) {
739         $ret = array();
740
741         // ignore anything in a code block
742         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
743
744         // Force line feeds at bbtags
745         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
746
747         // ignore anything in a bbtag
748         $s = preg_replace('/\[(.*?)\]/sm','',$s);
749
750         // Match full names against @tags including the space between first and last
751         // We will look these up afterward to see if they are full names or not recognisable.
752
753         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
754                 foreach($match[1] as $mtch) {
755                         if(strstr($mtch,"]")) {
756                                 // we might be inside a bbcode color tag - leave it alone
757                                 continue;
758                         }
759                         if(substr($mtch,-1,1) === '.')
760                                 $ret[] = substr($mtch,0,-1);
761                         else
762                                 $ret[] = $mtch;
763                 }
764         }
765
766         // Otherwise pull out single word tags. These can be @nickname, @first_last
767         // and #hash tags.
768
769         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
770                 foreach($match[1] as $mtch) {
771                         if(strstr($mtch,"]")) {
772                                 // we might be inside a bbcode color tag - leave it alone
773                                 continue;
774                         }
775                         if(substr($mtch,-1,1) === '.')
776                                 $mtch = substr($mtch,0,-1);
777                         // ignore strictly numeric tags like #1
778                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
779                                 continue;
780                         // try not to catch url fragments
781                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
782                                 continue;
783                         $ret[] = $mtch;
784                 }
785         }
786         return $ret;
787 }}
788
789
790 // 
791
792 if(! function_exists('qp')) {
793 /**
794  * quick and dirty quoted_printable encoding
795  * 
796  * @param string $s
797  * @return string
798  */     
799 function qp($s) {
800 return str_replace ("%","=",rawurlencode($s));
801 }} 
802
803
804
805 if(! function_exists('get_mentions')) {
806 /**
807  * @param array $item
808  * @return string html for mentions #FIXME: remove html
809  */
810 function get_mentions($item) {
811         $o = '';
812         if(! strlen($item['tag']))
813                 return $o;
814
815         $arr = explode(',',$item['tag']);
816         foreach($arr as $x) {
817                 $matches = null;
818                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
819                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
820                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
821                 }
822         }
823         return $o;
824 }}
825
826 if(! function_exists('contact_block')) {
827 /**
828  * Get html for contact block.
829  * 
830  * @template contact_block.tpl
831  * @hook contact_block_end (contacts=>array, output=>string)
832  * @return string
833  */
834 function contact_block() {
835         $o = '';
836         $a = get_app();
837
838         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
839         if($shown === false)
840                 $shown = 24;
841         if($shown == 0)
842                 return;
843
844         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
845                 return $o;
846         $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",
847                         intval($a->profile['uid'])
848         );
849         if(count($r)) {
850                 $total = intval($r[0]['total']);
851         }
852         if(! $total) {
853                 $contacts = t('No contacts');
854                 $micropro = Null;
855                 
856         } else {
857                 $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",
858                                 intval($a->profile['uid']),
859                                 intval($shown)
860                 );
861                 if(count($r)) {
862                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
863                         $micropro = Array();
864                         foreach($r as $rr) {
865                                 $micropro[] = micropro($rr,true,'mpfriend');
866                         }
867                 }
868         }
869         
870         $tpl = get_markup_template('contact_block.tpl');
871         $o = replace_macros($tpl, array(
872                 '$contacts' => $contacts,
873                 '$nickname' => $a->profile['nickname'],
874                 '$viewcontacts' => t('View Contacts'),
875                 '$micropro' => $micropro,
876         ));
877
878         $arr = array('contacts' => $r, 'output' => $o);
879
880         call_hooks('contact_block_end', $arr);
881         return $o;
882
883 }}
884
885 if(! function_exists('micropro')) {
886 /**
887  * 
888  * @param array $contact
889  * @param boolean $redirect
890  * @param string $class
891  * @param boolean $textmode
892  * @return string #FIXME: remove html
893  */
894 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
895
896         if($class)
897                 $class = ' ' . $class;
898
899         $url = $contact['url'];
900         $sparkle = '';
901         $redir = false;
902
903         if($redirect) {
904                 $a = get_app();
905                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
906                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
907                         $redir = true;
908                         $url = $redirect_url;
909                         $sparkle = ' sparkle';
910                 }
911                 else
912                         $url = zrl($url);
913         }
914         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
915         if($click)
916                 $url = '';
917         if($textmode) {
918                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle 
919                         . (($click) ? ' fakelink' : '') . '" '
920                         . (($redir) ? ' target="redir" ' : '')
921                         . (($url) ? ' href="' . $url . '"' : '') . $click
922                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
923                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
924         }
925         else {
926                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle 
927                         . (($click) ? ' fakelink' : '') . '" '
928                         . (($redir) ? ' target="redir" ' : '')
929                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="' 
930                         . $contact['micro'] . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
931                         . '" /></a></div>' . "\r\n";
932         }
933 }}
934
935
936
937 if(! function_exists('search')) {
938 /**
939  * search box
940  * 
941  * @param string $s search query
942  * @param string $id html id
943  * @param string $url search url
944  * @param boolean $save show save search button
945  * @return string html for search box #FIXME: remove html
946  */
947 function search($s,$id='search-box',$url='/search',$save = false) {
948         $a = get_app();
949         $o  = '<div id="' . $id . '">';
950         $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >';
951         $o .= '<input type="text" name="search" id="search-text" placeholder="' . t('Search') . '" value="' . $s .'" />';
952         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
953         if($save)
954                 $o .= '<input type="submit" name="save" id="search-save" value="' . t('Save') . '" />'; 
955         $o .= '</form></div>';
956         return $o;
957 }}
958
959 if(! function_exists('valid_email')) {
960 /**
961  * Check if $x is a valid email string
962  * 
963  * @param string $x
964  * @return boolean
965  */
966 function valid_email($x){
967
968         if(get_config('system','disable_email_validation'))
969                 return true;
970
971         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
972                 return true;
973         return false;
974 }}
975
976
977 if(! function_exists('linkify')) {
978 /**
979  * Replace naked text hyperlink with HTML formatted hyperlink
980  *
981  * @param string $s
982  */
983 function linkify($s) {
984         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
985         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
986         return($s);
987 }}
988
989
990 /**
991  * Load poke verbs
992  *
993  * @return array index is present tense verb
994                                  value is array containing past tense verb, translation of present, translation of past
995  * @hook poke_verbs pokes array
996  */
997 function get_poke_verbs() {
998         
999         // index is present tense verb
1000         // value is array containing past tense verb, translation of present, translation of past
1001
1002         $arr = array(
1003                 'poke' => array( 'poked', t('poke'), t('poked')),
1004                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1005                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1006                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1007                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1008                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1009         );
1010         call_hooks('poke_verbs', $arr);
1011         return $arr;
1012 }
1013
1014 /**
1015  * Load moods
1016  * @return array index is mood, value is translated mood
1017  * @hook mood_verbs moods array
1018  */
1019 function get_mood_verbs() {
1020         
1021         $arr = array(
1022                 'happy'      => t('happy'),
1023                 'sad'        => t('sad'),
1024                 'mellow'     => t('mellow'),
1025                 'tired'      => t('tired'),
1026                 'perky'      => t('perky'),
1027                 'angry'      => t('angry'),
1028                 'stupefied'  => t('stupified'),
1029                 'puzzled'    => t('puzzled'),
1030                 'interested' => t('interested'),
1031                 'bitter'     => t('bitter'),
1032                 'cheerful'   => t('cheerful'),
1033                 'alive'      => t('alive'),
1034                 'annoyed'    => t('annoyed'),
1035                 'anxious'    => t('anxious'),
1036                 'cranky'     => t('cranky'),
1037                 'disturbed'  => t('disturbed'),
1038                 'frustrated' => t('frustrated'),
1039                 'motivated'  => t('motivated'),
1040                 'relaxed'    => t('relaxed'),
1041                 'surprised'  => t('surprised'),
1042         );
1043
1044         call_hooks('mood_verbs', $arr);
1045         return $arr;
1046 }
1047
1048
1049
1050 if(! function_exists('smilies')) {
1051 /**
1052  * Replaces text emoticons with graphical images
1053  *
1054  * It is expected that this function will be called using HTML text.
1055  * We will escape text between HTML pre and code blocks from being 
1056  * processed. 
1057  * 
1058  * At a higher level, the bbcode [nosmile] tag can be used to prevent this 
1059  * function from being executed by the prepare_text() routine when preparing
1060  * bbcode source for HTML display
1061  *
1062  * @param string $s
1063  * @param boolean $sample
1064  * @return string
1065  * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
1066  */
1067 function smilies($s, $sample = false) {
1068         $a = get_app();
1069
1070         if(intval(get_config('system','no_smilies')) 
1071                 || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
1072                 return $s;
1073
1074         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
1075         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
1076
1077         $texts =  array( 
1078                 '&lt;3', 
1079                 '&lt;/3', 
1080                 '&lt;\\3', 
1081                 ':-)', 
1082                 ';-)', 
1083                 ':-(', 
1084                 ':-P', 
1085                 ':-p', 
1086                 ':-"', 
1087                 ':-&quot;', 
1088                 ':-x', 
1089                 ':-X', 
1090                 ':-D', 
1091                 '8-|', 
1092                 '8-O', 
1093                 ':-O', 
1094                 '\\o/', 
1095                 'o.O', 
1096                 'O.o', 
1097                 'o_O', 
1098                 'O_o', 
1099                 ":'(", 
1100                 ":-!", 
1101                 ":-/", 
1102                 ":-[", 
1103                 "8-)",
1104                 ':beer', 
1105                 ':homebrew', 
1106                 ':coffee', 
1107                 ':facepalm',
1108                 ':like',
1109                 ':dislike',
1110                 '~friendica',
1111                 'red#'
1112
1113         );
1114
1115         $icons = array(
1116                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
1117                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.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-smile.gif" alt=":-)" />',
1120                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1121                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1122                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
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-kiss.gif" alt=":-\"" />',
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=":-x" />',
1127                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1128                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1129                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1130                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />',
1131                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt=":-O" />',                
1132                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
1133                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o.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-cry.gif" alt=":\'(" />',
1138                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
1139                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-undecided.gif" alt=":-/" />',
1140                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-embarassed.gif" alt=":-[" />',
1141                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cool.gif" alt="8-)" />',
1142                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":beer" />',
1143                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":homebrew" />',
1144                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/coffee.gif" alt=":coffee" />',
1145                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
1146                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />',
1147                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />',
1148                 '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>',
1149                 '<a href="http://redmatrix.me/">red <img class="smiley" src="' . $a->get_baseurl() . '/images/rhash-16.png" alt="red" /></a>'
1150         );
1151
1152         $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
1153         call_hooks('smilie', $params);
1154
1155         if($sample) {
1156                 $s = '<div class="smiley-sample">';
1157                 for($x = 0; $x < count($params['texts']); $x ++) {
1158                         $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
1159                 }
1160         }
1161         else {
1162                 $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
1163                 $s = str_replace($params['texts'],$params['icons'],$params['string']);
1164         }
1165
1166         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
1167         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
1168
1169         return $s;
1170
1171 }}
1172
1173 function smile_encode($m) {
1174         return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
1175 }
1176
1177 function smile_decode($m) {
1178         return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
1179 }
1180
1181
1182 /**
1183  * expand <3333 to the correct number of hearts
1184  * 
1185  * @param string $x
1186  * @return string
1187  */
1188 function preg_heart($x) {
1189         $a = get_app();
1190         if(strlen($x[1]) == 1)
1191                 return $x[0];
1192         $t = '';
1193         for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
1194                 $t .= '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />';
1195         $r =  str_replace($x[0],$t,$x[0]);
1196         return $r;
1197 }
1198
1199
1200 if(! function_exists('day_translate')) {
1201 /**
1202  * Translate days and months names
1203  * 
1204  * @param string $s
1205  * @return string
1206  */
1207 function day_translate($s) {
1208         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1209                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1210                 $s);
1211
1212         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1213                 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')),
1214                 $ret);
1215
1216         return $ret;
1217 }}
1218
1219
1220 if(! function_exists('normalise_link')) {
1221 /**
1222  * Normalize url
1223  * 
1224  * @param string $url
1225  * @return string
1226  */
1227 function normalise_link($url) {
1228         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1229         return(rtrim($ret,'/'));
1230 }}
1231
1232
1233
1234 if(! function_exists('link_compare')) {
1235 /**
1236  * Compare two URLs to see if they are the same, but ignore
1237  * slight but hopefully insignificant differences such as if one 
1238  * is https and the other isn't, or if one is www.something and 
1239  * the other isn't - and also ignore case differences.
1240  *
1241  * @param string $a first url
1242  * @param string $b second url
1243  * @return boolean True if the URLs match, otherwise False
1244  *
1245  */     
1246 function link_compare($a,$b) {
1247         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1248                 return true;
1249         return false;
1250 }}
1251
1252
1253 if(! function_exists('redir_private_images')) {
1254 /**
1255  * Find any non-embedded images in private items and add redir links to them
1256  * 
1257  * @param App $a
1258  * @param array $item
1259  */
1260 function redir_private_images($a, &$item) {
1261
1262         $matches = false;
1263         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1264         if($cnt) {
1265                 //logger("redir_private_images: matches = " . print_r($matches, true));
1266                 foreach($matches as $mtch) {
1267                         if(strpos($mtch[1], '/redir') !== false)
1268                                 continue;
1269
1270                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1271                                 //logger("redir_private_images: redir");
1272                                 $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1273                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1274                         }
1275                 }
1276         }
1277
1278 }}
1279
1280
1281 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1282 // If attach is true, also add icons for item attachments
1283
1284 if(! function_exists('prepare_body')) {
1285 /**
1286  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1287  * If attach is true, also add icons for item attachments
1288  * 
1289  * @param array $item
1290  * @param boolean $attach
1291  * @return string item body html
1292  * @hook prepare_body_init item array before any work
1293  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1294  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1295  */
1296 function prepare_body(&$item,$attach = false) {
1297
1298         $a = get_app();
1299         call_hooks('prepare_body_init', $item);
1300
1301         $searchpath = $a->get_baseurl()."/search?tag=";
1302
1303         $tags=array();
1304         $hashtags = array();
1305         $mentions = array();
1306
1307         if (!get_config('system','suppress_tags')) {
1308                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1309                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1310
1311                 foreach($taglist as $tag) {
1312
1313                         if ($tag["url"] == "")
1314                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1315
1316                         if ($tag["type"] == TERM_HASHTAG) {
1317                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1318                                 $prefix = "#";
1319                         } elseif ($tag["type"] == TERM_MENTION) {
1320                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1321                                 $prefix = "@";
1322                         }
1323                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1324                 }
1325         }
1326
1327         $item['tags'] = $tags;
1328         $item['hashtags'] = $hashtags;
1329         $item['mentions'] = $mentions;
1330
1331
1332         //$cachefile = get_cachefile($item["guid"]."-".strtotime($item["edited"])."-".hash("crc32", $item['body']));
1333         $cachefile = get_cachefile($item["guid"]."-".hash("md5", $item['body']));
1334
1335         if (($cachefile != '')) {
1336                 if (file_exists($cachefile)) {
1337                         $stamp1 = microtime(true);
1338                         $s = file_get_contents($cachefile);
1339                         $a->save_timestamp($stamp1, "file");
1340                 } else {
1341                         redir_private_images($a, $item);
1342                         $s = prepare_text($item['body']);
1343
1344                         $stamp1 = microtime(true);
1345                         file_put_contents($cachefile, $s);
1346                         $a->save_timestamp($stamp1, "file");
1347
1348                         logger('prepare_body: put item '.$item["id"].' into cachefile '.$cachefile);
1349                 }
1350         } else {
1351                 redir_private_images($a, $item);
1352                 $s = prepare_text($item['body']);
1353         }
1354
1355
1356         $prep_arr = array('item' => $item, 'html' => $s);
1357         call_hooks('prepare_body', $prep_arr);
1358         $s = $prep_arr['html'];
1359
1360         if(! $attach) {
1361                 // Replace the blockquotes with quotes that are used in mails
1362                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1363                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1364                 return $s;
1365         }
1366
1367         $as = '';
1368         $vhead = false;
1369         $arr = explode('[/attach],',$item['attach']);
1370         if(count($arr)) {
1371                 $as .= '<div class="body-attach">';
1372                 foreach($arr as $r) {
1373                         $matches = false;
1374                         $icon = '';
1375                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1376                         if($cnt) {
1377                                 foreach($matches as $mtch) {
1378                                         $mime = $mtch[3];
1379
1380                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1381                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1382                                         else
1383                                                 $the_url = $mtch[1];
1384
1385                                         if(strpos($mime, 'video') !== false) {
1386                                                 if(!$vhead) {
1387                                                         $vhead = true;
1388                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1389                                                                 '$baseurl' => $a->get_baseurl(),
1390                                                         ));
1391                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1392                                                                 '$baseurl' => $a->get_baseurl(),
1393                                                         ));
1394                                                 }
1395
1396                                                 $id = end(explode('/', $the_url));
1397                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1398                                                         '$video'        => array(
1399                                                                 'id'       => $id,
1400                                                                 'title'         => t('View Video'),
1401                                                                 'src'           => $the_url,
1402                                                                 'mime'          => $mime,
1403                                                         ),
1404                                                 ));
1405                                         }
1406
1407                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1408                                         if($filetype) {
1409                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1410                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1411                                         }
1412                                         else {
1413                                                 $filetype = 'unkn';
1414                                                 $filesubtype = 'unkn';
1415                                         }
1416
1417                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1418                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1419                                         switch($icontype) {
1420                                                 case 'video':
1421                                                 case 'audio':
1422                                                 case 'image':
1423                                                 case 'text':
1424                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1425                                                         break;
1426                                                 default:
1427                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1428                                                         break;
1429                                         }*/
1430
1431                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1432                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1433
1434                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1435                                 }
1436                         }
1437                 }
1438                 $as .= '<div class="clear"></div></div>';
1439         }
1440         $s = $s . $as;
1441
1442
1443         // Look for spoiler
1444         $spoilersearch = '<blockquote class="spoiler">';
1445
1446         // Remove line breaks before the spoiler
1447         while ((strpos($s, "\n".$spoilersearch) !== false))
1448                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1449         while ((strpos($s, "<br />".$spoilersearch) !== false))
1450                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1451
1452         while ((strpos($s, $spoilersearch) !== false)) {
1453
1454                 $pos = strpos($s, $spoilersearch);
1455                 $rnd = random_string(8);
1456                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1457                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1458                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1459         }
1460
1461         // Look for quote with author
1462         $authorsearch = '<blockquote class="author">';
1463
1464         while ((strpos($s, $authorsearch) !== false)) {
1465
1466                 $pos = strpos($s, $authorsearch);
1467                 $rnd = random_string(8);
1468                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1469                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1470                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1471         }
1472
1473         $prep_arr = array('item' => $item, 'html' => $s);
1474         call_hooks('prepare_body_final', $prep_arr);
1475
1476         return $prep_arr['html'];
1477 }}
1478
1479
1480 if(! function_exists('prepare_text')) {
1481 /**
1482  * Given a text string, convert from bbcode to html and add smilie icons.
1483  * 
1484  * @param string $text
1485  * @return string
1486  */
1487 function prepare_text($text) {
1488
1489         require_once('include/bbcode.php');
1490
1491         if(stristr($text,'[nosmile]'))
1492                 $s = bbcode($text);
1493         else
1494                 $s = smilies(bbcode($text));
1495
1496         return $s;
1497 }}
1498
1499
1500
1501 /**
1502  * return array with details for categories and folders for an item
1503  * 
1504  * @param array $item
1505  * @return array
1506  * 
1507   * [
1508  *      [ // categories array
1509  *          {
1510  *               'name': 'category name',
1511  *               'removeurl': 'url to remove this category',
1512  *               'first': 'is the first in this array? true/false',
1513  *               'last': 'is the last in this array? true/false',
1514  *           } ,
1515  *           ....
1516  *       ],
1517  *       [ //folders array
1518  *                      {
1519  *               'name': 'folder name',
1520  *               'removeurl': 'url to remove this folder',
1521  *               'first': 'is the first in this array? true/false',
1522  *               'last': 'is the last in this array? true/false',
1523  *           } ,
1524  *           ....       
1525  *       ]
1526  *  ]
1527  */
1528 function get_cats_and_terms($item) {
1529
1530     $a = get_app();
1531     $categories = array();
1532     $folders = array();
1533
1534     $matches = false; $first = true;
1535     $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1536     if($cnt) {
1537         foreach($matches as $mtch) {
1538             $categories[] = array(
1539                 'name' => xmlify(file_tag_decode($mtch[1])),
1540                 'url' =>  "#",
1541                 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1542                 'first' => $first,
1543                 'last' => false
1544             );
1545             $first = false;
1546         }
1547     }
1548     if (count($categories)) $categories[count($categories)-1]['last'] = true;
1549     
1550
1551         if(local_user() == $item['uid']) {
1552             $matches = false; $first = true;
1553         $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1554             if($cnt) {
1555             foreach($matches as $mtch) {
1556                     $folders[] = array(
1557                     'name' => xmlify(file_tag_decode($mtch[1])),
1558                          'url' =>  "#",
1559                         'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1560                     'first' => $first,
1561                         'last' => false
1562                 );
1563                     $first = false;
1564                         }
1565         }
1566     }
1567
1568     if (count($folders)) $folders[count($folders)-1]['last'] = true;
1569
1570     return array($categories, $folders);
1571 }
1572
1573
1574
1575 if(! function_exists('feed_hublinks')) {
1576 /**
1577  * return atom link elements for all of our hubs
1578  * @return string hub link xml elements
1579  */
1580 function feed_hublinks() {
1581         $a = get_app();
1582         $hub = get_config('system','huburl');
1583
1584         $hubxml = '';
1585         if(strlen($hub)) {
1586                 $hubs = explode(',', $hub);
1587                 if(count($hubs)) {
1588                         foreach($hubs as $h) {
1589                                 $h = trim($h);
1590                                 if(! strlen($h))
1591                                         continue;
1592                                 if ($h === '[internal]')
1593                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1594                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
1595                         }
1596                 }
1597         }
1598         return $hubxml;
1599 }}
1600
1601
1602 if(! function_exists('feed_salmonlinks')) {
1603 /**
1604  * return atom link elements for salmon endpoints
1605  * @param string $nick user nickname
1606  * @return string salmon link xml elements
1607  */
1608 function feed_salmonlinks($nick) {
1609
1610         $a = get_app();
1611
1612         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1613
1614         // old style links that status.net still needed as of 12/2010 
1615
1616         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
1617         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
1618         return $salmon;
1619 }}
1620
1621 if(! function_exists('get_plink')) {
1622 /**
1623  * get private link for item
1624  * @param array $item
1625  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1626  */
1627 function get_plink($item) {
1628         $a = get_app();
1629
1630         if ($a->user['nickname'] != "") {
1631                 $ret = array(
1632                                 'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
1633                                 'title' => t('link to source'),
1634                         );
1635                 $ret["orig"] = $ret["href"];
1636
1637                 if (x($item,'plink'))
1638                         $ret["href"] = $item['plink'];
1639
1640         } elseif (x($item,'plink') && ($item['private'] != 1))
1641                 $ret = array(
1642                                 'href' => $item['plink'],
1643                                 'orig' => $item['plink'],
1644                                 'title' => t('link to source'),
1645                         );
1646         else
1647                 $ret = array();
1648
1649         //if (x($item,'plink') && ($item['private'] != 1))
1650
1651         return($ret);
1652 }}
1653
1654 if(! function_exists('unamp')) {
1655 /**
1656  * replace html amp entity with amp char
1657  * @param string $s
1658  * @return string
1659  */
1660 function unamp($s) {
1661         return str_replace('&amp;', '&', $s);
1662 }}
1663
1664
1665
1666
1667 if(! function_exists('lang_selector')) {
1668 /**
1669  * get html for language selector
1670  * @global string $lang
1671  * @return string 
1672  * @template lang_selector.tpl
1673  */
1674 function lang_selector() {
1675         global $lang;
1676         
1677         $langs = glob('view/*/strings.php');
1678         
1679         $lang_options = array();
1680         $selected = "";
1681         
1682         if(is_array($langs) && count($langs)) {
1683                 $langs[] = '';
1684                 if(! in_array('view/en/strings.php',$langs))
1685                         $langs[] = 'view/en/';
1686                 asort($langs);
1687                 foreach($langs as $l) {
1688                         if($l == '') {
1689                                 $lang_options[""] = t('default');
1690                                 continue;
1691                         }
1692                         $ll = substr($l,5);
1693                         $ll = substr($ll,0,strrpos($ll,'/'));
1694                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1695                         $lang_options[$ll]=$ll;
1696                 }
1697         }
1698
1699         $tpl = get_markup_template("lang_selector.tpl");        
1700         $o = replace_macros($tpl, array(
1701                 '$title' => t('Select an alternate language'),
1702                 '$langs' => array($lang_options, $selected),
1703                 
1704         ));
1705         return $o;
1706 }}
1707
1708
1709 if(! function_exists('return_bytes')) {
1710 /**
1711  * return number of bytes in size (K, M, G)
1712  * @param string $size_str
1713  * @return number
1714  */
1715 function return_bytes ($size_str) {
1716     switch (substr ($size_str, -1))
1717     {
1718         case 'M': case 'm': return (int)$size_str * 1048576;
1719         case 'K': case 'k': return (int)$size_str * 1024;
1720         case 'G': case 'g': return (int)$size_str * 1073741824;
1721         default: return $size_str;
1722     }
1723 }}
1724
1725 /**
1726  * @return string
1727  */
1728 function generate_user_guid() {
1729         $found = true;
1730         do {
1731                 $guid = random_string(16);
1732                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1733                         dbesc($guid)
1734                 );
1735                 if(! count($x))
1736                         $found = false;
1737         } while ($found == true );
1738         return $guid;
1739 }
1740
1741
1742 /**
1743  * @param string $s
1744  * @param boolean $strip_padding
1745  * @return string
1746  */
1747 function base64url_encode($s, $strip_padding = false) {
1748
1749         $s = strtr(base64_encode($s),'+/','-_');
1750
1751         if($strip_padding)
1752                 $s = str_replace('=','',$s);
1753
1754         return $s;
1755 }
1756
1757 /**
1758  * @param string $s
1759  * @return string
1760  */
1761 function base64url_decode($s) {
1762
1763         if(is_array($s)) {
1764                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1765                 return $s;
1766         }
1767
1768 /*
1769  *  // Placeholder for new rev of salmon which strips base64 padding.
1770  *  // PHP base64_decode handles the un-padded input without requiring this step
1771  *  // Uncomment if you find you need it.
1772  *
1773  *      $l = strlen($s);
1774  *      if(! strpos($s,'=')) {
1775  *              $m = $l % 4;
1776  *              if($m == 2)
1777  *                      $s .= '==';
1778  *              if($m == 3)
1779  *                      $s .= '=';
1780  *      }
1781  *
1782  */
1783
1784         return base64_decode(strtr($s,'-_','+/'));
1785 }
1786
1787
1788 if (!function_exists('str_getcsv')) {
1789         /**
1790          * Parse csv string
1791          * 
1792          * @param string $input
1793          * @param string $delimiter
1794          * @param string $enclosure
1795          * @param string $escape
1796          * @param string $eol
1797          * @return boolean|array False on error, otherwise array[row][column]
1798          */
1799     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1800         if (is_string($input) && !empty($input)) {
1801             $output = array();
1802             $tmp    = preg_split("/".$eol."/",$input);
1803             if (is_array($tmp) && !empty($tmp)) {
1804                 while (list($line_num, $line) = each($tmp)) {
1805                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1806                         while ($strlen = strlen($line)) {
1807                             $pos_delimiter       = strpos($line,$delimiter);
1808                             $pos_enclosure_start = strpos($line,$enclosure);
1809                             if (
1810                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1811                                 && ($pos_enclosure_start < $pos_delimiter)
1812                                 ) {
1813                                 $enclosed_str = substr($line,1);
1814                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1815                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1816                                 $output[$line_num][] = $enclosed_str;
1817                                 $offset = $pos_enclosure_end+3;
1818                             } else {
1819                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1820                                     $output[$line_num][] = substr($line,0);
1821                                     $offset = strlen($line);
1822                                 } else {
1823                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1824                                     $offset = (
1825                                                 !empty($pos_enclosure_start)
1826                                                 && ($pos_enclosure_start < $pos_delimiter)
1827                                                 )
1828                                                 ?$pos_enclosure_start
1829                                                 :$pos_delimiter+1;
1830                                 }
1831                             }
1832                             $line = substr($line,$offset);
1833                         }
1834                     } else {
1835                         $line = preg_split("/".$delimiter."/",$line);
1836    
1837                         /*
1838                          * Validating against pesky extra line breaks creating false rows.
1839                          */
1840                         if (is_array($line) && !empty($line[0])) {
1841                             $output[$line_num] = $line;
1842                         } 
1843                     }
1844                 }
1845                 return $output;
1846             } else {
1847                 return false;
1848             }
1849         } else {
1850             return false;
1851         }
1852     }
1853
1854
1855 /**
1856  * return div element with class 'clear'
1857  * @return string
1858  * @deprecated
1859  */
1860 function cleardiv() {
1861         return '<div class="clear"></div>';
1862 }
1863
1864
1865 function bb_translate_video($s) {
1866
1867         $matches = null;
1868         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1869         if($r) {
1870                 foreach($matches as $mtch) {
1871                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1872                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1873                         elseif(stristr($mtch[1],'vimeo'))
1874                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1875                 }
1876         }
1877         return $s;      
1878 }
1879
1880 function html2bb_video($s) {
1881
1882         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1883                         '[youtube]$2[/youtube]', $s);
1884
1885         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1886                         '[youtube]$2[/youtube]', $s);
1887
1888         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1889                         '[vimeo]$2[/vimeo]', $s);
1890
1891         return $s;
1892 }
1893
1894 /**
1895  * apply xmlify() to all values of array $val, recursively
1896  * @param array $val
1897  * @return array
1898  */
1899 function array_xmlify($val){
1900         if (is_bool($val)) return $val?"true":"false";
1901         if (is_array($val)) return array_map('array_xmlify', $val);
1902         return xmlify((string) $val);
1903 }
1904
1905
1906 /**
1907  * transorm link href and img src from relative to absolute
1908  * 
1909  * @param string $text
1910  * @param string $base base url
1911  * @return string
1912  */
1913 function reltoabs($text, $base)
1914 {
1915   if (empty($base))
1916     return $text;
1917
1918   $base = rtrim($base,'/');
1919
1920   $base2 = $base . "/";
1921         
1922   // Replace links
1923   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1924   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1925   $text = preg_replace($pattern, $replace, $text);
1926
1927   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1928   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1929   $text = preg_replace($pattern, $replace, $text);
1930
1931   // Replace images
1932   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1933   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1934   $text = preg_replace($pattern, $replace, $text); 
1935
1936   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1937   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1938   $text = preg_replace($pattern, $replace, $text); 
1939
1940
1941   // Done
1942   return $text;
1943 }
1944
1945 /**
1946  * get translated item type
1947  * 
1948  * @param array $itme
1949  * @return string
1950  */
1951 function item_post_type($item) {
1952         if(intval($item['event-id']))
1953                 return t('event');
1954         if(strlen($item['resource-id']))
1955                 return t('photo');
1956         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1957                 return t('activity');
1958         if($item['id'] != $item['parent'])
1959                 return t('comment');
1960         return t('post');
1961 }
1962
1963 // post categories and "save to file" use the same item.file table for storage.
1964 // We will differentiate the different uses by wrapping categories in angle brackets
1965 // and save to file categories in square brackets.
1966 // To do this we need to escape these characters if they appear in our tag. 
1967
1968 function file_tag_encode($s) {
1969         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1970 }
1971
1972 function file_tag_decode($s) {
1973         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1974 }
1975
1976 function file_tag_file_query($table,$s,$type = 'file') {
1977
1978         if($type == 'file')
1979                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1980         else
1981                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1982         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1983 }
1984
1985 // ex. given music,video return <music><video> or [music][video]
1986 function file_tag_list_to_file($list,$type = 'file') {
1987         $tag_list = '';
1988         if(strlen($list)) {
1989                 $list_array = explode(",",$list);
1990                 if($type == 'file') {
1991                         $lbracket = '[';
1992                         $rbracket = ']';
1993                 }
1994                 else {
1995                         $lbracket = '<';
1996                         $rbracket = '>';
1997                 }
1998
1999                 foreach($list_array as $item) {
2000                   if(strlen($item)) {
2001                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
2002                         }
2003                 }
2004         }
2005         return $tag_list;
2006 }
2007
2008 // ex. given <music><video>[friends], return music,video or friends
2009 function file_tag_file_to_list($file,$type = 'file') {
2010         $matches = false;
2011         $list = '';
2012         if($type == 'file') {
2013                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2014         }
2015         else {
2016                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2017         }
2018         if($cnt) {
2019                 foreach($matches as $mtch) {
2020                         if(strlen($list))
2021                                 $list .= ',';
2022                         $list .= file_tag_decode($mtch[1]);
2023                 }
2024         }
2025
2026         return $list;
2027 }
2028
2029 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2030         // $file_old - categories previously associated with an item
2031         // $file_new - new list of categories for an item
2032
2033         if(! intval($uid))
2034                 return false;
2035
2036         if($file_old == $file_new)
2037                 return true;
2038
2039         $saved = get_pconfig($uid,'system','filetags');
2040         if(strlen($saved)) {
2041                 if($type == 'file') {
2042                         $lbracket = '[';
2043                         $rbracket = ']';
2044                         $termtype = TERM_FILE;
2045                 }
2046                 else {
2047                         $lbracket = '<';
2048                         $rbracket = '>';
2049                         $termtype = TERM_CATEGORY;
2050                 }
2051
2052                 $filetags_updated = $saved;
2053
2054                 // check for new tags to be added as filetags in pconfig
2055                 $new_tags = array();
2056                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2057
2058                 foreach($check_new_tags as $tag) {
2059                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2060                                 $new_tags[] = $tag;
2061                 }
2062
2063                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2064
2065                 // check for deleted tags to be removed from filetags in pconfig
2066                 $deleted_tags = array();
2067                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2068
2069                 foreach($check_deleted_tags as $tag) {
2070                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2071                                 $deleted_tags[] = $tag;
2072                 }
2073
2074                 foreach($deleted_tags as $key => $tag) {
2075                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2076                                 dbesc($tag),
2077                                 intval(TERM_OBJ_POST),
2078                                 intval($termtype),
2079                                 intval($uid));
2080
2081                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2082                         //      intval($uid)
2083                         //);
2084
2085                         if(count($r)) {
2086                                 unset($deleted_tags[$key]);
2087                         }
2088                         else {
2089                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2090                         }
2091                 }
2092
2093                 if($saved != $filetags_updated) {
2094                         set_pconfig($uid,'system','filetags', $filetags_updated);
2095                 }
2096                 return true;
2097         }
2098         else
2099                 if(strlen($file_new)) {
2100                         set_pconfig($uid,'system','filetags', $file_new);
2101                 }
2102                 return true;
2103 }
2104
2105 function file_tag_save_file($uid,$item,$file) {
2106         require_once("include/files.php");
2107
2108         $result = false;
2109         if(! intval($uid))
2110                 return false;
2111         $r = q("select file from item where id = %d and uid = %d limit 1",
2112                 intval($item),
2113                 intval($uid)
2114         );
2115         if(count($r)) {
2116                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2117                         q("update item set file = '%s' where id = %d and uid = %d",
2118                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2119                                 intval($item),
2120                                 intval($uid)
2121                         );
2122
2123                 create_files_from_item($item);
2124
2125                 $saved = get_pconfig($uid,'system','filetags');
2126                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2127                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2128                 info( t('Item filed') );
2129         }
2130         return true;
2131 }
2132
2133 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2134         require_once("include/files.php");
2135
2136         $result = false;
2137         if(! intval($uid))
2138                 return false;
2139
2140         if($cat == true) {
2141                 $pattern = '<' . file_tag_encode($file) . '>' ;
2142                 $termtype = TERM_CATEGORY;
2143         } else {
2144                 $pattern = '[' . file_tag_encode($file) . ']' ;
2145                 $termtype = TERM_FILE;
2146         }
2147
2148
2149         $r = q("select file from item where id = %d and uid = %d limit 1",
2150                 intval($item),
2151                 intval($uid)
2152         );
2153         if(! count($r))
2154                 return false;
2155
2156         q("update item set file = '%s' where id = %d and uid = %d",
2157                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2158                 intval($item),
2159                 intval($uid)
2160         );
2161
2162         create_files_from_item($item);
2163
2164         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2165                 dbesc($file),
2166                 intval(TERM_OBJ_POST),
2167                 intval($termtype),
2168                 intval($uid));
2169
2170         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2171         //);
2172
2173         if(! count($r)) {
2174                 $saved = get_pconfig($uid,'system','filetags');
2175                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2176
2177         }
2178         return true;
2179 }
2180
2181 function normalise_openid($s) {
2182         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2183 }
2184
2185
2186 function undo_post_tagging($s) {
2187         $matches = null;
2188         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2189         if($cnt) {
2190                 foreach($matches as $mtch) {
2191                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2192                 }
2193         }
2194         return $s;
2195 }
2196
2197 function fix_mce_lf($s) {
2198         $s = str_replace("\r\n","\n",$s);
2199 //      $s = str_replace("\n\n","\n",$s);
2200         return $s;
2201 }
2202
2203
2204 function protect_sprintf($s) {
2205         return(str_replace('%','%%',$s));
2206 }
2207
2208
2209 function is_a_date_arg($s) {
2210         $i = intval($s);
2211         if($i > 1900) {
2212                 $y = date('Y');
2213                 if($i <= $y+1 && strpos($s,'-') == 4) {
2214                         $m = intval(substr($s,5));
2215                         if($m > 0 && $m <= 12)
2216                                 return true;
2217                 }
2218         }
2219         return false;
2220 }