]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge branch 'master' into develop
[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("include/map.php");
6 require_once("mod/proxy.php");
7
8
9 if(! function_exists('replace_macros')) {
10 /**
11  * This is our template processor
12  *
13  * @param string|FriendicaSmarty $s the string requiring macro substitution,
14  *                                                                      or an instance of FriendicaSmarty
15  * @param array $r key value pairs (search => replace)
16  * @return string substituted string
17  */
18 function replace_macros($s,$r) {
19
20         $stamp1 = microtime(true);
21
22         $a = get_app();
23
24         $t = $a->template_engine();
25         try {
26                 $output = $t->replace_macros($s,$r);
27         } catch (Exception $e) {
28                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
29         }
30
31         $a->save_timestamp($stamp1, "rendering");
32
33         return $output;
34 }}
35
36
37 // random string, there are 86 characters max in text mode, 128 for hex
38 // output is urlsafe
39
40 define('RANDOM_STRING_HEX',  0x00 );
41 define('RANDOM_STRING_TEXT', 0x01 );
42
43 if(! function_exists('random_string')) {
44 function random_string($size = 64,$type = RANDOM_STRING_HEX) {
45         // generate a bit of entropy and run it through the whirlpool
46         $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
47         $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
48         return(substr($s,0,$size));
49 }}
50
51 if(! function_exists('notags')) {
52 /**
53  * This is our primary input filter.
54  *
55  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
56  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
57  * after cleansing, and angle chars with the high bit set could get through as markup.
58  *
59  * This is now disabled because it was interfering with some legitimate unicode sequences
60  * and hopefully there aren't a lot of those browsers left.
61  *
62  * Use this on any text input where angle chars are not valid or permitted
63  * They will be replaced with safer brackets. This may be filtered further
64  * if these are not allowed either.
65  *
66  * @param string $string Input string
67  * @return string Filtered string
68  */
69 function notags($string) {
70
71         return(str_replace(array("<",">"), array('[',']'), $string));
72
73 //  High-bit filter no longer used
74 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
75 }}
76
77
78
79 if(! function_exists('escape_tags')) {
80 /**
81  * use this on "body" or "content" input where angle chars shouldn't be removed,
82  * and allow them to be safely displayed.
83  * @param string $string
84  * @return string
85  */
86 function escape_tags($string) {
87
88         return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
89 }}
90
91
92 // generate a string that's random, but usually pronounceable.
93 // used to generate initial passwords
94
95 if(! function_exists('autoname')) {
96 /**
97  * generate a string that's random, but usually pronounceable.
98  * used to generate initial passwords
99  * @param int $len
100  * @return string
101  */
102 function autoname($len) {
103
104         if($len <= 0)
105                 return '';
106
107         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
108         if(mt_rand(0,5) == 4)
109                 $vowels[] = 'y';
110
111         $cons = array(
112                         'b','bl','br',
113                         'c','ch','cl','cr',
114                         'd','dr',
115                         'f','fl','fr',
116                         'g','gh','gl','gr',
117                         'h',
118                         'j',
119                         'k','kh','kl','kr',
120                         'l',
121                         'm',
122                         'n',
123                         'p','ph','pl','pr',
124                         'qu',
125                         'r','rh',
126                         's','sc','sh','sm','sp','st',
127                         't','th','tr',
128                         'v',
129                         'w','wh',
130                         'x',
131                         'z','zh'
132                         );
133
134         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
135                                 'nd','ng','nk','nt','rn','rp','rt');
136
137         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
138                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
139
140         $start = mt_rand(0,2);
141         if($start == 0)
142                 $table = $vowels;
143         else
144                 $table = $cons;
145
146         $word = '';
147
148         for ($x = 0; $x < $len; $x ++) {
149                 $r = mt_rand(0,count($table) - 1);
150                 $word .= $table[$r];
151
152                 if($table == $vowels)
153                         $table = array_merge($cons,$midcons);
154                 else
155                         $table = $vowels;
156
157         }
158
159         $word = substr($word,0,$len);
160
161         foreach($noend as $noe) {
162                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
163                         $word = substr($word,0,-1);
164                         break;
165                 }
166         }
167         if(substr($word,-1) == 'q')
168                 $word = substr($word,0,-1);
169         return $word;
170 }}
171
172
173 // escape text ($str) for XML transport
174 // returns escaped text.
175
176 if(! function_exists('xmlify')) {
177 /**
178  * escape text ($str) for XML transport
179  * @param string $str
180  * @return string Escaped text.
181  */
182 function xmlify($str) {
183 /*      $buffer = '';
184
185         $len = mb_strlen($str);
186         for($x = 0; $x < $len; $x ++) {
187                 $char = mb_substr($str,$x,1);
188
189                 switch( $char ) {
190
191                         case "\r" :
192                                 break;
193                         case "&" :
194                                 $buffer .= '&amp;';
195                                 break;
196                         case "'" :
197                                 $buffer .= '&apos;';
198                                 break;
199                         case "\"" :
200                                 $buffer .= '&quot;';
201                                 break;
202                         case '<' :
203                                 $buffer .= '&lt;';
204                                 break;
205                         case '>' :
206                                 $buffer .= '&gt;';
207                                 break;
208                         case "\n" :
209                                 $buffer .= "\n";
210                                 break;
211                         default :
212                                 $buffer .= $char;
213                                 break;
214                 }
215         }*/
216         /*
217         $buffer = mb_ereg_replace("&", "&amp;", $str);
218         $buffer = mb_ereg_replace("'", "&apos;", $buffer);
219         $buffer = mb_ereg_replace('"', "&quot;", $buffer);
220         $buffer = mb_ereg_replace("<", "&lt;", $buffer);
221         $buffer = mb_ereg_replace(">", "&gt;", $buffer);
222         */
223         $buffer = htmlspecialchars($str, ENT_QUOTES);
224         $buffer = trim($buffer);
225
226         return($buffer);
227 }}
228
229 if(! function_exists('unxmlify')) {
230 /**
231  * undo an xmlify
232  * @param string $s xml escaped text
233  * @return string unescaped text
234  */
235 function unxmlify($s) {
236 //      $ret = str_replace('&amp;','&', $s);
237 //      $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
238         /*$ret = mb_ereg_replace('&amp;', '&', $s);
239         $ret = mb_ereg_replace('&apos;', "'", $ret);
240         $ret = mb_ereg_replace('&quot;', '"', $ret);
241         $ret = mb_ereg_replace('&lt;', "<", $ret);
242         $ret = mb_ereg_replace('&gt;', ">", $ret);
243         */
244         $ret = htmlspecialchars_decode($s, ENT_QUOTES);
245         return $ret;
246 }}
247
248 if(! function_exists('hex2bin')) {
249 /**
250  * convenience wrapper, reverse the operation "bin2hex"
251  * @param string $s
252  * @return number
253  */
254 function hex2bin($s) {
255         if(! (is_string($s) && strlen($s)))
256                 return '';
257
258         if(! ctype_xdigit($s)) {
259                 return($s);
260         }
261
262         return(pack("H*",$s));
263 }}
264
265
266 if(! function_exists('paginate_data')) {
267 /**
268  * Automatica pagination data.
269  *
270  * @param App $a App instance
271  * @param int $count [optional] item count (used with alt pager)
272  * @return Array data for pagination template
273  */
274 function paginate_data(&$a, $count=null) {
275         $stripped = preg_replace('/([&?]page=[0-9]*)/','',$a->query_string);
276
277         $stripped = str_replace('q=','',$stripped);
278         $stripped = trim($stripped,'/');
279         $pagenum = $a->pager['page'];
280
281         if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped))
282                 $stripped .= "&offset=".urlencode($a->page_offset);
283
284         $url = $a->get_baseurl() . '/' . $stripped;
285
286         $data = array();
287         function _l(&$d, $name, $url, $text, $class="") {
288                 if (!strpos($url, "?")) {
289                         if ($pos = strpos($url, "&"))
290                                 $url = substr($url, 0, $pos)."?".substr($url, $pos + 1);
291                 }
292
293                 $d[$name] = array('url'=>$url, 'text'=>$text, 'class'=>$class);
294         }
295
296         if (!is_null($count)){
297                 // alt pager
298                 if($a->pager['page']>1)
299                         _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('newer'));
300                 if($count>0)
301                         _l($data,  "next", $url.'&page='.($a->pager['page'] + 1), t('older'));
302         } else {
303                 // full pager
304                 if($a->pager['total'] > $a->pager['itemspage']) {
305                         if($a->pager['page'] != 1)
306                                 _l($data,  "prev", $url.'&page='.($a->pager['page'] - 1), t('prev'));
307
308                         _l($data, "first", $url."&page=1",  t('first'));
309
310
311                         $numpages = $a->pager['total'] / $a->pager['itemspage'];
312
313                         $numstart = 1;
314                         $numstop = $numpages;
315
316                         if($numpages > 14) {
317                                 $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
318                                 $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
319                         }
320
321                         $pages = array();
322
323                         for($i = $numstart; $i <= $numstop; $i++){
324                                 if($i == $a->pager['page'])
325                                         _l($pages, $i, "#",  $i, "current");
326                                 else
327                                         _l($pages, $i, $url."&page=$i", $i, "n");
328                         }
329
330                         if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
331                                 if($i == $a->pager['page'])
332                                         _l($pages, $i, "#",  $i, "current");
333                                 else
334                                         _l($pages, $i, $url."&page=$i", $i, "n");
335                         }
336
337                         $data['pages'] = $pages;
338
339                         $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
340                         _l($data, "last", $url."&page=$lastpage", t('last'));
341
342                         if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
343                                 _l($data, "next", $url."&page=".($a->pager['page'] + 1), t('next'));
344
345                 }
346         }
347         return $data;
348
349 }}
350
351 if(! function_exists('paginate')) {
352 /**
353  * Automatic pagination.
354  *
355  *  To use, get the count of total items.
356  * Then call $a->set_pager_total($number_items);
357  * Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
358  * Then call paginate($a) after the end of the display loop to insert the pager block on the page
359  * (assuming there are enough items to paginate).
360  * When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
361  * will limit the results to the correct items for the current page.
362  * The actual page handling is then accomplished at the application layer.
363  *
364  * @param App $a App instance
365  * @return string html for pagination #FIXME remove html
366  */
367 function paginate(&$a) {
368
369         $data = paginate_data($a);
370         $tpl = get_markup_template("paginate.tpl");
371         return replace_macros($tpl, array("pager" => $data));
372
373 }}
374
375 if(! function_exists('alt_pager')) {
376 /**
377  * Alternative pager
378  * @param App $a App instance
379  * @param int $i
380  * @return string html for pagination #FIXME remove html
381  */
382 function alt_pager(&$a, $i) {
383
384         $data = paginate_data($a, $i);
385         $tpl = get_markup_template("paginate.tpl");
386         return replace_macros($tpl, array('pager' => $data));
387
388 }}
389
390 if(! function_exists('scroll_loader')) {
391 /**
392  * Loader for infinite scrolling
393  * @return string html for loader
394  */
395 function scroll_loader() {
396         $tpl = get_markup_template("scroll_loader.tpl");
397         return replace_macros($tpl, array(
398                 'wait' => t('Loading more entries...'),
399                 'end' => t('The end')
400         ));
401 }}
402
403 if(! function_exists('expand_acl')) {
404 /**
405  * Turn user/group ACLs stored as angle bracketed text into arrays
406  *
407  * @param string $s
408  * @return array
409  */
410 function expand_acl($s) {
411         // turn string array of angle-bracketed elements into numeric array
412         // e.g. "<1><2><3>" => array(1,2,3);
413         $ret = array();
414
415         if(strlen($s)) {
416                 $t = str_replace('<','',$s);
417                 $a = explode('>',$t);
418                 foreach($a as $aa) {
419                         if(intval($aa))
420                                 $ret[] = intval($aa);
421                 }
422         }
423         return $ret;
424 }}
425
426 if(! function_exists('sanitise_acl')) {
427 /**
428  * Wrap ACL elements in angle brackets for storage
429  * @param string $item
430  */
431 function sanitise_acl(&$item) {
432         if(intval($item))
433                 $item = '<' . intval(notags(trim($item))) . '>';
434         else
435                 unset($item);
436 }}
437
438
439 if(! function_exists('perms2str')) {
440 /**
441  * Convert an ACL array to a storable string
442  *
443  * Normally ACL permissions will be an array.
444  * We'll also allow a comma-separated string.
445  *
446  * @param string|array $p
447  * @return string
448  */
449 function perms2str($p) {
450         $ret = '';
451         if(is_array($p))
452                 $tmp = $p;
453         else
454                 $tmp = explode(',',$p);
455
456         if(is_array($tmp)) {
457                 array_walk($tmp,'sanitise_acl');
458                 $ret = implode('',$tmp);
459         }
460         return $ret;
461 }}
462
463
464 if(! function_exists('item_new_uri')) {
465 /**
466  * generate a guaranteed unique (for this domain) item ID for ATOM
467  * safe from birthday paradox
468  *
469  * @param string $hostname
470  * @param int $uid
471  * @return string
472  */
473 function item_new_uri($hostname,$uid) {
474
475         do {
476                 $dups = false;
477                 $hash = random_string();
478
479                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
480
481                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
482                         dbesc($uri));
483                 if(count($r))
484                         $dups = true;
485         } while($dups == true);
486         return $uri;
487 }}
488
489 // Generate a guaranteed unique photo ID.
490 // safe from birthday paradox
491
492 if(! function_exists('photo_new_resource')) {
493 /**
494  * Generate a guaranteed unique photo ID.
495  * safe from birthday paradox
496  *
497  * @return string
498  */
499 function photo_new_resource() {
500
501         do {
502                 $found = false;
503                 $resource = hash('md5',uniqid(mt_rand(),true));
504                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
505                         dbesc($resource)
506                 );
507                 if(count($r))
508                         $found = true;
509         } while($found == true);
510         return $resource;
511 }}
512
513
514 if(! function_exists('load_view_file')) {
515 /**
516  * @deprecated
517  * wrapper to load a view template, checking for alternate
518  * languages before falling back to the default
519  *
520  * @global string $lang
521  * @global App $a
522  * @param string $s view name
523  * @return string
524  */
525 function load_view_file($s) {
526         global $lang, $a;
527         if(! isset($lang))
528                 $lang = 'en';
529         $b = basename($s);
530         $d = dirname($s);
531         if(file_exists("$d/$lang/$b")) {
532                 $stamp1 = microtime(true);
533                 $content = file_get_contents("$d/$lang/$b");
534                 $a->save_timestamp($stamp1, "file");
535                 return $content;
536         }
537
538         $theme = current_theme();
539
540         if(file_exists("$d/theme/$theme/$b")) {
541                 $stamp1 = microtime(true);
542                 $content = file_get_contents("$d/theme/$theme/$b");
543                 $a->save_timestamp($stamp1, "file");
544                 return $content;
545         }
546
547         $stamp1 = microtime(true);
548         $content = file_get_contents($s);
549         $a->save_timestamp($stamp1, "file");
550         return $content;
551 }}
552
553 if(! function_exists('get_intltext_template')) {
554 /**
555  * load a view template, checking for alternate
556  * languages before falling back to the default
557  *
558  * @global string $lang
559  * @param string $s view path
560  * @return string
561  */
562 function get_intltext_template($s) {
563         global $lang;
564
565         $a = get_app();
566         $engine = '';
567         if($a->theme['template_engine'] === 'smarty3')
568                 $engine = "/smarty3";
569
570         if(! isset($lang))
571                 $lang = 'en';
572
573         if(file_exists("view/$lang$engine/$s")) {
574                 $stamp1 = microtime(true);
575                 $content = file_get_contents("view/$lang$engine/$s");
576                 $a->save_timestamp($stamp1, "file");
577                 return $content;
578         } elseif(file_exists("view/en$engine/$s")) {
579                 $stamp1 = microtime(true);
580                 $content = file_get_contents("view/en$engine/$s");
581                 $a->save_timestamp($stamp1, "file");
582                 return $content;
583         } else {
584                 $stamp1 = microtime(true);
585                 $content = file_get_contents("view$engine/$s");
586                 $a->save_timestamp($stamp1, "file");
587                 return $content;
588         }
589 }}
590
591 if(! function_exists('get_markup_template')) {
592 /**
593  * load template $s
594  *
595  * @param string $s
596  * @param string $root
597  * @return string
598  */
599 function get_markup_template($s, $root = '') {
600         $stamp1 = microtime(true);
601
602         $a = get_app();
603         $t = $a->template_engine();
604         try {
605                 $template = $t->get_template_file($s, $root);
606         } catch (Exception $e) {
607                 echo "<pre><b>".__function__."</b>: ".$e->getMessage()."</pre>"; killme();
608         }
609
610         $a->save_timestamp($stamp1, "file");
611
612         return $template;
613 }}
614
615 if(! function_exists("get_template_file")) {
616 /**
617  *
618  * @param App $a
619  * @param string $filename
620  * @param string $root
621  * @return string
622  */
623 function get_template_file($a, $filename, $root = '') {
624         $theme = current_theme();
625
626         // Make sure $root ends with a slash /
627         if($root !== '' && $root[strlen($root)-1] !== '/')
628                 $root = $root . '/';
629
630         if(file_exists("{$root}view/theme/$theme/$filename"))
631                 $template_file = "{$root}view/theme/$theme/$filename";
632         elseif (x($a->theme_info,"extends") && file_exists("{$root}view/theme/{$a->theme_info["extends"]}/$filename"))
633                 $template_file = "{$root}view/theme/{$a->theme_info["extends"]}/$filename";
634         elseif (file_exists("{$root}/$filename"))
635                 $template_file = "{$root}/$filename";
636         else
637                 $template_file = "{$root}view/$filename";
638
639         return $template_file;
640 }}
641
642
643
644
645
646
647
648 if(! function_exists('attribute_contains')) {
649 /**
650  *  for html,xml parsing - let's say you've got
651  *  an attribute foobar="class1 class2 class3"
652  *  and you want to find out if it contains 'class3'.
653  *  you can't use a normal sub string search because you
654  *  might match 'notclass3' and a regex to do the job is
655  *  possible but a bit complicated.
656  *  pass the attribute string as $attr and the attribute you
657  *  are looking for as $s - returns true if found, otherwise false
658  *
659  * @param string $attr attribute value
660  * @param string $s string to search
661  * @return boolean True if found, False otherwise
662  */
663 function attribute_contains($attr,$s) {
664         $a = explode(' ', $attr);
665         if(count($a) && in_array($s,$a))
666                 return true;
667         return false;
668 }}
669
670 if(! function_exists('logger')) {
671 /* setup int->string log level map */
672 $LOGGER_LEVELS = array();
673
674 /**
675  * log levels:
676  * LOGGER_NORMAL (default)
677  * LOGGER_TRACE
678  * LOGGER_DEBUG
679  * LOGGER_DATA
680  * LOGGER_ALL
681  *
682  * @global App $a
683  * @global dba $db
684  * @param string $msg
685  * @param int $level
686  */
687 function logger($msg,$level = 0) {
688         // turn off logger in install mode
689         global $a;
690         global $db;
691         global $LOGGER_LEVELS;
692
693         if(($a->module == 'install') || (! ($db && $db->connected))) return;
694
695         if (count($LOGGER_LEVELS)==0){
696                 foreach (get_defined_constants() as $k=>$v){
697                         if (substr($k,0,7)=="LOGGER_")
698                                 $LOGGER_LEVELS[$v] = substr($k,7,7);
699                 }
700         }
701
702         $debugging = get_config('system','debugging');
703         $loglevel  = intval(get_config('system','loglevel'));
704         $logfile   = get_config('system','logfile');
705
706         if((! $debugging) || (! $logfile) || ($level > $loglevel))
707                 return;
708
709         $callers = debug_backtrace();
710         $logline =  sprintf("%s@%s\t[%s]:%s:%s:%s\t%s\n",
711                                  datetime_convert(),
712                                  session_id(),
713                                  $LOGGER_LEVELS[$level],
714                                  basename($callers[0]['file']),
715                                  $callers[0]['line'],
716                                  $callers[1]['function'],
717                                  $msg
718                                 );
719
720         $stamp1 = microtime(true);
721         @file_put_contents($logfile, $logline, FILE_APPEND);
722         $a->save_timestamp($stamp1, "file");
723         return;
724 }}
725
726
727 if(! function_exists('activity_match')) {
728 /**
729  * Compare activity uri. Knows about activity namespace.
730  *
731  * @param string $haystack
732  * @param string $needle
733  * @return boolean
734  */
735 function activity_match($haystack,$needle) {
736         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
737                 return true;
738         return false;
739 }}
740
741
742 if(! function_exists('get_tags')) {
743 /**
744  * Pull out all #hashtags and @person tags from $s;
745  * We also get @person@domain.com - which would make
746  * the regex quite complicated as tags can also
747  * end a sentence. So we'll run through our results
748  * and strip the period from any tags which end with one.
749  * Returns array of tags found, or empty array.
750  *
751  * @param string $s
752  * @return array
753  */
754 function get_tags($s) {
755         $ret = array();
756
757         // Convert hashtag links to hashtags
758         $s = preg_replace("/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism", "#$2", $s);
759
760         // ignore anything in a code block
761         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
762
763         // Force line feeds at bbtags
764         $s = str_replace(array("[", "]"), array("\n[", "]\n"), $s);
765
766         // ignore anything in a bbtag
767         $s = preg_replace('/\[(.*?)\]/sm','',$s);
768
769         // Match full names against @tags including the space between first and last
770         // We will look these up afterward to see if they are full names or not recognisable.
771
772         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) {
773                 foreach($match[1] as $mtch) {
774                         if(strstr($mtch,"]")) {
775                                 // we might be inside a bbcode color tag - leave it alone
776                                 continue;
777                         }
778                         if(substr($mtch,-1,1) === '.')
779                                 $ret[] = substr($mtch,0,-1);
780                         else
781                                 $ret[] = $mtch;
782                 }
783         }
784
785         // Otherwise pull out single word tags. These can be @nickname, @first_last
786         // and #hash tags.
787
788         if(preg_match_all('/([!#@][^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/',$s,$match)) {
789                 foreach($match[1] as $mtch) {
790                         if(strstr($mtch,"]")) {
791                                 // we might be inside a bbcode color tag - leave it alone
792                                 continue;
793                         }
794                         if(substr($mtch,-1,1) === '.')
795                                 $mtch = substr($mtch,0,-1);
796                         // ignore strictly numeric tags like #1
797                         if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1)))
798                                 continue;
799                         // try not to catch url fragments
800                         if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1)))
801                                 continue;
802                         $ret[] = $mtch;
803                 }
804         }
805         return $ret;
806 }}
807
808
809 //
810
811 if(! function_exists('qp')) {
812 /**
813  * quick and dirty quoted_printable encoding
814  *
815  * @param string $s
816  * @return string
817  */
818 function qp($s) {
819 return str_replace ("%","=",rawurlencode($s));
820 }}
821
822
823
824 if(! function_exists('get_mentions')) {
825 /**
826  * @param array $item
827  * @return string html for mentions #FIXME: remove html
828  */
829 function get_mentions($item) {
830         $o = '';
831         if(! strlen($item['tag']))
832                 return $o;
833
834         $arr = explode(',',$item['tag']);
835         foreach($arr as $x) {
836                 $matches = null;
837                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
838                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
839                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
840                 }
841         }
842         return $o;
843 }}
844
845 if(! function_exists('contact_block')) {
846 /**
847  * Get html for contact block.
848  *
849  * @template contact_block.tpl
850  * @hook contact_block_end (contacts=>array, output=>string)
851  * @return string
852  */
853 function contact_block() {
854         $o = '';
855         $a = get_app();
856
857         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
858         if($shown === false)
859                 $shown = 24;
860         if($shown == 0)
861                 return;
862
863         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
864                 return $o;
865         $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",
866                         intval($a->profile['uid'])
867         );
868         if(count($r)) {
869                 $total = intval($r[0]['total']);
870         }
871         if(! $total) {
872                 $contacts = t('No contacts');
873                 $micropro = Null;
874
875         } else {
876                 $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",
877                                 intval($a->profile['uid']),
878                                 intval($shown)
879                 );
880                 if(count($r)) {
881                         $contacts = sprintf( tt('%d Contact','%d Contacts', $total),$total);
882                         $micropro = Array();
883                         foreach($r as $rr) {
884                                 $micropro[] = micropro($rr,true,'mpfriend');
885                         }
886                 }
887         }
888
889         $tpl = get_markup_template('contact_block.tpl');
890         $o = replace_macros($tpl, array(
891                 '$contacts' => $contacts,
892                 '$nickname' => $a->profile['nickname'],
893                 '$viewcontacts' => t('View Contacts'),
894                 '$micropro' => $micropro,
895         ));
896
897         $arr = array('contacts' => $r, 'output' => $o);
898
899         call_hooks('contact_block_end', $arr);
900         return $o;
901
902 }}
903
904 if(! function_exists('micropro')) {
905 /**
906  *
907  * @param array $contact
908  * @param boolean $redirect
909  * @param string $class
910  * @param boolean $textmode
911  * @return string #FIXME: remove html
912  */
913 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
914
915         if($class)
916                 $class = ' ' . $class;
917
918         $url = $contact['url'];
919         $sparkle = '';
920         $redir = false;
921
922         if($redirect) {
923                 $a = get_app();
924                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
925                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) {
926                         $redir = true;
927                         $url = $redirect_url;
928                         $sparkle = ' sparkle';
929                 }
930                 else
931                         $url = zrl($url);
932         }
933         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
934         if($click)
935                 $url = '';
936         if($textmode) {
937                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle
938                         . (($click) ? ' fakelink' : '') . '" '
939                         . (($redir) ? ' target="redir" ' : '')
940                         . (($url) ? ' href="' . $url . '"' : '') . $click
941                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
942                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
943         }
944         else {
945                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle
946                         . (($click) ? ' fakelink' : '') . '" '
947                         . (($redir) ? ' target="redir" ' : '')
948                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="'
949                         . proxy_url($contact['micro']) . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name']
950                         . '" /></a></div>' . "\r\n";
951         }
952 }}
953
954
955
956 if(! function_exists('search')) {
957 /**
958  * search box
959  *
960  * @param string $s search query
961  * @param string $id html id
962  * @param string $url search url
963  * @param boolean $save show save search button
964  * @return string html for search box #FIXME: remove html
965  */
966 function search($s,$id='search-box',$url='/search',$save = false) {
967         $a = get_app();
968         $o  = '<div id="' . $id . '">';
969         $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >';
970         $o .= '<input type="text" name="search" id="search-text" placeholder="' . t('Search') . '" value="' . $s .'" />';
971         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />';
972         if($save)
973                 $o .= '<input type="submit" name="save" id="search-save" value="' . t('Save') . '" />';
974         $o .= '</form></div>';
975         return $o;
976 }}
977
978 if(! function_exists('valid_email')) {
979 /**
980  * Check if $x is a valid email string
981  *
982  * @param string $x
983  * @return boolean
984  */
985 function valid_email($x){
986
987         if(get_config('system','disable_email_validation'))
988                 return true;
989
990         if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
991                 return true;
992         return false;
993 }}
994
995
996 if(! function_exists('linkify')) {
997 /**
998  * Replace naked text hyperlink with HTML formatted hyperlink
999  *
1000  * @param string $s
1001  */
1002 function linkify($s) {
1003         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
1004         $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism",'<$1$2=$3&$4>',$s);
1005         return($s);
1006 }}
1007
1008
1009 /**
1010  * Load poke verbs
1011  *
1012  * @return array index is present tense verb
1013                                  value is array containing past tense verb, translation of present, translation of past
1014  * @hook poke_verbs pokes array
1015  */
1016 function get_poke_verbs() {
1017
1018         // index is present tense verb
1019         // value is array containing past tense verb, translation of present, translation of past
1020
1021         $arr = array(
1022                 'poke' => array( 'poked', t('poke'), t('poked')),
1023                 'ping' => array( 'pinged', t('ping'), t('pinged')),
1024                 'prod' => array( 'prodded', t('prod'), t('prodded')),
1025                 'slap' => array( 'slapped', t('slap'), t('slapped')),
1026                 'finger' => array( 'fingered', t('finger'), t('fingered')),
1027                 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')),
1028         );
1029         call_hooks('poke_verbs', $arr);
1030         return $arr;
1031 }
1032
1033 /**
1034  * Load moods
1035  * @return array index is mood, value is translated mood
1036  * @hook mood_verbs moods array
1037  */
1038 function get_mood_verbs() {
1039
1040         $arr = array(
1041                 'happy'      => t('happy'),
1042                 'sad'        => t('sad'),
1043                 'mellow'     => t('mellow'),
1044                 'tired'      => t('tired'),
1045                 'perky'      => t('perky'),
1046                 'angry'      => t('angry'),
1047                 'stupefied'  => t('stupified'),
1048                 'puzzled'    => t('puzzled'),
1049                 'interested' => t('interested'),
1050                 'bitter'     => t('bitter'),
1051                 'cheerful'   => t('cheerful'),
1052                 'alive'      => t('alive'),
1053                 'annoyed'    => t('annoyed'),
1054                 'anxious'    => t('anxious'),
1055                 'cranky'     => t('cranky'),
1056                 'disturbed'  => t('disturbed'),
1057                 'frustrated' => t('frustrated'),
1058                 'motivated'  => t('motivated'),
1059                 'relaxed'    => t('relaxed'),
1060                 'surprised'  => t('surprised'),
1061         );
1062
1063         call_hooks('mood_verbs', $arr);
1064         return $arr;
1065 }
1066
1067
1068
1069 if(! function_exists('smilies')) {
1070 /**
1071  * Replaces text emoticons with graphical images
1072  *
1073  * It is expected that this function will be called using HTML text.
1074  * We will escape text between HTML pre and code blocks from being
1075  * processed.
1076  *
1077  * At a higher level, the bbcode [nosmile] tag can be used to prevent this
1078  * function from being executed by the prepare_text() routine when preparing
1079  * bbcode source for HTML display
1080  *
1081  * @param string $s
1082  * @param boolean $sample
1083  * @return string
1084  * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
1085  */
1086 function smilies($s, $sample = false) {
1087         $a = get_app();
1088
1089         if(intval(get_config('system','no_smilies'))
1090                 || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
1091                 return $s;
1092
1093         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s);
1094         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s);
1095
1096         $texts =  array(
1097                 '&lt;3',
1098                 '&lt;/3',
1099                 '&lt;\\3',
1100                 ':-)',
1101                 ';-)',
1102                 ':-(',
1103                 ':-P',
1104                 ':-p',
1105                 ':-"',
1106                 ':-&quot;',
1107                 ':-x',
1108                 ':-X',
1109                 ':-D',
1110                 '8-|',
1111                 '8-O',
1112                 ':-O',
1113                 '\\o/',
1114                 'o.O',
1115                 'O.o',
1116                 'o_O',
1117                 'O_o',
1118                 ":'(",
1119                 ":-!",
1120                 ":-/",
1121                 ":-[",
1122                 "8-)",
1123                 ':beer',
1124                 ':homebrew',
1125                 ':coffee',
1126                 ':facepalm',
1127                 ':like',
1128                 ':dislike',
1129                 '~friendica',
1130                 'red#',
1131                 'red#matrix'
1132
1133         );
1134
1135         $icons = array(
1136                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
1137                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
1138                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
1139                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1140                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1141                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1142                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1143                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-p" />',
1144                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1145                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1146                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1147                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1148                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1149                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1150                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />',
1151                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt=":-O" />',
1152                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-thumbsup.gif" alt="\\o/" />',
1153                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o.O" />',
1154                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O.o" />',
1155                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="o_O" />',
1156                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-Oo.gif" alt="O_o" />',
1157                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cry.gif" alt=":\'(" />',
1158                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />',
1159                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-undecided.gif" alt=":-/" />',
1160                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-embarassed.gif" alt=":-[" />',
1161                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-cool.gif" alt="8-)" />',
1162                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":beer" />',
1163                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/beer_mug.gif" alt=":homebrew" />',
1164                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/coffee.gif" alt=":coffee" />',
1165                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />',
1166                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />',
1167                 '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />',
1168                 '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>',
1169                 '<a href="http://redmatrix.me/">red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="red" />matrix</a>',
1170                 '<a href="http://redmatrix.me/">red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="red" />matrix</a>'
1171         );
1172
1173         $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
1174         call_hooks('smilie', $params);
1175
1176         if($sample) {
1177                 $s = '<div class="smiley-sample">';
1178                 for($x = 0; $x < count($params['texts']); $x ++) {
1179                         $s .= '<dl><dt>' . $params['texts'][$x] . '</dt><dd>' . $params['icons'][$x] . '</dd></dl>';
1180                 }
1181         }
1182         else {
1183                 $params['string'] = preg_replace_callback('/&lt;(3+)/','preg_heart',$params['string']);
1184                 $s = str_replace($params['texts'],$params['icons'],$params['string']);
1185         }
1186
1187         $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s);
1188         $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s);
1189
1190         return $s;
1191
1192 }}
1193
1194 function smile_encode($m) {
1195         return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
1196 }
1197
1198 function smile_decode($m) {
1199         return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
1200 }
1201
1202
1203 /**
1204  * expand <3333 to the correct number of hearts
1205  *
1206  * @param string $x
1207  * @return string
1208  */
1209 function preg_heart($x) {
1210         $a = get_app();
1211         if(strlen($x[1]) == 1)
1212                 return $x[0];
1213         $t = '';
1214         for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
1215                 $t .= '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />';
1216         $r =  str_replace($x[0],$t,$x[0]);
1217         return $r;
1218 }
1219
1220
1221 if(! function_exists('day_translate')) {
1222 /**
1223  * Translate days and months names
1224  *
1225  * @param string $s
1226  * @return string
1227  */
1228 function day_translate($s) {
1229         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
1230                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
1231                 $s);
1232
1233         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
1234                 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')),
1235                 $ret);
1236
1237         return $ret;
1238 }}
1239
1240
1241 if(! function_exists('normalise_link')) {
1242 /**
1243  * Normalize url
1244  *
1245  * @param string $url
1246  * @return string
1247  */
1248 function normalise_link($url) {
1249         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
1250         return(rtrim($ret,'/'));
1251 }}
1252
1253
1254
1255 if(! function_exists('link_compare')) {
1256 /**
1257  * Compare two URLs to see if they are the same, but ignore
1258  * slight but hopefully insignificant differences such as if one
1259  * is https and the other isn't, or if one is www.something and
1260  * the other isn't - and also ignore case differences.
1261  *
1262  * @param string $a first url
1263  * @param string $b second url
1264  * @return boolean True if the URLs match, otherwise False
1265  *
1266  */
1267 function link_compare($a,$b) {
1268         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
1269                 return true;
1270         return false;
1271 }}
1272
1273
1274 if(! function_exists('redir_private_images')) {
1275 /**
1276  * Find any non-embedded images in private items and add redir links to them
1277  *
1278  * @param App $a
1279  * @param array $item
1280  */
1281 function redir_private_images($a, &$item) {
1282
1283         $matches = false;
1284         $cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
1285         if($cnt) {
1286                 //logger("redir_private_images: matches = " . print_r($matches, true));
1287                 foreach($matches as $mtch) {
1288                         if(strpos($mtch[1], '/redir') !== false)
1289                                 continue;
1290
1291                         if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) {
1292                                 //logger("redir_private_images: redir");
1293                                 $img_url = $a->get_baseurl() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link'];
1294                                 $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']);
1295                         }
1296                 }
1297         }
1298
1299 }}
1300
1301 function put_item_in_cache(&$item, $update = false) {
1302
1303         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1304                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1305
1306                 // The function "redir_private_images" changes the body.
1307                 // I'm not sure if we should store it permanently, so we save the old value.
1308                 $body = $item["body"];
1309
1310                 $a = get_app();
1311                 redir_private_images($a, $item);
1312
1313                 $item["rendered-html"] = prepare_text($item["body"]);
1314                 $item["rendered-hash"] = hash("md5", $item["body"]);
1315                 $item["body"] = $body;
1316
1317                 if ($update AND ($item["id"] != 0)) {
1318                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1319                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1320                 }
1321         }
1322 }
1323
1324 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1325 // If attach is true, also add icons for item attachments
1326
1327 if(! function_exists('prepare_body')) {
1328 /**
1329  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1330  * If attach is true, also add icons for item attachments
1331  *
1332  * @param array $item
1333  * @param boolean $attach
1334  * @return string item body html
1335  * @hook prepare_body_init item array before any work
1336  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1337  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1338  */
1339 function prepare_body(&$item,$attach = false, $preview = false) {
1340
1341         $a = get_app();
1342         call_hooks('prepare_body_init', $item);
1343
1344         $searchpath = $a->get_baseurl()."/search?tag=";
1345
1346         $tags=array();
1347         $hashtags = array();
1348         $mentions = array();
1349
1350         if (!get_config('system','suppress_tags')) {
1351                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1352                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1353
1354                 foreach($taglist as $tag) {
1355
1356                         if ($tag["url"] == "")
1357                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1358
1359                         if ($tag["type"] == TERM_HASHTAG) {
1360                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1361                                 $prefix = "#";
1362                         } elseif ($tag["type"] == TERM_MENTION) {
1363                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1364                                 $prefix = "@";
1365                         }
1366                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1367                 }
1368         }
1369
1370         $item['tags'] = $tags;
1371         $item['hashtags'] = $hashtags;
1372         $item['mentions'] = $mentions;
1373
1374         put_item_in_cache($item, true);
1375         $s = $item["rendered-html"];
1376
1377         require_once("mod/proxy.php");
1378         $s = proxy_parse_html($s);
1379
1380         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1381         call_hooks('prepare_body', $prep_arr);
1382         $s = $prep_arr['html'];
1383
1384         if(! $attach) {
1385                 // Replace the blockquotes with quotes that are used in mails
1386                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1387                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1388                 return $s;
1389         }
1390
1391         $as = '';
1392         $vhead = false;
1393         $arr = explode('[/attach],',$item['attach']);
1394         if(count($arr)) {
1395                 $as .= '<div class="body-attach">';
1396                 foreach($arr as $r) {
1397                         $matches = false;
1398                         $icon = '';
1399                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1400                         if($cnt) {
1401                                 foreach($matches as $mtch) {
1402                                         $mime = $mtch[3];
1403
1404                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1405                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1406                                         else
1407                                                 $the_url = $mtch[1];
1408
1409                                         if(strpos($mime, 'video') !== false) {
1410                                                 if(!$vhead) {
1411                                                         $vhead = true;
1412                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1413                                                                 '$baseurl' => $a->get_baseurl(),
1414                                                         ));
1415                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1416                                                                 '$baseurl' => $a->get_baseurl(),
1417                                                         ));
1418                                                 }
1419
1420                                                 $id = end(explode('/', $the_url));
1421                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1422                                                         '$video'        => array(
1423                                                                 'id'       => $id,
1424                                                                 'title'         => t('View Video'),
1425                                                                 'src'           => $the_url,
1426                                                                 'mime'          => $mime,
1427                                                         ),
1428                                                 ));
1429                                         }
1430
1431                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1432                                         if($filetype) {
1433                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1434                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1435                                         }
1436                                         else {
1437                                                 $filetype = 'unkn';
1438                                                 $filesubtype = 'unkn';
1439                                         }
1440
1441                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1442                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1443                                         switch($icontype) {
1444                                                 case 'video':
1445                                                 case 'audio':
1446                                                 case 'image':
1447                                                 case 'text':
1448                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1449                                                         break;
1450                                                 default:
1451                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1452                                                         break;
1453                                         }*/
1454
1455                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1456                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1457
1458                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1459                                 }
1460                         }
1461                 }
1462                 $as .= '<div class="clear"></div></div>';
1463         }
1464         $s = $s . $as;
1465
1466         // map
1467         if(strpos($s,'<div class="map">') !== false && $item['coord']) {
1468                 $x = generate_map(trim($item['coord']));
1469                 if($x) {
1470                         $s = preg_replace('/\<div class\=\"map\"\>/','$0' . $x,$s);
1471                 }
1472         }               
1473
1474
1475         // Look for spoiler
1476         $spoilersearch = '<blockquote class="spoiler">';
1477
1478         // Remove line breaks before the spoiler
1479         while ((strpos($s, "\n".$spoilersearch) !== false))
1480                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1481         while ((strpos($s, "<br />".$spoilersearch) !== false))
1482                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1483
1484         while ((strpos($s, $spoilersearch) !== false)) {
1485
1486                 $pos = strpos($s, $spoilersearch);
1487                 $rnd = random_string(8);
1488                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1489                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1490                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1491         }
1492
1493         // Look for quote with author
1494         $authorsearch = '<blockquote class="author">';
1495
1496         while ((strpos($s, $authorsearch) !== false)) {
1497
1498                 $pos = strpos($s, $authorsearch);
1499                 $rnd = random_string(8);
1500                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1501                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1502                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1503         }
1504
1505     // replace friendica image url size with theme preference
1506     if (x($a->theme_info,'item_image_size')){
1507         $ps = $a->theme_info['item_image_size'];
1508
1509         $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1510     }
1511
1512         $prep_arr = array('item' => $item, 'html' => $s);
1513         call_hooks('prepare_body_final', $prep_arr);
1514
1515         return $prep_arr['html'];
1516 }}
1517
1518
1519 if(! function_exists('prepare_text')) {
1520 /**
1521  * Given a text string, convert from bbcode to html and add smilie icons.
1522  *
1523  * @param string $text
1524  * @return string
1525  */
1526 function prepare_text($text) {
1527
1528         require_once('include/bbcode.php');
1529
1530         if(stristr($text,'[nosmile]'))
1531                 $s = bbcode($text);
1532         else
1533                 $s = smilies(bbcode($text));
1534
1535         return trim($s);
1536 }}
1537
1538
1539
1540 /**
1541  * return array with details for categories and folders for an item
1542  *
1543  * @param array $item
1544  * @return array
1545  *
1546   * [
1547  *      [ // categories array
1548  *          {
1549  *               'name': 'category name',
1550  *               'removeurl': 'url to remove this category',
1551  *               'first': 'is the first in this array? true/false',
1552  *               'last': 'is the last in this array? true/false',
1553  *           } ,
1554  *           ....
1555  *       ],
1556  *       [ //folders array
1557  *                      {
1558  *               'name': 'folder name',
1559  *               'removeurl': 'url to remove this folder',
1560  *               'first': 'is the first in this array? true/false',
1561  *               'last': 'is the last in this array? true/false',
1562  *           } ,
1563  *           ....
1564  *       ]
1565  *  ]
1566  */
1567 function get_cats_and_terms($item) {
1568
1569     $a = get_app();
1570     $categories = array();
1571     $folders = array();
1572
1573     $matches = false; $first = true;
1574     $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1575     if($cnt) {
1576         foreach($matches as $mtch) {
1577             $categories[] = array(
1578                 'name' => xmlify(file_tag_decode($mtch[1])),
1579                 'url' =>  "#",
1580                 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1581                 'first' => $first,
1582                 'last' => false
1583             );
1584             $first = false;
1585         }
1586     }
1587     if (count($categories)) $categories[count($categories)-1]['last'] = true;
1588
1589
1590         if(local_user() == $item['uid']) {
1591             $matches = false; $first = true;
1592         $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1593             if($cnt) {
1594             foreach($matches as $mtch) {
1595                     $folders[] = array(
1596                     'name' => xmlify(file_tag_decode($mtch[1])),
1597                          'url' =>  "#",
1598                         'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1599                     'first' => $first,
1600                         'last' => false
1601                 );
1602                     $first = false;
1603                         }
1604         }
1605     }
1606
1607     if (count($folders)) $folders[count($folders)-1]['last'] = true;
1608
1609     return array($categories, $folders);
1610 }
1611
1612
1613
1614 if(! function_exists('feed_hublinks')) {
1615 /**
1616  * return atom link elements for all of our hubs
1617  * @return string hub link xml elements
1618  */
1619 function feed_hublinks() {
1620         $a = get_app();
1621         $hub = get_config('system','huburl');
1622
1623         $hubxml = '';
1624         if(strlen($hub)) {
1625                 $hubs = explode(',', $hub);
1626                 if(count($hubs)) {
1627                         foreach($hubs as $h) {
1628                                 $h = trim($h);
1629                                 if(! strlen($h))
1630                                         continue;
1631                                 if ($h === '[internal]')
1632                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1633                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
1634                         }
1635                 }
1636         }
1637         return $hubxml;
1638 }}
1639
1640
1641 if(! function_exists('feed_salmonlinks')) {
1642 /**
1643  * return atom link elements for salmon endpoints
1644  * @param string $nick user nickname
1645  * @return string salmon link xml elements
1646  */
1647 function feed_salmonlinks($nick) {
1648
1649         $a = get_app();
1650
1651         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1652
1653         // old style links that status.net still needed as of 12/2010
1654
1655         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1656         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1657         return $salmon;
1658 }}
1659
1660 if(! function_exists('get_plink')) {
1661 /**
1662  * get private link for item
1663  * @param array $item
1664  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1665  */
1666 function get_plink($item) {
1667         $a = get_app();
1668
1669         if ($a->user['nickname'] != "") {
1670                 $ret = array(
1671                                 //'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
1672                                 'href' => $a->get_baseurl()."/display/".$item['guid'],
1673                                 'orig' => $a->get_baseurl()."/display/".$item['guid'],
1674                                 'title' => t('link to source'),
1675                         );
1676
1677                 if (x($item,'plink'))
1678                         $ret["href"] = $item['plink'];
1679
1680         } elseif (x($item,'plink') && ($item['private'] != 1))
1681                 $ret = array(
1682                                 'href' => $item['plink'],
1683                                 'orig' => $item['plink'],
1684                                 'title' => t('link to source'),
1685                         );
1686         else
1687                 $ret = array();
1688
1689         //if (x($item,'plink') && ($item['private'] != 1))
1690
1691         return($ret);
1692 }}
1693
1694 if(! function_exists('unamp')) {
1695 /**
1696  * replace html amp entity with amp char
1697  * @param string $s
1698  * @return string
1699  */
1700 function unamp($s) {
1701         return str_replace('&amp;', '&', $s);
1702 }}
1703
1704
1705
1706
1707 if(! function_exists('lang_selector')) {
1708 /**
1709  * get html for language selector
1710  * @global string $lang
1711  * @return string
1712  * @template lang_selector.tpl
1713  */
1714 function lang_selector() {
1715         global $lang;
1716
1717         $langs = glob('view/*/strings.php');
1718
1719         $lang_options = array();
1720         $selected = "";
1721
1722         if(is_array($langs) && count($langs)) {
1723                 $langs[] = '';
1724                 if(! in_array('view/en/strings.php',$langs))
1725                         $langs[] = 'view/en/';
1726                 asort($langs);
1727                 foreach($langs as $l) {
1728                         if($l == '') {
1729                                 $lang_options[""] = t('default');
1730                                 continue;
1731                         }
1732                         $ll = substr($l,5);
1733                         $ll = substr($ll,0,strrpos($ll,'/'));
1734                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1735                         $lang_options[$ll]=$ll;
1736                 }
1737         }
1738
1739         $tpl = get_markup_template("lang_selector.tpl");
1740         $o = replace_macros($tpl, array(
1741                 '$title' => t('Select an alternate language'),
1742                 '$langs' => array($lang_options, $selected),
1743
1744         ));
1745         return $o;
1746 }}
1747
1748
1749 if(! function_exists('return_bytes')) {
1750 /**
1751  * return number of bytes in size (K, M, G)
1752  * @param string $size_str
1753  * @return number
1754  */
1755 function return_bytes ($size_str) {
1756     switch (substr ($size_str, -1))
1757     {
1758         case 'M': case 'm': return (int)$size_str * 1048576;
1759         case 'K': case 'k': return (int)$size_str * 1024;
1760         case 'G': case 'g': return (int)$size_str * 1073741824;
1761         default: return $size_str;
1762     }
1763 }}
1764
1765 /**
1766  * @return string
1767  */
1768 function generate_user_guid() {
1769         $found = true;
1770         do {
1771                 $guid = random_string(16);
1772                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1773                         dbesc($guid)
1774                 );
1775                 if(! count($x))
1776                         $found = false;
1777         } while ($found == true );
1778         return $guid;
1779 }
1780
1781
1782 /**
1783  * @param string $s
1784  * @param boolean $strip_padding
1785  * @return string
1786  */
1787 function base64url_encode($s, $strip_padding = false) {
1788
1789         $s = strtr(base64_encode($s),'+/','-_');
1790
1791         if($strip_padding)
1792                 $s = str_replace('=','',$s);
1793
1794         return $s;
1795 }
1796
1797 /**
1798  * @param string $s
1799  * @return string
1800  */
1801 function base64url_decode($s) {
1802
1803         if(is_array($s)) {
1804                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1805                 return $s;
1806         }
1807
1808 /*
1809  *  // Placeholder for new rev of salmon which strips base64 padding.
1810  *  // PHP base64_decode handles the un-padded input without requiring this step
1811  *  // Uncomment if you find you need it.
1812  *
1813  *      $l = strlen($s);
1814  *      if(! strpos($s,'=')) {
1815  *              $m = $l % 4;
1816  *              if($m == 2)
1817  *                      $s .= '==';
1818  *              if($m == 3)
1819  *                      $s .= '=';
1820  *      }
1821  *
1822  */
1823
1824         return base64_decode(strtr($s,'-_','+/'));
1825 }
1826
1827
1828 if (!function_exists('str_getcsv')) {
1829         /**
1830          * Parse csv string
1831          *
1832          * @param string $input
1833          * @param string $delimiter
1834          * @param string $enclosure
1835          * @param string $escape
1836          * @param string $eol
1837          * @return boolean|array False on error, otherwise array[row][column]
1838          */
1839     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1840         if (is_string($input) && !empty($input)) {
1841             $output = array();
1842             $tmp    = preg_split("/".$eol."/",$input);
1843             if (is_array($tmp) && !empty($tmp)) {
1844                 while (list($line_num, $line) = each($tmp)) {
1845                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1846                         while ($strlen = strlen($line)) {
1847                             $pos_delimiter       = strpos($line,$delimiter);
1848                             $pos_enclosure_start = strpos($line,$enclosure);
1849                             if (
1850                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1851                                 && ($pos_enclosure_start < $pos_delimiter)
1852                                 ) {
1853                                 $enclosed_str = substr($line,1);
1854                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1855                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1856                                 $output[$line_num][] = $enclosed_str;
1857                                 $offset = $pos_enclosure_end+3;
1858                             } else {
1859                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1860                                     $output[$line_num][] = substr($line,0);
1861                                     $offset = strlen($line);
1862                                 } else {
1863                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1864                                     $offset = (
1865                                                 !empty($pos_enclosure_start)
1866                                                 && ($pos_enclosure_start < $pos_delimiter)
1867                                                 )
1868                                                 ?$pos_enclosure_start
1869                                                 :$pos_delimiter+1;
1870                                 }
1871                             }
1872                             $line = substr($line,$offset);
1873                         }
1874                     } else {
1875                         $line = preg_split("/".$delimiter."/",$line);
1876
1877                         /*
1878                          * Validating against pesky extra line breaks creating false rows.
1879                          */
1880                         if (is_array($line) && !empty($line[0])) {
1881                             $output[$line_num] = $line;
1882                         }
1883                     }
1884                 }
1885                 return $output;
1886             } else {
1887                 return false;
1888             }
1889         } else {
1890             return false;
1891         }
1892     }
1893 }
1894
1895 /**
1896  * return div element with class 'clear'
1897  * @return string
1898  * @deprecated
1899  */
1900 function cleardiv() {
1901         return '<div class="clear"></div>';
1902 }
1903
1904
1905 function bb_translate_video($s) {
1906
1907         $matches = null;
1908         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1909         if($r) {
1910                 foreach($matches as $mtch) {
1911                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1912                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1913                         elseif(stristr($mtch[1],'vimeo'))
1914                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1915                 }
1916         }
1917         return $s;
1918 }
1919
1920 function html2bb_video($s) {
1921
1922         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1923                         '[youtube]$2[/youtube]', $s);
1924
1925         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1926                         '[youtube]$2[/youtube]', $s);
1927
1928         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1929                         '[vimeo]$2[/vimeo]', $s);
1930
1931         return $s;
1932 }
1933
1934 /**
1935  * apply xmlify() to all values of array $val, recursively
1936  * @param array $val
1937  * @return array
1938  */
1939 function array_xmlify($val){
1940         if (is_bool($val)) return $val?"true":"false";
1941         if (is_array($val)) return array_map('array_xmlify', $val);
1942         return xmlify((string) $val);
1943 }
1944
1945
1946 /**
1947  * transorm link href and img src from relative to absolute
1948  *
1949  * @param string $text
1950  * @param string $base base url
1951  * @return string
1952  */
1953 function reltoabs($text, $base)
1954 {
1955   if (empty($base))
1956     return $text;
1957
1958   $base = rtrim($base,'/');
1959
1960   $base2 = $base . "/";
1961
1962   // Replace links
1963   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1964   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1965   $text = preg_replace($pattern, $replace, $text);
1966
1967   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1968   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1969   $text = preg_replace($pattern, $replace, $text);
1970
1971   // Replace images
1972   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1973   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1974   $text = preg_replace($pattern, $replace, $text);
1975
1976   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1977   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1978   $text = preg_replace($pattern, $replace, $text);
1979
1980
1981   // Done
1982   return $text;
1983 }
1984
1985 /**
1986  * get translated item type
1987  *
1988  * @param array $itme
1989  * @return string
1990  */
1991 function item_post_type($item) {
1992         if(intval($item['event-id']))
1993                 return t('event');
1994         if(strlen($item['resource-id']))
1995                 return t('photo');
1996         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1997                 return t('activity');
1998         if($item['id'] != $item['parent'])
1999                 return t('comment');
2000         return t('post');
2001 }
2002
2003 // post categories and "save to file" use the same item.file table for storage.
2004 // We will differentiate the different uses by wrapping categories in angle brackets
2005 // and save to file categories in square brackets.
2006 // To do this we need to escape these characters if they appear in our tag.
2007
2008 function file_tag_encode($s) {
2009         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
2010 }
2011
2012 function file_tag_decode($s) {
2013         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
2014 }
2015
2016 function file_tag_file_query($table,$s,$type = 'file') {
2017
2018         if($type == 'file')
2019                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
2020         else
2021                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
2022         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
2023 }
2024
2025 // ex. given music,video return <music><video> or [music][video]
2026 function file_tag_list_to_file($list,$type = 'file') {
2027         $tag_list = '';
2028         if(strlen($list)) {
2029                 $list_array = explode(",",$list);
2030                 if($type == 'file') {
2031                         $lbracket = '[';
2032                         $rbracket = ']';
2033                 }
2034                 else {
2035                         $lbracket = '<';
2036                         $rbracket = '>';
2037                 }
2038
2039                 foreach($list_array as $item) {
2040                   if(strlen($item)) {
2041                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
2042                         }
2043                 }
2044         }
2045         return $tag_list;
2046 }
2047
2048 // ex. given <music><video>[friends], return music,video or friends
2049 function file_tag_file_to_list($file,$type = 'file') {
2050         $matches = false;
2051         $list = '';
2052         if($type == 'file') {
2053                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2054         }
2055         else {
2056                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2057         }
2058         if($cnt) {
2059                 foreach($matches as $mtch) {
2060                         if(strlen($list))
2061                                 $list .= ',';
2062                         $list .= file_tag_decode($mtch[1]);
2063                 }
2064         }
2065
2066         return $list;
2067 }
2068
2069 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2070         // $file_old - categories previously associated with an item
2071         // $file_new - new list of categories for an item
2072
2073         if(! intval($uid))
2074                 return false;
2075
2076         if($file_old == $file_new)
2077                 return true;
2078
2079         $saved = get_pconfig($uid,'system','filetags');
2080         if(strlen($saved)) {
2081                 if($type == 'file') {
2082                         $lbracket = '[';
2083                         $rbracket = ']';
2084                         $termtype = TERM_FILE;
2085                 }
2086                 else {
2087                         $lbracket = '<';
2088                         $rbracket = '>';
2089                         $termtype = TERM_CATEGORY;
2090                 }
2091
2092                 $filetags_updated = $saved;
2093
2094                 // check for new tags to be added as filetags in pconfig
2095                 $new_tags = array();
2096                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2097
2098                 foreach($check_new_tags as $tag) {
2099                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2100                                 $new_tags[] = $tag;
2101                 }
2102
2103                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2104
2105                 // check for deleted tags to be removed from filetags in pconfig
2106                 $deleted_tags = array();
2107                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2108
2109                 foreach($check_deleted_tags as $tag) {
2110                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2111                                 $deleted_tags[] = $tag;
2112                 }
2113
2114                 foreach($deleted_tags as $key => $tag) {
2115                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2116                                 dbesc($tag),
2117                                 intval(TERM_OBJ_POST),
2118                                 intval($termtype),
2119                                 intval($uid));
2120
2121                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2122                         //      intval($uid)
2123                         //);
2124
2125                         if(count($r)) {
2126                                 unset($deleted_tags[$key]);
2127                         }
2128                         else {
2129                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2130                         }
2131                 }
2132
2133                 if($saved != $filetags_updated) {
2134                         set_pconfig($uid,'system','filetags', $filetags_updated);
2135                 }
2136                 return true;
2137         }
2138         else
2139                 if(strlen($file_new)) {
2140                         set_pconfig($uid,'system','filetags', $file_new);
2141                 }
2142                 return true;
2143 }
2144
2145 function file_tag_save_file($uid,$item,$file) {
2146         require_once("include/files.php");
2147
2148         $result = false;
2149         if(! intval($uid))
2150                 return false;
2151         $r = q("select file from item where id = %d and uid = %d limit 1",
2152                 intval($item),
2153                 intval($uid)
2154         );
2155         if(count($r)) {
2156                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2157                         q("update item set file = '%s' where id = %d and uid = %d",
2158                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2159                                 intval($item),
2160                                 intval($uid)
2161                         );
2162
2163                 create_files_from_item($item);
2164
2165                 $saved = get_pconfig($uid,'system','filetags');
2166                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2167                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2168                 info( t('Item filed') );
2169         }
2170         return true;
2171 }
2172
2173 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2174         require_once("include/files.php");
2175
2176         $result = false;
2177         if(! intval($uid))
2178                 return false;
2179
2180         if($cat == true) {
2181                 $pattern = '<' . file_tag_encode($file) . '>' ;
2182                 $termtype = TERM_CATEGORY;
2183         } else {
2184                 $pattern = '[' . file_tag_encode($file) . ']' ;
2185                 $termtype = TERM_FILE;
2186         }
2187
2188
2189         $r = q("select file from item where id = %d and uid = %d limit 1",
2190                 intval($item),
2191                 intval($uid)
2192         );
2193         if(! count($r))
2194                 return false;
2195
2196         q("update item set file = '%s' where id = %d and uid = %d",
2197                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2198                 intval($item),
2199                 intval($uid)
2200         );
2201
2202         create_files_from_item($item);
2203
2204         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2205                 dbesc($file),
2206                 intval(TERM_OBJ_POST),
2207                 intval($termtype),
2208                 intval($uid));
2209
2210         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2211         //);
2212
2213         if(! count($r)) {
2214                 $saved = get_pconfig($uid,'system','filetags');
2215                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2216
2217         }
2218         return true;
2219 }
2220
2221 function normalise_openid($s) {
2222         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2223 }
2224
2225
2226 function undo_post_tagging($s) {
2227         $matches = null;
2228         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2229         if($cnt) {
2230                 foreach($matches as $mtch) {
2231                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2232                 }
2233         }
2234         return $s;
2235 }
2236
2237 function fix_mce_lf($s) {
2238         $s = str_replace("\r\n","\n",$s);
2239 //      $s = str_replace("\n\n","\n",$s);
2240         return $s;
2241 }
2242
2243
2244 function protect_sprintf($s) {
2245         return(str_replace('%','%%',$s));
2246 }
2247
2248
2249 function is_a_date_arg($s) {
2250         $i = intval($s);
2251         if($i > 1900) {
2252                 $y = date('Y');
2253                 if($i <= $y+1 && strpos($s,'-') == 4) {
2254                         $m = intval(substr($s,5));
2255                         if($m > 0 && $m <= 12)
2256                                 return true;
2257                 }
2258         }
2259         return false;
2260 }
2261
2262 /**
2263  * remove intentation from a text
2264  */
2265 function deindent($text, $chr="[\t ]", $count=NULL) {
2266         $text = fix_mce_lf($text);
2267         $lines = explode("\n", $text);
2268         if (is_null($count)) {
2269                 $m = array();
2270                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2271                 preg_match("|^".$chr."*|", $lines[$k], $m);
2272                 $count = strlen($m[0]);
2273         }
2274         for ($k=0; $k<count($lines); $k++){
2275                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2276         }
2277
2278         return implode("\n", $lines);
2279 }