]> git.mxchange.org Git - friendica.git/blob - include/text.php
Use the GUID to refer to items (preparation for future changes)
[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, $preview = 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, 'preview' => $preview);
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                                 'href' => $a->get_baseurl()."/display/".$item['guid'],
1634                                 'orig' => $a->get_baseurl()."/display/".$item['guid'],
1635                                 'title' => t('link to source'),
1636                         );
1637
1638                 if (x($item,'plink'))
1639                         $ret["href"] = $item['plink'];
1640
1641         } elseif (x($item,'plink') && ($item['private'] != 1))
1642                 $ret = array(
1643                                 'href' => $item['plink'],
1644                                 'orig' => $item['plink'],
1645                                 'title' => t('link to source'),
1646                         );
1647         else
1648                 $ret = array();
1649
1650         //if (x($item,'plink') && ($item['private'] != 1))
1651
1652         return($ret);
1653 }}
1654
1655 if(! function_exists('unamp')) {
1656 /**
1657  * replace html amp entity with amp char
1658  * @param string $s
1659  * @return string
1660  */
1661 function unamp($s) {
1662         return str_replace('&amp;', '&', $s);
1663 }}
1664
1665
1666
1667
1668 if(! function_exists('lang_selector')) {
1669 /**
1670  * get html for language selector
1671  * @global string $lang
1672  * @return string 
1673  * @template lang_selector.tpl
1674  */
1675 function lang_selector() {
1676         global $lang;
1677         
1678         $langs = glob('view/*/strings.php');
1679         
1680         $lang_options = array();
1681         $selected = "";
1682         
1683         if(is_array($langs) && count($langs)) {
1684                 $langs[] = '';
1685                 if(! in_array('view/en/strings.php',$langs))
1686                         $langs[] = 'view/en/';
1687                 asort($langs);
1688                 foreach($langs as $l) {
1689                         if($l == '') {
1690                                 $lang_options[""] = t('default');
1691                                 continue;
1692                         }
1693                         $ll = substr($l,5);
1694                         $ll = substr($ll,0,strrpos($ll,'/'));
1695                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1696                         $lang_options[$ll]=$ll;
1697                 }
1698         }
1699
1700         $tpl = get_markup_template("lang_selector.tpl");        
1701         $o = replace_macros($tpl, array(
1702                 '$title' => t('Select an alternate language'),
1703                 '$langs' => array($lang_options, $selected),
1704                 
1705         ));
1706         return $o;
1707 }}
1708
1709
1710 if(! function_exists('return_bytes')) {
1711 /**
1712  * return number of bytes in size (K, M, G)
1713  * @param string $size_str
1714  * @return number
1715  */
1716 function return_bytes ($size_str) {
1717     switch (substr ($size_str, -1))
1718     {
1719         case 'M': case 'm': return (int)$size_str * 1048576;
1720         case 'K': case 'k': return (int)$size_str * 1024;
1721         case 'G': case 'g': return (int)$size_str * 1073741824;
1722         default: return $size_str;
1723     }
1724 }}
1725
1726 /**
1727  * @return string
1728  */
1729 function generate_user_guid() {
1730         $found = true;
1731         do {
1732                 $guid = random_string(16);
1733                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1734                         dbesc($guid)
1735                 );
1736                 if(! count($x))
1737                         $found = false;
1738         } while ($found == true );
1739         return $guid;
1740 }
1741
1742
1743 /**
1744  * @param string $s
1745  * @param boolean $strip_padding
1746  * @return string
1747  */
1748 function base64url_encode($s, $strip_padding = false) {
1749
1750         $s = strtr(base64_encode($s),'+/','-_');
1751
1752         if($strip_padding)
1753                 $s = str_replace('=','',$s);
1754
1755         return $s;
1756 }
1757
1758 /**
1759  * @param string $s
1760  * @return string
1761  */
1762 function base64url_decode($s) {
1763
1764         if(is_array($s)) {
1765                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1766                 return $s;
1767         }
1768
1769 /*
1770  *  // Placeholder for new rev of salmon which strips base64 padding.
1771  *  // PHP base64_decode handles the un-padded input without requiring this step
1772  *  // Uncomment if you find you need it.
1773  *
1774  *      $l = strlen($s);
1775  *      if(! strpos($s,'=')) {
1776  *              $m = $l % 4;
1777  *              if($m == 2)
1778  *                      $s .= '==';
1779  *              if($m == 3)
1780  *                      $s .= '=';
1781  *      }
1782  *
1783  */
1784
1785         return base64_decode(strtr($s,'-_','+/'));
1786 }
1787
1788
1789 if (!function_exists('str_getcsv')) {
1790         /**
1791          * Parse csv string
1792          * 
1793          * @param string $input
1794          * @param string $delimiter
1795          * @param string $enclosure
1796          * @param string $escape
1797          * @param string $eol
1798          * @return boolean|array False on error, otherwise array[row][column]
1799          */
1800     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1801         if (is_string($input) && !empty($input)) {
1802             $output = array();
1803             $tmp    = preg_split("/".$eol."/",$input);
1804             if (is_array($tmp) && !empty($tmp)) {
1805                 while (list($line_num, $line) = each($tmp)) {
1806                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1807                         while ($strlen = strlen($line)) {
1808                             $pos_delimiter       = strpos($line,$delimiter);
1809                             $pos_enclosure_start = strpos($line,$enclosure);
1810                             if (
1811                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1812                                 && ($pos_enclosure_start < $pos_delimiter)
1813                                 ) {
1814                                 $enclosed_str = substr($line,1);
1815                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1816                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1817                                 $output[$line_num][] = $enclosed_str;
1818                                 $offset = $pos_enclosure_end+3;
1819                             } else {
1820                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1821                                     $output[$line_num][] = substr($line,0);
1822                                     $offset = strlen($line);
1823                                 } else {
1824                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1825                                     $offset = (
1826                                                 !empty($pos_enclosure_start)
1827                                                 && ($pos_enclosure_start < $pos_delimiter)
1828                                                 )
1829                                                 ?$pos_enclosure_start
1830                                                 :$pos_delimiter+1;
1831                                 }
1832                             }
1833                             $line = substr($line,$offset);
1834                         }
1835                     } else {
1836                         $line = preg_split("/".$delimiter."/",$line);
1837    
1838                         /*
1839                          * Validating against pesky extra line breaks creating false rows.
1840                          */
1841                         if (is_array($line) && !empty($line[0])) {
1842                             $output[$line_num] = $line;
1843                         } 
1844                     }
1845                 }
1846                 return $output;
1847             } else {
1848                 return false;
1849             }
1850         } else {
1851             return false;
1852         }
1853     }
1854
1855
1856 /**
1857  * return div element with class 'clear'
1858  * @return string
1859  * @deprecated
1860  */
1861 function cleardiv() {
1862         return '<div class="clear"></div>';
1863 }
1864
1865
1866 function bb_translate_video($s) {
1867
1868         $matches = null;
1869         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1870         if($r) {
1871                 foreach($matches as $mtch) {
1872                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1873                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1874                         elseif(stristr($mtch[1],'vimeo'))
1875                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1876                 }
1877         }
1878         return $s;      
1879 }
1880
1881 function html2bb_video($s) {
1882
1883         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1884                         '[youtube]$2[/youtube]', $s);
1885
1886         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1887                         '[youtube]$2[/youtube]', $s);
1888
1889         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1890                         '[vimeo]$2[/vimeo]', $s);
1891
1892         return $s;
1893 }
1894
1895 /**
1896  * apply xmlify() to all values of array $val, recursively
1897  * @param array $val
1898  * @return array
1899  */
1900 function array_xmlify($val){
1901         if (is_bool($val)) return $val?"true":"false";
1902         if (is_array($val)) return array_map('array_xmlify', $val);
1903         return xmlify((string) $val);
1904 }
1905
1906
1907 /**
1908  * transorm link href and img src from relative to absolute
1909  * 
1910  * @param string $text
1911  * @param string $base base url
1912  * @return string
1913  */
1914 function reltoabs($text, $base)
1915 {
1916   if (empty($base))
1917     return $text;
1918
1919   $base = rtrim($base,'/');
1920
1921   $base2 = $base . "/";
1922         
1923   // Replace links
1924   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1925   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1926   $text = preg_replace($pattern, $replace, $text);
1927
1928   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1929   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1930   $text = preg_replace($pattern, $replace, $text);
1931
1932   // Replace images
1933   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1934   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1935   $text = preg_replace($pattern, $replace, $text); 
1936
1937   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1938   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1939   $text = preg_replace($pattern, $replace, $text); 
1940
1941
1942   // Done
1943   return $text;
1944 }
1945
1946 /**
1947  * get translated item type
1948  * 
1949  * @param array $itme
1950  * @return string
1951  */
1952 function item_post_type($item) {
1953         if(intval($item['event-id']))
1954                 return t('event');
1955         if(strlen($item['resource-id']))
1956                 return t('photo');
1957         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1958                 return t('activity');
1959         if($item['id'] != $item['parent'])
1960                 return t('comment');
1961         return t('post');
1962 }
1963
1964 // post categories and "save to file" use the same item.file table for storage.
1965 // We will differentiate the different uses by wrapping categories in angle brackets
1966 // and save to file categories in square brackets.
1967 // To do this we need to escape these characters if they appear in our tag. 
1968
1969 function file_tag_encode($s) {
1970         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1971 }
1972
1973 function file_tag_decode($s) {
1974         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1975 }
1976
1977 function file_tag_file_query($table,$s,$type = 'file') {
1978
1979         if($type == 'file')
1980                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1981         else
1982                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1983         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1984 }
1985
1986 // ex. given music,video return <music><video> or [music][video]
1987 function file_tag_list_to_file($list,$type = 'file') {
1988         $tag_list = '';
1989         if(strlen($list)) {
1990                 $list_array = explode(",",$list);
1991                 if($type == 'file') {
1992                         $lbracket = '[';
1993                         $rbracket = ']';
1994                 }
1995                 else {
1996                         $lbracket = '<';
1997                         $rbracket = '>';
1998                 }
1999
2000                 foreach($list_array as $item) {
2001                   if(strlen($item)) {
2002                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
2003                         }
2004                 }
2005         }
2006         return $tag_list;
2007 }
2008
2009 // ex. given <music><video>[friends], return music,video or friends
2010 function file_tag_file_to_list($file,$type = 'file') {
2011         $matches = false;
2012         $list = '';
2013         if($type == 'file') {
2014                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2015         }
2016         else {
2017                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2018         }
2019         if($cnt) {
2020                 foreach($matches as $mtch) {
2021                         if(strlen($list))
2022                                 $list .= ',';
2023                         $list .= file_tag_decode($mtch[1]);
2024                 }
2025         }
2026
2027         return $list;
2028 }
2029
2030 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2031         // $file_old - categories previously associated with an item
2032         // $file_new - new list of categories for an item
2033
2034         if(! intval($uid))
2035                 return false;
2036
2037         if($file_old == $file_new)
2038                 return true;
2039
2040         $saved = get_pconfig($uid,'system','filetags');
2041         if(strlen($saved)) {
2042                 if($type == 'file') {
2043                         $lbracket = '[';
2044                         $rbracket = ']';
2045                         $termtype = TERM_FILE;
2046                 }
2047                 else {
2048                         $lbracket = '<';
2049                         $rbracket = '>';
2050                         $termtype = TERM_CATEGORY;
2051                 }
2052
2053                 $filetags_updated = $saved;
2054
2055                 // check for new tags to be added as filetags in pconfig
2056                 $new_tags = array();
2057                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2058
2059                 foreach($check_new_tags as $tag) {
2060                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2061                                 $new_tags[] = $tag;
2062                 }
2063
2064                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2065
2066                 // check for deleted tags to be removed from filetags in pconfig
2067                 $deleted_tags = array();
2068                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2069
2070                 foreach($check_deleted_tags as $tag) {
2071                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2072                                 $deleted_tags[] = $tag;
2073                 }
2074
2075                 foreach($deleted_tags as $key => $tag) {
2076                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2077                                 dbesc($tag),
2078                                 intval(TERM_OBJ_POST),
2079                                 intval($termtype),
2080                                 intval($uid));
2081
2082                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2083                         //      intval($uid)
2084                         //);
2085
2086                         if(count($r)) {
2087                                 unset($deleted_tags[$key]);
2088                         }
2089                         else {
2090                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2091                         }
2092                 }
2093
2094                 if($saved != $filetags_updated) {
2095                         set_pconfig($uid,'system','filetags', $filetags_updated);
2096                 }
2097                 return true;
2098         }
2099         else
2100                 if(strlen($file_new)) {
2101                         set_pconfig($uid,'system','filetags', $file_new);
2102                 }
2103                 return true;
2104 }
2105
2106 function file_tag_save_file($uid,$item,$file) {
2107         require_once("include/files.php");
2108
2109         $result = false;
2110         if(! intval($uid))
2111                 return false;
2112         $r = q("select file from item where id = %d and uid = %d limit 1",
2113                 intval($item),
2114                 intval($uid)
2115         );
2116         if(count($r)) {
2117                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2118                         q("update item set file = '%s' where id = %d and uid = %d",
2119                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2120                                 intval($item),
2121                                 intval($uid)
2122                         );
2123
2124                 create_files_from_item($item);
2125
2126                 $saved = get_pconfig($uid,'system','filetags');
2127                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2128                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2129                 info( t('Item filed') );
2130         }
2131         return true;
2132 }
2133
2134 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2135         require_once("include/files.php");
2136
2137         $result = false;
2138         if(! intval($uid))
2139                 return false;
2140
2141         if($cat == true) {
2142                 $pattern = '<' . file_tag_encode($file) . '>' ;
2143                 $termtype = TERM_CATEGORY;
2144         } else {
2145                 $pattern = '[' . file_tag_encode($file) . ']' ;
2146                 $termtype = TERM_FILE;
2147         }
2148
2149
2150         $r = q("select file from item where id = %d and uid = %d limit 1",
2151                 intval($item),
2152                 intval($uid)
2153         );
2154         if(! count($r))
2155                 return false;
2156
2157         q("update item set file = '%s' where id = %d and uid = %d",
2158                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2159                 intval($item),
2160                 intval($uid)
2161         );
2162
2163         create_files_from_item($item);
2164
2165         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2166                 dbesc($file),
2167                 intval(TERM_OBJ_POST),
2168                 intval($termtype),
2169                 intval($uid));
2170
2171         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2172         //);
2173
2174         if(! count($r)) {
2175                 $saved = get_pconfig($uid,'system','filetags');
2176                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2177
2178         }
2179         return true;
2180 }
2181
2182 function normalise_openid($s) {
2183         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2184 }
2185
2186
2187 function undo_post_tagging($s) {
2188         $matches = null;
2189         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2190         if($cnt) {
2191                 foreach($matches as $mtch) {
2192                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2193                 }
2194         }
2195         return $s;
2196 }
2197
2198 function fix_mce_lf($s) {
2199         $s = str_replace("\r\n","\n",$s);
2200 //      $s = str_replace("\n\n","\n",$s);
2201         return $s;
2202 }
2203
2204
2205 function protect_sprintf($s) {
2206         return(str_replace('%','%%',$s));
2207 }
2208
2209
2210 function is_a_date_arg($s) {
2211         $i = intval($s);
2212         if($i > 1900) {
2213                 $y = date('Y');
2214                 if($i <= $y+1 && strpos($s,'-') == 4) {
2215                         $m = intval(substr($s,5));
2216                         if($m > 0 && $m <= 12)
2217                                 return true;
2218                 }
2219         }
2220         return false;
2221 }