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