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