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