]> git.mxchange.org Git - friendica.git/blob - boot.php
baf1f4812125b9b1a210d062dc38d1ab51f7b71a
[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) {
1164                 return false;
1165         }
1166         if(! checkdnsrr($h['host'], 'ANY')) {
1167                 return false;
1168         }
1169         return true;
1170 }}
1171
1172 // Check $url against our list of allowed sites,
1173 // wildcards allowed. If allowed_sites is unset return true;
1174 // If url is allowed, return true.
1175 // otherwise, return false
1176
1177 if(! function_exists('allowed_url')) {
1178 function allowed_url($url) {
1179
1180         $h = parse_url($url);
1181
1182         if(! $h) {
1183                 return false;
1184         }
1185
1186         $str_allowed = get_config('system','allowed_sites');
1187         if(! $str_allowed)
1188                 return true;
1189
1190         $found = false;
1191
1192         $host = strtolower($h['host']);
1193
1194         // always allow our own site
1195
1196         if($host == strtolower($_SERVER['SERVER_NAME']))
1197                 return true;
1198
1199         $fnmatch = function_exists('fnmatch');
1200         $allowed = explode(',',$str_allowed);
1201
1202         if(count($allowed)) {
1203                 foreach($allowed as $a) {
1204                         $pat = strtolower(trim($a));
1205                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1206                                 $found = true; 
1207                                 break;
1208                         }
1209                 }
1210         }
1211         return $found;
1212 }}
1213
1214 // check if email address is allowed to register here.
1215 // Compare against our list (wildcards allowed).
1216 // Returns false if not allowed, true if allowed or if
1217 // allowed list is not configured.
1218
1219 if(! function_exists('allowed_email')) {
1220 function allowed_email($email) {
1221
1222
1223         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1224         if(! $domain)
1225                 return false;
1226
1227         $str_allowed = get_config('system','allowed_email');
1228         if(! $str_allowed)
1229                 return true;
1230
1231         $found = false;
1232
1233         $fnmatch = function_exists('fnmatch');
1234         $allowed = explode(',',$str_allowed);
1235
1236         if(count($allowed)) {
1237                 foreach($allowed as $a) {
1238                         $pat = strtolower(trim($a));
1239                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1240                                 $found = true; 
1241                                 break;
1242                         }
1243                 }
1244         }
1245         return $found;
1246 }}
1247
1248 // Format the like/dislike text for a profile item
1249 // $cnt = number of people who like/dislike the item
1250 // $arr = array of pre-linked names of likers/dislikers
1251 // $type = one of 'like, 'dislike'
1252 // $id  = item id
1253 // returns formatted text
1254
1255 if(! function_exists('format_like')) {
1256 function format_like($cnt,$arr,$type,$id) {
1257         $o = '';
1258         if($cnt == 1)
1259                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1260         else {
1261                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1262                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1263                 $total = count($arr);
1264                 if($total >= MAX_LIKERS)
1265                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1266                 if($total < MAX_LIKERS)
1267                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1268                 $str = implode(', ', $arr);
1269                 if($total >= MAX_LIKERS)
1270                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1271                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1272                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1273         }
1274         return $o;
1275 }}
1276
1277
1278 // wrapper to load a view template, checking for alternate
1279 // languages before falling back to the default
1280
1281 if(! function_exists('load_view_file')) {
1282 function load_view_file($s) {
1283         $b = basename($s);
1284         $d = dirname($s);
1285         $lang = get_config('system','language');
1286         if($lang && file_exists("$d/$lang/$b"))
1287                 return file_get_contents("$d/$lang/$b");
1288         return file_get_contents($s);
1289 }}
1290
1291 // for html,xml parsing - let's say you've got
1292 // an attribute foobar="class1 class2 class3"
1293 // and you want to find out if it contains 'class3'.
1294 // you can't use a normal sub string search because you
1295 // might match 'notclass3' and a regex to do the job is 
1296 // possible but a bit complicated. 
1297 // pass the attribute string as $attr and the attribute you 
1298 // are looking for as $s - returns true if found, otherwise false
1299
1300 if(! function_exists('attribute_contains')) {
1301 function attribute_contains($attr,$s) {
1302         $a = explode(' ', $attr);
1303         if(count($a) && in_array($s,$a))
1304                 return true;
1305         return false;
1306 }}
1307
1308 if(! function_exists('logger')) {
1309 function logger($msg,$level = 0) {
1310
1311         $debugging = get_config('system','debugging');
1312         $loglevel  = intval(get_config('system','loglevel'));
1313         $logfile   = get_config('system','logfile');
1314
1315         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1316                 return;
1317         
1318         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1319         return;
1320 }}
1321
1322
1323 if(! function_exists('activity_match')) {
1324 function activity_match($haystack,$needle) {
1325         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1326                 return true;
1327         return false;
1328 }}
1329
1330
1331 // Pull out all #hashtags and @person tags from $s;
1332 // We also get @person@domain.com - which would make 
1333 // the regex quite complicated as tags can also
1334 // end a sentence. So we'll run through our results
1335 // and strip the period from any tags which end with one.
1336 // Returns array of tags found, or empty array.
1337
1338
1339 if(! function_exists('get_tags')) {
1340 function get_tags($s) {
1341         $ret = array();
1342         if(preg_match_all('/([@#][^ ,:?]*)([ ,:?]|$)/',$s,$match)) {
1343                 foreach($match[1] as $match) {
1344                         if(substr($match,-1,1) === '.')
1345                                 $ret[] = substr($match,0,-1);
1346                         else
1347                                 $ret[] = $match;
1348                 }
1349         }
1350
1351         return $ret;
1352 }}
1353
1354
1355 // quick and dirty quoted_printable encoding
1356
1357 if(! function_exists('qp')) {
1358 function qp($s) {
1359 return str_replace ("%","=",rawurlencode($s));
1360 }} 
1361
1362
1363 if(! function_exists('like_puller')) {
1364 function like_puller($a,$item,&$arr,$mode) {
1365
1366         $url = '';
1367         $sparkle = '';
1368         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1369
1370         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1371                 $url = $item['url'];
1372                 if(($item['network'] === 'dfrn') && (! $item['self'])) {
1373                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1374                         $sparkle = ' class="sparkle" ';
1375                 }
1376                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1377                         $arr[$item['parent'] . '-l'] = array();
1378                 if(! isset($arr[$item['parent']]))
1379                         $arr[$item['parent']] = 1;
1380                 else    
1381                         $arr[$item['parent']] ++;
1382                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['name'] . '</a>';
1383         }
1384         return;
1385 }}
1386
1387 if(! function_exists('get_mentions')) {
1388 function get_mentions($item) {
1389         $o = '';
1390         if(! strlen($item['tag']))
1391                 return $o;
1392
1393         $arr = explode(',',$item['tag']);
1394         foreach($arr as $x) {
1395                 $matches = null;
1396                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1397                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1398         }
1399         return $o;
1400 }}
1401
1402 if(! function_exists('contact_block')) {
1403 function contact_block() {
1404         $o = '';
1405         $a = get_app();
1406         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1407                 return $o;
1408         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1409                         intval($a->profile['uid'])
1410         );
1411         if(count($r)) {
1412                 $total = intval($r[0]['total']);
1413         }
1414         if(! $total) {
1415                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1416                 return $o;
1417         }
1418         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1419                         intval($a->profile['uid'])
1420         );
1421         if(count($r)) {
1422                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1423                 foreach($r as $rr) {
1424                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1425                         if(local_user() && ($rr['uid'] == local_user())
1426                                 && ($rr['network'] === 'dfrn')) {
1427                                 $url = $redirect_url;
1428                                 $sparkle = ' sparkle';
1429                         }
1430                         else {
1431                                 $url = $rr['url'];
1432                                 $sparkle = '';
1433                         }
1434
1435                         $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";
1436                 }
1437                 $o .= '</div><div id="contact-block-end"></div>';
1438                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1439                 
1440         }
1441         return $o;
1442
1443 }}
1444
1445 if(! function_exists('search')) {
1446 function search($s) {
1447         $a = get_app();
1448         $o  = '<div id="search-box">';
1449         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1450         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1451         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1452         $o .= '</form></div>';
1453         return $o;
1454 }}
1455
1456 if(! function_exists('valid_email')) {
1457 function valid_email($x){
1458         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1459                 return true;
1460         return false;
1461 }}
1462
1463
1464 if(! function_exists('gravatar_img')) {
1465 function gravatar_img($email) {
1466         $size = 175;
1467         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1468         $rating = 'pg';
1469         $hash = md5(trim(strtolower($email)));
1470         
1471         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1472                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1473
1474         logger('gravatar: ' . $email . ' ' . $url);
1475         return $url;
1476 }}
1477