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