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