]> git.mxchange.org Git - friendica.git/blob - boot.php
ad3a76e8e69727de3ed1c39050ef842d2067e115
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4 ini_set('pcre.backtrack_limit', 250000);
5
6 define ( 'FRIENDIKA_VERSION',      '2.1.970' );
7 define ( 'DFRN_PROTOCOL_VERSION',  '2.21'    );
8 define ( 'DB_UPDATE_VERSION',      1054      );
9
10 define ( 'EOL',                    "<br />\r\n"     );
11 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
12 define ( 'DOWN_ARROW',             '&#x21e9;'       );
13
14 /**
15  *
16  * Image storage quality. Lower numbers save space at cost of image detail.
17  * For ease of upgrade, please do not change here. Change jpeg quality with 
18  * set_config('system','jpeg_quality',n) in .htconfig.php
19  * where n is netween 1 and 100, and with very poor results below about 50 
20  *
21  */
22
23 define ( 'JPEG_QUALITY',            100              );         
24
25 /**
26  * SSL redirection policies
27  */
28
29 define ( 'SSL_POLICY_NONE',         0 );
30 define ( 'SSL_POLICY_FULL',         1 );
31 define ( 'SSL_POLICY_SELFSIGN',     2 );
32
33
34 /**
35  * log levels
36  */
37
38 define ( 'LOGGER_NORMAL',          0 );
39 define ( 'LOGGER_TRACE',           1 );
40 define ( 'LOGGER_DEBUG',           2 );
41 define ( 'LOGGER_DATA',            3 );
42 define ( 'LOGGER_ALL',             4 );
43
44 /**
45  * registration policies
46  */
47
48 define ( 'REGISTER_CLOSED',        0 );
49 define ( 'REGISTER_APPROVE',       1 );
50 define ( 'REGISTER_OPEN',          2 );
51
52 /**
53  * relationship types
54  * When used in contact records, this indicates that 'uid' has 
55  * this relationship with contact['name']
56  */
57
58 define ( 'REL_VIP',        1);
59 define ( 'REL_FAN',        2);
60 define ( 'REL_BUD',        3);
61
62 /**
63  * Hook array order
64  */
65  
66 define ( 'HOOK_HOOK',      0);
67 define ( 'HOOK_FILE',      1);
68 define ( 'HOOK_FUNCTION',  2);
69
70 /**
71  *
72  * page/profile types
73  *
74  * PAGE_NORMAL is a typical personal profile account
75  * PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
76  * PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
77  *      write access to wall and comments (no email and not included in page owner's ACL lists)
78  * PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
79  *
80  */
81
82 define ( 'PAGE_NORMAL',            0 );
83 define ( 'PAGE_SOAPBOX',           1 );
84 define ( 'PAGE_COMMUNITY',         2 );
85 define ( 'PAGE_FREELOVE',          3 );
86
87 /**
88  * Network and protocol family types 
89  */
90
91 define ( 'NETWORK_DFRN',             'dfrn');    // Friendika, Mistpark, other DFRN implementations
92 define ( 'NETWORK_OSTATUS',          'stat');    // status.net, identi.ca, GNU-social, other OStatus implementations
93 define ( 'NETWORK_FEED',             'feed');    // RSS/Atom feeds with no known "post/notify" protocol
94 define ( 'NETWORK_DIASPORA',         'dspr');    // Diaspora
95 define ( 'NETWORK_MAIL',             'mail');    // IMAP/POP
96 define ( 'NETWORK_FACEBOOK',         'face');    // Facebook API     
97
98
99 /**
100  * Maximum number of "people who like (or don't like) this"  that we will list by name
101  */
102
103 define ( 'MAX_LIKERS',    75);
104
105 /**
106  * email notification options
107  */
108
109 define ( 'NOTIFY_INTRO',   0x0001 );
110 define ( 'NOTIFY_CONFIRM', 0x0002 );
111 define ( 'NOTIFY_WALL',    0x0004 );
112 define ( 'NOTIFY_COMMENT', 0x0008 );
113 define ( 'NOTIFY_MAIL',    0x0010 );
114
115 /**
116  * various namespaces we may need to parse
117  */
118
119 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
120 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
121 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
122 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
123 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
124 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
125 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
126 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
127 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
128 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
129 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
130
131 /**
132  * activity stream defines
133  */
134
135 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
136 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
137 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
138
139 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
140 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
141 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'stop-following' );
142 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
143 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
144 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
145
146 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
147 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
148 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
149 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
150 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
151 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
152
153 /**
154  * item weight for query ordering
155  */
156
157 define ( 'GRAVITY_PARENT',       0);
158 define ( 'GRAVITY_LIKE',         3);
159 define ( 'GRAVITY_COMMENT',      6);
160
161 /**
162  *
163  * Reverse the effect of magic_quotes_gpc if it is enabled.
164  * Please disable magic_quotes_gpc so we don't have to do this.
165  * See http://php.net/manual/en/security.magicquotes.disabling.php
166  *
167  */
168
169 if (get_magic_quotes_gpc()) {
170     $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
171     while (list($key, $val) = each($process)) {
172         foreach ($val as $k => $v) {
173             unset($process[$key][$k]);
174             if (is_array($v)) {
175                 $process[$key][stripslashes($k)] = $v;
176                 $process[] = &$process[$key][stripslashes($k)];
177             } else {
178                 $process[$key][stripslashes($k)] = stripslashes($v);
179             }
180         }
181     }
182     unset($process);
183 }
184
185 /*
186  * translation system
187  */
188 require_once("include/pgettext.php");
189
190
191 /**
192  *
193  * class: App
194  *
195  * Our main application structure for the life of this page
196  * Primarily deals with the URL that got us here
197  * and tries to make some sense of it, and 
198  * stores our page contents and config storage
199  * and anything else that might need to be passed around 
200  * before we spit the page out. 
201  *
202  */
203
204 if(! class_exists('App')) {
205 class App {
206
207         public  $module_loaded = false;
208         public  $query_string;
209         public  $config;
210         public  $page;
211         public  $profile;
212         public  $user;
213         public  $cid;
214         public  $contact;
215         public  $contacts;
216         public  $page_contact;
217         public  $content;
218         public  $data;
219         public  $error = false;
220         public  $cmd;
221         public  $argv;
222         public  $argc;
223         public  $module;
224         public  $pager;
225         public  $strings;   
226         public  $path;
227         public  $hooks;
228         public  $timezone;
229         public  $interactive = true;
230         public  $plugins;
231         public  $apps;
232         public  $identities;
233
234         private $scheme;
235         private $hostname;
236         private $baseurl;
237         private $db;
238
239         private $curl_code;
240         private $curl_headers;
241
242         function __construct() {
243
244                 $this->config = array();
245                 $this->page = array();
246                 $this->pager= array();
247
248                 $this->query_string = '';
249
250                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
251
252                 if(x($_SERVER,'SERVER_NAME')) {
253                         $this->hostname = $_SERVER['SERVER_NAME'];
254
255                         /** 
256                          * Figure out if we are running at the top of a domain
257                          * or in a sub-directory and adjust accordingly
258                          */
259
260                         $path = trim(dirname($_SERVER['SCRIPT_NAME']),'/\\');
261                         if(isset($path) && strlen($path) && ($path != $this->path))
262                                 $this->path = $path;
263                 }
264
265                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
266
267                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
268                         $this->query_string = substr($_SERVER['QUERY_STRING'],2);
269                 if(x($_GET,'q'))
270                         $this->cmd = trim($_GET['q'],'/\\');
271
272
273
274                 /**
275                  *
276                  * Break the URL path into C style argc/argv style arguments for our
277                  * modules. Given "http://example.com/module/arg1/arg2", $this->argc
278                  * will be 3 (integer) and $this->argv will contain:
279                  *   [0] => 'module'
280                  *   [1] => 'arg1'
281                  *   [2] => 'arg2'
282                  *
283                  *
284                  * There will always be one argument. If provided a naked domain
285                  * URL, $this->argv[0] is set to "home".
286                  *
287                  */
288
289                 $this->argv = explode('/',$this->cmd);
290                 $this->argc = count($this->argv);
291                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
292                         $this->module = str_replace(".", "_", $this->argv[0]);
293                 }
294                 else {
295                         $this->module = 'home';
296                 }
297
298                 /**
299                  * Special handling for the webfinger/lrdd host XRD file
300                  * Just spit out the contents and exit.
301                  */
302
303                 if($this->cmd === '.well-known/host-meta') {
304                         require_once('include/hostxrd.php');
305                         hostxrd($this->get_baseurl());
306                         // NOTREACHED
307                 }
308
309                 /**
310                  * See if there is any page number information, and initialise 
311                  * pagination
312                  */
313
314                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
315                 $this->pager['itemspage'] = 50;
316                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
317                 $this->pager['total'] = 0;
318         }
319
320         function get_baseurl($ssl = false) {
321
322                 $scheme = $this->scheme;
323
324                 if(x($this->config,'ssl_policy')) {
325                         if(($ssl) || ($this->config['ssl_policy'] == SSL_POLICY_FULL)) 
326                                 $scheme = 'https';
327                         if(($this->config['ssl_policy'] == SSL_POLICY_SELFSIGN) && (local_user() || x($_POST,'auth-params')))
328                                 $scheme = 'https';
329                 }
330
331                 $this->baseurl = $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
332                 return $this->baseurl;
333         }
334
335         function set_baseurl($url) {
336                 $parsed = @parse_url($url);
337
338                 $this->baseurl = $url;
339
340                 if($parsed) {           
341                         $this->scheme = $parsed['scheme'];
342
343                         $this->hostname = $parsed['host'];
344                         if(x($parsed,'port'))
345                                 $this->hostname .= ':' . $parsed['port'];
346                         if(x($parsed,'path'))
347                                 $this->path = trim($parsed['path'],'\\/');
348                 }
349
350         }
351
352         function get_hostname() {
353                 return $this->hostname;
354         }
355
356         function set_hostname($h) {
357                 $this->hostname = $h;
358         }
359
360         function set_path($p) {
361                 $this->path = trim(trim($p),'/');
362         } 
363
364         function get_path() {
365                 return $this->path;
366         }
367
368         function set_pager_total($n) {
369                 $this->pager['total'] = intval($n);
370         }
371
372         function set_pager_itemspage($n) {
373                 $this->pager['itemspage'] = intval($n);
374                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
375
376         } 
377
378         function init_pagehead() {
379                 $this->page['title'] = $this->config['sitename'];
380                 $tpl = load_view_file('view/head.tpl');
381                 $this->page['htmlhead'] = replace_macros($tpl,array(
382                         '$baseurl' => $this->get_baseurl() . '/',
383                         '$generator' => 'Friendika' . ' ' . FRIENDIKA_VERSION,
384                         '$delitem' => t('Delete this item?'),
385                         '$comment' => t('Comment')
386                 ));
387         }
388
389         function set_curl_code($code) {
390                 $this->curl_code = $code;
391         }
392
393         function get_curl_code() {
394                 return $this->curl_code;
395         }
396
397         function set_curl_headers($headers) {
398                 $this->curl_headers = $headers;
399         }
400
401         function get_curl_headers() {
402                 return $this->curl_headers;
403         }
404
405
406 }}
407
408 // retrieve the App structure
409 // useful in functions which require it but don't get it passed to them
410
411 if(! function_exists('get_app')) {
412 function get_app() {
413         global $a;
414         return $a;
415 }};
416
417
418 // Multi-purpose function to check variable state.
419 // Usage: x($var) or $x($array,'key')
420 // returns false if variable/key is not set
421 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
422 // e.g. x('') or x(0) returns 0;
423
424 if(! function_exists('x')) {
425 function x($s,$k = NULL) {
426         if($k != NULL) {
427                 if((is_array($s)) && (array_key_exists($k,$s))) {
428                         if($s[$k])
429                                 return (int) 1;
430                         return (int) 0;
431                 }
432                 return false;
433         }
434         else {          
435                 if(isset($s)) {
436                         if($s) {
437                                 return (int) 1;
438                         }
439                         return (int) 0;
440                 }
441                 return false;
442         }
443 }}
444
445 // called from db initialisation if db is dead.
446
447 if(! function_exists('system_unavailable')) {
448 function system_unavailable() {
449         include('system_unavailable.php');
450         system_down();
451         killme();
452 }}
453
454 // Primarily involved with database upgrade, but also sets the 
455 // base url for use in cmdline programs which don't have
456 // $_SERVER variables, and synchronising the state of installed plugins.
457
458
459 if(! function_exists('check_config')) {
460 function check_config(&$a) {
461
462
463         load_config('system');
464
465         $build = get_config('system','build');
466         if(! x($build))
467                 $build = set_config('system','build',DB_UPDATE_VERSION);
468
469         $url = get_config('system','url');
470
471         // if the url isn't set or the stored url is radically different 
472         // than the currently visited url, store the current value accordingly.
473         // "Radically different" ignores common variations such as http vs https 
474         // and www.example.com vs example.com.
475
476         if((! x($url)) || (! link_compare($url,$a->get_baseurl())))
477                 $url = set_config('system','url',$a->get_baseurl());
478
479         if($build != DB_UPDATE_VERSION) {
480                 $stored = intval($build);
481                 $current = intval(DB_UPDATE_VERSION);
482                 if(($stored < $current) && file_exists('update.php')) {
483                         // We're reporting a different version than what is currently installed.
484                         // Run any existing update scripts to bring the database up to current.
485
486                         require_once('update.php');
487                         for($x = $stored; $x < $current; $x ++) {
488                                 if(function_exists('update_' . $x)) {
489                                         $func = 'update_' . $x;
490                                         $func($a);
491                                 }
492                         }
493                         set_config('system','build', DB_UPDATE_VERSION);
494                 }
495         }
496
497         /**
498          *
499          * Synchronise plugins:
500          *
501          * $a->config['system']['addon'] contains a comma-separated list of names
502          * of plugins/addons which are used on this system. 
503          * Go through the database list of already installed addons, and if we have
504          * an entry, but it isn't in the config list, call the uninstall procedure
505          * and mark it uninstalled in the database (for now we'll remove it).
506          * Then go through the config list and if we have a plugin that isn't installed,
507          * call the install procedure and add it to the database.
508          *
509          */
510
511         $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
512         if(count($r))
513                 $installed = $r;
514         else
515                 $installed = array();
516
517         $plugins = get_config('system','addon');
518         $plugins_arr = array();
519
520         if($plugins)
521                 $plugins_arr = explode(',',str_replace(' ', '',$plugins));
522
523         $a->plugins = $plugins_arr;
524
525         $installed_arr = array();
526
527         if(count($installed)) {
528                 foreach($installed as $i) {
529                         if(! in_array($i['name'],$plugins_arr)) {
530                                 logger("Addons: uninstalling " . $i['name']);
531                                 q("DELETE FROM `addon` WHERE `id` = %d LIMIT 1",
532                                         intval($i['id'])
533                                 );
534
535                                 @include_once('addon/' . $i['name'] . '/' . $i['name'] . '.php');
536                                 if(function_exists($i['name'] . '_uninstall')) {
537                                         $func = $i['name'] . '_uninstall';
538                                         $func();
539                                 }
540                         }
541                         else
542                                 $installed_arr[] = $i['name'];
543                 }
544         }
545
546         if(count($plugins_arr)) {
547                 foreach($plugins_arr as $p) {
548                         if(! in_array($p,$installed_arr)) {
549                                 logger("Addons: installing " . $p);
550                                 $t = filemtime('addon/' . $p . '/' . $p . '.php');
551                                 @include_once('addon/' . $p . '/' . $p . '.php');
552                                 if(function_exists($p . '_install')) {
553                                         $func = $p . '_install';
554                                         $func();
555                                         $r = q("INSERT INTO `addon` (`name`, `installed`, `timestamp`) VALUES ( '%s', 1, %d ) ",
556                                                 dbesc($p),
557                                                 intval($t)
558                                         );
559                                 }
560                         }
561                 }
562         }
563
564
565         load_hooks();
566
567         return;
568 }}
569
570 // reload all updated plugins
571
572 if(! function_exists('reload_plugins')) {
573 function reload_plugins() {
574         $plugins = get_config('system','addon');
575         if(strlen($plugins)) {
576
577                 $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
578                 if(count($r))
579                         $installed = $r;
580                 else
581                         $installed = array();
582
583                 $parr = explode(',',$plugins);
584                 if(count($parr)) {
585                         foreach($parr as $pl) {
586                                 $pl = trim($pl);
587                                 
588                                 $t = filemtime('addon/' . $pl . '/' . $pl . '.php');
589                                 foreach($installed as $i) {
590                                         if(($i['name'] == $pl) && ($i['timestamp'] != $t)) {    
591                                                 logger('Reloading plugin: ' . $i['name']);
592                                                 @include_once('addon/' . $pl . '/' . $pl . '.php');
593
594                                                 if(function_exists($pl . '_uninstall')) {
595                                                         $func = $pl . '_uninstall';
596                                                         $func();
597                                                 }
598                                                 if(function_exists($pl . '_install')) {
599                                                         $func = $pl . '_install';
600                                                         $func();
601                                                 }
602                                                 q("UPDATE `addon` SET `timestamp` = %d WHERE `id` = %d LIMIT 1",
603                                                         intval($t),
604                                                         intval($i['id'])
605                                                 );
606                                         }
607                                 }
608                         }
609                 }
610         }
611 }}
612                                 
613
614
615 // This is our template processor.
616 // $s is the string requiring macro substitution.
617 // $r is an array of key value pairs (search => replace)
618 // returns substituted string.
619 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
620 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
621 // depending on the order in which they were declared in the array.   
622
623 require_once("include/template_processor.php");
624
625 if(! function_exists('replace_macros')) {  
626 function replace_macros($s,$r) {
627         global $t;
628         
629         return $t->replace($s,$r);
630
631 }}
632
633
634 // curl wrapper. If binary flag is true, return binary
635 // results. 
636
637 if(! function_exists('fetch_url')) {
638 function fetch_url($url,$binary = false, &$redirects = 0) {
639
640         $a = get_app();
641
642         $ch = curl_init($url);
643         if(($redirects > 8) || (! $ch)) 
644                 return false;
645
646         curl_setopt($ch, CURLOPT_HEADER, true);
647         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
648
649
650         $curl_time = intval(get_config('system','curl_timeout'));
651         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
652
653         // by default we will allow self-signed certs
654         // but you can override this
655
656         $check_cert = get_config('system','verifyssl');
657         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
658
659         $prx = get_config('system','proxy');
660         if(strlen($prx)) {
661                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
662                 curl_setopt($ch, CURLOPT_PROXY, $prx);
663                 $prxusr = get_config('system','proxyuser');
664                 if(strlen($prxusr))
665                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
666         }
667         if($binary)
668                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
669
670         $a->set_curl_code(0);
671
672         // don't let curl abort the entire application
673         // if it throws any errors.
674
675         $s = @curl_exec($ch);
676
677         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
678         $header = substr($s,0,strpos($s,"\r\n\r\n"));
679         if(stristr($header,'100') && (strlen($header) < 30)) {
680                 // 100 Continue has two headers, get the real one
681                 $s = substr($s,strlen($header)+4);
682                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
683         }
684         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
685         $matches = array();
686         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
687         $url = trim(array_pop($matches));
688         $url_parsed = @parse_url($url);
689         if (isset($url_parsed)) {
690             $redirects++;
691             return fetch_url($url,$binary,$redirects);
692         }
693     }
694         $a->set_curl_code($http_code);
695
696         $body = substr($s,strlen($header)+4);
697
698         /* one more try to make sure there are no more headers */
699
700         if(strpos($body,'HTTP/') === 0) {
701                 $header = substr($body,0,strpos($body,"\r\n\r\n"));
702                 $body = substr($body,strlen($header)+4);
703         }
704
705         $a->set_curl_headers($header);
706
707         curl_close($ch);
708         return($body);
709 }}
710
711 // post request to $url. $params is an array of post variables.
712
713 if(! function_exists('post_url')) {
714 function post_url($url,$params, $headers = null, &$redirects = 0) {
715         $a = get_app();
716         $ch = curl_init($url);
717         if(($redirects > 8) || (! $ch)) 
718                 return false;
719
720         curl_setopt($ch, CURLOPT_HEADER, true);
721         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
722         curl_setopt($ch, CURLOPT_POST,1);
723         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
724
725         $curl_time = intval(get_config('system','curl_timeout'));
726         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
727
728         if(is_array($headers))
729                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
730
731         $check_cert = get_config('system','verifyssl');
732         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
733         $prx = get_config('system','proxy');
734         if(strlen($prx)) {
735                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
736                 curl_setopt($ch, CURLOPT_PROXY, $prx);
737                 $prxusr = get_config('system','proxyuser');
738                 if(strlen($prxusr))
739                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
740         }
741
742         $a->set_curl_code(0);
743
744         // don't let curl abort the entire application
745         // if it throws any errors.
746
747         $s = @curl_exec($ch);
748
749         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
750         $header = substr($s,0,strpos($s,"\r\n\r\n"));
751         if(stristr($header,'100') && (strlen($header) < 30)) {
752                 // 100 Continue has two headers, get the real one
753                 $s = substr($s,strlen($header)+4);
754                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
755         }
756         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
757         $matches = array();
758         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
759         $url = trim(array_pop($matches));
760         $url_parsed = @parse_url($url);
761         if (isset($url_parsed)) {
762             $redirects++;
763             return post_url($url,$binary,$headers,$redirects);
764         }
765     }
766         $a->set_curl_code($http_code);
767         $body = substr($s,strlen($header)+4);
768
769         /* one more try to make sure there are no more headers */
770
771         if(strpos($body,'HTTP/') === 0) {
772                 $header = substr($body,0,strpos($body,"\r\n\r\n"));
773                 $body = substr($body,strlen($header)+4);
774         }
775
776         $a->set_curl_headers($header);
777
778         curl_close($ch);
779         return($body);
780 }}
781
782 // random hash, 64 chars
783
784 if(! function_exists('random_string')) {
785 function random_string() {
786         return(hash('sha256',uniqid(rand(),true)));
787 }}
788
789 /**
790  * This is our primary input filter. 
791  *
792  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
793  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
794  * after cleansing, and angle chars with the high bit set could get through as markup.
795  * 
796  * This is now disabled because it was interfering with some legitimate unicode sequences 
797  * and hopefully there aren't a lot of those browsers left. 
798  *
799  * Use this on any text input where angle chars are not valid or permitted
800  * They will be replaced with safer brackets. This may be filtered further
801  * if these are not allowed either.   
802  *
803  */
804
805 if(! function_exists('notags')) {
806 function notags($string) {
807
808         return(str_replace(array("<",">"), array('[',']'), $string));
809
810 //  High-bit filter no longer used
811 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
812 }}
813
814 // use this on "body" or "content" input where angle chars shouldn't be removed,
815 // and allow them to be safely displayed.
816
817 if(! function_exists('escape_tags')) {
818 function escape_tags($string) {
819
820         return(htmlspecialchars($string));
821 }}
822
823 // wrapper for adding a login box. If $register == true provide a registration
824 // link. This will most always depend on the value of $a->config['register_policy'].
825 // returns the complete html for inserting into the page
826
827 if(! function_exists('login')) {
828 function login($register = false) {
829         $o = "";
830         $register_tpl = (($register) ? load_view_file("view/register-link.tpl") : "");
831         
832         $register_html = replace_macros($register_tpl,array(
833                 '$title' => t('Create a New Account'),
834                 '$desc' => t('Register')
835         ));
836
837         $noid = get_config('system','no_openid');
838         if($noid) {
839                 $classname = 'no-openid';
840                 $namelabel = t('Nickname or Email address: ');
841                 $passlabel = t('Password: ');
842                 $login     = t('Login');
843         }
844         else {
845                 $classname = 'openid';
846                 $namelabel = t('Nickname/Email/OpenID: ');
847                 $passlabel = t("Password \x28if not OpenID\x29: ");
848                 $login     = t('Login');
849         }
850         $lostpass = t('Forgot your password?');
851         $lostlink = t('Password Reset');
852
853         if(local_user()) {
854                 $tpl = load_view_file("view/logout.tpl");
855         }
856         else {
857                 $tpl = load_view_file("view/login.tpl");
858
859         }
860
861         $o = '<script type="text/javascript"> $(document).ready(function() { $("#login-name").focus();} );</script>';   
862
863         $o .= replace_macros($tpl,array(
864                 '$logout'        => t('Logout'),
865                 '$register_html' => $register_html, 
866                 '$classname'     => $classname,
867                 '$namelabel'     => $namelabel,
868                 '$passlabel'     => $passlabel,
869                 '$login'         => $login,
870                 '$lostpass'      => $lostpass,
871                 '$lostlink'      => $lostlink 
872         ));
873
874         return $o;
875 }}
876
877 // generate a string that's random, but usually pronounceable. 
878 // used to generate initial passwords
879
880 if(! function_exists('autoname')) {
881 function autoname($len) {
882
883         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
884         if(mt_rand(0,5) == 4)
885                 $vowels[] = 'y';
886
887         $cons = array(
888                         'b','bl','br',
889                         'c','ch','cl','cr',
890                         'd','dr',
891                         'f','fl','fr',
892                         'g','gh','gl','gr',
893                         'h',
894                         'j',
895                         'k','kh','kl','kr',
896                         'l',
897                         'm',
898                         'n',
899                         'p','ph','pl','pr',
900                         'qu',
901                         'r','rh',
902                         's','sc','sh','sm','sp','st',
903                         't','th','tr',
904                         'v',
905                         'w','wh',
906                         'x',
907                         'z','zh'
908                         );
909
910         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
911                                 'nd','ng','nk','nt','rn','rp','rt');
912
913         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
914                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
915
916         $start = mt_rand(0,2);
917         if($start == 0)
918                 $table = $vowels;
919         else
920                 $table = $cons;
921
922         $word = '';
923
924         for ($x = 0; $x < $len; $x ++) {
925                 $r = mt_rand(0,count($table) - 1);
926                 $word .= $table[$r];
927   
928                 if($table == $vowels)
929                         $table = array_merge($cons,$midcons);
930                 else
931                         $table = $vowels;
932
933         }
934
935         $word = substr($word,0,$len);
936
937         foreach($noend as $noe) {
938                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
939                         $word = substr($word,0,-1);
940                         break;
941                 }
942         }
943         if(substr($word,-1) == 'q')
944                 $word = substr($word,0,-1);    
945         return $word;
946 }}
947
948 // Used to end the current process, after saving session state. 
949
950 if(! function_exists('killme')) {
951 function killme() {
952         session_write_close();
953         exit;
954 }}
955
956 // redirect to another URL and terminate this process.
957
958 if(! function_exists('goaway')) {
959 function goaway($s) {
960         header("Location: $s");
961         killme();
962 }}
963
964 // Generic XML return
965 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
966 // of $st and an optional text <message> of $message and terminates the current process. 
967
968 if(! function_exists('xml_status')) {
969 function xml_status($st, $message = '') {
970
971         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
972
973         if($st)
974                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
975
976         header( "Content-type: text/xml" );
977         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
978         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
979         killme();
980 }}
981
982 // Returns the uid of locally logged in user or false.
983
984 if(! function_exists('local_user')) {
985 function local_user() {
986         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
987                 return intval($_SESSION['uid']);
988         return false;
989 }}
990
991 // Returns contact id of authenticated site visitor or false
992
993 if(! function_exists('remote_user')) {
994 function remote_user() {
995         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
996                 return intval($_SESSION['visitor_id']);
997         return false;
998 }}
999
1000 // contents of $s are displayed prominently on the page the next time
1001 // a page is loaded. Usually used for errors or alerts.
1002
1003 if(! function_exists('notice')) {
1004 function notice($s) {
1005         $a = get_app();
1006         if($a->interactive)
1007                 $_SESSION['sysmsg'] .= $s;
1008 }}
1009
1010 // wrapper around config to limit the text length of an incoming message
1011
1012 if(! function_exists('get_max_import_size')) {
1013 function get_max_import_size() {
1014         global $a;
1015         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
1016 }}
1017
1018
1019 // escape text ($str) for XML transport
1020 // returns escaped text.
1021
1022 if(! function_exists('xmlify')) {
1023 function xmlify($str) {
1024         $buffer = '';
1025         
1026         for($x = 0; $x < strlen($str); $x ++) {
1027                 $char = $str[$x];
1028         
1029                 switch( $char ) {
1030
1031                         case "\r" :
1032                                 break;
1033                         case "&" :
1034                                 $buffer .= '&amp;';
1035                                 break;
1036                         case "'" :
1037                                 $buffer .= '&apos;';
1038                                 break;
1039                         case "\"" :
1040                                 $buffer .= '&quot;';
1041                                 break;
1042                         case '<' :
1043                                 $buffer .= '&lt;';
1044                                 break;
1045                         case '>' :
1046                                 $buffer .= '&gt;';
1047                                 break;
1048                         case "\n" :
1049                                 $buffer .= "\n";
1050                                 break;
1051                         default :
1052                                 $buffer .= $char;
1053                                 break;
1054                 }       
1055         }
1056         $buffer = trim($buffer);
1057         return($buffer);
1058 }}
1059
1060 // undo an xmlify
1061 // pass xml escaped text ($s), returns unescaped text
1062
1063 if(! function_exists('unxmlify')) {
1064 function unxmlify($s) {
1065         $ret = str_replace('&amp;','&', $s);
1066         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
1067         return $ret;    
1068 }}
1069
1070 // convenience wrapper, reverse the operation "bin2hex"
1071
1072 if(! function_exists('hex2bin')) {
1073 function hex2bin($s) {
1074         if(! ctype_xdigit($s)) {
1075                 logger('hex2bin: illegal input: ' . print_r(debug_backtrace(), true));
1076                 return($s);
1077         }
1078
1079         return(pack("H*",$s));
1080 }}
1081
1082 // Automatic pagination.
1083 // To use, get the count of total items.
1084 // Then call $a->set_pager_total($number_items);
1085 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
1086 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
1087 // (assuming there are enough items to paginate).
1088 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
1089 // will limit the results to the correct items for the current page. 
1090 // The actual page handling is then accomplished at the application layer. 
1091
1092 if(! function_exists('paginate')) {
1093 function paginate(&$a) {
1094         $o = '';
1095         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
1096         $stripped = str_replace('q=','',$stripped);
1097         $stripped = trim($stripped,'/');
1098         $pagenum = $a->pager['page'];
1099         $url = $a->get_baseurl() . '/' . $stripped;
1100
1101
1102           if($a->pager['total'] > $a->pager['itemspage']) {
1103                 $o .= '<div class="pager">';
1104                 if($a->pager['page'] != 1)
1105                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
1106
1107                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
1108
1109                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
1110
1111                         $numstart = 1;
1112                 $numstop = $numpages;
1113
1114                 if($numpages > 14) {
1115                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
1116                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
1117                 }
1118    
1119                 for($i = $numstart; $i <= $numstop; $i++){
1120                         if($i == $a->pager['page'])
1121                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1122                         else
1123                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1124                         $o .= '</span> ';
1125                 }
1126
1127                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
1128                         if($i == $a->pager['page'])
1129                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1130                         else
1131                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1132                         $o .= '</span> ';
1133                 }
1134
1135                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
1136                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
1137
1138                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
1139                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
1140                 $o .= '</div>'."\r\n";
1141         }
1142         return $o;
1143 }}
1144
1145 // Turn user/group ACLs stored as angle bracketed text into arrays
1146
1147 if(! function_exists('expand_acl')) {
1148 function expand_acl($s) {
1149         // turn string array of angle-bracketed elements into numeric array
1150         // e.g. "<1><2><3>" => array(1,2,3);
1151         $ret = array();
1152
1153         if(strlen($s)) {
1154                 $t = str_replace('<','',$s);
1155                 $a = explode('>',$t);
1156                 foreach($a as $aa) {
1157                         if(intval($aa))
1158                                 $ret[] = intval($aa);
1159                 }
1160         }
1161         return $ret;
1162 }}              
1163
1164 // Used to wrap ACL elements in angle brackets for storage 
1165
1166 if(! function_exists('sanitise_acl')) {
1167 function sanitise_acl(&$item) {
1168         if(intval($item))
1169                 $item = '<' . intval(notags(trim($item))) . '>';
1170         else
1171                 unset($item);
1172 }}
1173
1174 // retrieve a "family" of config variables from database to cached storage
1175
1176 if(! function_exists('load_config')) {
1177 function load_config($family) {
1178         global $a;
1179         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
1180                 dbesc($family)
1181         );
1182         if(count($r)) {
1183                 foreach($r as $rr) {
1184                         $k = $rr['k'];
1185                         $a->config[$family][$k] = $rr['v'];
1186                 }
1187         }
1188 }}
1189
1190 // get a particular config variable given the family name
1191 // and key. Returns false if not set.
1192 // $instore is only used by the set_config function
1193 // to determine if the key already exists in the DB
1194 // If a key is found in the DB but doesn't exist in
1195 // local config cache, pull it into the cache so we don't have
1196 // to hit the DB again for this item.
1197
1198 if(! function_exists('get_config')) {
1199 function get_config($family, $key, $instore = false) {
1200
1201         global $a;
1202
1203         if(! $instore) {
1204                 if(isset($a->config[$family][$key])) {
1205                         if($a->config[$family][$key] === '!<unset>!') {
1206                                 return false;
1207                         }
1208                         return $a->config[$family][$key];
1209                 }
1210         }
1211         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1212                 dbesc($family),
1213                 dbesc($key)
1214         );
1215         if(count($ret)) {
1216                 $a->config[$family][$key] = $ret[0]['v'];
1217                 return $ret[0]['v'];
1218         }
1219         else {
1220                 $a->config[$family][$key] = '!<unset>!';
1221         }
1222         return false;
1223 }}
1224
1225 // Store a config value ($value) in the category ($family)
1226 // under the key ($key)
1227 // Return the value, or false if the database update failed
1228
1229 if(! function_exists('set_config')) {
1230 function set_config($family,$key,$value) {
1231
1232         global $a;
1233
1234         if(get_config($family,$key,true) === false) {
1235                 $a->config[$family][$key] = $value;
1236                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1237                         dbesc($family),
1238                         dbesc($key),
1239                         dbesc($value)
1240                 );
1241                 if($ret) 
1242                         return $value;
1243                 return $ret;
1244         }
1245         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1246                 dbesc($value),
1247                 dbesc($family),
1248                 dbesc($key)
1249         );
1250
1251         $a->config[$family][$key] = $value;
1252
1253         if($ret)
1254                 return $value;
1255         return $ret;
1256 }}
1257
1258
1259 if(! function_exists('load_pconfig')) {
1260 function load_pconfig($uid,$family) {
1261         global $a;
1262         $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
1263                 dbesc($family),
1264                 intval($uid)
1265         );
1266         if(count($r)) {
1267                 foreach($r as $rr) {
1268                         $k = $rr['k'];
1269                         $a->config[$uid][$family][$k] = $rr['v'];
1270                 }
1271         }
1272 }}
1273
1274
1275
1276 if(! function_exists('get_pconfig')) {
1277 function get_pconfig($uid,$family, $key, $instore = false) {
1278
1279         global $a;
1280
1281         if(! $instore) {
1282                 if(isset($a->config[$uid][$family][$key])) {
1283                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1284                                 return false;
1285                         }
1286                         return $a->config[$uid][$family][$key];
1287                 }
1288         }
1289
1290         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1291                 intval($uid),
1292                 dbesc($family),
1293                 dbesc($key)
1294         );
1295
1296         if(count($ret)) {
1297                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1298                 return $ret[0]['v'];
1299         }
1300         else {
1301                 $a->config[$uid][$family][$key] = '!<unset>!';
1302         }
1303         return false;
1304 }}
1305
1306 if(! function_exists('del_config')) {
1307 function del_config($family,$key) {
1308
1309         global $a;
1310         if(x($a->config[$family],$key))
1311                 unset($a->config[$family][$key]);
1312         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1313                 dbesc($cat),
1314                 dbesc($key)
1315         );
1316         return $ret;
1317 }}
1318
1319
1320
1321 // Same as above functions except these are for personal config storage and take an
1322 // additional $uid argument.
1323
1324 if(! function_exists('set_pconfig')) {
1325 function set_pconfig($uid,$family,$key,$value) {
1326
1327         global $a;
1328
1329         if(get_pconfig($uid,$family,$key,true) === false) {
1330                 $a->config[$uid][$family][$key] = $value;
1331                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1332                         intval($uid),
1333                         dbesc($family),
1334                         dbesc($key),
1335                         dbesc($value)
1336                 );
1337                 if($ret) 
1338                         return $value;
1339                 return $ret;
1340         }
1341         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1342                 dbesc($value),
1343                 intval($uid),
1344                 dbesc($family),
1345                 dbesc($key)
1346         );
1347
1348         $a->config[$uid][$family][$key] = $value;
1349
1350         if($ret)
1351                 return $value;
1352         return $ret;
1353 }}
1354
1355 if(! function_exists('del_pconfig')) {
1356 function del_pconfig($uid,$family,$key) {
1357
1358         global $a;
1359         if(x($a->config[$uid][$family],$key))
1360                 unset($a->config[$uid][$family][$key]);
1361         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1362                 intval($uid),
1363                 dbesc($family),
1364                 dbesc($key)
1365         );
1366         return $ret;
1367 }}
1368
1369
1370 // convert an XML document to a normalised, case-corrected array
1371 // used by webfinger
1372
1373 if(! function_exists('convert_xml_element_to_array')) {
1374 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1375
1376         // If we're getting too deep, bail out
1377         if ($recursion_depth > 512) {
1378                 return(null);
1379         }
1380
1381         if (!is_string($xml_element) &&
1382         !is_array($xml_element) &&
1383         (get_class($xml_element) == 'SimpleXMLElement')) {
1384                 $xml_element_copy = $xml_element;
1385                 $xml_element = get_object_vars($xml_element);
1386         }
1387
1388         if (is_array($xml_element)) {
1389                 $result_array = array();
1390                 if (count($xml_element) <= 0) {
1391                         return (trim(strval($xml_element_copy)));
1392                 }
1393
1394                 foreach($xml_element as $key=>$value) {
1395
1396                         $recursion_depth++;
1397                         $result_array[strtolower($key)] =
1398                 convert_xml_element_to_array($value, $recursion_depth);
1399                         $recursion_depth--;
1400                 }
1401                 if ($recursion_depth == 0) {
1402                         $temp_array = $result_array;
1403                         $result_array = array(
1404                                 strtolower($xml_element_copy->getName()) => $temp_array,
1405                         );
1406                 }
1407
1408                 return ($result_array);
1409
1410         } else {
1411                 return (trim(strval($xml_element)));
1412         }
1413 }}
1414
1415 // Given an email style address, perform webfinger lookup and 
1416 // return the resulting DFRN profile URL, or if no DFRN profile URL
1417 // is located, returns an OStatus subscription template (prefixed 
1418 // with the string 'stat:' to identify it as on OStatus template).
1419 // If this isn't an email style address just return $s.
1420 // Return an empty string if email-style addresses but webfinger fails,
1421 // or if the resultant personal XRD doesn't contain a supported 
1422 // subscription/friend-request attribute.
1423
1424 if(! function_exists('webfinger_dfrn')) {
1425 function webfinger_dfrn($s) {
1426         if(! strstr($s,'@')) {
1427                 return $s;
1428         }
1429         $links = webfinger($s);
1430         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1431         if(count($links)) {
1432                 foreach($links as $link)
1433                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1434                                 return $link['@attributes']['href'];
1435                 foreach($links as $link)
1436                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1437                                 return 'stat:' . $link['@attributes']['template'];              
1438         }
1439         return '';
1440 }}
1441
1442 // Given an email style address, perform webfinger lookup and 
1443 // return the array of link attributes from the personal XRD file.
1444 // On error/failure return an empty array.
1445
1446
1447 if(! function_exists('webfinger')) {
1448 function webfinger($s) {
1449         $host = '';
1450         if(strstr($s,'@')) {
1451                 $host = substr($s,strpos($s,'@') + 1);
1452         }
1453         if(strlen($host)) {
1454                 $tpl = fetch_lrdd_template($host);
1455                 logger('webfinger: lrdd template: ' . $tpl);
1456                 if(strlen($tpl)) {
1457                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
1458                         logger('webfinger: pxrd: ' . $pxrd);
1459                         $links = fetch_xrd_links($pxrd);
1460                         if(! count($links)) {
1461                                 // try with double slashes
1462                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
1463                                 logger('webfinger: pxrd: ' . $pxrd);
1464                                 $links = fetch_xrd_links($pxrd);
1465                         }
1466                         return $links;
1467                 }
1468         }
1469         return array();
1470 }}
1471
1472 if(! function_exists('lrdd')) {
1473 function lrdd($uri) {
1474
1475         $a = get_app();
1476
1477         // default priority is host priority, host-meta first
1478
1479         $priority = 'host';
1480
1481         // All we have is an email address. Resource-priority is irrelevant
1482         // because our URI isn't directly resolvable.
1483
1484         if(strstr($uri,'@')) {  
1485                 return(webfinger($uri));
1486         }
1487
1488         // get the host meta file
1489
1490         $host = @parse_url($uri);
1491
1492         if($host) {
1493                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
1494                 $url .= $host['host'] . '/.well-known/host-meta' ;
1495         }
1496         else
1497                 return array();
1498
1499         logger('lrdd: constructed url: ' . $url);
1500
1501         $xml = fetch_url($url);
1502         $headers = $a->get_curl_headers();
1503
1504         if (! $xml)
1505                 return array();
1506
1507         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
1508
1509         $h = parse_xml_string($xml);
1510
1511         $arr = convert_xml_element_to_array($h);
1512
1513         if(isset($arr['xrd']['property'])) {
1514                 $property = $arr['crd']['property'];
1515                 if(! isset($property[0]))
1516                         $properties = array($property);
1517                 else
1518                         $properties = $property;
1519                 foreach($properties as $prop)
1520                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
1521                                 $priority = 'resource';
1522         } 
1523
1524         // save the links in case we need them
1525
1526         $links = array();
1527
1528         if(isset($arr['xrd']['link'])) {
1529                 $link = $arr['xrd']['link'];
1530                 if(! isset($link[0]))
1531                         $links = array($link);
1532                 else
1533                         $links = $link;
1534         }
1535
1536         // do we have a template or href?
1537
1538         if(count($links)) {
1539                 foreach($links as $link) {
1540                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
1541                                 if(x($link['@attributes'],'template'))
1542                                         $tpl = $link['@attributes']['template'];
1543                                 elseif(x($link['@attributes'],'href'))
1544                                         $href = $link['@attributes']['href'];
1545                         }
1546                 }               
1547         }
1548
1549         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
1550                 $tpl = '';
1551
1552         if($priority === 'host') {
1553                 if(strlen($tpl)) 
1554                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
1555                 elseif(isset($href))
1556                         $pxrd = $href;
1557                 if(isset($pxrd)) {
1558                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
1559                         $links = fetch_xrd_links($pxrd);
1560                         return $links;
1561                 }
1562
1563                 $lines = explode("\n",$headers);
1564                 if(count($lines)) {
1565                         foreach($lines as $line) {                              
1566                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1567                                         return(fetch_xrd_links($matches[1]));
1568                                         break;
1569                                 }
1570                         }
1571                 }
1572         }
1573
1574
1575         // priority 'resource'
1576
1577
1578         $html = fetch_url($uri);
1579         $headers = $a->get_curl_headers();
1580         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
1581
1582         // don't try and parse raw xml as html
1583         if(! strstr($html,'<?xml')) {
1584                 require_once('library/HTML5/Parser.php');
1585                 $dom = @HTML5_Parser::parse($html);
1586
1587                 if($dom) {
1588                         $items = $dom->getElementsByTagName('link');
1589                         foreach($items as $item) {
1590                                 $x = $item->getAttribute('rel');
1591                                 if($x == "lrdd") {
1592                                         $pagelink = $item->getAttribute('href');
1593                                         break;
1594                                 }
1595                         }
1596                 }
1597         }
1598
1599         if(isset($pagelink))
1600                 return(fetch_xrd_links($pagelink));
1601
1602         // next look in HTTP headers
1603
1604         $lines = explode("\n",$headers);
1605         if(count($lines)) {
1606                 foreach($lines as $line) {                              
1607                         // TODO alter the following regex to support multiple relations (space separated)
1608                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1609                                 $pagelink = $matches[1];
1610                                 break;
1611                         }
1612                         // don't try and run feeds through the html5 parser
1613                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
1614                                 return array();
1615                         if(stristr($html,'<rss') || stristr($html,'<feed'))
1616                                 return array();
1617                 }
1618         }
1619
1620         if(isset($pagelink))
1621                 return(fetch_xrd_links($pagelink));
1622
1623         // If we haven't found any links, return the host xrd links (which we have already fetched)
1624
1625         if(isset($links))
1626                 return $links;
1627
1628         return array();
1629
1630 }}
1631
1632
1633
1634 // Given a host name, locate the LRDD template from that
1635 // host. Returns the LRDD template or an empty string on
1636 // error/failure.
1637
1638 if(! function_exists('fetch_lrdd_template')) {
1639 function fetch_lrdd_template($host) {
1640         $tpl = '';
1641
1642         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
1643         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
1644         $links = fetch_xrd_links($url1);
1645         logger('template (https): ' . print_r($links,true));
1646         if(! count($links)) {
1647                 $links = fetch_xrd_links($url2);
1648                 logger('template (http): ' . print_r($links,true));
1649         }
1650         if(count($links)) {
1651                 foreach($links as $link)
1652                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1653                                 $tpl = $link['@attributes']['template'];
1654         }
1655         if(! strpos($tpl,'{uri}'))
1656                 $tpl = '';
1657         return $tpl;
1658 }}
1659
1660 // Given a URL, retrieve the page as an XRD document.
1661 // Return an array of links.
1662 // on error/failure return empty array.
1663
1664 if(! function_exists('fetch_xrd_links')) {
1665 function fetch_xrd_links($url) {
1666
1667
1668         $xml = fetch_url($url);
1669         if (! $xml)
1670                 return array();
1671
1672         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1673         $h = parse_xml_string($xml);
1674         $arr = convert_xml_element_to_array($h);
1675
1676         $links = array();
1677
1678         if(isset($arr['xrd']['link'])) {
1679                 $link = $arr['xrd']['link'];
1680                 if(! isset($link[0]))
1681                         $links = array($link);
1682                 else
1683                         $links = $link;
1684         }
1685         if(isset($arr['xrd']['alias'])) {
1686                 $alias = $arr['xrd']['alias'];
1687                 if(! isset($alias[0]))
1688                         $aliases = array($alias);
1689                 else
1690                         $aliases = $alias;
1691                 foreach($aliases as $alias) {
1692                         $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
1693                 }
1694         }
1695
1696         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
1697
1698         return $links;
1699
1700 }}
1701
1702 // Convert an ACL array to a storable string
1703
1704 if(! function_exists('perms2str')) {
1705 function perms2str($p) {
1706         $ret = '';
1707         $tmp = $p;
1708         if(is_array($tmp)) {
1709                 array_walk($tmp,'sanitise_acl');
1710                 $ret = implode('',$tmp);
1711         }
1712         return $ret;
1713 }}
1714
1715 // generate a guaranteed unique (for this domain) item ID for ATOM
1716 // safe from birthday paradox
1717
1718 if(! function_exists('item_new_uri')) {
1719 function item_new_uri($hostname,$uid) {
1720
1721         do {
1722                 $dups = false;
1723                 $hash = random_string();
1724
1725                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1726
1727                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1728                         dbesc($uri));
1729                 if(count($r))
1730                         $dups = true;
1731         } while($dups == true);
1732         return $uri;
1733 }}
1734
1735 // Generate a guaranteed unique photo ID.
1736 // safe from birthday paradox
1737
1738 if(! function_exists('photo_new_resource')) {
1739 function photo_new_resource() {
1740
1741         do {
1742                 $found = false;
1743                 $resource = hash('md5',uniqid(mt_rand(),true));
1744                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1745                         dbesc($resource)
1746                 );
1747                 if(count($r))
1748                         $found = true;
1749         } while($found == true);
1750         return $resource;
1751 }}
1752
1753
1754 // Take a URL from the wild, prepend http:// if necessary
1755 // and check DNS to see if it's real
1756 // return true if it's OK, false if something is wrong with it
1757
1758 if(! function_exists('validate_url')) {
1759 function validate_url(&$url) {
1760         if(substr($url,0,4) != 'http')
1761                 $url = 'http://' . $url;
1762         $h = @parse_url($url);
1763
1764         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1765                 return true;
1766         }
1767         return false;
1768 }}
1769
1770 // checks that email is an actual resolvable internet address
1771
1772 if(! function_exists('validate_email')) {
1773 function validate_email($addr) {
1774
1775         if(! strpos($addr,'@'))
1776                 return false;
1777         $h = substr($addr,strpos($addr,'@') + 1);
1778
1779         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1780                 return true;
1781         }
1782         return false;
1783 }}
1784
1785 // Check $url against our list of allowed sites,
1786 // wildcards allowed. If allowed_sites is unset return true;
1787 // If url is allowed, return true.
1788 // otherwise, return false
1789
1790 if(! function_exists('allowed_url')) {
1791 function allowed_url($url) {
1792
1793         $h = @parse_url($url);
1794
1795         if(! $h) {
1796                 return false;
1797         }
1798
1799         $str_allowed = get_config('system','allowed_sites');
1800         if(! $str_allowed)
1801                 return true;
1802
1803         $found = false;
1804
1805         $host = strtolower($h['host']);
1806
1807         // always allow our own site
1808
1809         if($host == strtolower($_SERVER['SERVER_NAME']))
1810                 return true;
1811
1812         $fnmatch = function_exists('fnmatch');
1813         $allowed = explode(',',$str_allowed);
1814
1815         if(count($allowed)) {
1816                 foreach($allowed as $a) {
1817                         $pat = strtolower(trim($a));
1818                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1819                                 $found = true; 
1820                                 break;
1821                         }
1822                 }
1823         }
1824         return $found;
1825 }}
1826
1827 // check if email address is allowed to register here.
1828 // Compare against our list (wildcards allowed).
1829 // Returns false if not allowed, true if allowed or if
1830 // allowed list is not configured.
1831
1832 if(! function_exists('allowed_email')) {
1833 function allowed_email($email) {
1834
1835
1836         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1837         if(! $domain)
1838                 return false;
1839
1840         $str_allowed = get_config('system','allowed_email');
1841         if(! $str_allowed)
1842                 return true;
1843
1844         $found = false;
1845
1846         $fnmatch = function_exists('fnmatch');
1847         $allowed = explode(',',$str_allowed);
1848
1849         if(count($allowed)) {
1850                 foreach($allowed as $a) {
1851                         $pat = strtolower(trim($a));
1852                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
1853                                 $found = true; 
1854                                 break;
1855                         }
1856                 }
1857         }
1858         return $found;
1859 }}
1860
1861
1862
1863 // wrapper to load a view template, checking for alternate
1864 // languages before falling back to the default
1865
1866 if(! function_exists('load_view_file')) {
1867 function load_view_file($s) {
1868         global $lang, $a;
1869         if(! isset($lang))
1870                 $lang = 'en';
1871         $b = basename($s);
1872         $d = dirname($s);
1873         if(file_exists("$d/$lang/$b"))
1874                 return file_get_contents("$d/$lang/$b");
1875         
1876         $theme = current_theme();
1877         
1878         if(file_exists("$d/theme/$theme/$b"))
1879                 return file_get_contents("$d/theme/$theme/$b");
1880                         
1881         return file_get_contents($s);
1882 }}
1883
1884 // for html,xml parsing - let's say you've got
1885 // an attribute foobar="class1 class2 class3"
1886 // and you want to find out if it contains 'class3'.
1887 // you can't use a normal sub string search because you
1888 // might match 'notclass3' and a regex to do the job is 
1889 // possible but a bit complicated. 
1890 // pass the attribute string as $attr and the attribute you 
1891 // are looking for as $s - returns true if found, otherwise false
1892
1893 if(! function_exists('attribute_contains')) {
1894 function attribute_contains($attr,$s) {
1895         $a = explode(' ', $attr);
1896         if(count($a) && in_array($s,$a))
1897                 return true;
1898         return false;
1899 }}
1900
1901 if(! function_exists('logger')) {
1902 function logger($msg,$level = 0) {
1903         $debugging = get_config('system','debugging');
1904         $loglevel  = intval(get_config('system','loglevel'));
1905         $logfile   = get_config('system','logfile');
1906
1907         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1908                 return;
1909         
1910         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1911         return;
1912 }}
1913
1914
1915 if(! function_exists('activity_match')) {
1916 function activity_match($haystack,$needle) {
1917         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1918                 return true;
1919         return false;
1920 }}
1921
1922
1923 // Pull out all #hashtags and @person tags from $s;
1924 // We also get @person@domain.com - which would make 
1925 // the regex quite complicated as tags can also
1926 // end a sentence. So we'll run through our results
1927 // and strip the period from any tags which end with one.
1928 // Returns array of tags found, or empty array.
1929
1930
1931 if(! function_exists('get_tags')) {
1932 function get_tags($s) {
1933         $ret = array();
1934
1935         // ignore anything in a code block
1936
1937         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
1938
1939         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1940                 foreach($match[1] as $match) {
1941                         if(strstr($match,"]")) {
1942                                 // we might be inside a bbcode color tag - leave it alone
1943                                 continue;
1944                         }
1945                         if(substr($match,-1,1) === '.')
1946                                 $ret[] = substr($match,0,-1);
1947                         else
1948                                 $ret[] = $match;
1949                 }
1950         }
1951
1952         return $ret;
1953 }}
1954
1955
1956 // quick and dirty quoted_printable encoding
1957
1958 if(! function_exists('qp')) {
1959 function qp($s) {
1960 return str_replace ("%","=",rawurlencode($s));
1961 }} 
1962
1963
1964
1965 if(! function_exists('get_mentions')) {
1966 function get_mentions($item) {
1967         $o = '';
1968         if(! strlen($item['tag']))
1969                 return $o;
1970
1971         $arr = explode(',',$item['tag']);
1972         foreach($arr as $x) {
1973                 $matches = null;
1974                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
1975                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1976                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
1977                 }
1978         }
1979         return $o;
1980 }}
1981
1982 if(! function_exists('contact_block')) {
1983 function contact_block() {
1984         $o = '';
1985         $a = get_app();
1986
1987         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
1988         if(! $shown)
1989                 $shown = 24;
1990
1991         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1992                 return $o;
1993         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1994                         intval($a->profile['uid'])
1995         );
1996         if(count($r)) {
1997                 $total = intval($r[0]['total']);
1998         }
1999         if(! $total) {
2000                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
2001                 return $o;
2002         }
2003         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
2004                         intval($a->profile['uid']),
2005                         intval($shown)
2006         );
2007         if(count($r)) {
2008                 $o .= '<h4 class="contact-h4">' .  sprintf( tt('%d Contact','%d Contacts', $total),$total) . '</h4><div id="contact-block">';
2009                 foreach($r as $rr) {
2010                         $o .= micropro($rr,true,'mpfriend');
2011                 }
2012                 $o .= '</div><div id="contact-block-end"></div>';
2013                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
2014                 
2015         }
2016
2017         $arr = array('contacts' => $r, 'output' => $o);
2018
2019         call_hooks('contact_block_end', $arr);
2020         return $o;
2021
2022 }}
2023
2024 if(! function_exists('micropro')) {
2025 function micropro($contact, $redirect = false, $class = '') {
2026
2027         if($class)
2028                 $class = ' ' . $class;
2029
2030         $url = $contact['url'];
2031         $sparkle = '';
2032
2033         if($redirect) {
2034                 $a = get_app();
2035                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
2036                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
2037                         $url = $redirect_url;
2038                         $sparkle = ' sparkle';
2039                 }
2040         }
2041         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
2042         if($click)
2043                 $url = '';
2044         return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle 
2045                 . (($click) ? ' fakelink' : '') . '" '
2046                 . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="' 
2047                 . $contact['micro'] . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
2048                 . '" /></a></div>' . "\r\n";
2049 }}
2050
2051
2052
2053 if(! function_exists('search')) {
2054 function search($s) {
2055         $a = get_app();
2056         $o  = '<div id="search-box">';
2057         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
2058         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2059         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2060         $o .= '</form></div>';
2061         return $o;
2062 }}
2063
2064 if(! function_exists('valid_email')) {
2065 function valid_email($x){
2066         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2067                 return true;
2068         return false;
2069 }}
2070
2071
2072 if(! function_exists('gravatar_img')) {
2073 function gravatar_img($email) {
2074         $size = 175;
2075         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2076         $rating = 'pg';
2077         $hash = md5(trim(strtolower($email)));
2078         
2079         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2080                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2081
2082         logger('gravatar: ' . $email . ' ' . $url);
2083         return $url;
2084 }}
2085
2086 if(! function_exists('aes_decrypt')) {
2087 function aes_decrypt($val,$ky)
2088 {
2089     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2090     for($a=0;$a<strlen($ky);$a++)
2091       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2092     $mode = MCRYPT_MODE_ECB;
2093     $enc = MCRYPT_RIJNDAEL_128;
2094     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2095     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));
2096 }}
2097
2098
2099 if(! function_exists('aes_encrypt')) {
2100 function aes_encrypt($val,$ky)
2101 {
2102     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2103     for($a=0;$a<strlen($ky);$a++)
2104       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2105     $mode=MCRYPT_MODE_ECB;
2106     $enc=MCRYPT_RIJNDAEL_128;
2107     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2108     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2109 }} 
2110
2111
2112 /**
2113  *
2114  * Function: linkify
2115  *
2116  * Replace naked text hyperlink with HTML formatted hyperlink
2117  *
2118  */
2119
2120 if(! function_exists('linkify')) {
2121 function linkify($s) {
2122         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2123         return($s);
2124 }}
2125
2126
2127 /**
2128  * 
2129  * Function: smilies
2130  *
2131  * Description:
2132  * Replaces text emoticons with graphical images
2133  *
2134  * @Parameter: string $s
2135  *
2136  * Returns string
2137  */
2138
2139 if(! function_exists('smilies')) {
2140 function smilies($s) {
2141         $a = get_app();
2142
2143         return str_replace(
2144         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ';-)', ':-(', ':(', ':-P', ':P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2145         array(
2146                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2147                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2148                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2149                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2150                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2151                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2152                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2153                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2154                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":P" />',
2155                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2156                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2157                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2158                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2159                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2160                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2161         ), $s);
2162 }}
2163
2164
2165 /**
2166  *
2167  * Function : profile_load
2168  * @parameter App    $a
2169  * @parameter string $nickname
2170  * @parameter int    $profile
2171  *
2172  * Summary: Loads a profile into the page sidebar. 
2173  * The function requires a writeable copy of the main App structure, and the nickname
2174  * of a registered local account.
2175  *
2176  * If the viewer is an authenticated remote viewer, the profile displayed is the
2177  * one that has been configured for his/her viewing in the Contact manager.
2178  * Passing a non-zero profile ID can also allow a preview of a selected profile
2179  * by the owner.
2180  *
2181  * Profile information is placed in the App structure for later retrieval.
2182  * Honours the owner's chosen theme for display. 
2183  *
2184  */
2185
2186 if(! function_exists('profile_load')) {
2187 function profile_load(&$a, $nickname, $profile = 0) {
2188         if(remote_user()) {
2189                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2190                         intval($_SESSION['visitor_id']));
2191                 if(count($r))
2192                         $profile = $r[0]['profile-id'];
2193         } 
2194
2195         $r = null;
2196
2197         if($profile) {
2198                 $profile_int = intval($profile);
2199                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2200                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2201                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2202                         dbesc($nickname),
2203                         intval($profile_int)
2204                 );
2205         }
2206         if(! count($r)) {       
2207                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2208                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2209                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2210                         dbesc($nickname)
2211                 );
2212         }
2213
2214         if(($r === false) || (! count($r))) {
2215                 notice( t('No profile') . EOL );
2216                 $a->error = 404;
2217                 return;
2218         }
2219
2220         $a->profile = $r[0];
2221
2222
2223         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2224         $_SESSION['theme'] = $a->profile['theme'];
2225
2226         if(! (x($a->page,'aside')))
2227                 $a->page['aside'] = '';
2228
2229         $a->page['aside'] .= profile_sidebar($a->profile);
2230         $a->page['aside'] .= contact_block();
2231
2232         return;
2233 }}
2234
2235
2236 /**
2237  *
2238  * Function: profile_sidebar
2239  *
2240  * Formats a profile for display in the sidebar.
2241  * It is very difficult to templatise the HTML completely
2242  * because of all the conditional logic.
2243  *
2244  * @parameter: array $profile
2245  *
2246  * Returns HTML string stuitable for sidebar inclusion
2247  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2248  *
2249  */
2250
2251
2252 if(! function_exists('profile_sidebar')) {
2253 function profile_sidebar($profile) {
2254
2255         $o = '';
2256         $location = '';
2257         $address = false;
2258
2259         if((! is_array($profile)) && (! count($profile)))
2260                 return $o;
2261
2262         call_hooks('profile_sidebar_enter', $profile);
2263
2264         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2265
2266         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2267
2268         $tabs = '';
2269
2270         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2271
2272         // don't show connect link to yourself
2273         
2274         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2275
2276         // don't show connect link to authenticated visitors either
2277
2278         if((remote_user()) && ($_SESSION['visitor_visiting'] == $profile['uid']))
2279                 $connect = ''; 
2280
2281         if((x($profile,'address') == 1) 
2282                 || (x($profile,'locality') == 1) 
2283                 || (x($profile,'region') == 1) 
2284                 || (x($profile,'postal-code') == 1) 
2285                 || (x($profile,'country-name') == 1))
2286                 $address = true;
2287
2288         if($address) {
2289                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2290                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2291                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2292                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2293                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2294                         . '<span class="region">' . $profile['region'] . '</span>'
2295                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2296                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2297                 $location .= '</div></div><div class="profile-clear"></div>';
2298
2299         }
2300
2301         $gender = ((x($profile,'gender') == 1) ? '<div class="mf"><span class="gender-label">' . t('Gender:') . '</span> <span class="x-gender">' . $profile['gender'] . '</span></div><div class="profile-clear"></div>' : '');
2302
2303         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2304
2305         $marital = ((x($profile,'marital') == 1) ? '<div class="marital"><span class="marital-label"><span class="heart">&hearts;</span> ' . t('Status:') . ' </span><span class="marital-text">' . $profile['marital'] . '</span></div><div class="profile-clear"></div>' : '');
2306
2307         $homepage = ((x($profile,'homepage') == 1) ? '<div class="homepage"><span class="homepage-label">' . t('Homepage:') . ' </span><span class="homepage-url">' . linkify($profile['homepage']) . '</span></div><div class="profile-clear"></div>' : '');
2308
2309         $tpl = load_view_file('view/profile_vcard.tpl');
2310
2311         $o .= replace_macros($tpl, array(
2312                 '$fullname' => $fullname,
2313                 '$pdesc'    => $pdesc,
2314                 '$tabs'     => $tabs,
2315                 '$photo'    => $photo,
2316                 '$connect'  => $connect,                
2317                 '$location' => $location,
2318                 '$gender'   => $gender,
2319                 '$pubkey'   => $pubkey,
2320                 '$marital'  => $marital,
2321                 '$homepage' => $homepage
2322         ));
2323
2324
2325         $arr = array('profile' => $profile, 'entry' => $o);
2326
2327         call_hooks('profile_sidebar', $arr);
2328
2329         return $o;
2330 }}
2331
2332
2333 if(! function_exists('register_hook')) {
2334 function register_hook($hook,$file,$function) {
2335
2336         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2337                 dbesc($hook),
2338                 dbesc($file),
2339                 dbesc($function)
2340         );
2341         if(count($r))
2342                 return true;
2343
2344         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2345                 dbesc($hook),
2346                 dbesc($file),
2347                 dbesc($function)
2348         );
2349         return $r;
2350 }}
2351
2352 if(! function_exists('unregister_hook')) {
2353 function unregister_hook($hook,$file,$function) {
2354
2355         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2356                 dbesc($hook),
2357                 dbesc($file),
2358                 dbesc($function)
2359         );
2360         return $r;
2361 }}
2362
2363
2364 if(! function_exists('load_hooks')) {
2365 function load_hooks() {
2366         $a = get_app();
2367         $a->hooks = array();
2368         $r = q("SELECT * FROM `hook` WHERE 1");
2369         if(count($r)) {
2370                 foreach($r as $rr) {
2371                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2372                 }
2373         }
2374 }}
2375
2376
2377 if(! function_exists('call_hooks')) {
2378 function call_hooks($name, &$data = null) {
2379         $a = get_app();
2380
2381         if(count($a->hooks)) {
2382                 foreach($a->hooks as $hook) {
2383                         if($hook[HOOK_HOOK] === $name) {
2384                                 @include_once($hook[HOOK_FILE]);
2385                                 if(function_exists($hook[HOOK_FUNCTION])) {
2386                                         $func = $hook[HOOK_FUNCTION];
2387                                         $func($a,$data);
2388                                 }
2389                         }
2390                 }
2391         }
2392 }}
2393
2394
2395 if(! function_exists('day_translate')) {
2396 function day_translate($s) {
2397         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2398                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2399                 $s);
2400
2401         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2402                 array( t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')),
2403                 $ret);
2404
2405         return $ret;
2406 }}
2407
2408 if(! function_exists('get_birthdays')) {
2409 function get_birthdays() {
2410
2411         $a = get_app();
2412         $o = '';
2413
2414         if(! local_user())
2415                 return $o;
2416
2417         $bd_format = t('g A l F d') ; // 8 AM Friday January 18
2418
2419         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2420                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2421                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2422                 ORDER BY `start` DESC ",
2423                 intval(local_user()),
2424                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2425                 dbesc(datetime_convert('UTC','UTC','now'))
2426         );
2427
2428         if($r && count($r)) {
2429                 $total = 0;
2430                 foreach($r as $rr)
2431                         if(strlen($rr['name']))
2432                                 $total ++;
2433
2434                 $o .= '<div id="birthday-notice" class="birthday-notice fakelink" onclick=openClose(\'birthday-wrapper\'); >' . t('Birthday Reminders') . ' ' . '(' . $total . ')' . '</div>'; 
2435                 $o .= '<div id="birthday-wrapper" style="display: none;" ><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2436                 $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2437                 $o .= '<div id="birthday-title-end"></div>';
2438
2439                 foreach($r as $rr) {
2440                         if(! strlen($rr['name']))
2441                                 continue;
2442                         $now = strtotime('now');
2443                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2444
2445                         $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2446                         . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2447                         . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2448                         . '</div>' ;
2449                 }
2450
2451                 $o .= '</div></div>';
2452         }
2453
2454   return $o;
2455
2456 }}
2457
2458
2459 if(! function_exists('normalise_link')) {
2460 function normalise_link($url) {
2461         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
2462         return(rtrim($ret,'/'));
2463 }}
2464
2465 /**
2466  *
2467  * Compare two URLs to see if they are the same, but ignore
2468  * slight but hopefully insignificant differences such as if one 
2469  * is https and the other isn't, or if one is www.something and 
2470  * the other isn't - and also ignore case differences.
2471  *
2472  * Return true if the URLs match, otherwise false.
2473  *
2474  */
2475
2476 if(! function_exists('link_compare')) {
2477 function link_compare($a,$b) {
2478         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
2479                 return true;
2480         return false;
2481 }}
2482
2483
2484 if(! function_exists('prepare_body')) {
2485 function prepare_body($item) {
2486         return prepare_text($item['body']);
2487 }}
2488
2489 if(! function_exists('prepare_text')) {
2490 function prepare_text($text) {
2491
2492         require_once('include/bbcode.php');
2493
2494         $s = smilies(bbcode($text));
2495
2496         return $s;
2497 }}
2498
2499 /**
2500  * 
2501  * Wrap calls to proc_close(proc_open()) and call hook
2502  * so plugins can take part in process :)
2503  * 
2504  * args:
2505  * $cmd program to run
2506  *  next args are passed as $cmd command line
2507  * 
2508  * e.g.: proc_run("ls","-la","/tmp");
2509  * 
2510  * $cmd and string args are surrounded with ""
2511  */
2512
2513 if(! function_exists('proc_run')) {
2514 function proc_run($cmd){
2515
2516         $a = get_app();
2517
2518         $args = func_get_args();
2519         call_hooks("proc_run", $args);
2520
2521         if(count($args) && $args[0] === 'php')
2522         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2523         
2524         foreach ($args as $arg){
2525                 $arg = escapeshellarg($arg);
2526         }
2527         $cmdline = implode($args," ");
2528         proc_close(proc_open($cmdline." &",array(),$foo));
2529 }}
2530
2531 if(! function_exists('current_theme')) {
2532 function current_theme(){
2533         $app_base_themes = array('duepuntozero', 'loozah');
2534         
2535         $a = get_app();
2536         
2537         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2538         $theme_name = ((is_array($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2539         
2540         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2541                 return($theme_name);
2542         
2543         foreach($app_base_themes as $t) {
2544                 if(file_exists('view/theme/' . $t . '/style.css'))
2545                         return($t);
2546         }
2547         
2548         $fallback = glob('view/theme/*/style.css');
2549         if(count($fallback))
2550                 return (str_replace('view/theme/','', str_replace("/style.css","",$fallback[0])));
2551
2552 }}
2553
2554 /*
2555 * Return full URL to theme which is currently in effect.
2556 * Provide a sane default if nothing is chosen or the specified theme does not exist.
2557 */
2558 if(! function_exists('current_theme_url')) {
2559 function current_theme_url() {
2560         global $a;
2561         $t = current_theme();
2562         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css');
2563 }}
2564
2565 if(! function_exists('feed_birthday')) {
2566 function feed_birthday($uid,$tz) {
2567
2568         /**
2569          *
2570          * Determine the next birthday, but only if the birthday is published
2571          * in the default profile. We _could_ also look for a private profile that the
2572          * recipient can see, but somebody could get mad at us if they start getting
2573          * public birthday greetings when they haven't made this info public. 
2574          *
2575          * Assuming we are able to publish this info, we are then going to convert
2576          * the start time from the owner's timezone to UTC. 
2577          *
2578          * This will potentially solve the problem found with some social networks
2579          * where birthdays are converted to the viewer's timezone and salutations from
2580          * elsewhere in the world show up on the wrong day. We will convert it to the
2581          * viewer's timezone also, but first we are going to convert it from the birthday
2582          * person's timezone to GMT - so the viewer may find the birthday starting at
2583          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2584          *
2585          */
2586
2587         $birthday = '';
2588
2589         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2590                 intval($uid)
2591         );
2592
2593         if($p && count($p)) {
2594                 $tmp_dob = substr($p[0]['dob'],5);
2595                 if(intval($tmp_dob)) {
2596                         $y = datetime_convert($tz,$tz,'now','Y');
2597                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2598                         $t_dob = strtotime($bd);
2599                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2600                         if($t_dob < $now)
2601                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2602                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2603                 }
2604         }
2605
2606         return $birthday;
2607 }}
2608
2609 /**
2610  * return atom link elements for all of our hubs
2611  */
2612
2613 if(! function_exists('feed_hublinks')) {
2614 function feed_hublinks() {
2615
2616         $hub = get_config('system','huburl');
2617
2618         $hubxml = '';
2619         if(strlen($hub)) {
2620                 $hubs = explode(',', $hub);
2621                 if(count($hubs)) {
2622                         foreach($hubs as $h) {
2623                                 $h = trim($h);
2624                                 if(! strlen($h))
2625                                         continue;
2626                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2627                         }
2628                 }
2629         }
2630         return $hubxml;
2631 }}
2632
2633 /* return atom link elements for salmon endpoints */
2634
2635 if(! function_exists('feed_salmonlinks')) {
2636 function feed_salmonlinks($nick) {
2637
2638         $a = get_app();
2639
2640         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2641
2642         // old style links that status.net still needed as of 12/2010 
2643
2644         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2645         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2646         return $salmon;
2647 }}
2648
2649 if(! function_exists('get_plink')) {
2650 function get_plink($item) {
2651         $a = get_app(); 
2652         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2653                         . $item['plink'] . '" title="' . t('link to source') . '" target="external-link" class="icon remote-link"></a></div>' : '');
2654         return $plink;
2655 }}
2656
2657 if(! function_exists('unamp')) {
2658 function unamp($s) {
2659         return str_replace('&amp;', '&', $s);
2660 }}
2661
2662
2663 if(! function_exists('lang_selector')) {
2664 function lang_selector() {
2665         global $lang;
2666         $o .= '<div id="language-selector" style="display: none;" >';
2667         $o .= '<form action="" method="post" ><select name="system_language" onchange="this.form.submit();" >';
2668         $langs = glob('view/*/strings.php');
2669         if(is_array($langs) && count($langs)) {
2670                 if(! in_array('view/en/strings.php',$langs))
2671                         $langs[] = 'view/en/';
2672                 foreach($langs as $l) {
2673                         $ll = substr($l,5);
2674                         $ll = substr($ll,0,strrpos($ll,'/'));
2675                         $selected = (($ll === $lang) ? ' selected="selected" ' : '');
2676                         $o .= '<option value="' . $ll . '"' . $selected . '>' . $ll . '</option>';
2677                 }
2678         }
2679         $o .= '</select></form></div>';
2680         return $o;
2681 }}
2682
2683
2684 if(! function_exists('parse_xml_string')) {
2685 function parse_xml_string($s) {
2686         if(! strstr($s,'<?xml'))
2687                 return false;
2688         $s2 = substr($s,strpos($s,'<?xml'));
2689         libxml_use_internal_errors(true);
2690         $x = @simplexml_load_string($s2);
2691         if(count(libxml_get_errors()))
2692                 foreach(libxml_get_errors() as $err)
2693                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
2694         libxml_clear_errors();
2695         return $x;
2696 }}