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