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