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