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