]> git.mxchange.org Git - friendica.git/blob - boot.php
8c92487035d2e7545c4c46de68f764463e28dc72
[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, &$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         $check_cert = get_config('system','verifyssl');
423         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
424         $prx = get_config('system','proxy');
425         if(strlen($prx)) {
426                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
427                 curl_setopt($ch, CURLOPT_PROXY, $prx);
428                 $prxusr = get_config('system','proxyuser');
429                 if(strlen($prxusr))
430                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
431         }
432
433         $s = curl_exec($ch);
434
435         $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
436         $header = substr($s,0,strpos($s,"\r\n\r\n"));
437         if(stristr($header,'100') && (strlen($header) < 30)) {
438                 // 100 Continue has two headers, get the real one
439                 $s = substr($s,strlen($header)+4);
440                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
441         }
442         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
443         $matches = array();
444         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
445         $url = trim(array_pop($matches));
446         $url_parsed = parse_url($url);
447         if (isset($url_parsed)) {
448             $redirects++;
449             return post_url($url,$binary,$redirects);
450         }
451     }
452         $a = get_app();
453         $a->set_curl_code($http_code);
454         $body = substr($s,strlen($header)+4);
455         $a->set_curl_headers($header);
456
457         curl_close($ch);
458         return($body);
459 }}
460
461 // random hash, 64 chars
462
463 if(! function_exists('random_string')) {
464 function random_string() {
465         return(hash('sha256',uniqid(rand(),true)));
466 }}
467
468 // This is our primary input filter. The high bit hack only involved some old
469 // IE browser, forget which. 
470 // Use this on any text input where angle chars are not valid or permitted
471 // They will be replaced with safer brackets. This may be filtered further
472 // if these are not allowed either.   
473
474 if(! function_exists('notags')) {
475 function notags($string) {
476         // protect against :<> with high-bit set
477         return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
478 }}
479
480 // use this on "body" or "content" input where angle chars shouldn't be removed,
481 // and allow them to be safely displayed.
482
483 if(! function_exists('escape_tags')) {
484 function escape_tags($string) {
485
486         return(htmlspecialchars($string));
487 }}
488
489 // wrapper for adding a login box. If $register == true provide a registration
490 // link. This will most always depend on the value of $a->config['register_policy'].
491 // returns the complete html for inserting into the page
492
493 if(! function_exists('login')) {
494 function login($register = false) {
495         $o = "";
496         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
497
498
499         if(x($_SESSION,'authenticated')) {
500                 $o = load_view_file("view/logout.tpl");
501         }
502         else {
503                 $o = load_view_file("view/login.tpl");
504
505                 $o = replace_macros($o,array('$register_html' => $register_html ));
506         }
507         return $o;
508 }}
509
510 // generate a string that's random, but usually pronounceable. 
511 // used to generate initial passwords
512
513 if(! function_exists('autoname')) {
514 function autoname($len) {
515
516         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
517         if(mt_rand(0,5) == 4)
518                 $vowels[] = 'y';
519
520         $cons = array(
521                         'b','bl','br',
522                         'c','ch','cl','cr',
523                         'd','dr',
524                         'f','fl','fr',
525                         'g','gh','gl','gr',
526                         'h',
527                         'j',
528                         'k','kh','kl','kr',
529                         'l',
530                         'm',
531                         'n',
532                         'p','ph','pl','pr',
533                         'qu',
534                         'r','rh',
535                         's','sc','sh','sm','sp','st',
536                         't','th','tr',
537                         'v',
538                         'w','wh',
539                         'x',
540                         'z','zh'
541                         );
542
543         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
544                                 'nd','ng','nk','nt','rn','rp','rt');
545
546         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
547                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
548
549         $start = mt_rand(0,2);
550         if($start == 0)
551                 $table = $vowels;
552         else
553                 $table = $cons;
554
555         $word = '';
556
557         for ($x = 0; $x < $len; $x ++) {
558                 $r = mt_rand(0,count($table) - 1);
559                 $word .= $table[$r];
560   
561                 if($table == $vowels)
562                         $table = array_merge($cons,$midcons);
563                 else
564                         $table = $vowels;
565
566         }
567
568         $word = substr($word,0,$len);
569
570         foreach($noend as $noe) {
571                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
572                         $word = substr($word,0,-1);
573                         break;
574                 }
575         }
576         if(substr($word,-1) == 'q')
577                 $word = substr($word,0,-1);    
578         return $word;
579 }}
580
581 // Used to end the current process, after saving session state. 
582
583 if(! function_exists('killme')) {
584 function killme() {
585         session_write_close();
586         exit;
587 }}
588
589 // redirect to another URL and terminate this process.
590
591 if(! function_exists('goaway')) {
592 function goaway($s) {
593         header("Location: $s");
594         killme();
595 }}
596
597 // Generic XML return
598 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
599 // of $st and an optional text <message> of $message and terminates the current process. 
600
601 if(! function_exists('xml_status')) {
602 function xml_status($st, $message = '') {
603
604         if(strlen($message))
605                 $xml_message = "\t<message>" . xmlify($message) . "</message>\r\n";
606
607         header( "Content-type: text/xml" );
608         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
609         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
610         killme();
611 }}
612
613 // Returns the uid of locally logged in user or false.
614
615 if(! function_exists('local_user')) {
616 function local_user() {
617         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
618                 return intval($_SESSION['uid']);
619         return false;
620 }}
621
622 // Returns contact id of authenticated site visitor or false
623
624 if(! function_exists('remote_user')) {
625 function remote_user() {
626         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
627                 return intval($_SESSION['visitor_id']);
628         return false;
629 }}
630
631 // contents of $s are displayed prominently on the page the next time
632 // a page is loaded. Usually used for errors or alerts.
633
634 if(! function_exists('notice')) {
635 function notice($s) {
636         $a = get_app();
637         if($a->interactive)
638                 $_SESSION['sysmsg'] .= $s;
639 }}
640
641 // wrapper around config to limit the text length of an incoming message
642
643 if(! function_exists('get_max_import_size')) {
644 function get_max_import_size() {
645         global $a;
646         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
647 }}
648
649
650 // escape text ($str) for XML transport
651 // returns escaped text.
652
653 if(! function_exists('xmlify')) {
654 function xmlify($str) {
655         $buffer = '';
656         
657         for($x = 0; $x < strlen($str); $x ++) {
658                 $char = $str[$x];
659         
660                 switch( $char ) {
661
662                         case "\r" :
663                                 break;
664                         case "&" :
665                                 $buffer .= '&amp;';
666                                 break;
667                         case "'" :
668                                 $buffer .= '&apos;';
669                                 break;
670
671                         case "\"" :
672                                 $buffer .= '&quot;';
673                                 break;
674                         case '<' :
675                                 $buffer .= '&lt;';
676                                 break;
677                         case '>' :
678                                 $buffer .= '&gt;';
679                                 break;
680                         case "\n" :
681                                 $buffer .= ' ';
682                                 break;
683                         default :
684                                 $buffer .= $char;
685                                 break;
686                 }       
687         }
688         $buffer = trim($buffer);
689         return($buffer);
690 }}
691
692 // undo an xmlify
693 // pass xml escaped text ($s), returns unescaped text
694
695 if(! function_exists('unxmlify')) {
696 function unxmlify($s) {
697         $ret = str_replace('&amp;','&', $s);
698         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
699         return $ret;    
700 }}
701
702 // convenience wrapper, reverse the operation "bin2hex"
703
704 if(! function_exists('hex2bin')) {
705 function hex2bin($s) {
706         return(pack("H*",$s));
707 }}
708
709 // Automatic pagination.
710 // To use, get the count of total items.
711 // Then call $a->set_pager_total($number_items);
712 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
713 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
714 // (assuming there are enough items to paginate).
715 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
716 // will limit the results to the correct items for the current page. 
717 // The actual page handling is then accomplished at the application layer. 
718
719 if(! function_exists('paginate')) {
720 function paginate(&$a) {
721         $o = '';
722         $stripped = ereg_replace("(&page=[0-9]*)","",$_SERVER['QUERY_STRING']);
723         $stripped = str_replace('q=','',$stripped);
724         $stripped = trim($stripped,'/');
725         $url = $a->get_baseurl() . '/' . $stripped;
726
727
728           if($a->pager['total'] > $a->pager['itemspage']) {
729                 $o .= '<div class="pager">';
730                 if($a->pager['page'] != 1)
731                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
732
733                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
734
735                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
736
737                 $numstart = 1;
738                 $numstop = $numpages;
739
740                 if($numpages > 14) {
741                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
742                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
743                 }
744    
745                 for($i = $numstart; $i <= $numstop; $i++){
746                         if($i == $a->pager['page'])
747                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
748                         else
749                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
750                         $o .= '</span> ';
751                 }
752
753                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
754                         if($i == $a->pager['page'])
755                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
756                         else
757                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
758                         $o .= '</span> ';
759                 }
760
761                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
762                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
763
764                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
765                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
766                 $o .= '</div>'."\r\n";
767         }
768         return $o;
769 }}
770
771 // Turn user/group ACLs stored as angle bracketed text into arrays
772
773 if(! function_exists('expand_acl')) {
774 function expand_acl($s) {
775         // turn string array of angle-bracketed elements into numeric array
776         // e.g. "<1><2><3>" => array(1,2,3);
777         $ret = array();
778
779         if(strlen($s)) {
780                 $t = str_replace('<','',$s);
781                 $a = explode('>',$t);
782                 foreach($a as $aa) {
783                         if(intval($aa))
784                                 $ret[] = intval($aa);
785                 }
786         }
787         return $ret;
788 }}              
789
790 // Used to wrap ACL elements in angle brackets for storage 
791
792 if(! function_exists('sanitise_acl')) {
793 function sanitise_acl(&$item) {
794         if(intval($item))
795                 $item = '<' . intval(notags(trim($item))) . '>';
796         else
797                 unset($item);
798 }}
799
800 // retrieve a "family" of config variables from database to cached storage
801
802 if(! function_exists('load_config')) {
803 function load_config($family) {
804         global $a;
805         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
806                 dbesc($family)
807         );
808         if(count($r)) {
809                 foreach($r as $rr) {
810                         $k = $rr['k'];
811                         $a->config[$family][$k] = $rr['v'];
812                 }
813         }
814 }}
815
816 // get a particular config variable given the family name
817 // and key. Returns false if not set.
818 // $instore is only used by the set_config function
819 // to determine if the key already exists in the DB
820 // If a key is found in the DB but doesn't exist in
821 // local config cache, pull it into the cache so we don't have
822 // to hit the DB again for this item.
823
824 if(! function_exists('get_config')) {
825 function get_config($family, $key, $instore = false) {
826
827         global $a;
828
829         if(! $instore) {
830                 if(isset($a->config[$family][$key])) {
831                         if($a->config[$family][$key] === '!<unset>!') {
832                                 return false;
833                         }
834                         return $a->config[$family][$key];
835                 }
836         }
837         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
838                 dbesc($family),
839                 dbesc($key)
840         );
841         if(count($ret)) {
842                 $a->config[$family][$key] = $ret[0]['v'];
843                 return $ret[0]['v'];
844         }
845         else {
846                 $a->config[$family][$key] = '!<unset>!';
847         }
848         return false;
849 }}
850
851 // Store a config value ($value) in the category ($family)
852 // under the key ($key)
853 // Return the value, or false if the database update failed
854
855 if(! function_exists('set_config')) {
856 function set_config($family,$key,$value) {
857
858         global $a;
859         $a->config[$family][$key] = $value;
860
861         if(get_config($family,$key,true) === false) {
862                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
863                         dbesc($family),
864                         dbesc($key),
865                         dbesc($value)
866                 );
867                 if($ret) 
868                         return $value;
869                 return $ret;
870         }
871         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
872                 dbesc($value),
873                 dbesc($family),
874                 dbesc($key)
875         );
876         if($ret)
877                 return $value;
878         return $ret;
879 }}
880
881 // convert an XML document to a normalised, case-corrected array
882 // used by webfinger
883
884 if(! function_exists('convert_xml_element_to_array')) {
885 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
886
887         // If we're getting too deep, bail out
888         if ($recursion_depth > 512) {
889                 return(null);
890         }
891
892         if (!is_string($xml_element) &&
893         !is_array($xml_element) &&
894         (get_class($xml_element) == 'SimpleXMLElement')) {
895                 $xml_element_copy = $xml_element;
896                 $xml_element = get_object_vars($xml_element);
897         }
898
899         if (is_array($xml_element)) {
900                 $result_array = array();
901                 if (count($xml_element) <= 0) {
902                         return (trim(strval($xml_element_copy)));
903                 }
904
905                 foreach($xml_element as $key=>$value) {
906
907                         $recursion_depth++;
908                         $result_array[strtolower($key)] =
909                 convert_xml_element_to_array($value, $recursion_depth);
910                         $recursion_depth--;
911                 }
912                 if ($recursion_depth == 0) {
913                         $temp_array = $result_array;
914                         $result_array = array(
915                                 strtolower($xml_element_copy->getName()) => $temp_array,
916                         );
917                 }
918
919                 return ($result_array);
920
921         } else {
922                 return (trim(strval($xml_element)));
923         }
924 }}
925
926 // Given an email style address, perform webfinger lookup and 
927 // return the resulting DFRN profile URL, or if no DFRN profile URL
928 // is located, returns an OStatus subscription template (prefixed 
929 // with the string 'stat:' to identify it as on OStatus template).
930 // If this isn't an email style address just return $s.
931 // Return an empty string if email-style addresses but webfinger fails,
932 // or if the resultant personal XRD doesn't contain a supported 
933 // subscription/friend-request attribute.
934
935 if(! function_exists('webfinger_dfrn')) {
936 function webfinger_dfrn($s) {
937         if(! strstr($s,'@')) {
938                 return $s;
939         }
940         $links = webfinger($s);
941         if(count($links)) {
942                 foreach($links as $link)
943                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
944                                 return $link['@attributes']['href'];
945                 foreach($links as $link)
946                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
947                                 return 'stat:' . $link['@attributes']['template'];              
948         }
949         return '';
950 }}
951
952 // Given an email style address, perform webfinger lookup and 
953 // return the array of link attributes from the personal XRD file.
954 // On error/failure return an empty array.
955
956
957 if(! function_exists('webfinger')) {
958 function webfinger($s) {
959         $host = '';
960         if(strstr($s,'@')) {
961                 $host = substr($s,strpos($s,'@') + 1);
962         }
963         if(strlen($host)) {
964                 $tpl = fetch_lrdd_template($host);
965                 if(strlen($tpl)) {
966                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
967                         $links = fetch_xrd_links($pxrd);
968                         if(! count($links)) {
969                                 // try with double slashes
970                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
971                                 $links = fetch_xrd_links($pxrd);
972                         }
973                         return $links;
974                 }
975         }
976         return array();
977 }}
978
979 if(! function_exists('lrdd')) {
980 function lrdd($uri) {
981
982         $a = get_app();
983
984         if(strstr($uri,'@')) {  
985                 return(webfinger($uri));
986         }
987         else {
988                 $html = fetch_url($uri);
989                 $headers = $a->get_curl_headers();
990                 $lines = explode("\n",$headers);
991                 if(count($lines)) {
992                         foreach($lines as $line) {                              
993                                 // TODO alter the following regex to support multiple relations (space separated)
994                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
995                                         $link = $matches[1];
996                                         break;
997                                 }
998                         }
999                 }
1000                 if(! isset($link)) {
1001                         // parse the page of the supplied URL looking for rel links
1002
1003                         require_once('library/HTML5/Parser.php');
1004                         $dom = HTML5_Parser::parse($html);
1005
1006                         if($dom) {
1007                                 $items = $dom->getElementsByTagName('link');
1008
1009                                 foreach($items as $item) {
1010                                         $x = $item->getAttribute('rel');
1011                                         if($x == "lrdd") {
1012                                                 $link = $item->getAttribute('href');
1013                                                 break;
1014                                         }
1015                                 }
1016                         }
1017                 }
1018
1019                 if(isset($link))
1020                         return(fetch_xrd_links($link));
1021         }
1022         return array();
1023 }}
1024
1025
1026
1027 // Given a host name, locate the LRDD template from that
1028 // host. Returns the LRDD template or an empty string on
1029 // error/failure.
1030
1031 if(! function_exists('fetch_lrdd_template')) {
1032 function fetch_lrdd_template($host) {
1033         $tpl = '';
1034         $url = 'http://' . $host . '/.well-known/host-meta' ;
1035         $links = fetch_xrd_links($url);
1036         if(count($links)) {
1037                 foreach($links as $link)
1038                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1039                                 $tpl = $link['@attributes']['template'];
1040         }
1041         if(! strpos($tpl,'{uri}'))
1042                 $tpl = '';
1043         return $tpl;
1044 }}
1045
1046 // Given a URL, retrieve the page as an XRD document.
1047 // Return an array of links.
1048 // on error/failure return empty array.
1049
1050 if(! function_exists('fetch_xrd_links')) {
1051 function fetch_xrd_links($url) {
1052
1053         $xml = fetch_url($url);
1054         if (! $xml)
1055                 return array();
1056         $h = simplexml_load_string($xml);
1057         $arr = convert_xml_element_to_array($h);
1058
1059         if(isset($arr['xrd']['link'])) {
1060                 $link = $arr['xrd']['link'];
1061                 if(! isset($link[0]))
1062                         $links = array($link);
1063                 else
1064                         $links = $link;
1065                 return $links;
1066         }
1067         return array();
1068 }}
1069
1070 // Convert an ACL array to a storable string
1071
1072 if(! function_exists('perms2str')) {
1073 function perms2str($p) {
1074         $ret = '';
1075         $tmp = $p;
1076         if(is_array($tmp)) {
1077                 array_walk($tmp,'sanitise_acl');
1078                 $ret = implode('',$tmp);
1079         }
1080         return $ret;
1081 }}
1082
1083 // generate a guaranteed unique (for this domain) item ID for ATOM
1084 // safe from birthday paradox
1085
1086 if(! function_exists('item_new_uri')) {
1087 function item_new_uri($hostname,$uid) {
1088
1089         do {
1090                 $dups = false;
1091                 $hash = random_string();
1092
1093                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1094
1095                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1096                         dbesc($uri));
1097                 if(count($r))
1098                         $dups = true;
1099         } while($dups == true);
1100         return $uri;
1101 }}
1102
1103 // Generate a guaranteed unique photo ID.
1104 // safe from birthday paradox
1105
1106 if(! function_exists('photo_new_resource')) {
1107 function photo_new_resource() {
1108
1109         do {
1110                 $found = false;
1111                 $resource = hash('md5',uniqid(mt_rand(),true));
1112                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1113                         dbesc($resource)
1114                 );
1115                 if(count($r))
1116                         $found = true;
1117         } while($found == true);
1118         return $resource;
1119 }}
1120
1121
1122 // Take a URL from the wild, prepend http:// if necessary
1123 // and check DNS to see if it's real
1124 // return true if it's OK, false if something is wrong with it
1125
1126 if(! function_exists('validate_url')) {
1127 function validate_url(&$url) {
1128         if(substr($url,0,4) != 'http')
1129                 $url = 'http://' . $url;
1130         $h = parse_url($url);
1131
1132         if(! $h) {
1133                 return false;
1134         }
1135         if(! checkdnsrr($h['host'], 'ANY')) {
1136                 return false;
1137         }
1138         return true;
1139 }}
1140
1141 // Check $url against our list of allowed sites,
1142 // wildcards allowed. If allowed_sites is unset return true;
1143 // If url is allowed, return true.
1144 // otherwise, return false
1145
1146 if(! function_exists('allowed_url')) {
1147 function allowed_url($url) {
1148
1149         $h = parse_url($url);
1150
1151         if(! $h) {
1152                 return false;
1153         }
1154
1155         $str_allowed = get_config('system','allowed_sites');
1156         if(! $str_allowed)
1157                 return true;
1158
1159         $found = false;
1160
1161         $host = strtolower($h['host']);
1162
1163         // always allow our own site
1164
1165         if($host == strtolower($_SERVER['SERVER_NAME']))
1166                 return true;
1167
1168         $fnmatch = function_exists('fnmatch');
1169         $allowed = explode(',',$str_allowed);
1170
1171         if(count($allowed)) {
1172                 foreach($allowed as $a) {
1173                         $pat = strtolower(trim($a));
1174                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1175                                 $found = true; 
1176                                 break;
1177                         }
1178                 }
1179         }
1180         return $found;
1181 }}
1182
1183 // check if email address is allowed to register here.
1184 // Compare against our list (wildcards allowed).
1185 // Returns false if not allowed, true if allowed or if
1186 // allowed list is not configured.
1187
1188 if(! function_exists('allowed_email')) {
1189 function allowed_email($email) {
1190
1191
1192         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1193         if(! $domain)
1194                 return false;
1195
1196         $str_allowed = get_config('system','allowed_email');
1197         if(! $str_allowed)
1198                 return true;
1199
1200         $found = false;
1201
1202         $fnmatch = function_exists('fnmatch');
1203         $allowed = explode(',',$str_allowed);
1204
1205         if(count($allowed)) {
1206                 foreach($allowed as $a) {
1207                         $pat = strtolower(trim($a));
1208                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1209                                 $found = true; 
1210                                 break;
1211                         }
1212                 }
1213         }
1214         return $found;
1215 }}
1216
1217 // Format the like/dislike text for a profile item
1218 // $cnt = number of people who like/dislike the item
1219 // $arr = array of pre-linked names of likers/dislikers
1220 // $type = one of 'like, 'dislike'
1221 // $id  = item id
1222 // returns formatted text
1223
1224 if(! function_exists('format_like')) {
1225 function format_like($cnt,$arr,$type,$id) {
1226         if($cnt == 1)
1227                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1228         else {
1229                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1230                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1231                 $total = count($arr);
1232                 if($total >= MAX_LIKERS)
1233                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1234                 if($total < MAX_LIKERS)
1235                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1236                 $str = implode(', ', $arr);
1237                 if($total >= MAX_LIKERS)
1238                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1239                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1240                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1241         }
1242         return $o;
1243 }}
1244
1245
1246 // wrapper to load a view template, checking for alternate
1247 // languages before falling back to the default
1248
1249 if(! function_exists('load_view_file')) {
1250 function load_view_file($s) {
1251         $b = basename($s);
1252         $d = dirname($s);
1253         $lang = get_config('system','language');
1254         if($lang && file_exists("$d/$lang/$b"))
1255                 return file_get_contents("$d/$lang/$b");
1256         return file_get_contents($s);
1257 }}
1258
1259