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