]> git.mxchange.org Git - friendica.git/blob - include/text.php
Merge pull request #1 from friendica/develop
[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'] === 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, $update = false) {
1300
1301         if (($item["rendered-hash"] != hash("md5", $item["body"])) OR ($item["rendered-hash"] == "") OR
1302                 ($item["rendered-html"] == "") OR get_config("system", "ignore_cache")) {
1303
1304                 // The function "redir_private_images" changes the body.
1305                 // I'm not sure if we should store it permanently, so we save the old value.
1306                 $body = $item["body"];
1307
1308                 $a = get_app();
1309                 redir_private_images($a, $item);
1310
1311                 $item["rendered-html"] = prepare_text($item["body"]);
1312                 $item["rendered-hash"] = hash("md5", $item["body"]);
1313                 $item["body"] = $body;
1314
1315                 if ($update AND ($item["id"] != 0)) {
1316                         q("UPDATE `item` SET `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
1317                                 dbesc($item["rendered-html"]), dbesc($item["rendered-hash"]), intval($item["id"]));
1318                 }
1319         }
1320 }
1321
1322 // Given an item array, convert the body element from bbcode to html and add smilie icons.
1323 // If attach is true, also add icons for item attachments
1324
1325 if(! function_exists('prepare_body')) {
1326 /**
1327  * Given an item array, convert the body element from bbcode to html and add smilie icons.
1328  * If attach is true, also add icons for item attachments
1329  *
1330  * @param array $item
1331  * @param boolean $attach
1332  * @return string item body html
1333  * @hook prepare_body_init item array before any work
1334  * @hook prepare_body ('item'=>item array, 'html'=>body string) after first bbcode to html
1335  * @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
1336  */
1337 function prepare_body(&$item,$attach = false, $preview = false) {
1338
1339         $a = get_app();
1340         call_hooks('prepare_body_init', $item);
1341
1342         $searchpath = $a->get_baseurl()."/search?tag=";
1343
1344         $tags=array();
1345         $hashtags = array();
1346         $mentions = array();
1347
1348         if (!get_config('system','suppress_tags')) {
1349                 $taglist = q("SELECT `type`, `term`, `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` IN (%d, %d) ORDER BY `tid`",
1350                                 intval(TERM_OBJ_POST), intval($item['id']), intval(TERM_HASHTAG), intval(TERM_MENTION));
1351
1352                 foreach($taglist as $tag) {
1353
1354                         if ($tag["url"] == "")
1355                                 $tag["url"] = $searchpath.strtolower($tag["term"]);
1356
1357                         if ($tag["type"] == TERM_HASHTAG) {
1358                                 $hashtags[] = "#<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1359                                 $prefix = "#";
1360                         } elseif ($tag["type"] == TERM_MENTION) {
1361                                 $mentions[] = "@<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1362                                 $prefix = "@";
1363                         }
1364                         $tags[] = $prefix."<a href=\"".$tag["url"]."\" target=\"_blank\">".$tag["term"]."</a>";
1365                 }
1366         }
1367
1368         $item['tags'] = $tags;
1369         $item['hashtags'] = $hashtags;
1370         $item['mentions'] = $mentions;
1371
1372         put_item_in_cache($item, true);
1373         $s = $item["rendered-html"];
1374
1375         require_once("mod/proxy.php");
1376         $s = proxy_parse_html($s);
1377
1378         $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
1379         call_hooks('prepare_body', $prep_arr);
1380         $s = $prep_arr['html'];
1381
1382         if(! $attach) {
1383                 // Replace the blockquotes with quotes that are used in mails
1384                 $mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
1385                 $s = str_replace(array('<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'), array($mailquote, $mailquote, $mailquote), $s);
1386                 return $s;
1387         }
1388
1389         $as = '';
1390         $vhead = false;
1391         $arr = explode('[/attach],',$item['attach']);
1392         if(count($arr)) {
1393                 $as .= '<div class="body-attach">';
1394                 foreach($arr as $r) {
1395                         $matches = false;
1396                         $icon = '';
1397                         $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches, PREG_SET_ORDER);
1398                         if($cnt) {
1399                                 foreach($matches as $mtch) {
1400                                         $mime = $mtch[3];
1401
1402                                         if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN))
1403                                                 $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1];
1404                                         else
1405                                                 $the_url = $mtch[1];
1406
1407                                         if(strpos($mime, 'video') !== false) {
1408                                                 if(!$vhead) {
1409                                                         $vhead = true;
1410                                                         $a->page['htmlhead'] .= replace_macros(get_markup_template('videos_head.tpl'), array(
1411                                                                 '$baseurl' => $a->get_baseurl(),
1412                                                         ));
1413                                                         $a->page['end'] .= replace_macros(get_markup_template('videos_end.tpl'), array(
1414                                                                 '$baseurl' => $a->get_baseurl(),
1415                                                         ));
1416                                                 }
1417
1418                                                 $id = end(explode('/', $the_url));
1419                                                 $as .= replace_macros(get_markup_template('video_top.tpl'), array(
1420                                                         '$video'        => array(
1421                                                                 'id'       => $id,
1422                                                                 'title'         => t('View Video'),
1423                                                                 'src'           => $the_url,
1424                                                                 'mime'          => $mime,
1425                                                         ),
1426                                                 ));
1427                                         }
1428
1429                                         $filetype = strtolower(substr( $mime, 0, strpos($mime,'/') ));
1430                                         if($filetype) {
1431                                                 $filesubtype = strtolower(substr( $mime, strpos($mime,'/') + 1 ));
1432                                                 $filesubtype = str_replace('.', '-', $filesubtype);
1433                                         }
1434                                         else {
1435                                                 $filetype = 'unkn';
1436                                                 $filesubtype = 'unkn';
1437                                         }
1438
1439                                         $icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>';
1440                                         /*$icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/')));
1441                                         switch($icontype) {
1442                                                 case 'video':
1443                                                 case 'audio':
1444                                                 case 'image':
1445                                                 case 'text':
1446                                                         $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>';
1447                                                         break;
1448                                                 default:
1449                                                         $icon = '<div class="attachtype icon s22 type-unkn"></div>';
1450                                                         break;
1451                                         }*/
1452
1453                                         $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1]));
1454                                         $title .= ' ' . $mtch[2] . ' ' . t('bytes');
1455
1456                                         $as .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>';
1457                                 }
1458                         }
1459                 }
1460                 $as .= '<div class="clear"></div></div>';
1461         }
1462         $s = $s . $as;
1463
1464
1465         // Look for spoiler
1466         $spoilersearch = '<blockquote class="spoiler">';
1467
1468         // Remove line breaks before the spoiler
1469         while ((strpos($s, "\n".$spoilersearch) !== false))
1470                 $s = str_replace("\n".$spoilersearch, $spoilersearch, $s);
1471         while ((strpos($s, "<br />".$spoilersearch) !== false))
1472                 $s = str_replace("<br />".$spoilersearch, $spoilersearch, $s);
1473
1474         while ((strpos($s, $spoilersearch) !== false)) {
1475
1476                 $pos = strpos($s, $spoilersearch);
1477                 $rnd = random_string(8);
1478                 $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1479                                         '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
1480                 $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
1481         }
1482
1483         // Look for quote with author
1484         $authorsearch = '<blockquote class="author">';
1485
1486         while ((strpos($s, $authorsearch) !== false)) {
1487
1488                 $pos = strpos($s, $authorsearch);
1489                 $rnd = random_string(8);
1490                 $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
1491                                         '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
1492                 $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
1493         }
1494
1495     // replace friendica image url size with theme preference
1496     if (x($a->theme_info,'item_image_size')){
1497         $ps = $a->theme_info['item_image_size'];
1498
1499         $s = preg_replace('|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|',"$1-".$ps, $s);
1500     }
1501
1502         $prep_arr = array('item' => $item, 'html' => $s);
1503         call_hooks('prepare_body_final', $prep_arr);
1504
1505         return $prep_arr['html'];
1506 }}
1507
1508
1509 if(! function_exists('prepare_text')) {
1510 /**
1511  * Given a text string, convert from bbcode to html and add smilie icons.
1512  *
1513  * @param string $text
1514  * @return string
1515  */
1516 function prepare_text($text) {
1517
1518         require_once('include/bbcode.php');
1519
1520         if(stristr($text,'[nosmile]'))
1521                 $s = bbcode($text);
1522         else
1523                 $s = smilies(bbcode($text));
1524
1525         return trim($s);
1526 }}
1527
1528
1529
1530 /**
1531  * return array with details for categories and folders for an item
1532  *
1533  * @param array $item
1534  * @return array
1535  *
1536   * [
1537  *      [ // categories array
1538  *          {
1539  *               'name': 'category name',
1540  *               'removeurl': 'url to remove this category',
1541  *               'first': 'is the first in this array? true/false',
1542  *               'last': 'is the last in this array? true/false',
1543  *           } ,
1544  *           ....
1545  *       ],
1546  *       [ //folders array
1547  *                      {
1548  *               'name': 'folder name',
1549  *               'removeurl': 'url to remove this folder',
1550  *               'first': 'is the first in this array? true/false',
1551  *               'last': 'is the last in this array? true/false',
1552  *           } ,
1553  *           ....
1554  *       ]
1555  *  ]
1556  */
1557 function get_cats_and_terms($item) {
1558
1559     $a = get_app();
1560     $categories = array();
1561     $folders = array();
1562
1563     $matches = false; $first = true;
1564     $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1565     if($cnt) {
1566         foreach($matches as $mtch) {
1567             $categories[] = array(
1568                 'name' => xmlify(file_tag_decode($mtch[1])),
1569                 'url' =>  "#",
1570                 'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""),
1571                 'first' => $first,
1572                 'last' => false
1573             );
1574             $first = false;
1575         }
1576     }
1577     if (count($categories)) $categories[count($categories)-1]['last'] = true;
1578
1579
1580         if(local_user() == $item['uid']) {
1581             $matches = false; $first = true;
1582         $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1583             if($cnt) {
1584             foreach($matches as $mtch) {
1585                     $folders[] = array(
1586                     'name' => xmlify(file_tag_decode($mtch[1])),
1587                          'url' =>  "#",
1588                         'removeurl' => ((local_user() == $item['uid'])?$a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""),
1589                     'first' => $first,
1590                         'last' => false
1591                 );
1592                     $first = false;
1593                         }
1594         }
1595     }
1596
1597     if (count($folders)) $folders[count($folders)-1]['last'] = true;
1598
1599     return array($categories, $folders);
1600 }
1601
1602
1603
1604 if(! function_exists('feed_hublinks')) {
1605 /**
1606  * return atom link elements for all of our hubs
1607  * @return string hub link xml elements
1608  */
1609 function feed_hublinks() {
1610         $a = get_app();
1611         $hub = get_config('system','huburl');
1612
1613         $hubxml = '';
1614         if(strlen($hub)) {
1615                 $hubs = explode(',', $hub);
1616                 if(count($hubs)) {
1617                         foreach($hubs as $h) {
1618                                 $h = trim($h);
1619                                 if(! strlen($h))
1620                                         continue;
1621                                 if ($h === '[internal]')
1622                                         $h = $a->get_baseurl() . '/pubsubhubbub';
1623                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
1624                         }
1625                 }
1626         }
1627         return $hubxml;
1628 }}
1629
1630
1631 if(! function_exists('feed_salmonlinks')) {
1632 /**
1633  * return atom link elements for salmon endpoints
1634  * @param string $nick user nickname
1635  * @return string salmon link xml elements
1636  */
1637 function feed_salmonlinks($nick) {
1638
1639         $a = get_app();
1640
1641         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1642
1643         // old style links that status.net still needed as of 12/2010
1644
1645         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1646         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
1647         return $salmon;
1648 }}
1649
1650 if(! function_exists('get_plink')) {
1651 /**
1652  * get private link for item
1653  * @param array $item
1654  * @return boolean|array False if item has not plink, otherwise array('href'=>plink url, 'title'=>translated title)
1655  */
1656 function get_plink($item) {
1657         $a = get_app();
1658
1659         if ($a->user['nickname'] != "") {
1660                 $ret = array(
1661                                 //'href' => $a->get_baseurl()."/display/".$a->user['nickname']."/".$item['id'],
1662                                 'href' => $a->get_baseurl()."/display/".$item['guid'],
1663                                 'orig' => $a->get_baseurl()."/display/".$item['guid'],
1664                                 'title' => t('link to source'),
1665                         );
1666
1667                 if (x($item,'plink'))
1668                         $ret["href"] = $item['plink'];
1669
1670         } elseif (x($item,'plink') && ($item['private'] != 1))
1671                 $ret = array(
1672                                 'href' => $item['plink'],
1673                                 'orig' => $item['plink'],
1674                                 'title' => t('link to source'),
1675                         );
1676         else
1677                 $ret = array();
1678
1679         //if (x($item,'plink') && ($item['private'] != 1))
1680
1681         return($ret);
1682 }}
1683
1684 if(! function_exists('unamp')) {
1685 /**
1686  * replace html amp entity with amp char
1687  * @param string $s
1688  * @return string
1689  */
1690 function unamp($s) {
1691         return str_replace('&amp;', '&', $s);
1692 }}
1693
1694
1695
1696
1697 if(! function_exists('lang_selector')) {
1698 /**
1699  * get html for language selector
1700  * @global string $lang
1701  * @return string
1702  * @template lang_selector.tpl
1703  */
1704 function lang_selector() {
1705         global $lang;
1706
1707         $langs = glob('view/*/strings.php');
1708
1709         $lang_options = array();
1710         $selected = "";
1711
1712         if(is_array($langs) && count($langs)) {
1713                 $langs[] = '';
1714                 if(! in_array('view/en/strings.php',$langs))
1715                         $langs[] = 'view/en/';
1716                 asort($langs);
1717                 foreach($langs as $l) {
1718                         if($l == '') {
1719                                 $lang_options[""] = t('default');
1720                                 continue;
1721                         }
1722                         $ll = substr($l,5);
1723                         $ll = substr($ll,0,strrpos($ll,'/'));
1724                         $selected = (($ll === $lang && (x($_SESSION, 'language'))) ? $ll : $selected);
1725                         $lang_options[$ll]=$ll;
1726                 }
1727         }
1728
1729         $tpl = get_markup_template("lang_selector.tpl");
1730         $o = replace_macros($tpl, array(
1731                 '$title' => t('Select an alternate language'),
1732                 '$langs' => array($lang_options, $selected),
1733
1734         ));
1735         return $o;
1736 }}
1737
1738
1739 if(! function_exists('return_bytes')) {
1740 /**
1741  * return number of bytes in size (K, M, G)
1742  * @param string $size_str
1743  * @return number
1744  */
1745 function return_bytes ($size_str) {
1746     switch (substr ($size_str, -1))
1747     {
1748         case 'M': case 'm': return (int)$size_str * 1048576;
1749         case 'K': case 'k': return (int)$size_str * 1024;
1750         case 'G': case 'g': return (int)$size_str * 1073741824;
1751         default: return $size_str;
1752     }
1753 }}
1754
1755 /**
1756  * @return string
1757  */
1758 function generate_user_guid() {
1759         $found = true;
1760         do {
1761                 $guid = random_string(16);
1762                 $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
1763                         dbesc($guid)
1764                 );
1765                 if(! count($x))
1766                         $found = false;
1767         } while ($found == true );
1768         return $guid;
1769 }
1770
1771
1772 /**
1773  * @param string $s
1774  * @param boolean $strip_padding
1775  * @return string
1776  */
1777 function base64url_encode($s, $strip_padding = false) {
1778
1779         $s = strtr(base64_encode($s),'+/','-_');
1780
1781         if($strip_padding)
1782                 $s = str_replace('=','',$s);
1783
1784         return $s;
1785 }
1786
1787 /**
1788  * @param string $s
1789  * @return string
1790  */
1791 function base64url_decode($s) {
1792
1793         if(is_array($s)) {
1794                 logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true));
1795                 return $s;
1796         }
1797
1798 /*
1799  *  // Placeholder for new rev of salmon which strips base64 padding.
1800  *  // PHP base64_decode handles the un-padded input without requiring this step
1801  *  // Uncomment if you find you need it.
1802  *
1803  *      $l = strlen($s);
1804  *      if(! strpos($s,'=')) {
1805  *              $m = $l % 4;
1806  *              if($m == 2)
1807  *                      $s .= '==';
1808  *              if($m == 3)
1809  *                      $s .= '=';
1810  *      }
1811  *
1812  */
1813
1814         return base64_decode(strtr($s,'-_','+/'));
1815 }
1816
1817
1818 if (!function_exists('str_getcsv')) {
1819         /**
1820          * Parse csv string
1821          *
1822          * @param string $input
1823          * @param string $delimiter
1824          * @param string $enclosure
1825          * @param string $escape
1826          * @param string $eol
1827          * @return boolean|array False on error, otherwise array[row][column]
1828          */
1829     function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
1830         if (is_string($input) && !empty($input)) {
1831             $output = array();
1832             $tmp    = preg_split("/".$eol."/",$input);
1833             if (is_array($tmp) && !empty($tmp)) {
1834                 while (list($line_num, $line) = each($tmp)) {
1835                     if (preg_match("/".$escape.$enclosure."/",$line)) {
1836                         while ($strlen = strlen($line)) {
1837                             $pos_delimiter       = strpos($line,$delimiter);
1838                             $pos_enclosure_start = strpos($line,$enclosure);
1839                             if (
1840                                 is_int($pos_delimiter) && is_int($pos_enclosure_start)
1841                                 && ($pos_enclosure_start < $pos_delimiter)
1842                                 ) {
1843                                 $enclosed_str = substr($line,1);
1844                                 $pos_enclosure_end = strpos($enclosed_str,$enclosure);
1845                                 $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
1846                                 $output[$line_num][] = $enclosed_str;
1847                                 $offset = $pos_enclosure_end+3;
1848                             } else {
1849                                 if (empty($pos_delimiter) && empty($pos_enclosure_start)) {
1850                                     $output[$line_num][] = substr($line,0);
1851                                     $offset = strlen($line);
1852                                 } else {
1853                                     $output[$line_num][] = substr($line,0,$pos_delimiter);
1854                                     $offset = (
1855                                                 !empty($pos_enclosure_start)
1856                                                 && ($pos_enclosure_start < $pos_delimiter)
1857                                                 )
1858                                                 ?$pos_enclosure_start
1859                                                 :$pos_delimiter+1;
1860                                 }
1861                             }
1862                             $line = substr($line,$offset);
1863                         }
1864                     } else {
1865                         $line = preg_split("/".$delimiter."/",$line);
1866
1867                         /*
1868                          * Validating against pesky extra line breaks creating false rows.
1869                          */
1870                         if (is_array($line) && !empty($line[0])) {
1871                             $output[$line_num] = $line;
1872                         }
1873                     }
1874                 }
1875                 return $output;
1876             } else {
1877                 return false;
1878             }
1879         } else {
1880             return false;
1881         }
1882     }
1883 }
1884
1885 /**
1886  * return div element with class 'clear'
1887  * @return string
1888  * @deprecated
1889  */
1890 function cleardiv() {
1891         return '<div class="clear"></div>';
1892 }
1893
1894
1895 function bb_translate_video($s) {
1896
1897         $matches = null;
1898         $r = preg_match_all("/\[video\](.*?)\[\/video\]/ism",$s,$matches,PREG_SET_ORDER);
1899         if($r) {
1900                 foreach($matches as $mtch) {
1901                         if((stristr($mtch[1],'youtube')) || (stristr($mtch[1],'youtu.be')))
1902                                 $s = str_replace($mtch[0],'[youtube]' . $mtch[1] . '[/youtube]',$s);
1903                         elseif(stristr($mtch[1],'vimeo'))
1904                                 $s = str_replace($mtch[0],'[vimeo]' . $mtch[1] . '[/vimeo]',$s);
1905                 }
1906         }
1907         return $s;
1908 }
1909
1910 function html2bb_video($s) {
1911
1912         $s = preg_replace('#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
1913                         '[youtube]$2[/youtube]', $s);
1914
1915         $s = preg_replace('#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
1916                         '[youtube]$2[/youtube]', $s);
1917
1918         $s = preg_replace('#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
1919                         '[vimeo]$2[/vimeo]', $s);
1920
1921         return $s;
1922 }
1923
1924 /**
1925  * apply xmlify() to all values of array $val, recursively
1926  * @param array $val
1927  * @return array
1928  */
1929 function array_xmlify($val){
1930         if (is_bool($val)) return $val?"true":"false";
1931         if (is_array($val)) return array_map('array_xmlify', $val);
1932         return xmlify((string) $val);
1933 }
1934
1935
1936 /**
1937  * transorm link href and img src from relative to absolute
1938  *
1939  * @param string $text
1940  * @param string $base base url
1941  * @return string
1942  */
1943 function reltoabs($text, $base)
1944 {
1945   if (empty($base))
1946     return $text;
1947
1948   $base = rtrim($base,'/');
1949
1950   $base2 = $base . "/";
1951
1952   // Replace links
1953   $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
1954   $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
1955   $text = preg_replace($pattern, $replace, $text);
1956
1957   $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
1958   $replace = "<a\${1} href=\"" . $base . "\${2}\"";
1959   $text = preg_replace($pattern, $replace, $text);
1960
1961   // Replace images
1962   $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
1963   $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
1964   $text = preg_replace($pattern, $replace, $text);
1965
1966   $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
1967   $replace = "<img\${1} src=\"" . $base . "\${2}\"";
1968   $text = preg_replace($pattern, $replace, $text);
1969
1970
1971   // Done
1972   return $text;
1973 }
1974
1975 /**
1976  * get translated item type
1977  *
1978  * @param array $itme
1979  * @return string
1980  */
1981 function item_post_type($item) {
1982         if(intval($item['event-id']))
1983                 return t('event');
1984         if(strlen($item['resource-id']))
1985                 return t('photo');
1986         if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST)
1987                 return t('activity');
1988         if($item['id'] != $item['parent'])
1989                 return t('comment');
1990         return t('post');
1991 }
1992
1993 // post categories and "save to file" use the same item.file table for storage.
1994 // We will differentiate the different uses by wrapping categories in angle brackets
1995 // and save to file categories in square brackets.
1996 // To do this we need to escape these characters if they appear in our tag.
1997
1998 function file_tag_encode($s) {
1999         return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s);
2000 }
2001
2002 function file_tag_decode($s) {
2003         return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s);
2004 }
2005
2006 function file_tag_file_query($table,$s,$type = 'file') {
2007
2008         if($type == 'file')
2009                 $str = preg_quote( '[' . str_replace('%','%%',file_tag_encode($s)) . ']' );
2010         else
2011                 $str = preg_quote( '<' . str_replace('%','%%',file_tag_encode($s)) . '>' );
2012         return " AND " . (($table) ? dbesc($table) . '.' : '') . "file regexp '" . dbesc($str) . "' ";
2013 }
2014
2015 // ex. given music,video return <music><video> or [music][video]
2016 function file_tag_list_to_file($list,$type = 'file') {
2017         $tag_list = '';
2018         if(strlen($list)) {
2019                 $list_array = explode(",",$list);
2020                 if($type == 'file') {
2021                         $lbracket = '[';
2022                         $rbracket = ']';
2023                 }
2024                 else {
2025                         $lbracket = '<';
2026                         $rbracket = '>';
2027                 }
2028
2029                 foreach($list_array as $item) {
2030                   if(strlen($item)) {
2031                                 $tag_list .= $lbracket . file_tag_encode(trim($item))  . $rbracket;
2032                         }
2033                 }
2034         }
2035         return $tag_list;
2036 }
2037
2038 // ex. given <music><video>[friends], return music,video or friends
2039 function file_tag_file_to_list($file,$type = 'file') {
2040         $matches = false;
2041         $list = '';
2042         if($type == 'file') {
2043                 $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER);
2044         }
2045         else {
2046                 $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER);
2047         }
2048         if($cnt) {
2049                 foreach($matches as $mtch) {
2050                         if(strlen($list))
2051                                 $list .= ',';
2052                         $list .= file_tag_decode($mtch[1]);
2053                 }
2054         }
2055
2056         return $list;
2057 }
2058
2059 function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') {
2060         // $file_old - categories previously associated with an item
2061         // $file_new - new list of categories for an item
2062
2063         if(! intval($uid))
2064                 return false;
2065
2066         if($file_old == $file_new)
2067                 return true;
2068
2069         $saved = get_pconfig($uid,'system','filetags');
2070         if(strlen($saved)) {
2071                 if($type == 'file') {
2072                         $lbracket = '[';
2073                         $rbracket = ']';
2074                         $termtype = TERM_FILE;
2075                 }
2076                 else {
2077                         $lbracket = '<';
2078                         $rbracket = '>';
2079                         $termtype = TERM_CATEGORY;
2080                 }
2081
2082                 $filetags_updated = $saved;
2083
2084                 // check for new tags to be added as filetags in pconfig
2085                 $new_tags = array();
2086                 $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
2087
2088                 foreach($check_new_tags as $tag) {
2089                         if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket))
2090                                 $new_tags[] = $tag;
2091                 }
2092
2093                 $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type);
2094
2095                 // check for deleted tags to be removed from filetags in pconfig
2096                 $deleted_tags = array();
2097                 $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
2098
2099                 foreach($check_deleted_tags as $tag) {
2100                         if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket))
2101                                 $deleted_tags[] = $tag;
2102                 }
2103
2104                 foreach($deleted_tags as $key => $tag) {
2105                         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2106                                 dbesc($tag),
2107                                 intval(TERM_OBJ_POST),
2108                                 intval($termtype),
2109                                 intval($uid));
2110
2111                         //$r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type),
2112                         //      intval($uid)
2113                         //);
2114
2115                         if(count($r)) {
2116                                 unset($deleted_tags[$key]);
2117                         }
2118                         else {
2119                                 $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated);
2120                         }
2121                 }
2122
2123                 if($saved != $filetags_updated) {
2124                         set_pconfig($uid,'system','filetags', $filetags_updated);
2125                 }
2126                 return true;
2127         }
2128         else
2129                 if(strlen($file_new)) {
2130                         set_pconfig($uid,'system','filetags', $file_new);
2131                 }
2132                 return true;
2133 }
2134
2135 function file_tag_save_file($uid,$item,$file) {
2136         require_once("include/files.php");
2137
2138         $result = false;
2139         if(! intval($uid))
2140                 return false;
2141         $r = q("select file from item where id = %d and uid = %d limit 1",
2142                 intval($item),
2143                 intval($uid)
2144         );
2145         if(count($r)) {
2146                 if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']'))
2147                         q("update item set file = '%s' where id = %d and uid = %d",
2148                                 dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'),
2149                                 intval($item),
2150                                 intval($uid)
2151                         );
2152
2153                 create_files_from_item($item);
2154
2155                 $saved = get_pconfig($uid,'system','filetags');
2156                 if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']')))
2157                         set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']');
2158                 info( t('Item filed') );
2159         }
2160         return true;
2161 }
2162
2163 function file_tag_unsave_file($uid,$item,$file,$cat = false) {
2164         require_once("include/files.php");
2165
2166         $result = false;
2167         if(! intval($uid))
2168                 return false;
2169
2170         if($cat == true) {
2171                 $pattern = '<' . file_tag_encode($file) . '>' ;
2172                 $termtype = TERM_CATEGORY;
2173         } else {
2174                 $pattern = '[' . file_tag_encode($file) . ']' ;
2175                 $termtype = TERM_FILE;
2176         }
2177
2178
2179         $r = q("select file from item where id = %d and uid = %d limit 1",
2180                 intval($item),
2181                 intval($uid)
2182         );
2183         if(! count($r))
2184                 return false;
2185
2186         q("update item set file = '%s' where id = %d and uid = %d",
2187                 dbesc(str_replace($pattern,'',$r[0]['file'])),
2188                 intval($item),
2189                 intval($uid)
2190         );
2191
2192         create_files_from_item($item);
2193
2194         $r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
2195                 dbesc($file),
2196                 intval(TERM_OBJ_POST),
2197                 intval($termtype),
2198                 intval($uid));
2199
2200         //$r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')),
2201         //);
2202
2203         if(! count($r)) {
2204                 $saved = get_pconfig($uid,'system','filetags');
2205                 set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved));
2206
2207         }
2208         return true;
2209 }
2210
2211 function normalise_openid($s) {
2212         return trim(str_replace(array('http://','https://'),array('',''),$s),'/');
2213 }
2214
2215
2216 function undo_post_tagging($s) {
2217         $matches = null;
2218         $cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER);
2219         if($cnt) {
2220                 foreach($matches as $mtch) {
2221                         $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
2222                 }
2223         }
2224         return $s;
2225 }
2226
2227 function fix_mce_lf($s) {
2228         $s = str_replace("\r\n","\n",$s);
2229 //      $s = str_replace("\n\n","\n",$s);
2230         return $s;
2231 }
2232
2233
2234 function protect_sprintf($s) {
2235         return(str_replace('%','%%',$s));
2236 }
2237
2238
2239 function is_a_date_arg($s) {
2240         $i = intval($s);
2241         if($i > 1900) {
2242                 $y = date('Y');
2243                 if($i <= $y+1 && strpos($s,'-') == 4) {
2244                         $m = intval(substr($s,5));
2245                         if($m > 0 && $m <= 12)
2246                                 return true;
2247                 }
2248         }
2249         return false;
2250 }
2251
2252 /**
2253  * remove intentation from a text
2254  */
2255 function deindent($text, $chr="[\t ]", $count=NULL) {
2256         $text = fix_mce_lf($text);
2257         $lines = explode("\n", $text);
2258         if (is_null($count)) {
2259                 $m = array();
2260                 $k=0; while($k<count($lines) && strlen($lines[$k])==0) $k++;
2261                 preg_match("|^".$chr."*|", $lines[$k], $m);
2262                 $count = strlen($m[0]);
2263         }
2264         for ($k=0; $k<count($lines); $k++){
2265                 $lines[$k] = preg_replace("|^".$chr."{".$count."}|", "", $lines[$k]);
2266         }
2267
2268         return implode("\n", $lines);
2269 }