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