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