]> git.mxchange.org Git - friendica.git/blob - boot.php
1546429c3505e0be30cebb0eae7f6912d38496d9
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1023   );
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 define ( 'DOWN_ARROW',             '&#x21e9;'       );
11          
12 /**
13  * log levels
14  */
15
16 define ( 'LOGGER_NORMAL',          0 );
17 define ( 'LOGGER_TRACE',           1 );
18 define ( 'LOGGER_DEBUG',           2 );
19 define ( 'LOGGER_DATA',            3 );
20 define ( 'LOGGER_ALL',             4 );
21
22 /**
23  * registration policies
24  */
25
26 define ( 'REGISTER_CLOSED',        0 );
27 define ( 'REGISTER_APPROVE',       1 );
28 define ( 'REGISTER_OPEN',          2 );
29
30 /**
31  * relationship types
32  */
33
34 define ( 'REL_VIP',        1);
35 define ( 'REL_FAN',        2);
36 define ( 'REL_BUD',        3);
37
38
39 /**
40  *
41  * page/profile types
42  *
43  * PAGE_NORMAL is a typical personal profile account
44  * PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
45  * PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
46  *      write access to wall and comments (no email and not included in page owner's ACL lists)
47  * PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
48  *
49  */
50
51 define ( 'PAGE_NORMAL',            0 );
52 define ( 'PAGE_SOAPBOX',           1 );
53 define ( 'PAGE_COMMUNITY',         2 );
54 define ( 'PAGE_FREELOVE',          3 );
55
56 /**
57  * Maximum number of "people who like (or don't like) this"  that we will list by name
58  */
59
60 define ( 'MAX_LIKERS',    75);
61
62 /**
63  * email notification options
64  */
65
66 define ( 'NOTIFY_INTRO',   0x0001 );
67 define ( 'NOTIFY_CONFIRM', 0x0002 );
68 define ( 'NOTIFY_WALL',    0x0004 );
69 define ( 'NOTIFY_COMMENT', 0x0008 );
70 define ( 'NOTIFY_MAIL',    0x0010 );
71
72 /**
73  * various namespaces we may need to parse
74  */
75
76 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
77 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
78 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
79 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
80 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
81 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
82 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
83 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
84 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
85 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
86 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
87
88 /**
89  * activity stream defines
90  */
91
92 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
93 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
94 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
95
96 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
97 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
98 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'unfollow' );
99 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
100 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
101 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
102
103 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
104 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
105 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
106 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
107 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
108 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
109
110 /**
111  * item weight for query ordering
112  */
113
114 define ( 'GRAVITY_PARENT',       0);
115 define ( 'GRAVITY_LIKE',         3);
116 define ( 'GRAVITY_COMMENT',      6);
117
118 /**
119  *
120  * Reverse the effect of magic_quotes_gpc if it is enabled.
121  * Please disable magic_quotes_gpc so we don't have to do this.
122  * See http://php.net/manual/en/security.magicquotes.disabling.php
123  *
124  */
125
126 if (get_magic_quotes_gpc()) {
127     $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
128     while (list($key, $val) = each($process)) {
129         foreach ($val as $k => $v) {
130             unset($process[$key][$k]);
131             if (is_array($v)) {
132                 $process[$key][stripslashes($k)] = $v;
133                 $process[] = &$process[$key][stripslashes($k)];
134             } else {
135                 $process[$key][stripslashes($k)] = stripslashes($v);
136             }
137         }
138     }
139     unset($process);
140 }
141
142
143 /**
144  *
145  * class: App
146  *
147  * Our main application structure for the life of this page
148  * Primarily deals with the URL that got us here
149  * and tries to make some sense of it, and 
150  * stores our page contents and config storage
151  * and anything else that might need to be passed around 
152  * before we spit the page out. 
153  *
154  */
155
156 if(! class_exists('App')) {
157 class App {
158
159         public  $module_loaded = false;
160         public  $query_string;
161         public  $config;
162         public  $page;
163         public  $profile;
164         public  $user;
165         public  $cid;
166         public  $contact;
167         public  $content;
168         public  $data;
169         public  $error = false;
170         public  $cmd;
171         public  $argv;
172         public  $argc;
173         public  $module;
174         public  $pager;
175         public  $strings;   
176         public  $path;
177         public  $interactive = true;
178
179         private $scheme;
180         private $hostname;
181         private $baseurl;
182         private $db;
183
184         private $curl_code;
185         private $curl_headers;
186
187         function __construct() {
188
189                 $this->config = array();
190                 $this->page = array();
191                 $this->pager= array();
192
193                 $this->query_string = '';
194
195                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
196
197                 if(x($_SERVER,'SERVER_NAME'))
198                         $this->hostname = $_SERVER['SERVER_NAME'];
199
200                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
201
202                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
203                         $this->query_string = substr($_SERVER['QUERY_STRING'],2);
204                 if(x($_GET,'q'))
205                         $this->cmd = trim($_GET['q'],'/');
206
207                 /** 
208                  * Figure out if we are running at the top of a domain
209                  * or in a sub-directory and adjust accordingly
210                  */
211
212                 $path = trim(dirname($_SERVER['SCRIPT_NAME']),'/');
213                 if(isset($path) && strlen($path) && ($path != $this->path))
214                         $this->path = $path;
215
216
217                 /**
218                  *
219                  * Break the URL path into C style argc/argv style arguments for our
220                  * modules. Given "http://example.com/module/arg1/arg2", $this->argc
221                  * will be 3 (integer) and $this->argv will contain:
222                  *   [0] => 'module'
223                  *   [1] => 'arg1'
224                  *   [2] => 'arg2'
225                  *
226                  *
227                  * There will always be one argument. If provided a naked domain
228                  * URL, $this->argv[0] is set to "home".
229                  *
230                  */
231
232                 $this->argv = explode('/',$this->cmd);
233                 $this->argc = count($this->argv);
234                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
235                         $this->module = $this->argv[0];
236                 }
237                 else {
238                         $this->module = 'home';
239                 }
240
241                 /**
242                  * Special handling for the webfinger/lrdd host XRD file
243                  * Just spit out the contents and exit.
244                  */
245
246                 if($this->cmd === '.well-known/host-meta')
247                         require_once('include/hostxrd.php');
248
249
250                 /**
251                  * See if there is any page number information, and initialise 
252                  * pagination
253                  */
254
255                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
256                 $this->pager['itemspage'] = 50;
257                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
258                 $this->pager['total'] = 0;
259         }
260
261         function get_baseurl($ssl = false) {
262                 if(strlen($this->baseurl))
263                         return $this->baseurl;
264
265                 $this->baseurl = (($ssl) ? 'https' : $this->scheme) . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
266                 return $this->baseurl;
267         }
268
269         function set_baseurl($url) {
270                 $this->baseurl = $url;
271                 $this->hostname = basename($url);
272         }
273
274         function get_hostname() {
275                 return $this->hostname;
276         }
277
278         function set_hostname($h) {
279                 $this->hostname = $h;
280         }
281
282         function set_path($p) {
283                 $this->path = trim(trim($p),'/');
284         } 
285
286         function get_path() {
287                 return $this->path;
288         }
289
290         function set_pager_total($n) {
291                 $this->pager['total'] = intval($n);
292         }
293
294         function set_pager_itemspage($n) {
295                 $this->pager['itemspage'] = intval($n);
296                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
297
298         } 
299
300         function init_pagehead() {
301                 $tpl = load_view_file("view/head.tpl");
302                 $this->page['htmlhead'] = replace_macros($tpl,array(
303                         '$baseurl' => $this->get_baseurl() . '/'
304                 ));
305         }
306
307         function set_curl_code($code) {
308                 $this->curl_code = $code;
309         }
310
311         function get_curl_code() {
312                 return $this->curl_code;
313         }
314
315         function set_curl_headers($headers) {
316                 $this->curl_headers = $headers;
317         }
318
319         function get_curl_headers() {
320                 return $this->curl_headers;
321         }
322
323
324 }}
325
326 // retrieve the App structure
327 // useful in functions which require it but don't get it passed to them
328
329 if(! function_exists('get_app')) {
330 function get_app() {
331         global $a;
332         return $a;
333 }};
334
335
336 // Multi-purpose function to check variable state.
337 // Usage: x($var) or $x($array,'key')
338 // returns false if variable/key is not set
339 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
340 // e.g. x('') or x(0) returns 0;
341
342 if(! function_exists('x')) {
343 function x($s,$k = NULL) {
344         if($k != NULL) {
345                 if((is_array($s)) && (array_key_exists($k,$s))) {
346                         if($s[$k])
347                                 return (int) 1;
348                         return (int) 0;
349                 }
350                 return false;
351         }
352         else {          
353                 if(isset($s)) {
354                         if($s) {
355                                 return (int) 1;
356                         }
357                         return (int) 0;
358                 }
359                 return false;
360         }
361 }}
362
363 // called from db initialisation if db is dead.
364
365 if(! function_exists('system_unavailable')) {
366 function system_unavailable() {
367         include('system_unavailable.php');
368         killme();
369 }}
370
371 // Primarily involved with database upgrade, but also sets the 
372 // base url for use in cmdline programs which don't have
373 // $_SERVER variables.
374
375 if(! function_exists('check_config')) {
376 function check_config(&$a) {
377
378         load_config('system');
379
380         $build = get_config('system','build');
381         if(! x($build))
382                 $build = set_config('system','build',BUILD_ID);
383
384         $url = get_config('system','url');
385         if(! x($url))
386                 $url = set_config('system','url',$a->get_baseurl());
387
388         if($build != BUILD_ID) {
389                 $stored = intval($build);
390                 $current = intval(BUILD_ID);
391                 if(($stored < $current) && file_exists('update.php')) {
392                         // We're reporting a different version than what is currently installed.
393                         // Run any existing update scripts to bring the database up to current.
394
395                         require_once('update.php');
396                         for($x = $stored; $x < $current; $x ++) {
397                                 if(function_exists('update_' . $x)) {
398                                         $func = 'update_' . $x;
399                                         $func($a);
400                                 }
401                         }
402                         set_config('system','build', BUILD_ID);
403                 }
404         }
405         return;
406 }}
407
408
409 // This is our template processor.
410 // $s is the string requiring macro substitution.
411 // $r is an array of key value pairs (search => replace)
412 // returns substituted string.
413 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
414 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
415 // depending on the order in which they were declared in the array.   
416
417 if(! function_exists('replace_macros')) {  
418 function replace_macros($s,$r) {
419
420         $search = array();
421         $replace = array();
422
423         if(is_array($r) && count($r)) {
424                 foreach ($r as $k => $v ) {
425                         $search[] =  $k;
426                         $replace[] = $v;
427                 }
428         }
429         return str_replace($search,$replace,$s);
430 }}
431
432
433 // load string translation table for alternate language
434
435 if(! function_exists('load_translation_table')) {
436 function load_translation_table($lang) {
437         global $a;
438
439         if(file_exists("view/$lang/strings.php"))
440                 include("view/$lang/strings.php");
441 }}
442
443 // translate string if translation exists
444
445 if(! function_exists('t')) {
446 function t($s) {
447
448         $a = get_app();
449
450         if(x($a->strings,$s))
451                 return $a->strings[$s];
452         return $s;
453 }}
454
455 // curl wrapper. If binary flag is true, return binary
456 // results. 
457
458 if(! function_exists('fetch_url')) {
459 function fetch_url($url,$binary = false, &$redirects = 0) {
460
461         $a = get_app();
462
463         $ch = curl_init($url);
464         if(($redirects > 8) || (! $ch)) 
465                 return false;
466
467         curl_setopt($ch, CURLOPT_HEADER, true);
468         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
469
470
471         $curl_time = intval(get_config('system','curl_timeout'));
472         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
473
474         // by default we will allow self-signed certs
475         // but you can override this
476
477         $check_cert = get_config('system','verifyssl');
478         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
479
480         $prx = get_config('system','proxy');
481         if(strlen($prx)) {
482                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
483                 curl_setopt($ch, CURLOPT_PROXY, $prx);
484                 $prxusr = get_config('system','proxyuser');
485                 if(strlen($prxusr))
486                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
487         }
488         if($binary)
489                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
490
491         $a->set_curl_code(0);
492
493         // don't let curl abort the entire application
494         // if it throws any errors.
495
496         $s = @curl_exec($ch);
497
498         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
499         $header = substr($s,0,strpos($s,"\r\n\r\n"));
500         if(stristr($header,'100') && (strlen($header) < 30)) {
501                 // 100 Continue has two headers, get the real one
502                 $s = substr($s,strlen($header)+4);
503                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
504         }
505         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
506         $matches = array();
507         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
508         $url = trim(array_pop($matches));
509         $url_parsed = parse_url($url);
510         if (isset($url_parsed)) {
511             $redirects++;
512             return fetch_url($url,$binary,$redirects);
513         }
514     }
515         $a->set_curl_code($http_code);
516         $body = substr($s,strlen($header)+4);
517         $a->set_curl_headers($header);
518
519         curl_close($ch);
520         return($body);
521 }}
522
523 // post request to $url. $params is an array of post variables.
524
525 if(! function_exists('post_url')) {
526 function post_url($url,$params, $headers = null, &$redirects = 0) {
527         $a = get_app();
528         $ch = curl_init($url);
529         if(($redirects > 8) || (! $ch)) 
530                 return false;
531
532         curl_setopt($ch, CURLOPT_HEADER, true);
533         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
534         curl_setopt($ch, CURLOPT_POST,1);
535         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
536
537         $curl_time = intval(get_config('system','curl_timeout'));
538         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
539
540         if(is_array($headers))
541                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
542
543         $check_cert = get_config('system','verifyssl');
544         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
545         $prx = get_config('system','proxy');
546         if(strlen($prx)) {
547                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
548                 curl_setopt($ch, CURLOPT_PROXY, $prx);
549                 $prxusr = get_config('system','proxyuser');
550                 if(strlen($prxusr))
551                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
552         }
553
554         $a->set_curl_code(0);
555
556         // don't let curl abort the entire application
557         // if it throws any errors.
558
559         $s = @curl_exec($ch);
560
561         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
562         $header = substr($s,0,strpos($s,"\r\n\r\n"));
563         if(stristr($header,'100') && (strlen($header) < 30)) {
564                 // 100 Continue has two headers, get the real one
565                 $s = substr($s,strlen($header)+4);
566                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
567         }
568         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
569         $matches = array();
570         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
571         $url = trim(array_pop($matches));
572         $url_parsed = parse_url($url);
573         if (isset($url_parsed)) {
574             $redirects++;
575             return post_url($url,$binary,$headers,$redirects);
576         }
577     }
578         $a->set_curl_code($http_code);
579         $body = substr($s,strlen($header)+4);
580         $a->set_curl_headers($header);
581
582         curl_close($ch);
583         return($body);
584 }}
585
586 // random hash, 64 chars
587
588 if(! function_exists('random_string')) {
589 function random_string() {
590         return(hash('sha256',uniqid(rand(),true)));
591 }}
592
593 /**
594  * This is our primary input filter. 
595  *
596  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
597  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
598  * after cleansing, and angle chars with the high bit set could get through as markup.
599  * 
600  * This is now disabled because it was interfering with some legitimate unicode sequences 
601  * and hopefully there aren't a lot of those browsers left. 
602  *
603  * Use this on any text input where angle chars are not valid or permitted
604  * They will be replaced with safer brackets. This may be filtered further
605  * if these are not allowed either.   
606  *
607  */
608
609 if(! function_exists('notags')) {
610 function notags($string) {
611
612         return(str_replace(array("<",">"), array('[',']'), $string));
613
614 //  High-bit filter no longer used
615 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
616 }}
617
618 // use this on "body" or "content" input where angle chars shouldn't be removed,
619 // and allow them to be safely displayed.
620
621 if(! function_exists('escape_tags')) {
622 function escape_tags($string) {
623
624         return(htmlspecialchars($string));
625 }}
626
627 // wrapper for adding a login box. If $register == true provide a registration
628 // link. This will most always depend on the value of $a->config['register_policy'].
629 // returns the complete html for inserting into the page
630
631 if(! function_exists('login')) {
632 function login($register = false) {
633         $o = "";
634         $register_html = (($register) ? load_view_file("view/register-link.tpl") : "");
635
636         $noid = get_config('system','no_openid');
637         if($noid) {
638                 $classname = 'no-openid';
639                 $namelabel = t('Nickname or Email address: ');
640                 $passlabel = t('Password: ');
641                 $login     = t('Login');
642         }
643         else {
644                 $classname = 'openid';
645                 $namelabel = t('Nickname/Email/OpenID: ');
646                 $passlabel = t("Password \x28if not OpenID\x29: ");
647                 $login     = t('Login');
648         }
649         $lostpass = t('Forgot your password?');
650         $lostlink = t('Password Reset');
651
652         if(x($_SESSION,'authenticated')) {
653                 $tpl = load_view_file("view/logout.tpl");
654         }
655         else {
656                 $tpl = load_view_file("view/login.tpl");
657
658         }
659         
660         $o = replace_macros($tpl,array(
661                 '$register_html' => $register_html, 
662                 '$classname' => $classname,
663                 '$namelabel' => $namelabel,
664                 '$passlabel' => $passlabel,
665                 '$login' => $login,
666                 '$lostpass' => $lostpass,
667                 '$lostlink' => $lostlink 
668         ));
669
670         return $o;
671 }}
672
673 // generate a string that's random, but usually pronounceable. 
674 // used to generate initial passwords
675
676 if(! function_exists('autoname')) {
677 function autoname($len) {
678
679         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
680         if(mt_rand(0,5) == 4)
681                 $vowels[] = 'y';
682
683         $cons = array(
684                         'b','bl','br',
685                         'c','ch','cl','cr',
686                         'd','dr',
687                         'f','fl','fr',
688                         'g','gh','gl','gr',
689                         'h',
690                         'j',
691                         'k','kh','kl','kr',
692                         'l',
693                         'm',
694                         'n',
695                         'p','ph','pl','pr',
696                         'qu',
697                         'r','rh',
698                         's','sc','sh','sm','sp','st',
699                         't','th','tr',
700                         'v',
701                         'w','wh',
702                         'x',
703                         'z','zh'
704                         );
705
706         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
707                                 'nd','ng','nk','nt','rn','rp','rt');
708
709         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
710                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
711
712         $start = mt_rand(0,2);
713         if($start == 0)
714                 $table = $vowels;
715         else
716                 $table = $cons;
717
718         $word = '';
719
720         for ($x = 0; $x < $len; $x ++) {
721                 $r = mt_rand(0,count($table) - 1);
722                 $word .= $table[$r];
723   
724                 if($table == $vowels)
725                         $table = array_merge($cons,$midcons);
726                 else
727                         $table = $vowels;
728
729         }
730
731         $word = substr($word,0,$len);
732
733         foreach($noend as $noe) {
734                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
735                         $word = substr($word,0,-1);
736                         break;
737                 }
738         }
739         if(substr($word,-1) == 'q')
740                 $word = substr($word,0,-1);    
741         return $word;
742 }}
743
744 // Used to end the current process, after saving session state. 
745
746 if(! function_exists('killme')) {
747 function killme() {
748         session_write_close();
749         exit;
750 }}
751
752 // redirect to another URL and terminate this process.
753
754 if(! function_exists('goaway')) {
755 function goaway($s) {
756         header("Location: $s");
757         killme();
758 }}
759
760 // Generic XML return
761 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
762 // of $st and an optional text <message> of $message and terminates the current process. 
763
764 if(! function_exists('xml_status')) {
765 function xml_status($st, $message = '') {
766
767         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
768
769         if($st)
770                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
771
772         header( "Content-type: text/xml" );
773         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
774         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
775         killme();
776 }}
777
778 // Returns the uid of locally logged in user or false.
779
780 if(! function_exists('local_user')) {
781 function local_user() {
782         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
783                 return intval($_SESSION['uid']);
784         return false;
785 }}
786
787 // Returns contact id of authenticated site visitor or false
788
789 if(! function_exists('remote_user')) {
790 function remote_user() {
791         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
792                 return intval($_SESSION['visitor_id']);
793         return false;
794 }}
795
796 // contents of $s are displayed prominently on the page the next time
797 // a page is loaded. Usually used for errors or alerts.
798
799 if(! function_exists('notice')) {
800 function notice($s) {
801         $a = get_app();
802         if($a->interactive)
803                 $_SESSION['sysmsg'] .= $s;
804 }}
805
806 // wrapper around config to limit the text length of an incoming message
807
808 if(! function_exists('get_max_import_size')) {
809 function get_max_import_size() {
810         global $a;
811         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
812 }}
813
814
815 // escape text ($str) for XML transport
816 // returns escaped text.
817
818 if(! function_exists('xmlify')) {
819 function xmlify($str) {
820         $buffer = '';
821         
822         for($x = 0; $x < strlen($str); $x ++) {
823                 $char = $str[$x];
824         
825                 switch( $char ) {
826
827                         case "\r" :
828                                 break;
829                         case "&" :
830                                 $buffer .= '&amp;';
831                                 break;
832                         case "'" :
833                                 $buffer .= '&apos;';
834                                 break;
835
836                         case "\"" :
837                                 $buffer .= '&quot;';
838                                 break;
839                         case '<' :
840                                 $buffer .= '&lt;';
841                                 break;
842                         case '>' :
843                                 $buffer .= '&gt;';
844                                 break;
845                         case "\n" :
846                                 $buffer .= "\n";
847                                 break;
848                         default :
849                                 $buffer .= $char;
850                                 break;
851                 }       
852         }
853         $buffer = trim($buffer);
854         return($buffer);
855 }}
856
857 // undo an xmlify
858 // pass xml escaped text ($s), returns unescaped text
859
860 if(! function_exists('unxmlify')) {
861 function unxmlify($s) {
862         $ret = str_replace('&amp;','&', $s);
863         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
864         return $ret;    
865 }}
866
867 // convenience wrapper, reverse the operation "bin2hex"
868
869 if(! function_exists('hex2bin')) {
870 function hex2bin($s) {
871         return(pack("H*",$s));
872 }}
873
874 // Automatic pagination.
875 // To use, get the count of total items.
876 // Then call $a->set_pager_total($number_items);
877 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
878 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
879 // (assuming there are enough items to paginate).
880 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
881 // will limit the results to the correct items for the current page. 
882 // The actual page handling is then accomplished at the application layer. 
883
884 if(! function_exists('paginate')) {
885 function paginate(&$a) {
886         $o = '';
887         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
888         $stripped = str_replace('q=','',$stripped);
889         $stripped = trim($stripped,'/');
890         $url = $a->get_baseurl() . '/' . $stripped;
891
892
893           if($a->pager['total'] > $a->pager['itemspage']) {
894                 $o .= '<div class="pager">';
895                 if($a->pager['page'] != 1)
896                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
897
898                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
899
900                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
901
902                 $numstart = 1;
903                 $numstop = $numpages;
904
905                 if($numpages > 14) {
906                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
907                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
908                 }
909    
910                 for($i = $numstart; $i <= $numstop; $i++){
911                         if($i == $a->pager['page'])
912                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
913                         else
914                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
915                         $o .= '</span> ';
916                 }
917
918                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
919                         if($i == $a->pager['page'])
920                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
921                         else
922                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
923                         $o .= '</span> ';
924                 }
925
926                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
927                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
928
929                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
930                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
931                 $o .= '</div>'."\r\n";
932         }
933         return $o;
934 }}
935
936 // Turn user/group ACLs stored as angle bracketed text into arrays
937
938 if(! function_exists('expand_acl')) {
939 function expand_acl($s) {
940         // turn string array of angle-bracketed elements into numeric array
941         // e.g. "<1><2><3>" => array(1,2,3);
942         $ret = array();
943
944         if(strlen($s)) {
945                 $t = str_replace('<','',$s);
946                 $a = explode('>',$t);
947                 foreach($a as $aa) {
948                         if(intval($aa))
949                                 $ret[] = intval($aa);
950                 }
951         }
952         return $ret;
953 }}              
954
955 // Used to wrap ACL elements in angle brackets for storage 
956
957 if(! function_exists('sanitise_acl')) {
958 function sanitise_acl(&$item) {
959         if(intval($item))
960                 $item = '<' . intval(notags(trim($item))) . '>';
961         else
962                 unset($item);
963 }}
964
965 // retrieve a "family" of config variables from database to cached storage
966
967 if(! function_exists('load_config')) {
968 function load_config($family) {
969         global $a;
970         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
971                 dbesc($family)
972         );
973         if(count($r)) {
974                 foreach($r as $rr) {
975                         $k = $rr['k'];
976                         $a->config[$family][$k] = $rr['v'];
977                 }
978         }
979 }}
980
981 // get a particular config variable given the family name
982 // and key. Returns false if not set.
983 // $instore is only used by the set_config function
984 // to determine if the key already exists in the DB
985 // If a key is found in the DB but doesn't exist in
986 // local config cache, pull it into the cache so we don't have
987 // to hit the DB again for this item.
988
989 if(! function_exists('get_config')) {
990 function get_config($family, $key, $instore = false) {
991
992         global $a;
993
994         if(! $instore) {
995                 if(isset($a->config[$family][$key])) {
996                         if($a->config[$family][$key] === '!<unset>!') {
997                                 return false;
998                         }
999                         return $a->config[$family][$key];
1000                 }
1001         }
1002         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1003                 dbesc($family),
1004                 dbesc($key)
1005         );
1006         if(count($ret)) {
1007                 $a->config[$family][$key] = $ret[0]['v'];
1008                 return $ret[0]['v'];
1009         }
1010         else {
1011                 $a->config[$family][$key] = '!<unset>!';
1012         }
1013         return false;
1014 }}
1015
1016 // Store a config value ($value) in the category ($family)
1017 // under the key ($key)
1018 // Return the value, or false if the database update failed
1019
1020 if(! function_exists('set_config')) {
1021 function set_config($family,$key,$value) {
1022
1023         global $a;
1024         $a->config[$family][$key] = $value;
1025
1026         if(get_config($family,$key,true) === false) {
1027                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1028                         dbesc($family),
1029                         dbesc($key),
1030                         dbesc($value)
1031                 );
1032                 if($ret) 
1033                         return $value;
1034                 return $ret;
1035         }
1036         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1037                 dbesc($value),
1038                 dbesc($family),
1039                 dbesc($key)
1040         );
1041         if($ret)
1042                 return $value;
1043         return $ret;
1044 }}
1045
1046
1047 if(! function_exists('get_pconfig')) {
1048 function get_pconfig($uid,$family, $key, $instore = false) {
1049
1050         global $a;
1051
1052         if(! $instore) {
1053                 if(isset($a->config[$uid][$family][$key])) {
1054                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1055                                 return false;
1056                         }
1057                         return $a->config[$uid][$family][$key];
1058                 }
1059         }
1060         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1061                 intval($uid),
1062                 dbesc($family),
1063                 dbesc($key)
1064         );
1065         if(count($ret)) {
1066                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1067                 return $ret[0]['v'];
1068         }
1069         else {
1070                 $a->config[$uid][$family][$key] = '!<unset>!';
1071         }
1072         return false;
1073 }}
1074
1075 if(! function_exists('del_config')) {
1076 function del_config($family,$key) {
1077
1078         global $a;
1079         if(x($a->config[$family],$key))
1080                 unset($a->config[$family][$key]);
1081         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1082                 dbesc($cat),
1083                 dbesc($key)
1084         );
1085         return $ret;
1086 }}
1087
1088
1089
1090 // Same as above functions except these are for personal config storage and take an
1091 // additional $uid argument.
1092
1093 if(! function_exists('set_pconfig')) {
1094 function set_pconfig($uid,$family,$key,$value) {
1095
1096         global $a;
1097         $a->config[$uid][$family][$key] = $value;
1098
1099         if(get_pconfig($uid,$family,$key,true) === false) {
1100                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1101                         intval($uid),
1102                         dbesc($family),
1103                         dbesc($key),
1104                         dbesc($value)
1105                 );
1106                 if($ret) 
1107                         return $value;
1108                 return $ret;
1109         }
1110         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1111                 intval($uid),
1112                 dbesc($value),
1113                 dbesc($family),
1114                 dbesc($key)
1115         );
1116         if($ret)
1117                 return $value;
1118         return $ret;
1119 }}
1120
1121 if(! function_exists('del_pconfig')) {
1122 function del_pconfig($uid,$family,$key) {
1123
1124         global $a;
1125         if(x($a->config[$uid][$family],$key))
1126                 unset($a->config[$uid][$family][$key]);
1127         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1128                 intval($uid),
1129                 dbesc($cat),
1130                 dbesc($key)
1131         );
1132         return $ret;
1133 }}
1134
1135
1136 // convert an XML document to a normalised, case-corrected array
1137 // used by webfinger
1138
1139 if(! function_exists('convert_xml_element_to_array')) {
1140 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1141
1142         // If we're getting too deep, bail out
1143         if ($recursion_depth > 512) {
1144                 return(null);
1145         }
1146
1147         if (!is_string($xml_element) &&
1148         !is_array($xml_element) &&
1149         (get_class($xml_element) == 'SimpleXMLElement')) {
1150                 $xml_element_copy = $xml_element;
1151                 $xml_element = get_object_vars($xml_element);
1152         }
1153
1154         if (is_array($xml_element)) {
1155                 $result_array = array();
1156                 if (count($xml_element) <= 0) {
1157                         return (trim(strval($xml_element_copy)));
1158                 }
1159
1160                 foreach($xml_element as $key=>$value) {
1161
1162                         $recursion_depth++;
1163                         $result_array[strtolower($key)] =
1164                 convert_xml_element_to_array($value, $recursion_depth);
1165                         $recursion_depth--;
1166                 }
1167                 if ($recursion_depth == 0) {
1168                         $temp_array = $result_array;
1169                         $result_array = array(
1170                                 strtolower($xml_element_copy->getName()) => $temp_array,
1171                         );
1172                 }
1173
1174                 return ($result_array);
1175
1176         } else {
1177                 return (trim(strval($xml_element)));
1178         }
1179 }}
1180
1181 // Given an email style address, perform webfinger lookup and 
1182 // return the resulting DFRN profile URL, or if no DFRN profile URL
1183 // is located, returns an OStatus subscription template (prefixed 
1184 // with the string 'stat:' to identify it as on OStatus template).
1185 // If this isn't an email style address just return $s.
1186 // Return an empty string if email-style addresses but webfinger fails,
1187 // or if the resultant personal XRD doesn't contain a supported 
1188 // subscription/friend-request attribute.
1189
1190 if(! function_exists('webfinger_dfrn')) {
1191 function webfinger_dfrn($s) {
1192         if(! strstr($s,'@')) {
1193                 return $s;
1194         }
1195         $links = webfinger($s);
1196         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1197         if(count($links)) {
1198                 foreach($links as $link)
1199                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1200                                 return $link['@attributes']['href'];
1201                 foreach($links as $link)
1202                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1203                                 return 'stat:' . $link['@attributes']['template'];              
1204         }
1205         return '';
1206 }}
1207
1208 // Given an email style address, perform webfinger lookup and 
1209 // return the array of link attributes from the personal XRD file.
1210 // On error/failure return an empty array.
1211
1212
1213 if(! function_exists('webfinger')) {
1214 function webfinger($s) {
1215         $host = '';
1216         if(strstr($s,'@')) {
1217                 $host = substr($s,strpos($s,'@') + 1);
1218         }
1219         if(strlen($host)) {
1220                 $tpl = fetch_lrdd_template($host);
1221                 logger('webfinger: lrdd template: ' . $tpl);
1222                 if(strlen($tpl)) {
1223                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
1224                         $links = fetch_xrd_links($pxrd);
1225                         if(! count($links)) {
1226                                 // try with double slashes
1227                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
1228                                 $links = fetch_xrd_links($pxrd);
1229                         }
1230                         return $links;
1231                 }
1232         }
1233         return array();
1234 }}
1235
1236 if(! function_exists('lrdd')) {
1237 function lrdd($uri) {
1238
1239         $a = get_app();
1240
1241         if(strstr($uri,'@')) {  
1242                 return(webfinger($uri));
1243         }
1244         else {
1245                 $html = fetch_url($uri);
1246                 $headers = $a->get_curl_headers();
1247                 $lines = explode("\n",$headers);
1248                 if(count($lines)) {
1249                         foreach($lines as $line) {                              
1250                                 // TODO alter the following regex to support multiple relations (space separated)
1251                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1252                                         $link = $matches[1];
1253                                         break;
1254                                 }
1255                         }
1256                 }
1257                 if(! isset($link)) {
1258                         // parse the page of the supplied URL looking for rel links
1259
1260                         require_once('library/HTML5/Parser.php');
1261                         $dom = HTML5_Parser::parse($html);
1262
1263                         if($dom) {
1264                                 $items = $dom->getElementsByTagName('link');
1265
1266                                 foreach($items as $item) {
1267                                         $x = $item->getAttribute('rel');
1268                                         if($x == "lrdd") {
1269                                                 $link = $item->getAttribute('href');
1270                                                 break;
1271                                         }
1272                                 }
1273                         }
1274                 }
1275
1276                 if(isset($link))
1277                         return(fetch_xrd_links($link));
1278         }
1279         return array();
1280 }}
1281
1282
1283
1284 // Given a host name, locate the LRDD template from that
1285 // host. Returns the LRDD template or an empty string on
1286 // error/failure.
1287
1288 if(! function_exists('fetch_lrdd_template')) {
1289 function fetch_lrdd_template($host) {
1290         $tpl = '';
1291         $url = 'http://' . $host . '/.well-known/host-meta' ;
1292         $links = fetch_xrd_links($url);
1293         if(count($links)) {
1294                 foreach($links as $link)
1295                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1296                                 $tpl = $link['@attributes']['template'];
1297         }
1298         if(! strpos($tpl,'{uri}'))
1299                 $tpl = '';
1300         return $tpl;
1301 }}
1302
1303 // Given a URL, retrieve the page as an XRD document.
1304 // Return an array of links.
1305 // on error/failure return empty array.
1306
1307 if(! function_exists('fetch_xrd_links')) {
1308 function fetch_xrd_links($url) {
1309
1310
1311         $xml = fetch_url($url);
1312         if (! $xml)
1313                 return array();
1314
1315         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1316         $h = simplexml_load_string($xml);
1317         $arr = convert_xml_element_to_array($h);
1318
1319         if(isset($arr['xrd']['link'])) {
1320                 $link = $arr['xrd']['link'];
1321                 if(! isset($link[0]))
1322                         $links = array($link);
1323                 else
1324                         $links = $link;
1325                 return $links;
1326         }
1327         return array();
1328 }}
1329
1330 // Convert an ACL array to a storable string
1331
1332 if(! function_exists('perms2str')) {
1333 function perms2str($p) {
1334         $ret = '';
1335         $tmp = $p;
1336         if(is_array($tmp)) {
1337                 array_walk($tmp,'sanitise_acl');
1338                 $ret = implode('',$tmp);
1339         }
1340         return $ret;
1341 }}
1342
1343 // generate a guaranteed unique (for this domain) item ID for ATOM
1344 // safe from birthday paradox
1345
1346 if(! function_exists('item_new_uri')) {
1347 function item_new_uri($hostname,$uid) {
1348
1349         do {
1350                 $dups = false;
1351                 $hash = random_string();
1352
1353                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1354
1355                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1356                         dbesc($uri));
1357                 if(count($r))
1358                         $dups = true;
1359         } while($dups == true);
1360         return $uri;
1361 }}
1362
1363 // Generate a guaranteed unique photo ID.
1364 // safe from birthday paradox
1365
1366 if(! function_exists('photo_new_resource')) {
1367 function photo_new_resource() {
1368
1369         do {
1370                 $found = false;
1371                 $resource = hash('md5',uniqid(mt_rand(),true));
1372                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1373                         dbesc($resource)
1374                 );
1375                 if(count($r))
1376                         $found = true;
1377         } while($found == true);
1378         return $resource;
1379 }}
1380
1381
1382 // Take a URL from the wild, prepend http:// if necessary
1383 // and check DNS to see if it's real
1384 // return true if it's OK, false if something is wrong with it
1385
1386 if(! function_exists('validate_url')) {
1387 function validate_url(&$url) {
1388         if(substr($url,0,4) != 'http')
1389                 $url = 'http://' . $url;
1390         $h = parse_url($url);
1391
1392         if(($h) && (checkdnsrr($h['host'], 'ANY'))) {
1393                 return true;
1394         }
1395         return false;
1396 }}
1397
1398 // checks that email is an actual resolvable internet address
1399
1400 if(! function_exists('validate_email')) {
1401 function validate_email($addr) {
1402
1403         if(! strpos($addr,'@'))
1404                 return false;
1405         $h = substr($addr,strpos($addr,'@') + 1);
1406
1407         if(($h) && (checkdnsrr($h, 'ANY'))) {
1408                 return true;
1409         }
1410         return false;
1411 }}
1412
1413 // Check $url against our list of allowed sites,
1414 // wildcards allowed. If allowed_sites is unset return true;
1415 // If url is allowed, return true.
1416 // otherwise, return false
1417
1418 if(! function_exists('allowed_url')) {
1419 function allowed_url($url) {
1420
1421         $h = parse_url($url);
1422
1423         if(! $h) {
1424                 return false;
1425         }
1426
1427         $str_allowed = get_config('system','allowed_sites');
1428         if(! $str_allowed)
1429                 return true;
1430
1431         $found = false;
1432
1433         $host = strtolower($h['host']);
1434
1435         // always allow our own site
1436
1437         if($host == strtolower($_SERVER['SERVER_NAME']))
1438                 return true;
1439
1440         $fnmatch = function_exists('fnmatch');
1441         $allowed = explode(',',$str_allowed);
1442
1443         if(count($allowed)) {
1444                 foreach($allowed as $a) {
1445                         $pat = strtolower(trim($a));
1446                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1447                                 $found = true; 
1448                                 break;
1449                         }
1450                 }
1451         }
1452         return $found;
1453 }}
1454
1455 // check if email address is allowed to register here.
1456 // Compare against our list (wildcards allowed).
1457 // Returns false if not allowed, true if allowed or if
1458 // allowed list is not configured.
1459
1460 if(! function_exists('allowed_email')) {
1461 function allowed_email($email) {
1462
1463
1464         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1465         if(! $domain)
1466                 return false;
1467
1468         $str_allowed = get_config('system','allowed_email');
1469         if(! $str_allowed)
1470                 return true;
1471
1472         $found = false;
1473
1474         $fnmatch = function_exists('fnmatch');
1475         $allowed = explode(',',$str_allowed);
1476
1477         if(count($allowed)) {
1478                 foreach($allowed as $a) {
1479                         $pat = strtolower(trim($a));
1480                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1481                                 $found = true; 
1482                                 break;
1483                         }
1484                 }
1485         }
1486         return $found;
1487 }}
1488
1489 // Format the like/dislike text for a profile item
1490 // $cnt = number of people who like/dislike the item
1491 // $arr = array of pre-linked names of likers/dislikers
1492 // $type = one of 'like, 'dislike'
1493 // $id  = item id
1494 // returns formatted text
1495
1496 if(! function_exists('format_like')) {
1497 function format_like($cnt,$arr,$type,$id) {
1498         $o = '';
1499         if($cnt == 1)
1500                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1501         else {
1502                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1503                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1504                 $total = count($arr);
1505                 if($total >= MAX_LIKERS)
1506                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1507                 if($total < MAX_LIKERS)
1508                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1509                 $str = implode(', ', $arr);
1510                 if($total >= MAX_LIKERS)
1511                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1512                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1513                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1514         }
1515         return $o;
1516 }}
1517
1518
1519 // wrapper to load a view template, checking for alternate
1520 // languages before falling back to the default
1521
1522 if(! function_exists('load_view_file')) {
1523 function load_view_file($s) {
1524         $b = basename($s);
1525         $d = dirname($s);
1526         $lang = get_config('system','language');
1527         if($lang === false)
1528                 $lang = 'en';
1529         if(file_exists("$d/$lang/$b"))
1530                 return file_get_contents("$d/$lang/$b");
1531         return file_get_contents($s);
1532 }}
1533
1534 // for html,xml parsing - let's say you've got
1535 // an attribute foobar="class1 class2 class3"
1536 // and you want to find out if it contains 'class3'.
1537 // you can't use a normal sub string search because you
1538 // might match 'notclass3' and a regex to do the job is 
1539 // possible but a bit complicated. 
1540 // pass the attribute string as $attr and the attribute you 
1541 // are looking for as $s - returns true if found, otherwise false
1542
1543 if(! function_exists('attribute_contains')) {
1544 function attribute_contains($attr,$s) {
1545         $a = explode(' ', $attr);
1546         if(count($a) && in_array($s,$a))
1547                 return true;
1548         return false;
1549 }}
1550
1551 if(! function_exists('logger')) {
1552 function logger($msg,$level = 0) {
1553
1554         $debugging = get_config('system','debugging');
1555         $loglevel  = intval(get_config('system','loglevel'));
1556         $logfile   = get_config('system','logfile');
1557
1558         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1559                 return;
1560         
1561         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1562         return;
1563 }}
1564
1565
1566 if(! function_exists('activity_match')) {
1567 function activity_match($haystack,$needle) {
1568         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1569                 return true;
1570         return false;
1571 }}
1572
1573
1574 // Pull out all #hashtags and @person tags from $s;
1575 // We also get @person@domain.com - which would make 
1576 // the regex quite complicated as tags can also
1577 // end a sentence. So we'll run through our results
1578 // and strip the period from any tags which end with one.
1579 // Returns array of tags found, or empty array.
1580
1581
1582 if(! function_exists('get_tags')) {
1583 function get_tags($s) {
1584         $ret = array();
1585         if(preg_match_all('/([@#][^ \x0D\x0A,:?]*)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1586                 foreach($match[1] as $match) {
1587                         if(strstr($match,"]")) {
1588                                 // we might be inside a bbcode color tag - leave it alone
1589                                 continue;
1590                         }
1591                         if(substr($match,-1,1) === '.')
1592                                 $ret[] = substr($match,0,-1);
1593                         else
1594                                 $ret[] = $match;
1595                 }
1596         }
1597
1598         return $ret;
1599 }}
1600
1601
1602 // quick and dirty quoted_printable encoding
1603
1604 if(! function_exists('qp')) {
1605 function qp($s) {
1606 return str_replace ("%","=",rawurlencode($s));
1607 }} 
1608
1609
1610 if(! function_exists('like_puller')) {
1611 function like_puller($a,$item,&$arr,$mode) {
1612
1613         $url = '';
1614         $sparkle = '';
1615         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1616
1617         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1618                 $url = $item['author-link'];
1619                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1620                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1621                         $sparkle = ' class="sparkle" ';
1622                 }
1623                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1624                         $arr[$item['parent'] . '-l'] = array();
1625                 if(! isset($arr[$item['parent']]))
1626                         $arr[$item['parent']] = 1;
1627                 else    
1628                         $arr[$item['parent']] ++;
1629                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1630         }
1631         return;
1632 }}
1633
1634 if(! function_exists('get_mentions')) {
1635 function get_mentions($item) {
1636         $o = '';
1637         if(! strlen($item['tag']))
1638                 return $o;
1639
1640         $arr = explode(',',$item['tag']);
1641         foreach($arr as $x) {
1642                 $matches = null;
1643                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1644                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1645         }
1646         return $o;
1647 }}
1648
1649 if(! function_exists('contact_block')) {
1650 function contact_block() {
1651         $o = '';
1652         $a = get_app();
1653         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1654                 return $o;
1655         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1656                         intval($a->profile['uid'])
1657         );
1658         if(count($r)) {
1659                 $total = intval($r[0]['total']);
1660         }
1661         if(! $total) {
1662                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1663                 return $o;
1664         }
1665         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT 24",
1666                         intval($a->profile['uid'])
1667         );
1668         if(count($r)) {
1669                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1670                 foreach($r as $rr) {
1671                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1672                         if(local_user() && ($rr['uid'] == local_user())
1673                                 && ($rr['network'] === 'dfrn')) {
1674                                 $url = $redirect_url;
1675                                 $sparkle = ' sparkle';
1676                         }
1677                         else {
1678                                 $url = $rr['url'];
1679                                 $sparkle = '';
1680                         }
1681
1682                         $o .= '<div class="contact-block-div"><a class="contact-block-link' . $sparkle . '" href="' . $url . '" ><img class="contact-block-img' . $sparkle . '" src="' . $rr['micro'] . '" title="' . $rr['name'] . ' [' . $rr['url'] . ']" alt="' . $rr['name'] . '" /></a></div>' . "\r\n";
1683                 }
1684                 $o .= '</div><div id="contact-block-end"></div>';
1685                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1686                 
1687         }
1688         return $o;
1689
1690 }}
1691
1692 if(! function_exists('search')) {
1693 function search($s) {
1694         $a = get_app();
1695         $o  = '<div id="search-box">';
1696         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1697         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1698         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1699         $o .= '</form></div>';
1700         return $o;
1701 }}
1702
1703 if(! function_exists('valid_email')) {
1704 function valid_email($x){
1705         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1706                 return true;
1707         return false;
1708 }}
1709
1710
1711 if(! function_exists('gravatar_img')) {
1712 function gravatar_img($email) {
1713         $size = 175;
1714         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1715         $rating = 'pg';
1716         $hash = md5(trim(strtolower($email)));
1717         
1718         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1719                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1720
1721         logger('gravatar: ' . $email . ' ' . $url);
1722         return $url;
1723 }}
1724
1725 if(! function_exists('aes_decrypt')) {
1726 function aes_decrypt($val,$ky)
1727 {
1728     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1729     for($a=0;$a<strlen($ky);$a++)
1730       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1731     $mode = MCRYPT_MODE_ECB;
1732     $enc = MCRYPT_RIJNDAEL_128;
1733     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
1734     return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null));
1735 }}
1736
1737
1738 if(! function_exists('aes_encrypt')) {
1739 function aes_encrypt($val,$ky)
1740 {
1741     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1742     for($a=0;$a<strlen($ky);$a++)
1743       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1744     $mode=MCRYPT_MODE_ECB;
1745     $enc=MCRYPT_RIJNDAEL_128;
1746     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
1747     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
1748 }} 
1749
1750
1751 /**
1752  *
1753  * Function: linkify
1754  *
1755  * Replace naked text hyperlink with HTML formatted hyperlink
1756  *
1757  */
1758
1759 if(! function_exists('linkify')) {
1760 function linkify($s) {
1761         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%]*)/", ' <a href="$1" >$1</a>', $s);
1762         return($s);
1763 }}
1764
1765
1766 /**
1767  * 
1768  * Function: smilies
1769  *
1770  * Description:
1771  * Replaces text emoticons with graphical images
1772  *
1773  * @Parameter: string $s
1774  *
1775  * Returns string
1776  */
1777
1778 if(! function_exists('smilies')) {
1779 function smilies($s) {
1780         $a = get_app();
1781
1782         return str_replace(
1783         array( ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
1784         array(
1785                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1786                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1787                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1788                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
1789                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1790                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1791                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1792                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1793                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1794                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1795                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
1796         ), $s);
1797 }}
1798
1799
1800 /**
1801  *
1802  * Function : profile_load
1803  * @parameter App    $a
1804  * @parameter string $nickname
1805  * @parameter int    $profile
1806  *
1807  * Summary: Loads a profile into the page sidebar. 
1808  * The function requires a writeable copy of the main App structure, and the nickname
1809  * of a registered local account.
1810  *
1811  * If the viewer is an authenticated remote viewer, the profile displayed is the
1812  * one that has been configured for his/her viewing in the Contact manager.
1813  * Passing a non-zero profile ID can also allow a preview of a selected profile
1814  * by the owner.
1815  *
1816  * Profile information is placed in the App structure for later retrieval.
1817  * Honours the owner's chosen theme for display. 
1818  *
1819  */
1820
1821 if(! function_exists('profile_load')) {
1822 function profile_load(&$a, $nickname, $profile = 0) {
1823         if(remote_user()) {
1824                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
1825                         intval($_SESSION['visitor_id']));
1826                 if(count($r))
1827                         $profile = $r[0]['profile-id'];
1828         } 
1829
1830         $r = null;
1831
1832         if($profile) {
1833                 $profile_int = intval($profile);
1834                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1835                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1836                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
1837                         dbesc($nickname),
1838                         intval($profile_int)
1839                 );
1840         }
1841         if(! count($r)) {       
1842                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1843                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1844                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
1845                         dbesc($nickname)
1846                 );
1847         }
1848
1849         if(($r === false) || (! count($r))) {
1850                 notice( t('No profile') . EOL );
1851                 $a->error = 404;
1852                 return;
1853         }
1854
1855         $a->profile = $r[0];
1856
1857
1858         $a->page['title'] = $a->profile['name'];
1859         $_SESSION['theme'] = $a->profile['theme'];
1860
1861         if(! (x($a->page,'aside')))
1862                 $a->page['aside'] = '';
1863
1864         $a->page['aside'] .= profile_sidebar($a->profile);
1865         $a->page['aside'] .= contact_block();
1866
1867         return;
1868 }}
1869
1870
1871 /**
1872  *
1873  * Function: profile_sidebar
1874  *
1875  * Formats a profile for display in the sidebar.
1876  * It is very difficult to templatise the HTML completely
1877  * because of all the conditional logic.
1878  *
1879  * @parameter: array $profile
1880  *
1881  * Returns HTML string stuitable for sidebar inclusion
1882  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
1883  *
1884  */
1885
1886
1887 if(! function_exists('profile_sidebar')) {
1888 function profile_sidebar($profile) {
1889
1890         $o = '';
1891         $location = '';
1892         $address = false;
1893
1894         if((! is_array($profile)) && (! count($profile)))
1895                 return $o;
1896
1897         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
1898
1899         $tabs = '';
1900
1901         $photo = '<div id="profile=photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
1902
1903         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
1904  
1905         if((x($profile,'address') == 1) 
1906                 || (x($profile,'locality') == 1) 
1907                 || (x($profile,'region') == 1) 
1908                 || (x($profile,'postal-code') == 1) 
1909                 || (x($profile,'country-name') == 1))
1910                 $address = true;
1911
1912         if($address) {
1913                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
1914                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
1915                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
1916                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
1917                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
1918                         . '<span class="region">' . $profile['region'] . '</span>'
1919                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
1920                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
1921                 $location .= '</div></div><div class="profile-clear"></div>';
1922
1923         }
1924
1925         $gender = ((x($profile,'gender') == 1) ? '<div class="mf"><span class="gender-label">' . t('Gender:') . '</span> <span class="x-gender">' . $profile['gender'] . '</span></div><div class="profile-clear"></div>' : '');
1926
1927         $pubkey = ((x($profile,'key') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
1928
1929         $marital = ((x($profile,'marital') == 1) ? '<div class="marital"><span class="marital-label"><span class="heart">&hearts;</span> ' . t('Status:') . ' </span><span class="marital-text">' . $profile['marital'] . '</span></div></div><div class="profile-clear"></div>' : '');
1930
1931         $homepage = ((x($profile,'homepage') == 1) ? '<div class="homepage"><span class="homepage-label">' . t('Homepage:') . ' </span><span class="homepage-url">' . linkify($profile['homepage']) . '</span></div></div><div class="profile-clear"></div>' : '');
1932
1933         $tpl = load_view_file('view/profile_vcard.tpl');
1934
1935         $o .= replace_macros($tpl, array(
1936                 '$fullname' => $fullname,
1937                 '$tabs'     => $tabs,
1938                 '$photo'    => $photo,
1939                 '$connect'  => $connect,                
1940                 '$location' => $location,
1941                 '$gender'   => $gender,
1942                 '$pubkey'   => $pubkey,
1943                 '$marital'  => $marital,
1944                 '$homepage' => $homepage
1945         ));
1946
1947         return $o;
1948 }}