]> git.mxchange.org Git - friendica.git/blob - boot.php
lrdd link on profile page (for salmon m/e), remove dfrn-template, qualify some contac...
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1008   );
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 // registration policy
12
13 define ( 'REGISTER_CLOSED',        0 );
14 define ( 'REGISTER_APPROVE',       1 );
15 define ( 'REGISTER_OPEN',          2 );
16
17 // relationship types
18
19 define ( 'REL_VIP',        1);
20 define ( 'REL_FAN',        2);
21 define ( 'REL_BUD',        3);
22
23
24 // page/profile types
25 // PAGE_NORMAL is a typical personal profile account
26 // PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
27 // PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
28 // write access to wall and comments (no email and not included in page owner's ACL lists)
29 // PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
30
31 define ( 'PAGE_NORMAL',            0 );
32 define ( 'PAGE_SOAPBOX',           1 );
33 define ( 'PAGE_COMMUNITY',         2 );
34 define ( 'PAGE_FREELOVE',          3 );
35
36 // Maximum number of "people who like (or don't like) this" 
37 // that we will list by name
38
39 define ( 'MAX_LIKERS',    75);
40
41 // email notification options
42
43 define ( 'NOTIFY_INTRO',   0x0001 );
44 define ( 'NOTIFY_CONFIRM', 0x0002 );
45 define ( 'NOTIFY_WALL',    0x0004 );
46 define ( 'NOTIFY_COMMENT', 0x0008 );
47 define ( 'NOTIFY_MAIL',    0x0010 );
48
49 // various namespaces we may need to parse
50
51 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
52 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
53 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
54 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
55 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/');
56 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env');
57 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe');
58
59 // activity stream defines
60
61 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
62 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
63 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
64
65 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
66 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
67 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
68
69 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
70 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
71 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
72 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
73 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
74 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
75
76 // item weight for query ordering
77
78 define ( 'GRAVITY_PARENT',       0);
79 define ( 'GRAVITY_LIKE',         3);
80 define ( 'GRAVITY_COMMENT',      6);
81
82
83 // Our main application structure for the life of this page
84 // Primarily deals with the URL that got us here
85 // and tries to make some sense of it, and 
86 // stores our page contents and config storage
87 // and anything else that might need to be passed around 
88 // before we spit the page out. 
89
90 if(! class_exists('App')) {
91 class App {
92
93         public  $module_loaded = false;
94         public  $config;
95         public  $page;
96         public  $profile;
97         public  $user;
98         public  $cid;
99         public  $contact;
100         public  $content;
101         public  $data;
102         public  $error = false;
103         public  $cmd;
104         public  $argv;
105         public  $argc;
106         public  $module;
107         public  $pager;
108         public  $strings;   
109         public  $path;
110         public  $interactive = true;
111
112         private $scheme;
113         private $hostname;
114         private $baseurl;
115         private $db;
116
117         private $curl_code;
118
119         function __construct() {
120
121                 $this->config = array();
122                 $this->page = array();
123                 $this->pager= array();
124
125                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
126                 $this->hostname = $_SERVER['SERVER_NAME'];
127                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
128
129                 if(substr($_SERVER['QUERY_STRING'],0,2) == "q=")
130                         $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'],2);
131                 $this->cmd = trim($_GET['q'],'/');
132
133
134                 $this->argv = explode('/',$this->cmd);
135                 $this->argc = count($this->argv);
136                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
137                         $this->module = $this->argv[0];
138                 }
139                 else {
140                         $this->module = 'home';
141                 }
142
143                 if($this->cmd === '.well-known/host-meta')
144                         require_once('include/hostxrd.php');
145
146                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
147                 $this->pager['itemspage'] = 50;
148                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
149                 $this->pager['total'] = 0;
150         }
151
152         function get_baseurl($ssl = false) {
153                 if(strlen($this->baseurl))
154                         return $this->baseurl;
155
156                 $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
157                 return $this->baseurl;
158         }
159
160         function set_baseurl($url) {
161                 $this->baseurl = $url;
162                 $this->hostname = basename($url);
163         }
164
165         function get_hostname() {
166                 return $this->hostname;
167         }
168
169         function set_hostname($h) {
170                 $this->hostname = $h;
171         }
172
173         function set_path($p) {
174                 $this->path = trim(trim($p),'/');
175         } 
176
177         function get_path() {
178                 return $this->path;
179         }
180
181         function set_pager_total($n) {
182                 $this->pager['total'] = intval($n);
183         }
184
185         function set_pager_itemspage($n) {
186                 $this->pager['itemspage'] = intval($n);
187                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
188
189         } 
190
191         function init_pagehead() {
192                 $tpl = load_view_file("view/head.tpl");
193                 $this->page['htmlhead'] = replace_macros($tpl,array(
194                         '$baseurl' => $this->get_baseurl()
195                 ));
196         }
197
198         function set_curl_code($code) {
199                 $this->curl_code = $code;
200         }
201
202         function get_curl_code() {
203                 return $this->curl_code;
204         }
205
206 }}
207
208 // retrieve the App structure
209 // useful in functions which require it but don't get it passed to them
210
211 if(! function_exists('get_app')) {
212 function get_app() {
213         global $a;
214         return $a;
215 }};
216
217
218 // Multi-purpose function to check variable state.
219 // Usage: x($var) or $x($array,'key')
220 // returns false if variable/key is not set
221 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
222 // e.g. x('') or x(0) returns 0;
223
224 if(! function_exists('x')) {
225 function x($s,$k = NULL) {
226         if($k != NULL) {
227                 if((is_array($s)) && (array_key_exists($k,$s))) {
228                         if($s[$k])
229                                 return (int) 1;
230                         return (int) 0;
231                 }
232                 return false;
233         }
234         else {          
235                 if(isset($s)) {
236                         if($s) {
237                                 return (int) 1;
238                         }
239                         return (int) 0;
240                 }
241                 return false;
242         }
243 }}
244
245 // called from db initialisation if db is dead.
246
247 if(! function_exists('system_unavailable')) {
248 function system_unavailable() {
249         include('system_unavailable.php');
250         killme();
251 }}
252
253 // Primarily involved with database upgrade, but also sets the 
254 // base url for use in cmdline programs which don't have
255 // $_SERVER variables.
256
257 if(! function_exists('check_config')) {
258 function check_config(&$a) {
259
260         load_config('system');
261
262         $build = get_config('system','build');
263         if(! x($build))
264                 $build = set_config('system','build',BUILD_ID);
265
266         $url = get_config('system','url');
267         if(! x($url))
268                 $url = set_config('system','url',$a->get_baseurl());
269
270         if($build != BUILD_ID) {
271                 $stored = intval($build);
272                 $current = intval(BUILD_ID);
273                 if(($stored < $current) && file_exists('update.php')) {
274                         // We're reporting a different version than what is currently installed.
275                         // Run any existing update scripts to bring the database up to current.
276
277                         require_once('update.php');
278                         for($x = $stored; $x < $current; $x ++) {
279                                 if(function_exists('update_' . $x)) {
280                                         $func = 'update_' . $x;
281                                         $func($a);
282                                 }
283                         }
284                         set_config('system','build', BUILD_ID);
285                 }
286         }
287         return;
288 }}
289
290
291 // This is our template processor.
292 // $s is the string requiring macro substitution.
293 // $r is an array of key value pairs (search => replace)
294 // returns substituted string.
295 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
296 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
297 // depending on the order in which they were declared in the array.   
298
299 if(! function_exists('replace_macros')) {  
300 function replace_macros($s,$r) {
301
302         $search = array();
303         $replace = array();
304
305         if(is_array($r) && count($r)) {
306                 foreach ($r as $k => $v ) {
307                         $search[] =  $k;
308                         $replace[] = $v;
309                 }
310         }
311         return str_replace($search,$replace,$s);
312 }}
313
314
315 // load string translation table for alternate language
316
317 if(! function_exists('load_translation_table')) {
318 function load_translation_table($lang) {
319         global $a;
320
321         if(file_exists("view/$lang/strings.php"))
322                 include("view/$lang/strings.php");
323 }}
324
325 // translate string if translation exists
326
327 if(! function_exists('t')) {
328 function t($s) {
329         
330         $a = get_app();
331
332         if($a->strings[$s])
333                 return $a->strings[$s];
334         return $s;
335 }}
336
337 // curl wrapper. If binary flag is true, return binary
338 // results. 
339
340 if(! function_exists('fetch_url')) {
341 function fetch_url($url,$binary = false) {
342         $ch = curl_init($url);
343         if(! $ch) return false;
344
345         curl_setopt($ch, CURLOPT_HEADER, 0);
346         curl_setopt($ch, CURLOPT_FOLLOWLOCATION,true);
347         curl_setopt($ch, CURLOPT_MAXREDIRS,8);
348         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
349
350         // by default we will allow self-signed certs
351         // but you can override this
352
353         $check_cert = get_config('system','verifyssl');
354         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
355
356         $prx = get_config('system','proxy');
357         if(strlen($prx)) {
358                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
359                 curl_setopt($ch, CURLOPT_PROXY, $prx);
360                 $prxusr = get_config('system','proxyuser');
361                 if(strlen($prxusr))
362                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
363         }
364         if($binary)
365                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
366
367         $s = curl_exec($ch);
368         $info = curl_getinfo($ch);
369         $a = get_app();
370         $a->set_curl_code($info['http_code']);
371         curl_close($ch);
372         return($s);
373 }}
374
375 // post request to $url. $params is an array of post variables.
376
377 if(! function_exists('post_url')) {
378 function post_url($url,$params) {
379         $ch = curl_init($url);
380         if(! $ch) return false;
381
382         curl_setopt($ch, CURLOPT_HEADER, 0);
383         curl_setopt($ch, CURLOPT_FOLLOWLOCATION,true);
384         curl_setopt($ch, CURLOPT_MAXREDIRS,8);
385         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
386         curl_setopt($ch, CURLOPT_POST,1);
387         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
388         $check_cert = get_config('system','verifyssl');
389         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
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
399         $s = curl_exec($ch);
400         $info = curl_getinfo($ch);
401         $a = get_app();
402         $a->set_curl_code($info['http_code']);
403         curl_close($ch);
404         return($s);
405 }}
406
407 // random hash, 64 chars
408
409 if(! function_exists('random_string')) {
410 function random_string() {
411         return(hash('sha256',uniqid(rand(),true)));
412 }}
413
414 // This is our primary input filter. The high bit hack only involved some old
415 // IE browser, forget which. 
416 // Use this on any text input where angle chars are not valid or permitted
417 // They will be replaced with safer brackets. This may be filtered further
418 // if these are not allowed either.   
419
420 if(! function_exists('notags')) {
421 function notags($string) {
422         // protect against :<> with high-bit set
423         return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
424 }}
425
426 // use this on "body" or "content" input where angle chars shouldn't be removed,
427 // and allow them to be safely displayed.
428
429 if(! function_exists('escape_tags')) {
430 function escape_tags($string) {
431
432         return(htmlspecialchars($string));
433 }}
434
435 // wrapper for adding a login box. If $register == true provide a registration
436 // link. This will most always depend on the value of $a->config['register_policy'].
437 // returns the complete html for inserting into the page
438
439 if(! function_exists('login')) {
440 function login($register = false) {
441         $o = "";
442         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
443
444
445         if(x($_SESSION,'authenticated')) {
446                 $o = load_view_file("view/logout.tpl");
447         }
448         else {
449                 $o = load_view_file("view/login.tpl");
450
451                 $o = replace_macros($o,array('$register_html' => $register_html ));
452         }
453         return $o;
454 }}
455
456 // generate a string that's random, but usually pronounceable. 
457 // used to generate initial passwords
458
459 if(! function_exists('autoname')) {
460 function autoname($len) {
461
462         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
463         if(mt_rand(0,5) == 4)
464                 $vowels[] = 'y';
465
466         $cons = array(
467                         'b','bl','br',
468                         'c','ch','cl','cr',
469                         'd','dr',
470                         'f','fl','fr',
471                         'g','gh','gl','gr',
472                         'h',
473                         'j',
474                         'k','kh','kl','kr',
475                         'l',
476                         'm',
477                         'n',
478                         'p','ph','pl','pr',
479                         'qu',
480                         'r','rh',
481                         's','sc','sh','sm','sp','st',
482                         't','th','tr',
483                         'v',
484                         'w','wh',
485                         'x',
486                         'z','zh'
487                         );
488
489         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
490                                 'nd','ng','nk','nt','rn','rp','rt');
491
492         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
493                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
494
495         $start = mt_rand(0,2);
496         if($start == 0)
497                 $table = $vowels;
498         else
499                 $table = $cons;
500
501         $word = '';
502
503         for ($x = 0; $x < $len; $x ++) {
504                 $r = mt_rand(0,count($table) - 1);
505                 $word .= $table[$r];
506   
507                 if($table == $vowels)
508                         $table = array_merge($cons,$midcons);
509                 else
510                         $table = $vowels;
511
512         }
513
514         $word = substr($word,0,$len);
515
516         foreach($noend as $noe) {
517                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
518                         $word = substr($word,0,-1);
519                         break;
520                 }
521         }
522         if(substr($word,-1) == 'q')
523                 $word = substr($word,0,-1);    
524         return $word;
525 }}
526
527 // Used to end the current process, after saving session state. 
528
529 if(! function_exists('killme')) {
530 function killme() {
531         session_write_close();
532         exit;
533 }}
534
535 // redirect to another URL and terminate this process.
536
537 if(! function_exists('goaway')) {
538 function goaway($s) {
539         header("Location: $s");
540         killme();
541 }}
542
543 // Generic XML return
544 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
545 // of $st and an optional text <message> of $message and terminates the current process. 
546
547 if(! function_exists('xml_status')) {
548 function xml_status($st, $message = '') {
549
550         if(strlen($message))
551                 $xml_message = "\t<message>" . xmlify($message) . "</message>\r\n";
552
553         header( "Content-type: text/xml" );
554         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
555         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
556         killme();
557 }}
558
559 // Returns the uid of locally logged in user or false.
560
561 if(! function_exists('local_user')) {
562 function local_user() {
563         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
564                 return intval($_SESSION['uid']);
565         return false;
566 }}
567
568 // Returns contact id of authenticated site visitor or false
569
570 if(! function_exists('remote_user')) {
571 function remote_user() {
572         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
573                 return intval($_SESSION['visitor_id']);
574         return false;
575 }}
576
577 // contents of $s are displayed prominently on the page the next time
578 // a page is loaded. Usually used for errors or alerts.
579
580 if(! function_exists('notice')) {
581 function notice($s) {
582         $a = get_app();
583         if($a->interactive)
584                 $_SESSION['sysmsg'] .= $s;
585 }}
586
587 // wrapper around config to limit the text length of an incoming message
588
589 if(! function_exists('get_max_import_size')) {
590 function get_max_import_size() {
591         global $a;
592         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
593 }}
594
595
596 // escape text ($str) for XML transport
597 // returns escaped text.
598
599 if(! function_exists('xmlify')) {
600 function xmlify($str) {
601         $buffer = '';
602         
603         for($x = 0; $x < strlen($str); $x ++) {
604                 $char = $str[$x];
605         
606                 switch( $char ) {
607
608                         case "\r" :
609                                 break;
610                         case "&" :
611                                 $buffer .= '&amp;';
612                                 break;
613                         case "'" :
614                                 $buffer .= '&apos;';
615                                 break;
616
617                         case "\"" :
618                                 $buffer .= '&quot;';
619                                 break;
620                         case '<' :
621                                 $buffer .= '&lt;';
622                                 break;
623                         case '>' :
624                                 $buffer .= '&gt;';
625                                 break;
626                         case "\n" :
627                                 $buffer .= ' ';
628                                 break;
629                         default :
630                                 $buffer .= $char;
631                                 break;
632                 }       
633         }
634         $buffer = trim($buffer);
635         return($buffer);
636 }}
637
638 // undo an xmlify
639 // pass xml escaped text ($s), returns unescaped text
640
641 if(! function_exists('unxmlify')) {
642 function unxmlify($s) {
643         $ret = str_replace('&amp;','&', $s);
644         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
645         return $ret;    
646 }}
647
648 // convenience wrapper, reverse the operation "bin2hex"
649
650 if(! function_exists('hex2bin')) {
651 function hex2bin($s) {
652         return(pack("H*",$s));
653 }}
654
655 // Automatic pagination.
656 // To use, get the count of total items.
657 // Then call $a->set_pager_total($number_items);
658 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
659 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
660 // (assuming there are enough items to paginate).
661 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
662 // will limit the results to the correct items for the current page. 
663 // The actual page handling is then accomplished at the application layer. 
664
665 if(! function_exists('paginate')) {
666 function paginate(&$a) {
667         $o = '';
668         $stripped = ereg_replace("(&page=[0-9]*)","",$_SERVER['QUERY_STRING']);
669         $stripped = str_replace('q=','',$stripped);
670         $stripped = trim($stripped,'/');
671         $url = $a->get_baseurl() . '/' . $stripped;
672
673
674           if($a->pager['total'] > $a->pager['itemspage']) {
675                 $o .= '<div class="pager">';
676                 if($a->pager['page'] != 1)
677                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
678
679                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
680
681                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
682
683                 $numstart = 1;
684                 $numstop = $numpages;
685
686                 if($numpages > 14) {
687                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
688                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
689                 }
690    
691                 for($i = $numstart; $i <= $numstop; $i++){
692                         if($i == $a->pager['page'])
693                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
694                         else
695                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
696                         $o .= '</span> ';
697                 }
698
699                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
700                         if($i == $a->pager['page'])
701                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
702                         else
703                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
704                         $o .= '</span> ';
705                 }
706
707                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
708                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
709
710                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
711                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
712                 $o .= '</div>'."\r\n";
713         }
714         return $o;
715 }}
716
717 // Turn user/group ACLs stored as angle bracketed text into arrays
718
719 if(! function_exists('expand_acl')) {
720 function expand_acl($s) {
721         // turn string array of angle-bracketed elements into numeric array
722         // e.g. "<1><2><3>" => array(1,2,3);
723         $ret = array();
724
725         if(strlen($s)) {
726                 $t = str_replace('<','',$s);
727                 $a = explode('>',$t);
728                 foreach($a as $aa) {
729                         if(intval($aa))
730                                 $ret[] = intval($aa);
731                 }
732         }
733         return $ret;
734 }}              
735
736 // Used to wrap ACL elements in angle brackets for storage 
737
738 if(! function_exists('sanitise_acl')) {
739 function sanitise_acl(&$item) {
740         if(intval($item))
741                 $item = '<' . intval(notags(trim($item))) . '>';
742         else
743                 unset($item);
744 }}
745
746 // retrieve a "family" of config variables from database to cached storage
747
748 if(! function_exists('load_config')) {
749 function load_config($family) {
750         global $a;
751         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
752                 dbesc($family)
753         );
754         if(count($r)) {
755                 foreach($r as $rr) {
756                         $k = $rr['k'];
757                         $a->config[$family][$k] = $rr['v'];
758                 }
759         }
760 }}
761
762 // get a particular config variable given the family name
763 // and key. Returns false if not set.
764 // $instore is only used by the set_config function
765 // to determine if the key already exists in the DB
766 // If a key is found in the DB but doesn't exist in
767 // local config cache, pull it into the cache so we don't have
768 // to hit the DB again for this item.
769
770 if(! function_exists('get_config')) {
771 function get_config($family, $key, $instore = false) {
772
773         global $a;
774
775         if(! $instore) {
776                 if(isset($a->config[$family][$key])) {
777                         if($a->config[$family][$key] === '!<unset>!') {
778                                 return false;
779                         }
780                         return $a->config[$family][$key];
781                 }
782         }
783         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
784                 dbesc($family),
785                 dbesc($key)
786         );
787         if(count($ret)) {
788                 $a->config[$family][$key] = $ret[0]['v'];
789                 return $ret[0]['v'];
790         }
791         else {
792                 $a->config[$family][$key] = '!<unset>!';
793         }
794         return false;
795 }}
796
797 // Store a config value ($value) in the category ($family)
798 // under the key ($key)
799 // Return the value, or false if the database update failed
800
801 if(! function_exists('set_config')) {
802 function set_config($family,$key,$value) {
803
804         global $a;
805         $a->config[$family][$key] = $value;
806
807         if(get_config($family,$key,true) === false) {
808                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
809                         dbesc($family),
810                         dbesc($key),
811                         dbesc($value)
812                 );
813                 if($ret) 
814                         return $value;
815                 return $ret;
816         }
817         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
818                 dbesc($value),
819                 dbesc($family),
820                 dbesc($key)
821         );
822         if($ret)
823                 return $value;
824         return $ret;
825 }}
826
827 // convert an XML document to a normalised, case-corrected array
828 // used by webfinger
829
830 if(! function_exists('convert_xml_element_to_array')) {
831 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
832
833         // If we're getting too deep, bail out
834         if ($recursion_depth > 512) {
835                 return(null);
836         }
837
838         if (!is_string($xml_element) &&
839         !is_array($xml_element) &&
840         (get_class($xml_element) == 'SimpleXMLElement')) {
841                 $xml_element_copy = $xml_element;
842                 $xml_element = get_object_vars($xml_element);
843         }
844
845         if (is_array($xml_element)) {
846                 $result_array = array();
847                 if (count($xml_element) <= 0) {
848                         return (trim(strval($xml_element_copy)));
849                 }
850
851                 foreach($xml_element as $key=>$value) {
852
853                         $recursion_depth++;
854                         $result_array[strtolower($key)] =
855                 convert_xml_element_to_array($value, $recursion_depth);
856                         $recursion_depth--;
857                 }
858                 if ($recursion_depth == 0) {
859                         $temp_array = $result_array;
860                         $result_array = array(
861                                 strtolower($xml_element_copy->getName()) => $temp_array,
862                         );
863                 }
864
865                 return ($result_array);
866
867         } else {
868                 return (trim(strval($xml_element)));
869         }
870 }}
871
872 // Given an email style address, perform webfinger lookup and 
873 // return the resulting DFRN profile URL, or if no DFRN profile URL
874 // is located, returns an OStatus subscription template (prefixed 
875 // with the string 'stat:' to identify it as on OStatus template).
876 // If this isn't an email style address just return $s.
877 // Return an empty string if email-style addresses but webfinger fails,
878 // or if the resultant personal XRD doesn't contain a supported 
879 // subscription/friend-request attribute.
880
881 if(! function_exists('webfinger_dfrn')) {
882 function webfinger_dfrn($s) {
883         if(! strstr($s,'@')) {
884                 return $s;
885         }
886         $links = webfinger($s);
887         if(count($links)) {
888                 foreach($links as $link)
889                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
890                                 return $link['@attributes']['href'];
891                 foreach($links as $link)
892                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
893                                 return 'stat:' . $link['@attributes']['template'];              
894         }
895         return '';
896 }}
897
898 // Given an email style address, perform webfinger lookup and 
899 // return the array of link attributes from the personal XRD file.
900 // On error/failure return an empty array.
901
902
903 if(! function_exists('webfinger')) {
904 function webfinger($s) {
905         $host = '';
906         if(strstr($s,'@')) {
907                 $host = substr($s,strpos($s,'@') + 1);
908         }
909         if(strlen($host)) {
910                 $tpl = fetch_lrdd_template($host);
911                 if(strlen($tpl)) {
912                         $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
913                         $links = fetch_xrd_links($pxrd);
914                         if(! count($links)) {
915                                 // try without the double slashes
916                                 $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
917                                 $links = fetch_xrd_links($pxrd);
918                         }
919                         return $links;
920                 }
921         }
922         return array();
923 }}
924
925 // Given a host name, locate the LRDD template from that
926 // host. Returns the LRDD template or an empty string on
927 // error/failure.
928
929 if(! function_exists('fetch_lrdd_template')) {
930 function fetch_lrdd_template($host) {
931         $tpl = '';
932         $url = 'http://' . $host . '/.well-known/host-meta' ;
933         $links = fetch_xrd_links($url);
934         if(count($links)) {
935                 foreach($links as $link)
936                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
937                                 $tpl = $link['@attributes']['template'];
938         }
939         if(! strpos($tpl,'{uri}'))
940                 $tpl = '';
941         return $tpl;
942 }}
943
944 // Given a URL, retrieve the page as an XRD document.
945 // Return an array of links.
946 // on error/failure return empty array.
947
948 if(! function_exists('fetch_xrd_links')) {
949 function fetch_xrd_links($url) {
950
951         $xml = fetch_url($url);
952         if (! $xml)
953                 return array();
954         $h = simplexml_load_string($xml);
955         $arr = convert_xml_element_to_array($h);
956
957         if(isset($arr['xrd']['link'])) {
958                 $link = $arr['xrd']['link'];
959                 if(! isset($link[0]))
960                         $links = array($link);
961                 else
962                         $links = $link;
963                 return $links;
964         }
965         return array();
966 }}
967
968 // Convert an ACL array to a storable string
969
970 if(! function_exists('perms2str')) {
971 function perms2str($p) {
972         $ret = '';
973         $tmp = $p;
974         if(is_array($tmp)) {
975                 array_walk($tmp,'sanitise_acl');
976                 $ret = implode('',$tmp);
977         }
978         return $ret;
979 }}
980
981 // generate a guaranteed unique (for this domain) item ID for ATOM
982 // safe from birthday paradox
983
984 if(! function_exists('item_new_uri')) {
985 function item_new_uri($hostname,$uid) {
986
987         do {
988                 $dups = false;
989                 $hash = random_string();
990
991                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
992
993                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
994                         dbesc($uri));
995                 if(count($r))
996                         $dups = true;
997         } while($dups == true);
998         return $uri;
999 }}
1000
1001 // Generate a guaranteed unique photo ID.
1002 // safe from birthday paradox
1003
1004 if(! function_exists('photo_new_resource')) {
1005 function photo_new_resource() {
1006
1007         do {
1008                 $found = false;
1009                 $resource = hash('md5',uniqid(mt_rand(),true));
1010                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1011                         dbesc($resource)
1012                 );
1013                 if(count($r))
1014                         $found = true;
1015         } while($found == true);
1016         return $resource;
1017 }}
1018
1019
1020 // Take a URL from the wild, prepend http:// if necessary
1021 // and check DNS to see if it's real
1022 // return true if it's OK, false if something is wrong with it
1023
1024 if(! function_exists('validate_url')) {
1025 function validate_url(&$url) {
1026         if(substr($url,0,4) != 'http')
1027                 $url = 'http://' . $url;
1028         $h = parse_url($url);
1029
1030         if(! $h) {
1031                 return false;
1032         }
1033         if(! checkdnsrr($h['host'], 'ANY')) {
1034                 return false;
1035         }
1036         return true;
1037 }}
1038
1039 // Check $url against our list of allowed sites,
1040 // wildcards allowed. If allowed_sites is unset return true;
1041 // If url is allowed, return true.
1042 // otherwise, return false
1043
1044 if(! function_exists('allowed_url')) {
1045 function allowed_url($url) {
1046
1047         $h = parse_url($url);
1048
1049         if(! $h) {
1050                 return false;
1051         }
1052
1053         $str_allowed = get_config('system','allowed_sites');
1054         if(! $str_allowed)
1055                 return true;
1056
1057         $found = false;
1058
1059         $host = strtolower($h['host']);
1060
1061         // always allow our own site
1062
1063         if($host == strtolower($_SERVER['SERVER_NAME']))
1064                 return true;
1065
1066         $fnmatch = function_exists('fnmatch');
1067         $allowed = explode(',',$str_allowed);
1068
1069         if(count($allowed)) {
1070                 foreach($allowed as $a) {
1071                         $pat = strtolower(trim($a));
1072                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1073                                 $found = true; 
1074                                 break;
1075                         }
1076                 }
1077         }
1078         return $found;
1079 }}
1080
1081 // check if email address is allowed to register here.
1082 // Compare against our list (wildcards allowed).
1083 // Returns false if not allowed, true if allowed or if
1084 // allowed list is not configured.
1085
1086 if(! function_exists('allowed_email')) {
1087 function allowed_email($email) {
1088
1089
1090         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1091         if(! $domain)
1092                 return false;
1093
1094         $str_allowed = get_config('system','allowed_email');
1095         if(! $str_allowed)
1096                 return true;
1097
1098         $found = false;
1099
1100         $fnmatch = function_exists('fnmatch');
1101         $allowed = explode(',',$str_allowed);
1102
1103         if(count($allowed)) {
1104                 foreach($allowed as $a) {
1105                         $pat = strtolower(trim($a));
1106                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1107                                 $found = true; 
1108                                 break;
1109                         }
1110                 }
1111         }
1112         return $found;
1113 }}
1114
1115 // Format the like/dislike text for a profile item
1116 // $cnt = number of people who like/dislike the item
1117 // $arr = array of pre-linked names of likers/dislikers
1118 // $type = one of 'like, 'dislike'
1119 // $id  = item id
1120 // returns formatted text
1121
1122 if(! function_exists('format_like')) {
1123 function format_like($cnt,$arr,$type,$id) {
1124         if($cnt == 1)
1125                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1126         else {
1127                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1128                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1129                 $total = count($arr);
1130                 if($total >= MAX_LIKERS)
1131                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1132                 if($total < MAX_LIKERS)
1133                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1134                 $str = implode(', ', $arr);
1135                 if($total >= MAX_LIKERS)
1136                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1137                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1138                 $o .= '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1139         }
1140         return $o;
1141 }}
1142
1143
1144 // wrapper to load a view template, checking for alternate
1145 // languages before falling back to the default
1146
1147 if(! function_exists('load_view_file')) {
1148 function load_view_file($s) {
1149         $b = basename($s);
1150         $d = dirname($s);
1151         $lang = get_config('system','language');
1152         if($lang && file_exists("$d/$lang/$b"))
1153                 return file_get_contents("$d/$lang/$b");
1154         return file_get_contents($s);
1155 }}
1156
1157