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