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