]> git.mxchange.org Git - friendica.git/blob - boot.php
6cac527eb5b94a8a6474ffc5ee1672e94c8a3bd0
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1012   );
6 define ( 'DFRN_PROTOCOL_VERSION',  '2.0'  );
7
8 define ( 'EOL',                    "<br />\r\n"     );
9 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
10
11 // registration policy
12
13 define ( 'REGISTER_CLOSED',        0 );
14 define ( 'REGISTER_APPROVE',       1 );
15 define ( 'REGISTER_OPEN',          2 );
16
17 // relationship types
18
19 define ( 'REL_VIP',        1);
20 define ( 'REL_FAN',        2);
21 define ( 'REL_BUD',        3);
22
23
24 // page/profile types
25 // PAGE_NORMAL is a typical personal profile account
26 // PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
27 // PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
28 // write access to wall and comments (no email and not included in page owner's ACL lists)
29 // PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
30
31 define ( 'PAGE_NORMAL',            0 );
32 define ( 'PAGE_SOAPBOX',           1 );
33 define ( 'PAGE_COMMUNITY',         2 );
34 define ( 'PAGE_FREELOVE',          3 );
35
36 // Maximum number of "people who like (or don't like) this" 
37 // that we will list by name
38
39 define ( 'MAX_LIKERS',    75);
40
41 // email notification options
42
43 define ( 'NOTIFY_INTRO',   0x0001 );
44 define ( 'NOTIFY_CONFIRM', 0x0002 );
45 define ( 'NOTIFY_WALL',    0x0004 );
46 define ( 'NOTIFY_COMMENT', 0x0008 );
47 define ( 'NOTIFY_MAIL',    0x0010 );
48
49 // various namespaces we may need to parse
50
51 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
52 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
53 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
54 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
55 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
56 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
57 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
58 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
59 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
60 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
61
62 // activity stream defines
63
64 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
65 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
66 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
67
68 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
69 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
70 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
71 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
72 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
73
74 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
75 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
76 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
77 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
78 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
79 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
80
81 // item weight for query ordering
82
83 define ( 'GRAVITY_PARENT',       0);
84 define ( 'GRAVITY_LIKE',         3);
85 define ( 'GRAVITY_COMMENT',      6);
86
87
88 // Our main application structure for the life of this page
89 // Primarily deals with the URL that got us here
90 // and tries to make some sense of it, and 
91 // stores our page contents and config storage
92 // and anything else that might need to be passed around 
93 // before we spit the page out. 
94
95 if(! class_exists('App')) {
96 class App {
97
98         public  $module_loaded = false;
99         public  $config;
100         public  $page;
101         public  $profile;
102         public  $user;
103         public  $cid;
104         public  $contact;
105         public  $content;
106         public  $data;
107         public  $error = false;
108         public  $cmd;
109         public  $argv;
110         public  $argc;
111         public  $module;
112         public  $pager;
113         public  $strings;   
114         public  $path;
115         public  $interactive = true;
116
117         private $scheme;
118         private $hostname;
119         private $baseurl;
120         private $db;
121
122         private $curl_code;
123         private $curl_headers;
124
125         function __construct() {
126
127                 $this->config = array();
128                 $this->page = array();
129                 $this->pager= array();
130
131                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
132
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         if(strlen($message))
612                 $xml_message = "\t<message>" . xmlify($message) . "</message>\r\n";
613
614         header( "Content-type: text/xml" );
615         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
616         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
617         killme();
618 }}
619
620 // Returns the uid of locally logged in user or false.
621
622 if(! function_exists('local_user')) {
623 function local_user() {
624         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
625                 return intval($_SESSION['uid']);
626         return false;
627 }}
628
629 // Returns contact id of authenticated site visitor or false
630
631 if(! function_exists('remote_user')) {
632 function remote_user() {
633         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
634                 return intval($_SESSION['visitor_id']);
635         return false;
636 }}
637
638 // contents of $s are displayed prominently on the page the next time
639 // a page is loaded. Usually used for errors or alerts.
640
641 if(! function_exists('notice')) {
642 function notice($s) {
643         $a = get_app();
644         if($a->interactive)
645                 $_SESSION['sysmsg'] .= $s;
646 }}
647
648 // wrapper around config to limit the text length of an incoming message
649
650 if(! function_exists('get_max_import_size')) {
651 function get_max_import_size() {
652         global $a;
653         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
654 }}
655
656
657 // escape text ($str) for XML transport
658 // returns escaped text.
659
660 if(! function_exists('xmlify')) {
661 function xmlify($str) {
662         $buffer = '';
663         
664         for($x = 0; $x < strlen($str); $x ++) {
665                 $char = $str[$x];
666         
667                 switch( $char ) {
668
669                         case "\r" :
670                                 break;
671                         case "&" :
672                                 $buffer .= '&amp;';
673                                 break;
674                         case "'" :
675                                 $buffer .= '&apos;';
676                                 break;
677
678                         case "\"" :
679                                 $buffer .= '&quot;';
680                                 break;
681                         case '<' :
682                                 $buffer .= '&lt;';
683                                 break;
684                         case '>' :
685                                 $buffer .= '&gt;';
686                                 break;
687                         case "\n" :
688                                 $buffer .= ' ';
689                                 break;
690                         default :
691                                 $buffer .= $char;
692                                 break;
693                 }       
694         }
695         $buffer = trim($buffer);
696         return($buffer);
697 }}
698
699 // undo an xmlify
700 // pass xml escaped text ($s), returns unescaped text
701
702 if(! function_exists('unxmlify')) {
703 function unxmlify($s) {
704         $ret = str_replace('&amp;','&', $s);
705         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
706         return $ret;    
707 }}
708
709 // convenience wrapper, reverse the operation "bin2hex"
710
711 if(! function_exists('hex2bin')) {
712 function hex2bin($s) {
713         return(pack("H*",$s));
714 }}
715
716 // Automatic pagination.
717 // To use, get the count of total items.
718 // Then call $a->set_pager_total($number_items);
719 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
720 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
721 // (assuming there are enough items to paginate).
722 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
723 // will limit the results to the correct items for the current page. 
724 // The actual page handling is then accomplished at the application layer. 
725
726 if(! function_exists('paginate')) {
727 function paginate(&$a) {
728         $o = '';
729         $stripped = ereg_replace("(&page=[0-9]*)","",$_SERVER['QUERY_STRING']);
730         $stripped = str_replace('q=','',$stripped);
731         $stripped = trim($stripped,'/');
732         $url = $a->get_baseurl() . '/' . $stripped;
733
734
735           if($a->pager['total'] > $a->pager['itemspage']) {
736                 $o .= '<div class="pager">';
737                 if($a->pager['page'] != 1)
738                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
739
740                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
741
742                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
743
744                 $numstart = 1;
745                 $numstop = $numpages;
746
747                 if($numpages > 14) {
748                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
749                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
750                 }
751    
752                 for($i = $numstart; $i <= $numstop; $i++){
753                         if($i == $a->pager['page'])
754                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
755                         else
756                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
757                         $o .= '</span> ';
758                 }
759
760                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
761                         if($i == $a->pager['page'])
762                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
763                         else
764                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
765                         $o .= '</span> ';
766                 }
767
768                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
769                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
770
771                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
772                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
773                 $o .= '</div>'."\r\n";
774         }
775         return $o;
776 }}
777
778 // Turn user/group ACLs stored as angle bracketed text into arrays
779
780 if(! function_exists('expand_acl')) {
781 function expand_acl($s) {
782         // turn string array of angle-bracketed elements into numeric array
783         // e.g. "<1><2><3>" => array(1,2,3);
784         $ret = array();
785
786         if(strlen($s)) {
787                 $t = str_replace('<','',$s);
788                 $a = explode('>',$t);
789                 foreach($a as $aa) {
790                         if(intval($aa))
791                                 $ret[] = intval($aa);
792                 }
793         }
794         return $ret;
795 }}              
796
797 // Used to wrap ACL elements in angle brackets for storage 
798
799 if(! function_exists('sanitise_acl')) {
800 function sanitise_acl(&$item) {
801         if(intval($item))
802                 $item = '<' . intval(notags(trim($item))) . '>';
803         else
804                 unset($item);
805 }}
806
807 // retrieve a "family" of config variables from database to cached storage
808
809 if(! function_exists('load_config')) {
810 function load_config($family) {
811         global $a;
812         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
813                 dbesc($family)
814         );
815         if(count($r)) {
816                 foreach($r as $rr) {
817                         $k = $rr['k'];
818                         $a->config[$family][$k] = $rr['v'];
819                 }
820         }
821 }}
822
823 // get a particular config variable given the family name
824 // and key. Returns false if not set.
825 // $instore is only used by the set_config function
826 // to determine if the key already exists in the DB
827 // If a key is found in the DB but doesn't exist in
828 // local config cache, pull it into the cache so we don't have
829 // to hit the DB again for this item.
830
831 if(! function_exists('get_config')) {
832 function get_config($family, $key, $instore = false) {
833
834         global $a;
835
836         if(! $instore) {
837                 if(isset($a->config[$family][$key])) {
838                         if($a->config[$family][$key] === '!<unset>!') {
839                                 return false;
840                         }
841                         return $a->config[$family][$key];
842                 }
843         }
844         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
845                 dbesc($family),
846                 dbesc($key)
847         );
848         if(count($ret)) {
849                 $a->config[$family][$key] = $ret[0]['v'];
850                 return $ret[0]['v'];
851         }
852         else {
853                 $a->config[$family][$key] = '!<unset>!';
854         }
855         return false;
856 }}
857
858 // Store a config value ($value) in the category ($family)
859 // under the key ($key)
860 // Return the value, or false if the database update failed
861
862 if(! function_exists('set_config')) {
863 function set_config($family,$key,$value) {
864
865         global $a;
866         $a->config[$family][$key] = $value;
867
868         if(get_config($family,$key,true) === false) {
869                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
870                         dbesc($family),
871                         dbesc($key),
872                         dbesc($value)
873                 );
874                 if($ret) 
875                         return $value;
876                 return $ret;
877         }
878         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
879                 dbesc($value),
880                 dbesc($family),
881                 dbesc($key)
882         );
883         if($ret)
884                 return $value;
885         return $ret;
886 }}
887
888 // convert an XML document to a normalised, case-corrected array
889 // used by webfinger
890
891 if(! function_exists('convert_xml_element_to_array')) {
892 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
893
894         // If we're getting too deep, bail out
895         if ($recursion_depth > 512) {
896                 return(null);
897         }
898
899         if (!is_string($xml_element) &&
900         !is_array($xml_element) &&
901         (get_class($xml_element) == 'SimpleXMLElement')) {
902                 $xml_element_copy = $xml_element;
903                 $xml_element = get_object_vars($xml_element);
904         }
905
906         if (is_array($xml_element)) {
907                 $result_array = array();
908                 if (count($xml_element) <= 0) {
909                         return (trim(strval($xml_element_copy)));
910                 }
911
912                 foreach($xml_element as $key=>$value) {
913
914                         $recursion_depth++;
915                         $result_array[strtolower($key)] =
916                 convert_xml_element_to_array($value, $recursion_depth);
917                         $recursion_depth--;
918                 }
919                 if ($recursion_depth == 0) {
920                         $temp_array = $result_array;
921                         $result_array = array(
922                                 strtolower($xml_element_copy->getName()) => $temp_array,
923                         );
924                 }
925
926                 return ($result_array);
927
928         } else {
929                 return (trim(strval($xml_element)));
930         }
931 }}
932
933 // Given an email style address, perform webfinger lookup and 
934 // return the resulting DFRN profile URL, or if no DFRN profile URL
935 // is located, returns an OStatus subscription template (prefixed 
936 // with the string 'stat:' to identify it as on OStatus template).
937 // If this isn't an email style address just return $s.
938 // Return an empty string if email-style addresses but webfinger fails,
939 // or if the resultant personal XRD doesn't contain a supported 
940 // subscription/friend-request attribute.
941
942 if(! function_exists('webfinger_dfrn')) {
943 function webfinger_dfrn($s) {
944         if(! strstr($s,'@')) {
945                 return $s;
946         }
947         $links = webfinger($s);
948         if(count($links)) {
949                 foreach($links as $link)
950                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
951                                 return $link['@attributes']['href'];
952                 foreach($links as $link)
953                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
954                                 return 'stat:' . $link['@attributes']['template'];              
955         }
956         return '';
957 }}
958
959 // Given an email style address, perform webfinger lookup and 
960 // return the array of link attributes from the personal XRD file.
961 // On error/failure return an empty array.
962
963
964 if(! function_exists('webfinger')) {
965 function webfinger($s) {
966         $host = '';
967         if(strstr($s,'@')) {
968                 $host = substr($s,strpos($s,'@') + 1);
969         }
970         if(strlen($host)) {
971                 $tpl = fetch_lrdd_template($host);
972                 if(strlen($tpl)) {
973                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
974                         $links = fetch_xrd_links($pxrd);
975                         if(! count($links)) {
976                                 // try with double slashes
977                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
978                                 $links = fetch_xrd_links($pxrd);
979                         }
980                         return $links;
981                 }
982         }
983         return array();
984 }}
985
986 if(! function_exists('lrdd')) {
987 function lrdd($uri) {
988
989         $a = get_app();
990
991         if(strstr($uri,'@')) {  
992                 return(webfinger($uri));
993         }
994         else {
995                 $html = fetch_url($uri);
996                 $headers = $a->get_curl_headers();
997                 $lines = explode("\n",$headers);
998                 if(count($lines)) {
999                         foreach($lines as $line) {                              
1000                                 // TODO alter the following regex to support multiple relations (space separated)
1001                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1002                                         $link = $matches[1];
1003                                         break;
1004                                 }
1005                         }
1006                 }
1007                 if(! isset($link)) {
1008                         // parse the page of the supplied URL looking for rel links
1009
1010                         require_once('library/HTML5/Parser.php');
1011                         $dom = HTML5_Parser::parse($html);
1012
1013                         if($dom) {
1014                                 $items = $dom->getElementsByTagName('link');
1015
1016                                 foreach($items as $item) {
1017                                         $x = $item->getAttribute('rel');
1018                                         if($x == "lrdd") {
1019                                                 $link = $item->getAttribute('href');
1020                                                 break;
1021                                         }
1022                                 }
1023                         }
1024                 }
1025
1026                 if(isset($link))
1027                         return(fetch_xrd_links($link));
1028         }
1029         return array();
1030 }}
1031
1032
1033
1034 // Given a host name, locate the LRDD template from that
1035 // host. Returns the LRDD template or an empty string on
1036 // error/failure.
1037
1038 if(! function_exists('fetch_lrdd_template')) {
1039 function fetch_lrdd_template($host) {
1040         $tpl = '';
1041         $url = 'http://' . $host . '/.well-known/host-meta' ;
1042         $links = fetch_xrd_links($url);
1043         if(count($links)) {
1044                 foreach($links as $link)
1045                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1046                                 $tpl = $link['@attributes']['template'];
1047         }
1048         if(! strpos($tpl,'{uri}'))
1049                 $tpl = '';
1050         return $tpl;
1051 }}
1052
1053 // Given a URL, retrieve the page as an XRD document.
1054 // Return an array of links.
1055 // on error/failure return empty array.
1056
1057 if(! function_exists('fetch_xrd_links')) {
1058 function fetch_xrd_links($url) {
1059
1060         $xml = fetch_url($url);
1061         if (! $xml)
1062                 return array();
1063         $h = simplexml_load_string($xml);
1064         $arr = convert_xml_element_to_array($h);
1065
1066         if(isset($arr['xrd']['link'])) {
1067                 $link = $arr['xrd']['link'];
1068                 if(! isset($link[0]))
1069                         $links = array($link);
1070                 else
1071                         $links = $link;
1072                 return $links;
1073         }
1074         return array();
1075 }}
1076
1077 // Convert an ACL array to a storable string
1078
1079 if(! function_exists('perms2str')) {
1080 function perms2str($p) {
1081         $ret = '';
1082         $tmp = $p;
1083         if(is_array($tmp)) {
1084                 array_walk($tmp,'sanitise_acl');
1085                 $ret = implode('',$tmp);
1086         }
1087         return $ret;
1088 }}
1089
1090 // generate a guaranteed unique (for this domain) item ID for ATOM
1091 // safe from birthday paradox
1092
1093 if(! function_exists('item_new_uri')) {
1094 function item_new_uri($hostname,$uid) {
1095
1096         do {
1097                 $dups = false;
1098                 $hash = random_string();
1099
1100                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1101
1102                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1103                         dbesc($uri));
1104                 if(count($r))
1105                         $dups = true;
1106         } while($dups == true);
1107         return $uri;
1108 }}
1109
1110 // Generate a guaranteed unique photo ID.
1111 // safe from birthday paradox
1112
1113 if(! function_exists('photo_new_resource')) {
1114 function photo_new_resource() {
1115
1116         do {
1117                 $found = false;
1118                 $resource = hash('md5',uniqid(mt_rand(),true));
1119                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1120                         dbesc($resource)
1121                 );
1122                 if(count($r))
1123                         $found = true;
1124         } while($found == true);
1125         return $resource;
1126 }}
1127
1128
1129 // Take a URL from the wild, prepend http:// if necessary
1130 // and check DNS to see if it's real
1131 // return true if it's OK, false if something is wrong with it
1132
1133 if(! function_exists('validate_url')) {
1134 function validate_url(&$url) {
1135         if(substr($url,0,4) != 'http')
1136                 $url = 'http://' . $url;
1137         $h = parse_url($url);
1138
1139         if(! $h) {
1140                 return false;
1141         }
1142         if(! checkdnsrr($h['host'], 'ANY')) {
1143                 return false;
1144         }
1145         return true;
1146 }}
1147
1148 // Check $url against our list of allowed sites,
1149 // wildcards allowed. If allowed_sites is unset return true;
1150 // If url is allowed, return true.
1151 // otherwise, return false
1152
1153 if(! function_exists('allowed_url')) {
1154 function allowed_url($url) {
1155
1156         $h = parse_url($url);
1157
1158         if(! $h) {
1159                 return false;
1160         }
1161
1162         $str_allowed = get_config('system','allowed_sites');
1163         if(! $str_allowed)
1164                 return true;
1165
1166         $found = false;
1167
1168         $host = strtolower($h['host']);
1169
1170         // always allow our own site
1171
1172         if($host == strtolower($_SERVER['SERVER_NAME']))
1173                 return true;
1174
1175         $fnmatch = function_exists('fnmatch');
1176         $allowed = explode(',',$str_allowed);
1177
1178         if(count($allowed)) {
1179                 foreach($allowed as $a) {
1180                         $pat = strtolower(trim($a));
1181                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1182                                 $found = true; 
1183                                 break;
1184                         }
1185                 }
1186         }
1187         return $found;
1188 }}
1189
1190 // check if email address is allowed to register here.
1191 // Compare against our list (wildcards allowed).
1192 // Returns false if not allowed, true if allowed or if
1193 // allowed list is not configured.
1194
1195 if(! function_exists('allowed_email')) {
1196 function allowed_email($email) {
1197
1198
1199         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1200         if(! $domain)
1201                 return false;
1202
1203         $str_allowed = get_config('system','allowed_email');
1204         if(! $str_allowed)
1205                 return true;
1206
1207         $found = false;
1208
1209         $fnmatch = function_exists('fnmatch');
1210         $allowed = explode(',',$str_allowed);
1211
1212         if(count($allowed)) {
1213                 foreach($allowed as $a) {
1214                         $pat = strtolower(trim($a));
1215                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1216                                 $found = true; 
1217                                 break;
1218                         }
1219                 }
1220         }
1221         return $found;
1222 }}
1223
1224 // Format the like/dislike text for a profile item
1225 // $cnt = number of people who like/dislike the item
1226 // $arr = array of pre-linked names of likers/dislikers
1227 // $type = one of 'like, 'dislike'
1228 // $id  = item id
1229 // returns formatted text
1230
1231 if(! function_exists('format_like')) {
1232 function format_like($cnt,$arr,$type,$id) {
1233         $o = '';
1234         if($cnt == 1)
1235                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1236         else {
1237                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1238                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1239                 $total = count($arr);
1240                 if($total >= MAX_LIKERS)
1241                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1242                 if($total < MAX_LIKERS)
1243                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1244                 $str = implode(', ', $arr);
1245                 if($total >= MAX_LIKERS)
1246                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1247                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1248                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1249         }
1250         return $o;
1251 }}
1252
1253
1254 // wrapper to load a view template, checking for alternate
1255 // languages before falling back to the default
1256
1257 if(! function_exists('load_view_file')) {
1258 function load_view_file($s) {
1259         $b = basename($s);
1260         $d = dirname($s);
1261         $lang = get_config('system','language');
1262         if($lang && file_exists("$d/$lang/$b"))
1263                 return file_get_contents("$d/$lang/$b");
1264         return file_get_contents($s);
1265 }}
1266
1267 // for html,xml parsing - let's say you've got
1268 // an attribute foobar="class1 class2 class3"
1269 // and you want to find out if it contains 'class3'.
1270 // you can't use a normal sub string search because you
1271 // might match 'notclass3' and a regex to do the job is 
1272 // possible but a bit complicated. 
1273 // pass the attribute string as $attr and the attribute you 
1274 // are looking for as $s - returns true if found, otherwise false
1275
1276 if(! function_exists('attribute_contains')) {
1277 function attribute_contains($attr,$s) {
1278         $a = explode(' ', $attr);
1279         if(count($a) && in_array($s,$a))
1280                 return true;
1281         return false;
1282 }}
1283
1284 if(! function_exists('logger')) {
1285 function logger($msg) {
1286
1287         $debugging = get_config('system','debugging');
1288         $logfile   = get_config('system','logfile');
1289
1290         if((! $debugging) || (! $logfile))
1291                 return;
1292         
1293         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1294         return;
1295 }}
1296
1297
1298 if(! function_exists('activity_match')) {
1299 function activity_match($haystack,$needle) {
1300         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1301                 return true;
1302         return false;
1303 }}
1304
1305
1306 // Pull out all #hashtags and @person tags from $s;
1307 // We also get @person@domain.com - which would make 
1308 // the regex quite complicated as tags can also
1309 // end a sentence. So we'll run through our results
1310 // and strip the period from any tags which end with one.
1311 // Returns array of tags found, or empty array.
1312
1313
1314 if(! function_exists('get_tags')) {
1315 function get_tags($s) {
1316         $ret = array();
1317         if(preg_match_all('/([@#][^ ,:?]*)([ ,:?]|$)/',$s,$match)) {
1318                 foreach($match[1] as $match) {
1319                         if(substr($match,-1,1) === '.')
1320                                 $ret[] = substr($match,0,-1);
1321                         else
1322                                 $ret[] = $match;
1323                 }
1324         }
1325
1326         return $ret;
1327 }}
1328
1329
1330 // quick and dirty quoted_printable encoding
1331
1332 if(! function_exists('qp')) {
1333 function qp($s) {
1334 return str_replace ("%","=",rawurlencode($s));
1335 }} 
1336