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