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