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