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