]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge branch 'develop' of github.com:annando/friendica into 1409-shadow-items
[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(urlencode($item["guid"])."-".hash("md5", $item['body']));
1334
1335         if (($cachefile != '')) {
1336                 if (file_exists($cachefile)) {
1337                         $stamp1 = microtime(true);
1338                         $s = file_get_contents($cachefile);
1339                         $a->save_timestamp($stamp1, "file");
1340                 } else {
1341                         redir_private_images($a, $item);
1342                         $s = prepare_text($item['body']);
1343
1344                         $stamp1 = microtime(true);
1345                         file_put_contents($cachefile, $s);
1346                         $a->save_timestamp($stamp1, "file");
1347
1348                         logger('prepare_body: put item '.$item["id"].' into cachefile '.$cachefile);
1349                 }
1350         } else {
1351                 redir_private_images($a, $item);
1352                 $s = prepare_text($item['body']);
1353         }
1354
1355         require_once("mod/proxy.php");
1356         $s = proxy_parse_html($s);
1357
1358         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1359         call_hooks('prepare_body', $prep_arr);
1360         $s = $prep_arr['html'];
1361
1362         if(! $attach) {
1363                 // Replace the blockquotes with quotes that are used in mails
1364                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1365                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1366                 return $s;
1367         }
1368
1369         $as = '';
1370         $vhead = false;
1371         $arr = explode('[/attach],',$item['attach']);
1372         if(count($arr)) {
1373                 $as .= '<div class="body-attach">';
1374                 foreach($arr as $r) {
1375                         $matches = false;
1376                         $icon = '';
1377                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1378                         if($cnt) {
1379                                 foreach($matches as $mtch) {
1380                                         $mime = $mtch[3];
1381
1382                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1383                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1384                                         else
1385                                                 $the_url = $mtch[1];
1386
1387                                         if(strpos($mime, 'video') !== false) {
1388                                                 if(!$vhead) {
1389                                                         $vhead = true;
1390                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1391                                                                 '$baseurl' => $a->get_baseurl(),
1392                                                         ));
1393                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1394                                                                 '$baseurl' => $a->get_baseurl(),
1395                                                         ));
1396                                                 }
1397
1398                                                 $id = end(explode('/', $the_url));
1399                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1400                                                         '$video'        => array(
1401                                                                 'id'       => $id,
1402                                                                 'title'         => t('View Video'),
1403                                                                 'src'           => $the_url,
1404                                                                 'mime'          => $mime,
1405                                                         ),
1406                                                 ));
1407                                         }
1408
1409                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1410                                         if($filetype) {
1411                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1412                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1413                                         }
1414                                         else {
1415                                                 $filetype = 'unkn';
1416                                                 $filesubtype = 'unkn';
1417                                         }
1418
1419                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1420                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1421                                         switch($icontype) {
1422                                                 case 'video':
1423                                                 case 'audio':
1424                                                 case 'image':
1425                                                 case 'text':
1426                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1427                                                         break;
1428                                                 default:
1429                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1430                                                         break;
1431                                         }*/
1432
1433                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1434                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1435
1436                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1437                                 }
1438                         }
1439                 }
1440                 $as .= '<div class="clear"></div></div>';
1441         }
1442         $s = $s . $as;
1443
1444
1445         // Look for spoiler
1446         $spoilersearch = '<blockquote class="spoiler">';
1447
1448         // Remove line breaks before the spoiler
1449         while ((strpos($s, "\n".$spoilersearch) !== false))
1450                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1451         while ((strpos($s, "<br />".$spoilersearch) !== false))
1452                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1453
1454         while ((strpos($s, $spoilersearch) !== false)) {
1455
1456                 $pos = strpos($s, $spoilersearch);
1457                 $rnd = random_string(8);
1458                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1459                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1460                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1461         }
1462
1463         // Look for quote with author
1464         $authorsearch = '<blockquote class="author">';
1465
1466         while ((strpos($s, $authorsearch) !== false)) {
1467
1468                 $pos = strpos($s, $authorsearch);
1469                 $rnd = random_string(8);
1470                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1471                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1472                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1473         }
1474
1475     // replace friendica image url size with theme preference
1476     if (x($a->theme_info,'item_image_size')){
1477         $ps = $a->theme_info['item_image_size'];
1478
1479         $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1480     }
1481
1482         $prep_arr = array('item' => $item, 'html' => $s);
1483         call_hooks('prepare_body_final', $prep_arr);
1484
1485         return $prep_arr['html'];
1486 }}
1487
1488
1489 if(! function_exists('prepare_text')) {
1490 /**
1491  * Given a text string, convert from bbcode to html and add smilie icons.
1492  *
1493  * @param string $text
1494  * @return string
1495  */
1496 function prepare_text($text) {
1497
1498         require_once('include/bbcode.php');
1499
1500         if(stristr($text,'[nosmile]'))
1501                 $s = bbcode($text);
1502         else
1503                 $s = smilies(bbcode($text));
1504
1505         return trim($s);
1506 }}
1507
1508
1509
1510 /**
1511  * return array with details for categories and folders for an item
1512  *
1513  * @param array $item
1514  * @return array
1515  *
1516   * [
1517  *      [ // categories array
1518  *          {
1519  *               'name': 'category name',
1520  *               'removeurl': 'url to remove this category',
1521  *               'first': 'is the first in this array? true/false',
1522  *               'last': 'is the last in this array? true/false',
1523  *           } ,
1524  *           ....
1525  *       ],
1526  *       [ //folders array
1527  *                      {
1528  *               'name': 'folder name',
1529  *               'removeurl': 'url to remove this folder',
1530  *               'first': 'is the first in this array? true/false',
1531  *               'last': 'is the last in this array? true/false',
1532  *           } ,
1533  *           ....
1534  *       ]
1535  *  ]
1536  */
1537 function get_cats_and_terms($item) {
1538
1539     $a = get_app();
1540     $categories = array();
1541     $folders = array();
1542
1543     $matches = false; $first = true;
1544     $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1545     if($cnt) {
1546         foreach($matches as $mtch) {
1547             $categories[] = array(
1548                 'name' => xmlify(file_tag_decode($mtch[1])),
1549                 'url' =>  "#",
1550                 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1551                 'first' => $first,
1552                 'last' => false
1553             );
1554             $first = false;
1555         }
1556     }
1557     if (count($categories)) $categories[count($categories)-1]['last'] = true;
1558
1559
1560         if(local_user() == $item['uid']) {
1561             $matches = false; $first = true;
1562         $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1563             if($cnt) {
1564             foreach($matches as $mtch) {
1565                     $folders[] = array(
1566                     'name' => xmlify(file_tag_decode($mtch[1])),
1567                          'url' =>  "#",
1568                         'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1569                     'first' => $first,
1570                         'last' => false
1571                 );
1572                     $first = false;
1573                         }
1574         }
1575     }
1576
1577     if (count($folders)) $folders[count($folders)-1]['last'] = true;
1578
1579     return array($categories, $folders);
1580 }
1581
1582
1583
1584 if(! function_exists('feed_hublinks')) {
1585 /**
1586  * return atom link elements for all of our hubs
1587  * @return string hub link xml elements
1588  */
1589 function feed_hublinks() {
1590         $a = get_app();
1591         $hub = get_config('system','huburl');
1592
1593         $hubxml = '';
1594         if(strlen($hub)) {
1595                 $hubs = explode(',', $hub);
1596                 if(count($hubs)) {
1597                         foreach($hubs as $h) {
1598                                 $h = trim($h);
1599                                 if(! strlen($h))
1600                                         continue;
1601                                 if ($h === '[internal]')
1602                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1603                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
1604                         }
1605                 }
1606         }
1607         return $hubxml;
1608 }}
1609
1610
1611 if(! function_exists('feed_salmonlinks')) {
1612 /**
1613  * return atom link elements for salmon endpoints
1614  * @param string $nick user nickname
1615  * @return string salmon link xml elements
1616  */
1617 function feed_salmonlinks($nick) {
1618
1619         $a = get_app();
1620
1621         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1622
1623         // old style links that status.net still needed as of 12/2010
1624
1625         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1626         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1627         return $salmon;
1628 }}
1629
1630 if(! function_exists('get_plink')) {
1631 /**
1632  * get private link for item
1633  * @param array $item
1634  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1635  */
1636 function get_plink($item) {
1637         $a = get_app();
1638
1639         if ($a->user['nickname'] != "") {
1640                 $ret = array(
1641                                 //'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
1642                                 'href' => $a->get_baseurl()."/display/".$item['guid'],
1643                                 'orig' => $a->get_baseurl()."/display/".$item['guid'],
1644                                 'title' => t('link to source'),
1645                         );
1646
1647                 if (x($item,'plink'))
1648                         $ret["href"] = $item['plink'];
1649
1650         } elseif (x($item,'plink') && ($item['private'] != 1))
1651                 $ret = array(
1652                                 'href' => $item['plink'],
1653                                 'orig' => $item['plink'],
1654                                 'title' => t('link to source'),
1655                         );
1656         else
1657                 $ret = array();
1658
1659         //if (x($item,'plink') && ($item['private'] != 1))
1660
1661         return($ret);
1662 }}
1663
1664 if(! function_exists('unamp')) {
1665 /**
1666  * replace html amp entity with amp char
1667  * @param string $s
1668  * @return string
1669  */
1670 function unamp($s) {
1671         return str_replace('&amp;', '&', $s);
1672 }}
1673
1674
1675
1676
1677 if(! function_exists('lang_selector')) {
1678 /**
1679  * get html for language selector
1680  * @global string $lang
1681  * @return string
1682  * @template lang_selector.tpl
1683  */
1684 function lang_selector() {
1685         global $lang;
1686
1687         $langs = glob('view/*/strings.php');
1688
1689         $lang_options = array();
1690         $selected = "";
1691
1692         if(is_array($langs) && count($langs)) {
1693                 $langs[] = '';
1694                 if(! in_array('view/en/strings.php',$langs))
1695                         $langs[] = 'view/en/';
1696                 asort($langs);
1697                 foreach($langs as $l) {
1698                         if($l == '') {
1699                                 $lang_options[""] = t('default');
1700                                 continue;
1701                         }
1702                         $ll = substr($l,5);
1703                         $ll = substr($ll,0,strrpos($ll,'/'));
1704                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1705                         $lang_options[$ll]=$ll;
1706                 }
1707         }
1708
1709         $tpl = get_markup_template("lang_selector.tpl");
1710         $o = replace_macros($tpl, array(
1711                 '$title' => t('Select an alternate language'),
1712                 '$langs' => array($lang_options, $selected),
1713
1714         ));
1715         return $o;
1716 }}
1717
1718
1719 if(! function_exists('return_bytes')) {
1720 /**
1721  * return number of bytes in size (K, M, G)
1722  * @param string $size_str
1723  * @return number
1724  */
1725 function return_bytes ($size_str) {
1726     switch (substr ($size_str, -1))
1727     {
1728         case 'M': case 'm': return (int)$size_str * 1048576;
1729         case 'K': case 'k': return (int)$size_str * 1024;
1730         case 'G': case 'g': return (int)$size_str * 1073741824;
1731         default: return $size_str;
1732     }
1733 }}
1734
1735 /**
1736  * @return string
1737  */
1738 function generate_user_guid() {
1739         $found = true;
1740         do {
1741                 $guid = random_string(16);
1742                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1743                         dbesc($guid)
1744                 );
1745                 if(! count($x))
1746                         $found = false;
1747         } while ($found == true );
1748         return $guid;
1749 }
1750
1751
1752 /**
1753  * @param string $s
1754  * @param boolean $strip_padding
1755  * @return string
1756  */
1757 function base64url_encode($s, $strip_padding = false) {
1758
1759         $s = strtr(base64_encode($s),'+/','-_');
1760
1761         if($strip_padding)
1762                 $s = str_replace('=','',$s);
1763
1764         return $s;
1765 }
1766
1767 /**
1768  * @param string $s
1769  * @return string
1770  */
1771 function base64url_decode($s) {
1772
1773         if(is_array($s)) {
1774                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1775                 return $s;
1776         }
1777
1778 /*
1779  *  // Placeholder for new rev of salmon which strips base64 padding.
1780  *  // PHP base64_decode handles the un-padded input without requiring this step
1781  *  // Uncomment if you find you need it.
1782  *
1783  *      $l = strlen($s);
1784  *      if(! strpos($s,'=')) {
1785  *              $m = $l % 4;
1786  *              if($m == 2)
1787  *                      $s .= '==';
1788  *              if($m == 3)
1789  *                      $s .= '=';
1790  *      }
1791  *
1792  */
1793
1794         return base64_decode(strtr($s,'-_','+/'));
1795 }
1796
1797
1798 if (!function_exists('str_getcsv')) {
1799         /**
1800          * Parse csv string
1801          *
1802          * @param string $input
1803          * @param string $delimiter
1804          * @param string $enclosure
1805          * @param string $escape
1806          * @param string $eol
1807          * @return boolean|array False on error, otherwise array[row][column]
1808          */
1809     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1810         if (is_string($input) && !empty($input)) {
1811             $output = array();
1812             $tmp    = preg_split("/".$eol."/",$input);
1813             if (is_array($tmp) && !empty($tmp)) {
1814                 while (list($line_num, $line) = each($tmp)) {
1815                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1816                         while ($strlen = strlen($line)) {
1817                             $pos_delimiter       = strpos($line,$delimiter);
1818                             $pos_enclosure_start = strpos($line,$enclosure);
1819                             if (
1820                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1821                                 && ($pos_enclosure_start < $pos_delimiter)
1822                                 ) {
1823                                 $enclosed_str = substr($line,1);
1824                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1825                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1826                                 $output[$line_num][] = $enclosed_str;
1827                                 $offset = $pos_enclosure_end+3;
1828                             } else {
1829                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1830                                     $output[$line_num][] = substr($line,0);
1831                                     $offset = strlen($line);
1832                                 } else {
1833                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1834                                     $offset = (
1835                                                 !empty($pos_enclosure_start)
1836                                                 && ($pos_enclosure_start < $pos_delimiter)
1837                                                 )
1838                                                 ?$pos_enclosure_start
1839                                                 :$pos_delimiter+1;
1840                                 }
1841                             }
1842                             $line = substr($line,$offset);
1843                         }
1844                     } else {
1845                         $line = preg_split("/".$delimiter."/",$line);
1846
1847                         /*
1848                          * Validating against pesky extra line breaks creating false rows.
1849                          */
1850                         if (is_array($line) && !empty($line[0])) {
1851                             $output[$line_num] = $line;
1852                         }
1853                     }
1854                 }
1855                 return $output;
1856             } else {
1857                 return false;
1858             }
1859         } else {
1860             return false;
1861         }
1862     }
1863 }
1864
1865 /**
1866  * return div element with class 'clear'
1867  * @return string
1868  * @deprecated
1869  */
1870 function cleardiv() {
1871         return '<div class="clear"></div>';
1872 }
1873
1874
1875 function bb_translate_video($s) {
1876
1877         $matches = null;
1878         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1879         if($r) {
1880                 foreach($matches as $mtch) {
1881                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1882                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1883                         elseif(stristr($mtch[1],'vimeo'))
1884                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1885                 }
1886         }
1887         return $s;
1888 }
1889
1890 function html2bb_video($s) {
1891
1892         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1893                         '[youtube]$2[/youtube]', $s);
1894
1895         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1896                         '[youtube]$2[/youtube]', $s);
1897
1898         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1899                         '[vimeo]$2[/vimeo]', $s);
1900
1901         return $s;
1902 }
1903
1904 /**
1905  * apply xmlify() to all values of array $val, recursively
1906  * @param array $val
1907  * @return array
1908  */
1909 function array_xmlify($val){
1910         if (is_bool($val)) return $val?"true":"false";
1911         if (is_array($val)) return array_map('array_xmlify', $val);
1912         return xmlify((string) $val);
1913 }
1914
1915
1916 /**
1917  * transorm link href and img src from relative to absolute
1918  *
1919  * @param string $text
1920  * @param string $base base url
1921  * @return string
1922  */
1923 function reltoabs($text, $base)
1924 {
1925   if (empty($base))
1926     return $text;
1927
1928   $base = rtrim($base,'/');
1929
1930   $base2 = $base . "/";
1931
1932   // Replace links
1933   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1934   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1935   $text = preg_replace($pattern, $replace, $text);
1936
1937   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1938   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1939   $text = preg_replace($pattern, $replace, $text);
1940
1941   // Replace images
1942   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1943   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1944   $text = preg_replace($pattern, $replace, $text);
1945
1946   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1947   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1948   $text = preg_replace($pattern, $replace, $text);
1949
1950
1951   // Done
1952   return $text;
1953 }
1954
1955 /**
1956  * get translated item type
1957  *
1958  * @param array $itme
1959  * @return string
1960  */
1961 function item_post_type($item) {
1962         if(intval($item['event-id']))
1963                 return t('event');
1964         if(strlen($item['resource-id']))
1965                 return t('photo');
1966         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1967                 return t('activity');
1968         if($item['id'] != $item['parent'])
1969                 return t('comment');
1970         return t('post');
1971 }
1972
1973 // post categories and "save to file" use the same item.file table for storage.
1974 // We will differentiate the different uses by wrapping categories in angle brackets
1975 // and save to file categories in square brackets.
1976 // To do this we need to escape these characters if they appear in our tag.
1977
1978 function file_tag_encode($s) {
1979         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
1980 }
1981
1982 function file_tag_decode($s) {
1983         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
1984 }
1985
1986 function file_tag_file_query($table,$s,$type = 'file') {
1987
1988         if($type == 'file')
1989                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
1990         else
1991                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
1992         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
1993 }
1994
1995 // ex. given music,video return <music><video> or [music][video]
1996 function file_tag_list_to_file($list,$type = 'file') {
1997         $tag_list = '';
1998         if(strlen($list)) {
1999                 $list_array = explode(",",$list);
2000                 if($type == 'file') {
2001                         $lbracket = '[';
2002                         $rbracket = ']';
2003                 }
2004                 else {
2005                         $lbracket = '<';
2006                         $rbracket = '>';
2007                 }
2008
2009                 foreach($list_array as $item) {
2010                   if(strlen($item)) {
2011                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
2012                         }
2013                 }
2014         }
2015         return $tag_list;
2016 }
2017
2018 // ex. given <music><video>[friends], return music,video or friends
2019 function file_tag_file_to_list($file,$type = 'file') {
2020         $matches = false;
2021         $list = '';
2022         if($type == 'file') {
2023                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2024         }
2025         else {
2026                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2027         }
2028         if($cnt) {
2029                 foreach($matches as $mtch) {
2030                         if(strlen($list))
2031                                 $list .= ',';
2032                         $list .= file_tag_decode($mtch[1]);
2033                 }
2034         }
2035
2036         return $list;
2037 }
2038
2039 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2040         // $file_old - categories previously associated with an item
2041         // $file_new - new list of categories for an item
2042
2043         if(! intval($uid))
2044                 return false;
2045
2046         if($file_old == $file_new)
2047                 return true;
2048
2049         $saved = get_pconfig($uid,'system','filetags');
2050         if(strlen($saved)) {
2051                 if($type == 'file') {
2052                         $lbracket = '[';
2053                         $rbracket = ']';
2054                         $termtype = TERM_FILE;
2055                 }
2056                 else {
2057                         $lbracket = '<';
2058                         $rbracket = '>';
2059                         $termtype = TERM_CATEGORY;
2060                 }
2061
2062                 $filetags_updated = $saved;
2063
2064                 // check for new tags to be added as filetags in pconfig
2065                 $new_tags = array();
2066                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2067
2068                 foreach($check_new_tags as $tag) {
2069                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2070                                 $new_tags[] = $tag;
2071                 }
2072
2073                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2074
2075                 // check for deleted tags to be removed from filetags in pconfig
2076                 $deleted_tags = array();
2077                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2078
2079                 foreach($check_deleted_tags as $tag) {
2080                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2081                                 $deleted_tags[] = $tag;
2082                 }
2083
2084                 foreach($deleted_tags as $key => $tag) {
2085                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2086                                 dbesc($tag),
2087                                 intval(TERM_OBJ_POST),
2088                                 intval($termtype),
2089                                 intval($uid));
2090
2091                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2092                         //      intval($uid)
2093                         //);
2094
2095                         if(count($r)) {
2096                                 unset($deleted_tags[$key]);
2097                         }
2098                         else {
2099                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2100                         }
2101                 }
2102
2103                 if($saved != $filetags_updated) {
2104                         set_pconfig($uid,'system','filetags', $filetags_updated);
2105                 }
2106                 return true;
2107         }
2108         else
2109                 if(strlen($file_new)) {
2110                         set_pconfig($uid,'system','filetags', $file_new);
2111                 }
2112                 return true;
2113 }
2114
2115 function file_tag_save_file($uid,$item,$file) {
2116         require_once("include/files.php");
2117
2118         $result = false;
2119         if(! intval($uid))
2120                 return false;
2121         $r = q("select file from item where id = %d and uid = %d limit 1",
2122                 intval($item),
2123                 intval($uid)
2124         );
2125         if(count($r)) {
2126                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2127                         q("update item set file = '%s' where id = %d and uid = %d",
2128                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2129                                 intval($item),
2130                                 intval($uid)
2131                         );
2132
2133                 create_files_from_item($item);
2134
2135                 $saved = get_pconfig($uid,'system','filetags');
2136                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2137                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2138                 info( t('Item filed') );
2139         }
2140         return true;
2141 }
2142
2143 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2144         require_once("include/files.php");
2145
2146         $result = false;
2147         if(! intval($uid))
2148                 return false;
2149
2150         if($cat == true) {
2151                 $pattern = '<' . file_tag_encode($file) . '>' ;
2152                 $termtype = TERM_CATEGORY;
2153         } else {
2154                 $pattern = '[' . file_tag_encode($file) . ']' ;
2155                 $termtype = TERM_FILE;
2156         }
2157
2158
2159         $r = q("select file from item where id = %d and uid = %d limit 1",
2160                 intval($item),
2161                 intval($uid)
2162         );
2163         if(! count($r))
2164                 return false;
2165
2166         q("update item set file = '%s' where id = %d and uid = %d",
2167                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2168                 intval($item),
2169                 intval($uid)
2170         );
2171
2172         create_files_from_item($item);
2173
2174         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2175                 dbesc($file),
2176                 intval(TERM_OBJ_POST),
2177                 intval($termtype),
2178                 intval($uid));
2179
2180         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2181         //);
2182
2183         if(! count($r)) {
2184                 $saved = get_pconfig($uid,'system','filetags');
2185                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2186
2187         }
2188         return true;
2189 }
2190
2191 function normalise_openid($s) {
2192         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2193 }
2194
2195
2196 function undo_post_tagging($s) {
2197         $matches = null;
2198         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2199         if($cnt) {
2200                 foreach($matches as $mtch) {
2201                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2202                 }
2203         }
2204         return $s;
2205 }
2206
2207 function fix_mce_lf($s) {
2208         $s = str_replace("\r\n","\n",$s);
2209 //      $s = str_replace("\n\n","\n",$s);
2210         return $s;
2211 }
2212
2213
2214 function protect_sprintf($s) {
2215         return(str_replace('%','%%',$s));
2216 }
2217
2218
2219 function is_a_date_arg($s) {
2220         $i = intval($s);
2221         if($i > 1900) {
2222                 $y = date('Y');
2223                 if($i <= $y+1 && strpos($s,'-') == 4) {
2224                         $m = intval(substr($s,5));
2225                         if($m > 0 && $m <= 12)
2226                                 return true;
2227                 }
2228         }
2229         return false;
2230 }
2231
2232 /**
2233  * remove intentation from a text
2234  */
2235 function deindent($text, $chr="[\t ]", $count=NULL) {
2236         $text = fix_mce_lf($text);
2237         $lines = explode("\n", $text);
2238         if (is_null($count)) {
2239                 $m = array();
2240                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2241                 preg_match("|^".$chr."*|", $lines[$k], $m);
2242                 $count = strlen($m[0]);
2243         }
2244         for ($k=0; $k<count($lines); $k++){
2245                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2246         }
2247
2248         return implode("\n", $lines);
2249 }