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