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