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