]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge pull request #1338 from annando/1501-global-contacts
[friendica.git] / include / text.php
1 <?php
2
3 require_once("include/template_processor.php");
4 require_once("include/friendica_smarty.php");
5 require_once("mod/proxy.php");
6
7 if(! function_exists('replace_macros')) {
8 /**
9  * This is our template processor
10  *
11  * @param string|FriendicaSmarty $s the string requiring macro substitution,
12  *                                                                      or an instance of FriendicaSmarty
13  * @param array $r key value pairs (search => replace)
14  * @return string substituted string
15  */
16 function replace_macros($s,$r) {
17
18         $stamp1 = microtime(true);
19
20         $a = get_app();
21
22         $t = $a->template_engine();
23         try {
24                 $output = $t->replace_macros($s,$r);
25         } catch (Exception $e) {
26                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
27         }
28
29         $a->save_timestamp($stamp1, "rendering");
30
31         return $output;
32 }}
33
34
35 // random string, there are 86 characters max in text mode, 128 for hex
36 // output is urlsafe
37
38 define('RANDOM_STRING_HEX',  0x00 );
39 define('RANDOM_STRING_TEXT', 0x01 );
40
41 if(! function_exists('random_string')) {
42 function random_string($size = 64,$type = RANDOM_STRING_HEX) {
43         // generate a bit of entropy and run it through the whirlpool
44         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
45         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
46         return(substr($s,0,$size));
47 }}
48
49 if(! function_exists('notags')) {
50 /**
51  * This is our primary input filter.
52  *
53  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
54  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
55  * after cleansing, and angle chars with the high bit set could get through as markup.
56  *
57  * This is now disabled because it was interfering with some legitimate unicode sequences
58  * and hopefully there aren't a lot of those browsers left.
59  *
60  * Use this on any text input where angle chars are not valid or permitted
61  * They will be replaced with safer brackets. This may be filtered further
62  * if these are not allowed either.
63  *
64  * @param string $string Input string
65  * @return string Filtered string
66  */
67 function notags($string) {
68
69         return(str_replace(array("<",">"), array('[',']'), $string));
70
71 //  High-bit filter no longer used
72 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
73 }}
74
75
76
77 if(! function_exists('escape_tags')) {
78 /**
79  * use this on "body" or "content" input where angle chars shouldn't be removed,
80  * and allow them to be safely displayed.
81  * @param string $string
82  * @return string
83  */
84 function escape_tags($string) {
85
86         return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
87 }}
88
89
90 // generate a string that's random, but usually pronounceable.
91 // used to generate initial passwords
92
93 if(! function_exists('autoname')) {
94 /**
95  * generate a string that's random, but usually pronounceable.
96  * used to generate initial passwords
97  * @param int $len
98  * @return string
99  */
100 function autoname($len) {
101
102         if($len <= 0)
103                 return '';
104
105         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
106         if(mt_rand(0,5) == 4)
107                 $vowels[] = 'y';
108
109         $cons = array(
110                         'b','bl','br',
111                         'c','ch','cl','cr',
112                         'd','dr',
113                         'f','fl','fr',
114                         'g','gh','gl','gr',
115                         'h',
116                         'j',
117                         'k','kh','kl','kr',
118                         'l',
119                         'm',
120                         'n',
121                         'p','ph','pl','pr',
122                         'qu',
123                         'r','rh',
124                         's','sc','sh','sm','sp','st',
125                         't','th','tr',
126                         'v',
127                         'w','wh',
128                         'x',
129                         'z','zh'
130                         );
131
132         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
133                                 'nd','ng','nk','nt','rn','rp','rt');
134
135         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
136                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
137
138         $start = mt_rand(0,2);
139         if($start == 0)
140                 $table = $vowels;
141         else
142                 $table = $cons;
143
144         $word = '';
145
146         for ($x = 0; $x < $len; $x ++) {
147                 $r = mt_rand(0,count($table) - 1);
148                 $word .= $table[$r];
149
150                 if($table == $vowels)
151                         $table = array_merge($cons,$midcons);
152                 else
153                         $table = $vowels;
154
155         }
156
157         $word = substr($word,0,$len);
158
159         foreach($noend as $noe) {
160                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
161                         $word = substr($word,0,-1);
162                         break;
163                 }
164         }
165         if(substr($word,-1) == 'q')
166                 $word = substr($word,0,-1);
167         return $word;
168 }}
169
170
171 // escape text ($str) for XML transport
172 // returns escaped text.
173
174 if(! function_exists('xmlify')) {
175 /**
176  * escape text ($str) for XML transport
177  * @param string $str
178  * @return string Escaped text.
179  */
180 function xmlify($str) {
181 /*      $buffer = '';
182
183         $len = mb_strlen($str);
184         for($x = 0; $x < $len; $x ++) {
185                 $char = mb_substr($str,$x,1);
186
187                 switch( $char ) {
188
189                         case "\r" :
190                                 break;
191                         case "&" :
192                                 $buffer .= '&amp;';
193                                 break;
194                         case "'" :
195                                 $buffer .= '&apos;';
196                                 break;
197                         case "\"" :
198                                 $buffer .= '&quot;';
199                                 break;
200                         case '<' :
201                                 $buffer .= '&lt;';
202                                 break;
203                         case '>' :
204                                 $buffer .= '&gt;';
205                                 break;
206                         case "\n" :
207                                 $buffer .= "\n";
208                                 break;
209                         default :
210                                 $buffer .= $char;
211                                 break;
212                 }
213         }*/
214         /*
215         $buffer = mb_ereg_replace("&", "&amp;", $str);
216         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
217         $buffer = mb_ereg_replace('"', "&quot;", $buffer);
218         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
219         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
220         */
221         $buffer = htmlspecialchars($str, ENT_QUOTES);
222         $buffer = trim($buffer);
223
224         return($buffer);
225 }}
226
227 if(! function_exists('unxmlify')) {
228 /**
229  * undo an xmlify
230  * @param string $s xml escaped text
231  * @return string unescaped text
232  */
233 function unxmlify($s) {
234 //      $ret = str_replace('&amp;','&', $s);
235 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
236         /*$ret = mb_ereg_replace('&amp;', '&', $s);
237         $ret = mb_ereg_replace('&apos;', "'", $ret);
238         $ret = mb_ereg_replace('&quot;', '"', $ret);
239         $ret = mb_ereg_replace('&lt;', "<", $ret);
240         $ret = mb_ereg_replace('&gt;', ">", $ret);
241         */
242         $ret = htmlspecialchars_decode($s, ENT_QUOTES);
243         return $ret;
244 }}
245
246 if(! function_exists('hex2bin')) {
247 /**
248  * convenience wrapper, reverse the operation "bin2hex"
249  * @param string $s
250  * @return number
251  */
252 function hex2bin($s) {
253         if(! (is_string($s) && strlen($s)))
254                 return '';
255
256         if(! ctype_xdigit($s)) {
257                 return($s);
258         }
259
260         return(pack("H*",$s));
261 }}
262
263
264 if(! function_exists('paginate_data')) {
265 /**
266  * Automatica pagination data.
267  *
268  * @param App $a App instance
269  * @param int $count [optional] item count (used with alt pager)
270  * @return Array data for pagination template
271  */
272 function paginate_data(&$a, $count=null) {
273         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
274
275         $stripped = str_replace('q=','',$stripped);
276         $stripped = trim($stripped,'/');
277         $pagenum = $a->pager['page'];
278
279         if (($a->page_offset != "") AND !strstr($stripped, "&offset="))
280                 $stripped .= "&offset=".urlencode($a->page_offset);
281         if (!strpos($stripped, "?")) {
282                 if ($pos = strpos($stripped, "&"))
283                         $stripped = substr($stripped, 0, $pos)."?".substr($stripped, $pos + 1);
284         }
285
286         $url = $a->get_baseurl() . '/' . $stripped;
287
288         $data = array();
289         function _l(&$d, $name, $url, $text, $class="") {
290
291                 $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
292         }
293
294         if (!is_null($count)){
295                 // alt pager
296                 if($a->pager['page']>1)
297                         _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
298                 if($count>0)
299                         _l($data,  "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
300         } else {
301                 // full pager
302                 if($a->pager['total'] > $a->pager['itemspage']) {
303                         if($a->pager['page'] != 1)
304                                 _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
305
306                         _l($data, "first", $url."&page=1",  t('first'));
307
308
309                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
310
311                         $numstart = 1;
312                         $numstop = $numpages;
313
314                         if($numpages > 14) {
315                                 $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
316                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
317                         }
318
319                         $pages = array();
320
321                         for($i = $numstart; $i <= $numstop; $i++){
322                                 if($i == $a->pager['page'])
323                                         _l($pages, $i, "#",  $i, "current");
324                                 else
325                                         _l($pages, $i, $url."&page=$i", $i, "n");
326                         }
327
328                         if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
329                                 if($i == $a->pager['page'])
330                                         _l($pages, $i, "#",  $i, "current");
331                                 else
332                                         _l($pages, $i, $url."&page=$i", $i, "n");
333                         }
334
335                         $data['pages'] = $pages;
336
337                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
338                         _l($data, "last", $url."&page=$lastpage", t('last'));
339
340                         if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
341                                 _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
342
343                 }
344         }
345         return $data;
346
347 }}
348
349 if(! function_exists('paginate')) {
350 /**
351  * Automatic pagination.
352  *
353  *  To use, get the count of total items.
354  * Then call $a->set_pager_total($number_items);
355  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
356  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
357  * (assuming there are enough items to paginate).
358  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
359  * will limit the results to the correct items for the current page.
360  * The actual page handling is then accomplished at the application layer.
361  *
362  * @param App $a App instance
363  * @return string html for pagination #FIXME remove html
364  */
365 function paginate(&$a) {
366
367         $data = paginate_data($a);
368         $tpl = get_markup_template("paginate.tpl");
369         return replace_macros($tpl, array("pager" => $data));
370
371 }}
372
373 if(! function_exists('alt_pager')) {
374 /**
375  * Alternative pager
376  * @param App $a App instance
377  * @param int $i
378  * @return string html for pagination #FIXME remove html
379  */
380 function alt_pager(&$a, $i) {
381
382         $data = paginate_data($a, $i);
383         $tpl = get_markup_template("paginate.tpl");
384         return replace_macros($tpl, array('pager' => $data));
385
386 }}
387
388
389 if(! function_exists('expand_acl')) {
390 /**
391  * Turn user/group ACLs stored as angle bracketed text into arrays
392  *
393  * @param string $s
394  * @return array
395  */
396 function expand_acl($s) {
397         // turn string array of angle-bracketed elements into numeric array
398         // e.g. "<1><2><3>" => array(1,2,3);
399         $ret = array();
400
401         if(strlen($s)) {
402                 $t = str_replace('<','',$s);
403                 $a = explode('>',$t);
404                 foreach($a as $aa) {
405                         if(intval($aa))
406                                 $ret[] = intval($aa);
407                 }
408         }
409         return $ret;
410 }}
411
412 if(! function_exists('sanitise_acl')) {
413 /**
414  * Wrap ACL elements in angle brackets for storage
415  * @param string $item
416  */
417 function sanitise_acl(&$item) {
418         if(intval($item))
419                 $item = '<' . intval(notags(trim($item))) . '>';
420         else
421                 unset($item);
422 }}
423
424
425 if(! function_exists('perms2str')) {
426 /**
427  * Convert an ACL array to a storable string
428  *
429  * Normally ACL permissions will be an array.
430  * We'll also allow a comma-separated string.
431  *
432  * @param string|array $p
433  * @return string
434  */
435 function perms2str($p) {
436         $ret = '';
437         if(is_array($p))
438                 $tmp = $p;
439         else
440                 $tmp = explode(',',$p);
441
442         if(is_array($tmp)) {
443                 array_walk($tmp,'sanitise_acl');
444                 $ret = implode('',$tmp);
445         }
446         return $ret;
447 }}
448
449
450 if(! function_exists('item_new_uri')) {
451 /**
452  * generate a guaranteed unique (for this domain) item ID for ATOM
453  * safe from birthday paradox
454  *
455  * @param string $hostname
456  * @param int $uid
457  * @return string
458  */
459 function item_new_uri($hostname,$uid) {
460
461         do {
462                 $dups = false;
463                 $hash = random_string();
464
465                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
466
467                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
468                         dbesc($uri));
469                 if(count($r))
470                         $dups = true;
471         } while($dups == true);
472         return $uri;
473 }}
474
475 // Generate a guaranteed unique photo ID.
476 // safe from birthday paradox
477
478 if(! function_exists('photo_new_resource')) {
479 /**
480  * Generate a guaranteed unique photo ID.
481  * safe from birthday paradox
482  *
483  * @return string
484  */
485 function photo_new_resource() {
486
487         do {
488                 $found = false;
489                 $resource = hash('md5',uniqid(mt_rand(),true));
490                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
491                         dbesc($resource)
492                 );
493                 if(count($r))
494                         $found = true;
495         } while($found == true);
496         return $resource;
497 }}
498
499
500 if(! function_exists('load_view_file')) {
501 /**
502  * @deprecated
503  * wrapper to load a view template, checking for alternate
504  * languages before falling back to the default
505  *
506  * @global string $lang
507  * @global App $a
508  * @param string $s view name
509  * @return string
510  */
511 function load_view_file($s) {
512         global $lang, $a;
513         if(! isset($lang))
514                 $lang = 'en';
515         $b = basename($s);
516         $d = dirname($s);
517         if(file_exists("$d/$lang/$b")) {
518                 $stamp1 = microtime(true);
519                 $content = file_get_contents("$d/$lang/$b");
520                 $a->save_timestamp($stamp1, "file");
521                 return $content;
522         }
523
524         $theme = current_theme();
525
526         if(file_exists("$d/theme/$theme/$b")) {
527                 $stamp1 = microtime(true);
528                 $content = file_get_contents("$d/theme/$theme/$b");
529                 $a->save_timestamp($stamp1, "file");
530                 return $content;
531         }
532
533         $stamp1 = microtime(true);
534         $content = file_get_contents($s);
535         $a->save_timestamp($stamp1, "file");
536         return $content;
537 }}
538
539 if(! function_exists('get_intltext_template')) {
540 /**
541  * load a view template, checking for alternate
542  * languages before falling back to the default
543  *
544  * @global string $lang
545  * @param string $s view path
546  * @return string
547  */
548 function get_intltext_template($s) {
549         global $lang;
550
551         $a = get_app();
552         $engine = '';
553         if($a->theme['template_engine'] === 'smarty3')
554                 $engine = "/smarty3";
555
556         if(! isset($lang))
557                 $lang = 'en';
558
559         if(file_exists("view/$lang$engine/$s")) {
560                 $stamp1 = microtime(true);
561                 $content = file_get_contents("view/$lang$engine/$s");
562                 $a->save_timestamp($stamp1, "file");
563                 return $content;
564         } elseif(file_exists("view/en$engine/$s")) {
565                 $stamp1 = microtime(true);
566                 $content = file_get_contents("view/en$engine/$s");
567                 $a->save_timestamp($stamp1, "file");
568                 return $content;
569         } else {
570                 $stamp1 = microtime(true);
571                 $content = file_get_contents("view$engine/$s");
572                 $a->save_timestamp($stamp1, "file");
573                 return $content;
574         }
575 }}
576
577 if(! function_exists('get_markup_template')) {
578 /**
579  * load template $s
580  *
581  * @param string $s
582  * @param string $root
583  * @return string
584  */
585 function get_markup_template($s, $root = '') {
586         $stamp1 = microtime(true);
587
588         $a = get_app();
589         $t = $a->template_engine();
590         try {
591                 $template = $t->get_template_file($s, $root);
592         } catch (Exception $e) {
593                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
594         }
595
596         $a->save_timestamp($stamp1, "file");
597
598         return $template;
599 }}
600
601 if(! function_exists("get_template_file")) {
602 /**
603  *
604  * @param App $a
605  * @param string $filename
606  * @param string $root
607  * @return string
608  */
609 function get_template_file($a, $filename, $root = '') {
610         $theme = current_theme();
611
612         // Make sure $root ends with a slash /
613         if($root !== '' && $root[strlen($root)-1] !== '/')
614                 $root = $root . '/';
615
616         if(file_exists("{$root}view/theme/$theme/$filename"))
617                 $template_file = "{$root}view/theme/$theme/$filename";
618         elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
619                 $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
620         elseif (file_exists("{$root}/$filename"))
621                 $template_file = "{$root}/$filename";
622         else
623                 $template_file = "{$root}view/$filename";
624
625         return $template_file;
626 }}
627
628
629
630
631
632
633
634 if(! function_exists('attribute_contains')) {
635 /**
636  *  for html,xml parsing - let's say you've got
637  *  an attribute foobar="class1 class2 class3"
638  *  and you want to find out if it contains 'class3'.
639  *  you can't use a normal sub string search because you
640  *  might match 'notclass3' and a regex to do the job is
641  *  possible but a bit complicated.
642  *  pass the attribute string as $attr and the attribute you
643  *  are looking for as $s - returns true if found, otherwise false
644  *
645  * @param string $attr attribute value
646  * @param string $s string to search
647  * @return boolean True if found, False otherwise
648  */
649 function attribute_contains($attr,$s) {
650         $a = explode(' ', $attr);
651         if(count($a) && in_array($s,$a))
652                 return true;
653         return false;
654 }}
655
656 if(! function_exists('logger')) {
657 /* setup int->string log level map */
658 $LOGGER_LEVELS = array();
659
660 /**
661  * log levels:
662  * LOGGER_NORMAL (default)
663  * LOGGER_TRACE
664  * LOGGER_DEBUG
665  * LOGGER_DATA
666  * LOGGER_ALL
667  *
668  * @global App $a
669  * @global dba $db
670  * @param string $msg
671  * @param int $level
672  */
673 function logger($msg,$level = 0) {
674         // turn off logger in install mode
675         global $a;
676         global $db;
677         global $LOGGER_LEVELS;
678
679         if(($a->module == 'install') || (! ($db && $db->connected))) return;
680
681         if (count($LOGGER_LEVELS)==0){
682                 foreach (get_defined_constants() as $k=>$v){
683                         if (substr($k,0,7)=="LOGGER_")
684                                 $LOGGER_LEVELS[$v] = substr($k,7,7);
685                 }
686         }
687
688         $debugging = get_config('system','debugging');
689         $loglevel  = intval(get_config('system','loglevel'));
690         $logfile   = get_config('system','logfile');
691
692         if((! $debugging) || (! $logfile) || ($level > $loglevel))
693                 return;
694
695         $callers = debug_backtrace();
696         $logline =  sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
697                                  datetime_convert(),
698                                  session_id(),
699                                  $LOGGER_LEVELS[$level],
700                                  basename($callers[0]['file']),
701                                  $callers[0]['line'],
702                                  $callers[1]['function'],
703                                  $msg
704                                 );
705
706         $stamp1 = microtime(true);
707         @file_put_contents($logfile, $logline, FILE_APPEND);
708         $a->save_timestamp($stamp1, "file");
709         return;
710 }}
711
712
713 if(! function_exists('activity_match')) {
714 /**
715  * Compare activity uri. Knows about activity namespace.
716  *
717  * @param string $haystack
718  * @param string $needle
719  * @return boolean
720  */
721 function activity_match($haystack,$needle) {
722         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
723                 return true;
724         return false;
725 }}
726
727
728 if(! function_exists('get_tags')) {
729 /**
730  * Pull out all #hashtags and @person tags from $s;
731  * We also get @person@domain.com - which would make
732  * the regex quite complicated as tags can also
733  * end a sentence. So we'll run through our results
734  * and strip the period from any tags which end with one.
735  * Returns array of tags found, or empty array.
736  *
737  * @param string $s
738  * @return array
739  */
740 function get_tags($s) {
741         $ret = array();
742
743         // ignore anything in a code block
744         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
745
746         // Force line feeds at bbtags
747         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
748
749         // ignore anything in a bbtag
750         $s = preg_replace('/\[(.*?)\]/sm','',$s);
751
752         // Match full names against @tags including the space between first and last
753         // We will look these up afterward to see if they are full names or not recognisable.
754
755         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
756                 foreach($match[1] as $mtch) {
757                         if(strstr($mtch,"]")) {
758                                 // we might be inside a bbcode color tag - leave it alone
759                                 continue;
760                         }
761                         if(substr($mtch,-1,1) === '.')
762                                 $ret[] = substr($mtch,0,-1);
763                         else
764                                 $ret[] = $mtch;
765                 }
766         }
767
768         // Otherwise pull out single word tags. These can be @nickname, @first_last
769         // and #hash tags.
770
771         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
772                 foreach($match[1] as $mtch) {
773                         if(strstr($mtch,"]")) {
774                                 // we might be inside a bbcode color tag - leave it alone
775                                 continue;
776                         }
777                         if(substr($mtch,-1,1) === '.')
778                                 $mtch = substr($mtch,0,-1);
779                         // ignore strictly numeric tags like #1
780                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
781                                 continue;
782                         // try not to catch url fragments
783                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
784                                 continue;
785                         $ret[] = $mtch;
786                 }
787         }
788         return $ret;
789 }}
790
791
792 //
793
794 if(! function_exists('qp')) {
795 /**
796  * quick and dirty quoted_printable encoding
797  *
798  * @param string $s
799  * @return string
800  */
801 function qp($s) {
802 return str_replace ("%","=",rawurlencode($s));
803 }}
804
805
806
807 if(! function_exists('get_mentions')) {
808 /**
809  * @param array $item
810  * @return string html for mentions #FIXME: remove html
811  */
812 function get_mentions($item) {
813         $o = '';
814         if(! strlen($item['tag']))
815                 return $o;
816
817         $arr = explode(',',$item['tag']);
818         foreach($arr as $x) {
819                 $matches = null;
820                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
821                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
822                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
823                 }
824         }
825         return $o;
826 }}
827
828 if(! function_exists('contact_block')) {
829 /**
830  * Get html for contact block.
831  *
832  * @template contact_block.tpl
833  * @hook contact_block_end (contacts=>array, output=>string)
834  * @return string
835  */
836 function contact_block() {
837         $o = '';
838         $a = get_app();
839
840         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
841         if($shown === false)
842                 $shown = 24;
843         if($shown == 0)
844                 return;
845
846         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
847                 return $o;
848         $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",
849                         intval($a->profile['uid'])
850         );
851         if(count($r)) {
852                 $total = intval($r[0]['total']);
853         }
854         if(! $total) {
855                 $contacts = t('No contacts');
856                 $micropro = Null;
857
858         } else {
859                 $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",
860                                 intval($a->profile['uid']),
861                                 intval($shown)
862                 );
863                 if(count($r)) {
864                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
865                         $micropro = Array();
866                         foreach($r as $rr) {
867                                 $micropro[] = micropro($rr,true,'mpfriend');
868                         }
869                 }
870         }
871
872         $tpl = get_markup_template('contact_block.tpl');
873         $o = replace_macros($tpl, array(
874                 '$contacts' => $contacts,
875                 '$nickname' => $a->profile['nickname'],
876                 '$viewcontacts' => t('View Contacts'),
877                 '$micropro' => $micropro,
878         ));
879
880         $arr = array('contacts' => $r, 'output' => $o);
881
882         call_hooks('contact_block_end', $arr);
883         return $o;
884
885 }}
886
887 if(! function_exists('micropro')) {
888 /**
889  *
890  * @param array $contact
891  * @param boolean $redirect
892  * @param string $class
893  * @param boolean $textmode
894  * @return string #FIXME: remove html
895  */
896 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
897
898         if($class)
899                 $class = ' ' . $class;
900
901         $url = $contact['url'];
902         $sparkle = '';
903         $redir = false;
904
905         if($redirect) {
906                 $a = get_app();
907                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
908                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
909                         $redir = true;
910                         $url = $redirect_url;
911                         $sparkle = ' sparkle';
912                 }
913                 else
914                         $url = zrl($url);
915         }
916         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
917         if($click)
918                 $url = '';
919         if($textmode) {
920                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle
921                         . (($click) ? ' fakelink' : '') . '" '
922                         . (($redir) ? ' target="redir" ' : '')
923                         . (($url) ? ' href="' . $url . '"' : '') . $click
924                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
925                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
926         }
927         else {
928                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle
929                         . (($click) ? ' fakelink' : '') . '" '
930                         . (($redir) ? ' target="redir" ' : '')
931                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="'
932                         . proxy_url($contact['micro']) . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
933                         . '" /></a></div>' . "\r\n";
934         }
935 }}
936
937
938
939 if(! function_exists('search')) {
940 /**
941  * search box
942  *
943  * @param string $s search query
944  * @param string $id html id
945  * @param string $url search url
946  * @param boolean $save show save search button
947  * @return string html for search box #FIXME: remove html
948  */
949 function search($s,$id='search-box',$url='/search',$save = false) {
950         $a = get_app();
951         $o  = '<div id="' . $id . '">';
952         $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >';
953         $o .= '<input type="text" name="search" id="search-text" placeholder="' . t('Search') . '" value="' . $s .'" />';
954         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />';
955         if($save)
956                 $o .= '<input type="submit" name="save" id="search-save" value="' . t('Save') . '" />';
957         $o .= '</form></div>';
958         return $o;
959 }}
960
961 if(! function_exists('valid_email')) {
962 /**
963  * Check if $x is a valid email string
964  *
965  * @param string $x
966  * @return boolean
967  */
968 function valid_email($x){
969
970         if(get_config('system','disable_email_validation'))
971                 return true;
972
973         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
974                 return true;
975         return false;
976 }}
977
978
979 if(! function_exists('linkify')) {
980 /**
981  * Replace naked text hyperlink with HTML formatted hyperlink
982  *
983  * @param string $s
984  */
985 function linkify($s) {
986         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
987         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
988         return($s);
989 }}
990
991
992 /**
993  * Load poke verbs
994  *
995  * @return array index is present tense verb
996                                  value is array containing past tense verb, translation of present, translation of past
997  * @hook poke_verbs pokes array
998  */
999 function get_poke_verbs() {
1000
1001         // index is present tense verb
1002         // value is array containing past tense verb, translation of present, translation of past
1003
1004         $arr = array(
1005                 'poke' => array( 'poked', t('poke'), t('poked')),
1006                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1007                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1008                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1009                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1010                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1011         );
1012         call_hooks('poke_verbs', $arr);
1013         return $arr;
1014 }
1015
1016 /**
1017  * Load moods
1018  * @return array index is mood, value is translated mood
1019  * @hook mood_verbs moods array
1020  */
1021 function get_mood_verbs() {
1022
1023         $arr = array(
1024                 'happy'      => t('happy'),
1025                 'sad'        => t('sad'),
1026                 'mellow'     => t('mellow'),
1027                 'tired'      => t('tired'),
1028                 'perky'      => t('perky'),
1029                 'angry'      => t('angry'),
1030                 'stupefied'  => t('stupified'),
1031                 'puzzled'    => t('puzzled'),
1032                 'interested' => t('interested'),
1033                 'bitter'     => t('bitter'),
1034                 'cheerful'   => t('cheerful'),
1035                 'alive'      => t('alive'),
1036                 'annoyed'    => t('annoyed'),
1037                 'anxious'    => t('anxious'),
1038                 'cranky'     => t('cranky'),
1039                 'disturbed'  => t('disturbed'),
1040                 'frustrated' => t('frustrated'),
1041                 'motivated'  => t('motivated'),
1042                 'relaxed'    => t('relaxed'),
1043                 'surprised'  => t('surprised'),
1044         );
1045
1046         call_hooks('mood_verbs', $arr);
1047         return $arr;
1048 }
1049
1050
1051
1052 if(! function_exists('smilies')) {
1053 /**
1054  * Replaces text emoticons with graphical images
1055  *
1056  * It is expected that this function will be called using HTML text.
1057  * We will escape text between HTML pre and code blocks from being
1058  * processed.
1059  *
1060  * At a higher level, the bbcode [nosmile] tag can be used to prevent this
1061  * function from being executed by the prepare_text() routine when preparing
1062  * bbcode source for HTML display
1063  *
1064  * @param string $s
1065  * @param boolean $sample
1066  * @return string
1067  * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
1068  */
1069 function smilies($s, $sample = false) {
1070         $a = get_app();
1071
1072         if(intval(get_config('system','no_smilies'))
1073                 || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
1074                 return $s;
1075
1076         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
1077         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
1078
1079         $texts =  array(
1080                 '&lt;3',
1081                 '&lt;/3',
1082                 '&lt;\\3',
1083                 ':-)',
1084                 ';-)',
1085                 ':-(',
1086                 ':-P',
1087                 ':-p',
1088                 ':-"',
1089                 ':-&quot;',
1090                 ':-x',
1091                 ':-X',
1092                 ':-D',
1093                 '8-|',
1094                 '8-O',
1095                 ':-O',
1096                 '\\o/',
1097                 'o.O',
1098                 'O.o',
1099                 'o_O',
1100                 'O_o',
1101                 ":'(",
1102                 ":-!",
1103                 ":-/",
1104                 ":-[",
1105                 "8-)",
1106                 ':beer',
1107                 ':homebrew',
1108                 ':coffee',
1109                 ':facepalm',
1110                 ':like',
1111                 ':dislike',
1112                 '~friendica',
1113                 'red#'
1114
1115         );
1116
1117         $icons = array(
1118                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.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-brokenheart.gif" alt="<\\3" />',
1121                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1122                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1123                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
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-tongue-out.gif" alt=":-p" />',
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=":-\"" />',
1128                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1129                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1130                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1131                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1132                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />',
1133                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt=":-O" />',
1134                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-thumbsup.gif" alt="\\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-Oo.gif" alt="O_o" />',
1139                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cry.gif" alt=":\'(" />',
1140                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
1141                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-undecided.gif" alt=":-/" />',
1142                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-embarassed.gif" alt=":-[" />',
1143                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cool.gif" alt="8-)" />',
1144                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":beer" />',
1145                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":homebrew" />',
1146                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/coffee.gif" alt=":coffee" />',
1147                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
1148                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />',
1149                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />',
1150                 '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>',
1151                 '<a href="http://redmatrix.me/">red <img class="smiley" src="' . $a->get_baseurl() . '/images/rhash-16.png" alt="red" /></a>'
1152         );
1153
1154         $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
1155         call_hooks('smilie', $params);
1156
1157         if($sample) {
1158                 $s = '<div class="smiley-sample">';
1159                 for($x = 0; $x < count($params['texts']); $x ++) {
1160                         $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
1161                 }
1162         }
1163         else {
1164                 $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
1165                 $s = str_replace($params['texts'],$params['icons'],$params['string']);
1166         }
1167
1168         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
1169         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
1170
1171         return $s;
1172
1173 }}
1174
1175 function smile_encode($m) {
1176         return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
1177 }
1178
1179 function smile_decode($m) {
1180         return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
1181 }
1182
1183
1184 /**
1185  * expand <3333 to the correct number of hearts
1186  *
1187  * @param string $x
1188  * @return string
1189  */
1190 function preg_heart($x) {
1191         $a = get_app();
1192         if(strlen($x[1]) == 1)
1193                 return $x[0];
1194         $t = '';
1195         for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
1196                 $t .= '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />';
1197         $r =  str_replace($x[0],$t,$x[0]);
1198         return $r;
1199 }
1200
1201
1202 if(! function_exists('day_translate')) {
1203 /**
1204  * Translate days and months names
1205  *
1206  * @param string $s
1207  * @return string
1208  */
1209 function day_translate($s) {
1210         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1211                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1212                 $s);
1213
1214         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1215                 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')),
1216                 $ret);
1217
1218         return $ret;
1219 }}
1220
1221
1222 if(! function_exists('normalise_link')) {
1223 /**
1224  * Normalize url
1225  *
1226  * @param string $url
1227  * @return string
1228  */
1229 function normalise_link($url) {
1230         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1231         return(rtrim($ret,'/'));
1232 }}
1233
1234
1235
1236 if(! function_exists('link_compare')) {
1237 /**
1238  * Compare two URLs to see if they are the same, but ignore
1239  * slight but hopefully insignificant differences such as if one
1240  * is https and the other isn't, or if one is www.something and
1241  * the other isn't - and also ignore case differences.
1242  *
1243  * @param string $a first url
1244  * @param string $b second url
1245  * @return boolean True if the URLs match, otherwise False
1246  *
1247  */
1248 function link_compare($a,$b) {
1249         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1250                 return true;
1251         return false;
1252 }}
1253
1254
1255 if(! function_exists('redir_private_images')) {
1256 /**
1257  * Find any non-embedded images in private items and add redir links to them
1258  *
1259  * @param App $a
1260  * @param array $item
1261  */
1262 function redir_private_images($a, &$item) {
1263
1264         $matches = false;
1265         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1266         if($cnt) {
1267                 //logger("redir_private_images: matches = " . print_r($matches, true));
1268                 foreach($matches as $mtch) {
1269                         if(strpos($mtch[1], '/redir') !== false)
1270                                 continue;
1271
1272                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1273                                 //logger("redir_private_images: redir");
1274                                 $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1275                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1276                         }
1277                 }
1278         }
1279
1280 }}
1281
1282
1283 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1284 // If attach is true, also add icons for item attachments
1285
1286 if(! function_exists('prepare_body')) {
1287 /**
1288  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1289  * If attach is true, also add icons for item attachments
1290  *
1291  * @param array $item
1292  * @param boolean $attach
1293  * @return string item body html
1294  * @hook prepare_body_init item array before any work
1295  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1296  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1297  */
1298 function prepare_body(&$item,$attach = false, $preview = false) {
1299
1300         $a = get_app();
1301         call_hooks('prepare_body_init', $item);
1302
1303         $searchpath = $a->get_baseurl()."/search?tag=";
1304
1305         $tags=array();
1306         $hashtags = array();
1307         $mentions = array();
1308
1309         if (!get_config('system','suppress_tags')) {
1310                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1311                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1312
1313                 foreach($taglist as $tag) {
1314
1315                         if ($tag["url"] == "")
1316                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1317
1318                         if ($tag["type"] == TERM_HASHTAG) {
1319                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1320                                 $prefix = "#";
1321                         } elseif ($tag["type"] == TERM_MENTION) {
1322                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1323                                 $prefix = "@";
1324                         }
1325                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1326                 }
1327         }
1328
1329         $item['tags'] = $tags;
1330         $item['hashtags'] = $hashtags;
1331         $item['mentions'] = $mentions;
1332
1333
1334         $cachefile = get_cachefile(urlencode($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 trim($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 }
2232
2233 /**
2234  * remove intentation from a text
2235  */
2236 function deindent($text, $chr="[\t ]", $count=NULL) {
2237         $text = fix_mce_lf($text);
2238         $lines = explode("\n", $text);
2239         if (is_null($count)) {
2240                 $m = array();
2241                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2242                 preg_match("|^".$chr."*|", $lines[$k], $m);
2243                 $count = strlen($m[0]);
2244         }
2245         for ($k=0; $k<count($lines); $k++){
2246                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2247         }
2248
2249         return implode("\n", $lines);
2250 }