]> git.mxchange.org Git - friendica.git/blob - boot.php
pull some template strings
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1017   );
6 define ( 'DFRN_PROTOCOL_VERSION',  '2.0'  );
7
8 define ( 'EOL',                    "<br />\r\n"     );
9 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
10
11 // log levels
12
13 define ( 'LOGGER_NORMAL',          0 );
14 define ( 'LOGGER_TRACE',           1 );
15 define ( 'LOGGER_DEBUG',           2 );
16 define ( 'LOGGER_DATA',            3 );
17 define ( 'LOGGER_ALL',             4 );
18
19 // registration policy
20
21 define ( 'REGISTER_CLOSED',        0 );
22 define ( 'REGISTER_APPROVE',       1 );
23 define ( 'REGISTER_OPEN',          2 );
24
25 // relationship types
26
27 define ( 'REL_VIP',        1);
28 define ( 'REL_FAN',        2);
29 define ( 'REL_BUD',        3);
30
31
32 // page/profile types
33 // PAGE_NORMAL is a typical personal profile account
34 // PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
35 // PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
36 // write access to wall and comments (no email and not included in page owner's ACL lists)
37 // PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
38
39 define ( 'PAGE_NORMAL',            0 );
40 define ( 'PAGE_SOAPBOX',           1 );
41 define ( 'PAGE_COMMUNITY',         2 );
42 define ( 'PAGE_FREELOVE',          3 );
43
44 // Maximum number of "people who like (or don't like) this" 
45 // that we will list by name
46
47 define ( 'MAX_LIKERS',    75);
48
49 // email notification options
50
51 define ( 'NOTIFY_INTRO',   0x0001 );
52 define ( 'NOTIFY_CONFIRM', 0x0002 );
53 define ( 'NOTIFY_WALL',    0x0004 );
54 define ( 'NOTIFY_COMMENT', 0x0008 );
55 define ( 'NOTIFY_MAIL',    0x0010 );
56
57 // various namespaces we may need to parse
58
59 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
60 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
61 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
62 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
63 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
64 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
65 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
66 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
67 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
68 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
69 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
70
71 // activity stream defines
72
73 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
74 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
75 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
76
77 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
78 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
79 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
80 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
81 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
82 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
83
84 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
85 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
86 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
87 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
88 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
89 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
90
91 // item weight for query ordering
92
93 define ( 'GRAVITY_PARENT',       0);
94 define ( 'GRAVITY_LIKE',         3);
95 define ( 'GRAVITY_COMMENT',      6);
96
97
98 // Our main application structure for the life of this page
99 // Primarily deals with the URL that got us here
100 // and tries to make some sense of it, and 
101 // stores our page contents and config storage
102 // and anything else that might need to be passed around 
103 // before we spit the page out. 
104
105 if(! class_exists('App')) {
106 class App {
107
108         public  $module_loaded = false;
109         public  $config;
110         public  $page;
111         public  $profile;
112         public  $user;
113         public  $cid;
114         public  $contact;
115         public  $content;
116         public  $data;
117         public  $error = false;
118         public  $cmd;
119         public  $argv;
120         public  $argc;
121         public  $module;
122         public  $pager;
123         public  $strings;   
124         public  $path;
125         public  $interactive = true;
126
127         private $scheme;
128         private $hostname;
129         private $baseurl;
130         private $db;
131
132         private $curl_code;
133         private $curl_headers;
134
135         function __construct() {
136
137                 $this->config = array();
138                 $this->page = array();
139                 $this->pager= array();
140
141                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
142
143                 if(x($_SERVER,'SERVER_NAME'))
144                         $this->hostname = $_SERVER['SERVER_NAME'];
145
146                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
147
148                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
149                         $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'],2);
150                 if(x($_GET,'q'))
151                         $this->cmd = trim($_GET['q'],'/');
152
153
154                 $this->argv = explode('/',$this->cmd);
155                 $this->argc = count($this->argv);
156                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
157                         $this->module = $this->argv[0];
158                 }
159                 else {
160                         $this->module = 'home';
161                 }
162
163                 if($this->cmd === '.well-known/host-meta')
164                         require_once('include/hostxrd.php');
165
166
167                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
168                 $this->pager['itemspage'] = 50;
169                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
170                 $this->pager['total'] = 0;
171         }
172
173         function get_baseurl($ssl = false) {
174                 if(strlen($this->baseurl))
175                         return $this->baseurl;
176
177                 $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
178                 return $this->baseurl;
179         }
180
181         function set_baseurl($url) {
182                 $this->baseurl = $url;
183                 $this->hostname = basename($url);
184         }
185
186         function get_hostname() {
187                 return $this->hostname;
188         }
189
190         function set_hostname($h) {
191                 $this->hostname = $h;
192         }
193
194         function set_path($p) {
195                 $this->path = trim(trim($p),'/');
196         } 
197
198         function get_path() {
199                 return $this->path;
200         }
201
202         function set_pager_total($n) {
203                 $this->pager['total'] = intval($n);
204         }
205
206         function set_pager_itemspage($n) {
207                 $this->pager['itemspage'] = intval($n);
208                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
209
210         } 
211
212         function init_pagehead() {
213                 $tpl = load_view_file("view/head.tpl");
214                 $this->page['htmlhead'] = replace_macros($tpl,array(
215                         '$baseurl' => $this->get_baseurl()
216                 ));
217         }
218
219         function set_curl_code($code) {
220                 $this->curl_code = $code;
221         }
222
223         function get_curl_code() {
224                 return $this->curl_code;
225         }
226
227         function set_curl_headers($headers) {
228                 $this->curl_headers = $headers;
229         }
230
231         function get_curl_headers() {
232                 return $this->curl_headers;
233         }
234
235
236 }}
237
238 // retrieve the App structure
239 // useful in functions which require it but don't get it passed to them
240
241 if(! function_exists('get_app')) {
242 function get_app() {
243         global $a;
244         return $a;
245 }};
246
247
248 // Multi-purpose function to check variable state.
249 // Usage: x($var) or $x($array,'key')
250 // returns false if variable/key is not set
251 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
252 // e.g. x('') or x(0) returns 0;
253
254 if(! function_exists('x')) {
255 function x($s,$k = NULL) {
256         if($k != NULL) {
257                 if((is_array($s)) && (array_key_exists($k,$s))) {
258                         if($s[$k])
259                                 return (int) 1;
260                         return (int) 0;
261                 }
262                 return false;
263         }
264         else {          
265                 if(isset($s)) {
266                         if($s) {
267                                 return (int) 1;
268                         }
269                         return (int) 0;
270                 }
271                 return false;
272         }
273 }}
274
275 // called from db initialisation if db is dead.
276
277 if(! function_exists('system_unavailable')) {
278 function system_unavailable() {
279         include('system_unavailable.php');
280         killme();
281 }}
282
283 // Primarily involved with database upgrade, but also sets the 
284 // base url for use in cmdline programs which don't have
285 // $_SERVER variables.
286
287 if(! function_exists('check_config')) {
288 function check_config(&$a) {
289
290         load_config('system');
291
292         $build = get_config('system','build');
293         if(! x($build))
294                 $build = set_config('system','build',BUILD_ID);
295
296         $url = get_config('system','url');
297         if(! x($url))
298                 $url = set_config('system','url',$a->get_baseurl());
299
300         if($build != BUILD_ID) {
301                 $stored = intval($build);
302                 $current = intval(BUILD_ID);
303                 if(($stored < $current) && file_exists('update.php')) {
304                         // We're reporting a different version than what is currently installed.
305                         // Run any existing update scripts to bring the database up to current.
306
307                         require_once('update.php');
308                         for($x = $stored; $x < $current; $x ++) {
309                                 if(function_exists('update_' . $x)) {
310                                         $func = 'update_' . $x;
311                                         $func($a);
312                                 }
313                         }
314                         set_config('system','build', BUILD_ID);
315                 }
316         }
317         return;
318 }}
319
320
321 // This is our template processor.
322 // $s is the string requiring macro substitution.
323 // $r is an array of key value pairs (search => replace)
324 // returns substituted string.
325 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
326 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
327 // depending on the order in which they were declared in the array.   
328
329 if(! function_exists('replace_macros')) {  
330 function replace_macros($s,$r) {
331
332         $search = array();
333         $replace = array();
334
335         if(is_array($r) && count($r)) {
336                 foreach ($r as $k => $v ) {
337                         $search[] =  $k;
338                         $replace[] = $v;
339                 }
340         }
341         return str_replace($search,$replace,$s);
342 }}
343
344
345 // load string translation table for alternate language
346
347 if(! function_exists('load_translation_table')) {
348 function load_translation_table($lang) {
349         global $a;
350
351         if(file_exists("view/$lang/strings.php"))
352                 include("view/$lang/strings.php");
353 }}
354
355 // translate string if translation exists
356
357 if(! function_exists('t')) {
358 function t($s) {
359
360         $a = get_app();
361
362         if(x($a->strings,$s))
363                 return $a->strings[$s];
364         return $s;
365 }}
366
367 // curl wrapper. If binary flag is true, return binary
368 // results. 
369
370 if(! function_exists('fetch_url')) {
371 function fetch_url($url,$binary = false, &$redirects = 0) {
372         $ch = curl_init($url);
373         if(($redirects > 8) || (! $ch)) 
374                 return false;
375
376         curl_setopt($ch, CURLOPT_HEADER, true);
377         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
378
379
380         $curl_time = intval(get_config('system','curl_timeout'));
381         if($curl_time)
382                 curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time);
383
384         // by default we will allow self-signed certs
385         // but you can override this
386
387         $check_cert = get_config('system','verifyssl');
388         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
389
390         $prx = get_config('system','proxy');
391         if(strlen($prx)) {
392                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
393                 curl_setopt($ch, CURLOPT_PROXY, $prx);
394                 $prxusr = get_config('system','proxyuser');
395                 if(strlen($prxusr))
396                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
397         }
398         if($binary)
399                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
400
401         $s = curl_exec($ch);
402
403         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
404         $header = substr($s,0,strpos($s,"\r\n\r\n"));
405         if(stristr($header,'100') && (strlen($header) < 30)) {
406                 // 100 Continue has two headers, get the real one
407                 $s = substr($s,strlen($header)+4);
408                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
409         }
410         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
411         $matches = array();
412         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
413         $url = trim(array_pop($matches));
414         $url_parsed = parse_url($url);
415         if (isset($url_parsed)) {
416             $redirects++;
417             return fetch_url($url,$binary,$redirects);
418         }
419     }
420         $a = get_app();
421         $a->set_curl_code($http_code);
422         $body = substr($s,strlen($header)+4);
423         $a->set_curl_headers($header);
424
425         curl_close($ch);
426         return($body);
427 }}
428
429 // post request to $url. $params is an array of post variables.
430
431 if(! function_exists('post_url')) {
432 function post_url($url,$params, $headers = null, &$redirects = 0) {
433         $ch = curl_init($url);
434         if(($redirects > 8) || (! $ch)) 
435                 return false;
436
437         curl_setopt($ch, CURLOPT_HEADER, true);
438         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
439         curl_setopt($ch, CURLOPT_POST,1);
440         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
441
442         $curl_time = intval(get_config('system','curl_timeout'));
443         if($curl_time)
444                 curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time);
445
446         if(is_array($headers))
447                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
448
449         $check_cert = get_config('system','verifyssl');
450         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
451         $prx = get_config('system','proxy');
452         if(strlen($prx)) {
453                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
454                 curl_setopt($ch, CURLOPT_PROXY, $prx);
455                 $prxusr = get_config('system','proxyuser');
456                 if(strlen($prxusr))
457                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
458         }
459
460         $s = curl_exec($ch);
461
462         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
463         $header = substr($s,0,strpos($s,"\r\n\r\n"));
464         if(stristr($header,'100') && (strlen($header) < 30)) {
465                 // 100 Continue has two headers, get the real one
466                 $s = substr($s,strlen($header)+4);
467                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
468         }
469         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
470         $matches = array();
471         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
472         $url = trim(array_pop($matches));
473         $url_parsed = parse_url($url);
474         if (isset($url_parsed)) {
475             $redirects++;
476             return post_url($url,$binary,$headers,$redirects);
477         }
478     }
479         $a = get_app();
480         $a->set_curl_code($http_code);
481         $body = substr($s,strlen($header)+4);
482         $a->set_curl_headers($header);
483
484         curl_close($ch);
485         return($body);
486 }}
487
488 // random hash, 64 chars
489
490 if(! function_exists('random_string')) {
491 function random_string() {
492         return(hash('sha256',uniqid(rand(),true)));
493 }}
494
495 // This is our primary input filter. The high bit hack only involved some old
496 // IE browser, forget which. 
497 // Use this on any text input where angle chars are not valid or permitted
498 // They will be replaced with safer brackets. This may be filtered further
499 // if these are not allowed either.   
500
501 if(! function_exists('notags')) {
502 function notags($string) {
503         // protect against :<> with high-bit set
504         return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
505 }}
506
507 // use this on "body" or "content" input where angle chars shouldn't be removed,
508 // and allow them to be safely displayed.
509
510 if(! function_exists('escape_tags')) {
511 function escape_tags($string) {
512
513         return(htmlspecialchars($string));
514 }}
515
516 // wrapper for adding a login box. If $register == true provide a registration
517 // link. This will most always depend on the value of $a->config['register_policy'].
518 // returns the complete html for inserting into the page
519
520 if(! function_exists('login')) {
521 function login($register = false) {
522         $o = "";
523         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
524
525
526         if(x($_SESSION,'authenticated')) {
527                 $o = load_view_file("view/logout.tpl");
528         }
529         else {
530                 $o = load_view_file("view/login.tpl");
531
532                 $o = replace_macros($o,array('$register_html' => $register_html ));
533         }
534         return $o;
535 }}
536
537 // generate a string that's random, but usually pronounceable. 
538 // used to generate initial passwords
539
540 if(! function_exists('autoname')) {
541 function autoname($len) {
542
543         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
544         if(mt_rand(0,5) == 4)
545                 $vowels[] = 'y';
546
547         $cons = array(
548                         'b','bl','br',
549                         'c','ch','cl','cr',
550                         'd','dr',
551                         'f','fl','fr',
552                         'g','gh','gl','gr',
553                         'h',
554                         'j',
555                         'k','kh','kl','kr',
556                         'l',
557                         'm',
558                         'n',
559                         'p','ph','pl','pr',
560                         'qu',
561                         'r','rh',
562                         's','sc','sh','sm','sp','st',
563                         't','th','tr',
564                         'v',
565                         'w','wh',
566                         'x',
567                         'z','zh'
568                         );
569
570         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
571                                 'nd','ng','nk','nt','rn','rp','rt');
572
573         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
574                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
575
576         $start = mt_rand(0,2);
577         if($start == 0)
578                 $table = $vowels;
579         else
580                 $table = $cons;
581
582         $word = '';
583
584         for ($x = 0; $x < $len; $x ++) {
585                 $r = mt_rand(0,count($table) - 1);
586                 $word .= $table[$r];
587   
588                 if($table == $vowels)
589                         $table = array_merge($cons,$midcons);
590                 else
591                         $table = $vowels;
592
593         }
594
595         $word = substr($word,0,$len);
596
597         foreach($noend as $noe) {
598                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
599                         $word = substr($word,0,-1);
600                         break;
601                 }
602         }
603         if(substr($word,-1) == 'q')
604                 $word = substr($word,0,-1);    
605         return $word;
606 }}
607
608 // Used to end the current process, after saving session state. 
609
610 if(! function_exists('killme')) {
611 function killme() {
612         session_write_close();
613         exit;
614 }}
615
616 // redirect to another URL and terminate this process.
617
618 if(! function_exists('goaway')) {
619 function goaway($s) {
620         header("Location: $s");
621         killme();
622 }}
623
624 // Generic XML return
625 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
626 // of $st and an optional text <message> of $message and terminates the current process. 
627
628 if(! function_exists('xml_status')) {
629 function xml_status($st, $message = '') {
630
631         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
632
633         header( "Content-type: text/xml" );
634         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
635         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
636         killme();
637 }}
638
639 // Returns the uid of locally logged in user or false.
640
641 if(! function_exists('local_user')) {
642 function local_user() {
643         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
644                 return intval($_SESSION['uid']);
645         return false;
646 }}
647
648 // Returns contact id of authenticated site visitor or false
649
650 if(! function_exists('remote_user')) {
651 function remote_user() {
652         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
653                 return intval($_SESSION['visitor_id']);
654         return false;
655 }}
656
657 // contents of $s are displayed prominently on the page the next time
658 // a page is loaded. Usually used for errors or alerts.
659
660 if(! function_exists('notice')) {
661 function notice($s) {
662         $a = get_app();
663         if($a->interactive)
664                 $_SESSION['sysmsg'] .= $s;
665 }}
666
667 // wrapper around config to limit the text length of an incoming message
668
669 if(! function_exists('get_max_import_size')) {
670 function get_max_import_size() {
671         global $a;
672         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
673 }}
674
675
676 // escape text ($str) for XML transport
677 // returns escaped text.
678
679 if(! function_exists('xmlify')) {
680 function xmlify($str) {
681         $buffer = '';
682         
683         for($x = 0; $x < strlen($str); $x ++) {
684                 $char = $str[$x];
685         
686                 switch( $char ) {
687
688                         case "\r" :
689                                 break;
690                         case "&" :
691                                 $buffer .= '&amp;';
692                                 break;
693                         case "'" :
694                                 $buffer .= '&apos;';
695                                 break;
696
697                         case "\"" :
698                                 $buffer .= '&quot;';
699                                 break;
700                         case '<' :
701                                 $buffer .= '&lt;';
702                                 break;
703                         case '>' :
704                                 $buffer .= '&gt;';
705                                 break;
706                         case "\n" :
707                                 $buffer .= "\n";
708                                 break;
709                         default :
710                                 $buffer .= $char;
711                                 break;
712                 }       
713         }
714         $buffer = trim($buffer);
715         return($buffer);
716 }}
717
718 // undo an xmlify
719 // pass xml escaped text ($s), returns unescaped text
720
721 if(! function_exists('unxmlify')) {
722 function unxmlify($s) {
723         $ret = str_replace('&amp;','&', $s);
724         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
725         return $ret;    
726 }}
727
728 // convenience wrapper, reverse the operation "bin2hex"
729
730 if(! function_exists('hex2bin')) {
731 function hex2bin($s) {
732         return(pack("H*",$s));
733 }}
734
735 // Automatic pagination.
736 // To use, get the count of total items.
737 // Then call $a->set_pager_total($number_items);
738 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
739 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
740 // (assuming there are enough items to paginate).
741 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
742 // will limit the results to the correct items for the current page. 
743 // The actual page handling is then accomplished at the application layer. 
744
745 if(! function_exists('paginate')) {
746 function paginate(&$a) {
747         $o = '';
748         $stripped = preg_replace('/(&page=[0-9]*)/','',$_SERVER['QUERY_STRING']);
749         $stripped = str_replace('q=','',$stripped);
750         $stripped = trim($stripped,'/');
751         $url = $a->get_baseurl() . '/' . $stripped;
752
753
754           if($a->pager['total'] > $a->pager['itemspage']) {
755                 $o .= '<div class="pager">';
756                 if($a->pager['page'] != 1)
757                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
758
759                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
760
761                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
762
763                 $numstart = 1;
764                 $numstop = $numpages;
765
766                 if($numpages > 14) {
767                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
768                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
769                 }
770    
771                 for($i = $numstart; $i <= $numstop; $i++){
772                         if($i == $a->pager['page'])
773                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
774                         else
775                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
776                         $o .= '</span> ';
777                 }
778
779                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
780                         if($i == $a->pager['page'])
781                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
782                         else
783                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
784                         $o .= '</span> ';
785                 }
786
787                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
788                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
789
790                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
791                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
792                 $o .= '</div>'."\r\n";
793         }
794         return $o;
795 }}
796
797 // Turn user/group ACLs stored as angle bracketed text into arrays
798
799 if(! function_exists('expand_acl')) {
800 function expand_acl($s) {
801         // turn string array of angle-bracketed elements into numeric array
802         // e.g. "<1><2><3>" => array(1,2,3);
803         $ret = array();
804
805         if(strlen($s)) {
806                 $t = str_replace('<','',$s);
807                 $a = explode('>',$t);
808                 foreach($a as $aa) {
809                         if(intval($aa))
810                                 $ret[] = intval($aa);
811                 }
812         }
813         return $ret;
814 }}              
815
816 // Used to wrap ACL elements in angle brackets for storage 
817
818 if(! function_exists('sanitise_acl')) {
819 function sanitise_acl(&$item) {
820         if(intval($item))
821                 $item = '<' . intval(notags(trim($item))) . '>';
822         else
823                 unset($item);
824 }}
825
826 // retrieve a "family" of config variables from database to cached storage
827
828 if(! function_exists('load_config')) {
829 function load_config($family) {
830         global $a;
831         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
832                 dbesc($family)
833         );
834         if(count($r)) {
835                 foreach($r as $rr) {
836                         $k = $rr['k'];
837                         $a->config[$family][$k] = $rr['v'];
838                 }
839         }
840 }}
841
842 // get a particular config variable given the family name
843 // and key. Returns false if not set.
844 // $instore is only used by the set_config function
845 // to determine if the key already exists in the DB
846 // If a key is found in the DB but doesn't exist in
847 // local config cache, pull it into the cache so we don't have
848 // to hit the DB again for this item.
849
850 if(! function_exists('get_config')) {
851 function get_config($family, $key, $instore = false) {
852
853         global $a;
854
855         if(! $instore) {
856                 if(isset($a->config[$family][$key])) {
857                         if($a->config[$family][$key] === '!<unset>!') {
858                                 return false;
859                         }
860                         return $a->config[$family][$key];
861                 }
862         }
863         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
864                 dbesc($family),
865                 dbesc($key)
866         );
867         if(count($ret)) {
868                 $a->config[$family][$key] = $ret[0]['v'];
869                 return $ret[0]['v'];
870         }
871         else {
872                 $a->config[$family][$key] = '!<unset>!';
873         }
874         return false;
875 }}
876
877 // Store a config value ($value) in the category ($family)
878 // under the key ($key)
879 // Return the value, or false if the database update failed
880
881 if(! function_exists('set_config')) {
882 function set_config($family,$key,$value) {
883
884         global $a;
885         $a->config[$family][$key] = $value;
886
887         if(get_config($family,$key,true) === false) {
888                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
889                         dbesc($family),
890                         dbesc($key),
891                         dbesc($value)
892                 );
893                 if($ret) 
894                         return $value;
895                 return $ret;
896         }
897         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
898                 dbesc($value),
899                 dbesc($family),
900                 dbesc($key)
901         );
902         if($ret)
903                 return $value;
904         return $ret;
905 }}
906
907 // convert an XML document to a normalised, case-corrected array
908 // used by webfinger
909
910 if(! function_exists('convert_xml_element_to_array')) {
911 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
912
913         // If we're getting too deep, bail out
914         if ($recursion_depth > 512) {
915                 return(null);
916         }
917
918         if (!is_string($xml_element) &&
919         !is_array($xml_element) &&
920         (get_class($xml_element) == 'SimpleXMLElement')) {
921                 $xml_element_copy = $xml_element;
922                 $xml_element = get_object_vars($xml_element);
923         }
924
925         if (is_array($xml_element)) {
926                 $result_array = array();
927                 if (count($xml_element) <= 0) {
928                         return (trim(strval($xml_element_copy)));
929                 }
930
931                 foreach($xml_element as $key=>$value) {
932
933                         $recursion_depth++;
934                         $result_array[strtolower($key)] =
935                 convert_xml_element_to_array($value, $recursion_depth);
936                         $recursion_depth--;
937                 }
938                 if ($recursion_depth == 0) {
939                         $temp_array = $result_array;
940                         $result_array = array(
941                                 strtolower($xml_element_copy->getName()) => $temp_array,
942                         );
943                 }
944
945                 return ($result_array);
946
947         } else {
948                 return (trim(strval($xml_element)));
949         }
950 }}
951
952 // Given an email style address, perform webfinger lookup and 
953 // return the resulting DFRN profile URL, or if no DFRN profile URL
954 // is located, returns an OStatus subscription template (prefixed 
955 // with the string 'stat:' to identify it as on OStatus template).
956 // If this isn't an email style address just return $s.
957 // Return an empty string if email-style addresses but webfinger fails,
958 // or if the resultant personal XRD doesn't contain a supported 
959 // subscription/friend-request attribute.
960
961 if(! function_exists('webfinger_dfrn')) {
962 function webfinger_dfrn($s) {
963         if(! strstr($s,'@')) {
964                 return $s;
965         }
966         $links = webfinger($s);
967         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
968         if(count($links)) {
969                 foreach($links as $link)
970                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
971                                 return $link['@attributes']['href'];
972                 foreach($links as $link)
973                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
974                                 return 'stat:' . $link['@attributes']['template'];              
975         }
976         return '';
977 }}
978
979 // Given an email style address, perform webfinger lookup and 
980 // return the array of link attributes from the personal XRD file.
981 // On error/failure return an empty array.
982
983
984 if(! function_exists('webfinger')) {
985 function webfinger($s) {
986         $host = '';
987         if(strstr($s,'@')) {
988                 $host = substr($s,strpos($s,'@') + 1);
989         }
990         if(strlen($host)) {
991                 $tpl = fetch_lrdd_template($host);
992                 logger('webfinger: lrdd template: ' . $tpl);
993                 if(strlen($tpl)) {
994                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
995                         $links = fetch_xrd_links($pxrd);
996                         if(! count($links)) {
997                                 // try with double slashes
998                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
999                                 $links = fetch_xrd_links($pxrd);
1000                         }
1001                         return $links;
1002                 }
1003         }
1004         return array();
1005 }}
1006
1007 if(! function_exists('lrdd')) {
1008 function lrdd($uri) {
1009
1010         $a = get_app();
1011
1012         if(strstr($uri,'@')) {  
1013                 return(webfinger($uri));
1014         }
1015         else {
1016                 $html = fetch_url($uri);
1017                 $headers = $a->get_curl_headers();
1018                 $lines = explode("\n",$headers);
1019                 if(count($lines)) {
1020                         foreach($lines as $line) {                              
1021                                 // TODO alter the following regex to support multiple relations (space separated)
1022                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1023                                         $link = $matches[1];
1024                                         break;
1025                                 }
1026                         }
1027                 }
1028                 if(! isset($link)) {
1029                         // parse the page of the supplied URL looking for rel links
1030
1031                         require_once('library/HTML5/Parser.php');
1032                         $dom = HTML5_Parser::parse($html);
1033
1034                         if($dom) {
1035                                 $items = $dom->getElementsByTagName('link');
1036
1037                                 foreach($items as $item) {
1038                                         $x = $item->getAttribute('rel');
1039                                         if($x == "lrdd") {
1040                                                 $link = $item->getAttribute('href');
1041                                                 break;
1042                                         }
1043                                 }
1044                         }
1045                 }
1046
1047                 if(isset($link))
1048                         return(fetch_xrd_links($link));
1049         }
1050         return array();
1051 }}
1052
1053
1054
1055 // Given a host name, locate the LRDD template from that
1056 // host. Returns the LRDD template or an empty string on
1057 // error/failure.
1058
1059 if(! function_exists('fetch_lrdd_template')) {
1060 function fetch_lrdd_template($host) {
1061         $tpl = '';
1062         $url = 'http://' . $host . '/.well-known/host-meta' ;
1063         $links = fetch_xrd_links($url);
1064         if(count($links)) {
1065                 foreach($links as $link)
1066                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1067                                 $tpl = $link['@attributes']['template'];
1068         }
1069         if(! strpos($tpl,'{uri}'))
1070                 $tpl = '';
1071         return $tpl;
1072 }}
1073
1074 // Given a URL, retrieve the page as an XRD document.
1075 // Return an array of links.
1076 // on error/failure return empty array.
1077
1078 if(! function_exists('fetch_xrd_links')) {
1079 function fetch_xrd_links($url) {
1080
1081
1082         $xml = fetch_url($url);
1083         if (! $xml)
1084                 return array();
1085
1086         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1087         $h = simplexml_load_string($xml);
1088         $arr = convert_xml_element_to_array($h);
1089
1090         if(isset($arr['xrd']['link'])) {
1091                 $link = $arr['xrd']['link'];
1092                 if(! isset($link[0]))
1093                         $links = array($link);
1094                 else
1095                         $links = $link;
1096                 return $links;
1097         }
1098         return array();
1099 }}
1100
1101 // Convert an ACL array to a storable string
1102
1103 if(! function_exists('perms2str')) {
1104 function perms2str($p) {
1105         $ret = '';
1106         $tmp = $p;
1107         if(is_array($tmp)) {
1108                 array_walk($tmp,'sanitise_acl');
1109                 $ret = implode('',$tmp);
1110         }
1111         return $ret;
1112 }}
1113
1114 // generate a guaranteed unique (for this domain) item ID for ATOM
1115 // safe from birthday paradox
1116
1117 if(! function_exists('item_new_uri')) {
1118 function item_new_uri($hostname,$uid) {
1119
1120         do {
1121                 $dups = false;
1122                 $hash = random_string();
1123
1124                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1125
1126                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1127                         dbesc($uri));
1128                 if(count($r))
1129                         $dups = true;
1130         } while($dups == true);
1131         return $uri;
1132 }}
1133
1134 // Generate a guaranteed unique photo ID.
1135 // safe from birthday paradox
1136
1137 if(! function_exists('photo_new_resource')) {
1138 function photo_new_resource() {
1139
1140         do {
1141                 $found = false;
1142                 $resource = hash('md5',uniqid(mt_rand(),true));
1143                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1144                         dbesc($resource)
1145                 );
1146                 if(count($r))
1147                         $found = true;
1148         } while($found == true);
1149         return $resource;
1150 }}
1151
1152
1153 // Take a URL from the wild, prepend http:// if necessary
1154 // and check DNS to see if it's real
1155 // return true if it's OK, false if something is wrong with it
1156
1157 if(! function_exists('validate_url')) {
1158 function validate_url(&$url) {
1159         if(substr($url,0,4) != 'http')
1160                 $url = 'http://' . $url;
1161         $h = parse_url($url);
1162
1163         if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
1164                 return true;
1165         }
1166         return false;
1167 }}
1168
1169 // checks that email is an actual resolvable internet address
1170
1171 if(! function_exists('validate_email')) {
1172 function validate_email($addr) {
1173
1174         if(! strpos($addr,'@'))
1175                 return false;
1176         $h = substr($addr,strpos($addr,'@') + 1);
1177
1178         if(($h) && (checkdnsrr($h, 'ANY'))) {
1179                 return true;
1180         }
1181         return false;
1182 }}
1183
1184 // Check $url against our list of allowed sites,
1185 // wildcards allowed. If allowed_sites is unset return true;
1186 // If url is allowed, return true.
1187 // otherwise, return false
1188
1189 if(! function_exists('allowed_url')) {
1190 function allowed_url($url) {
1191
1192         $h = parse_url($url);
1193
1194         if(! $h) {
1195                 return false;
1196         }
1197
1198         $str_allowed = get_config('system','allowed_sites');
1199         if(! $str_allowed)
1200                 return true;
1201
1202         $found = false;
1203
1204         $host = strtolower($h['host']);
1205
1206         // always allow our own site
1207
1208         if($host == strtolower($_SERVER['SERVER_NAME']))
1209                 return true;
1210
1211         $fnmatch = function_exists('fnmatch');
1212         $allowed = explode(',',$str_allowed);
1213
1214         if(count($allowed)) {
1215                 foreach($allowed as $a) {
1216                         $pat = strtolower(trim($a));
1217                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1218                                 $found = true; 
1219                                 break;
1220                         }
1221                 }
1222         }
1223         return $found;
1224 }}
1225
1226 // check if email address is allowed to register here.
1227 // Compare against our list (wildcards allowed).
1228 // Returns false if not allowed, true if allowed or if
1229 // allowed list is not configured.
1230
1231 if(! function_exists('allowed_email')) {
1232 function allowed_email($email) {
1233
1234
1235         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1236         if(! $domain)
1237                 return false;
1238
1239         $str_allowed = get_config('system','allowed_email');
1240         if(! $str_allowed)
1241                 return true;
1242
1243         $found = false;
1244
1245         $fnmatch = function_exists('fnmatch');
1246         $allowed = explode(',',$str_allowed);
1247
1248         if(count($allowed)) {
1249                 foreach($allowed as $a) {
1250                         $pat = strtolower(trim($a));
1251                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1252                                 $found = true; 
1253                                 break;
1254                         }
1255                 }
1256         }
1257         return $found;
1258 }}
1259
1260 // Format the like/dislike text for a profile item
1261 // $cnt = number of people who like/dislike the item
1262 // $arr = array of pre-linked names of likers/dislikers
1263 // $type = one of 'like, 'dislike'
1264 // $id  = item id
1265 // returns formatted text
1266
1267 if(! function_exists('format_like')) {
1268 function format_like($cnt,$arr,$type,$id) {
1269         $o = '';
1270         if($cnt == 1)
1271                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1272         else {
1273                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1274                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1275                 $total = count($arr);
1276                 if($total >= MAX_LIKERS)
1277                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1278                 if($total < MAX_LIKERS)
1279                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1280                 $str = implode(', ', $arr);
1281                 if($total >= MAX_LIKERS)
1282                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1283                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1284                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1285         }
1286         return $o;
1287 }}
1288
1289
1290 // wrapper to load a view template, checking for alternate
1291 // languages before falling back to the default
1292
1293 if(! function_exists('load_view_file')) {
1294 function load_view_file($s) {
1295         $b = basename($s);
1296         $d = dirname($s);
1297         $lang = get_config('system','language');
1298         if($lang === false)
1299                 $lang = 'en';
1300         if(file_exists("$d/$lang/$b"))
1301                 return file_get_contents("$d/$lang/$b");
1302         return file_get_contents($s);
1303 }}
1304
1305 // for html,xml parsing - let's say you've got
1306 // an attribute foobar="class1 class2 class3"
1307 // and you want to find out if it contains 'class3'.
1308 // you can't use a normal sub string search because you
1309 // might match 'notclass3' and a regex to do the job is 
1310 // possible but a bit complicated. 
1311 // pass the attribute string as $attr and the attribute you 
1312 // are looking for as $s - returns true if found, otherwise false
1313
1314 if(! function_exists('attribute_contains')) {
1315 function attribute_contains($attr,$s) {
1316         $a = explode(' ', $attr);
1317         if(count($a) && in_array($s,$a))
1318                 return true;
1319         return false;
1320 }}
1321
1322 if(! function_exists('logger')) {
1323 function logger($msg,$level = 0) {
1324
1325         $debugging = get_config('system','debugging');
1326         $loglevel  = intval(get_config('system','loglevel'));
1327         $logfile   = get_config('system','logfile');
1328
1329         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1330                 return;
1331         
1332         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1333         return;
1334 }}
1335
1336
1337 if(! function_exists('activity_match')) {
1338 function activity_match($haystack,$needle) {
1339         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1340                 return true;
1341         return false;
1342 }}
1343
1344
1345 // Pull out all #hashtags and @person tags from $s;
1346 // We also get @person@domain.com - which would make 
1347 // the regex quite complicated as tags can also
1348 // end a sentence. So we'll run through our results
1349 // and strip the period from any tags which end with one.
1350 // Returns array of tags found, or empty array.
1351
1352
1353 if(! function_exists('get_tags')) {
1354 function get_tags($s) {
1355         $ret = array();
1356         if(preg_match_all('/([@#][^ ,:?]*)([ ,:?]|$)/',$s,$match)) {
1357                 foreach($match[1] as $match) {
1358                         if(substr($match,-1,1) === '.')
1359                                 $ret[] = substr($match,0,-1);
1360                         else
1361                                 $ret[] = $match;
1362                 }
1363         }
1364
1365         return $ret;
1366 }}
1367
1368
1369 // quick and dirty quoted_printable encoding
1370
1371 if(! function_exists('qp')) {
1372 function qp($s) {
1373 return str_replace ("%","=",rawurlencode($s));
1374 }} 
1375
1376
1377 if(! function_exists('like_puller')) {
1378 function like_puller($a,$item,&$arr,$mode) {
1379
1380         $url = '';
1381         $sparkle = '';
1382         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1383
1384         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1385                 $url = $item['url'];
1386                 if(($item['network'] === 'dfrn') && (! $item['self'])) {
1387                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1388                         $sparkle = ' class="sparkle" ';
1389                 }
1390                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1391                         $arr[$item['parent'] . '-l'] = array();
1392                 if(! isset($arr[$item['parent']]))
1393                         $arr[$item['parent']] = 1;
1394                 else    
1395                         $arr[$item['parent']] ++;
1396                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['name'] . '</a>';
1397         }
1398         return;
1399 }}
1400
1401 if(! function_exists('get_mentions')) {
1402 function get_mentions($item) {
1403         $o = '';
1404         if(! strlen($item['tag']))
1405                 return $o;
1406
1407         $arr = explode(',',$item['tag']);
1408         foreach($arr as $x) {
1409                 $matches = null;
1410                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1411                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1412         }
1413         return $o;
1414 }}
1415
1416 if(! function_exists('contact_block')) {
1417 function contact_block() {
1418         $o = '';
1419         $a = get_app();
1420         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1421                 return $o;
1422         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1423                         intval($a->profile['uid'])
1424         );
1425         if(count($r)) {
1426                 $total = intval($r[0]['total']);
1427         }
1428         if(! $total) {
1429                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1430                 return $o;
1431         }
1432         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1433                         intval($a->profile['uid'])
1434         );
1435         if(count($r)) {
1436                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1437                 foreach($r as $rr) {
1438                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1439                         if(local_user() && ($rr['uid'] == local_user())
1440                                 && ($rr['network'] === 'dfrn')) {
1441                                 $url = $redirect_url;
1442                                 $sparkle = ' sparkle';
1443                         }
1444                         else {
1445                                 $url = $rr['url'];
1446                                 $sparkle = '';
1447                         }
1448
1449                         $o .= '<div class="contact-block-div"><a class="contact-block-link' . $sparkle . '" href="' . $url . '" ><img class="contact-block-img' . $sparkle . '" src="' . $rr['micro'] . '" title="' . $rr['name'] . ' [' . $rr['url'] . ']" alt="' . $rr['name'] . '" /></a></div>' . "\r\n";
1450                 }
1451                 $o .= '</div><div id="contact-block-end"></div>';
1452                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1453                 
1454         }
1455         return $o;
1456
1457 }}
1458
1459 if(! function_exists('search')) {
1460 function search($s) {
1461         $a = get_app();
1462         $o  = '<div id="search-box">';
1463         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1464         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1465         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1466         $o .= '</form></div>';
1467         return $o;
1468 }}
1469
1470 if(! function_exists('valid_email')) {
1471 function valid_email($x){
1472         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1473                 return true;
1474         return false;
1475 }}
1476
1477
1478 if(! function_exists('gravatar_img')) {
1479 function gravatar_img($email) {
1480         $size = 175;
1481         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1482         $rating = 'pg';
1483         $hash = md5(trim(strtolower($email)));
1484         
1485         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1486                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1487
1488         logger('gravatar: ' . $email . ' ' . $url);
1489         return $url;
1490 }}
1491