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