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