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