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