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