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