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