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