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