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