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