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