]> git.mxchange.org Git - friendica.git/blob - boot.php
fix banding on logo, xhtml compliant list and basic table support
[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.1018' );
8 define ( 'DFRN_PROTOCOL_VERSION',  '2.21'    );
9 define ( 'DB_UPDATE_VERSION',      1065      );
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('template (https): ' . print_r($links,true));
1689         if(! count($links)) {
1690                 $links = fetch_xrd_links($url2);
1691                 logger('template (http): ' . print_r($links,true));
1692         }
1693         if(count($links)) {
1694                 foreach($links as $link)
1695                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1696                                 $tpl = $link['@attributes']['template'];
1697         }
1698         if(! strpos($tpl,'{uri}'))
1699                 $tpl = '';
1700         return $tpl;
1701 }}
1702
1703 // Given a URL, retrieve the page as an XRD document.
1704 // Return an array of links.
1705 // on error/failure return empty array.
1706
1707 if(! function_exists('fetch_xrd_links')) {
1708 function fetch_xrd_links($url) {
1709
1710
1711         $xml = fetch_url($url);
1712         if (! $xml)
1713                 return array();
1714
1715         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1716         $h = parse_xml_string($xml);
1717         if(! $h)
1718                 return array();
1719
1720         $arr = convert_xml_element_to_array($h);
1721
1722         $links = array();
1723
1724         if(isset($arr['xrd']['link'])) {
1725                 $link = $arr['xrd']['link'];
1726                 if(! isset($link[0]))
1727                         $links = array($link);
1728                 else
1729                         $links = $link;
1730         }
1731         if(isset($arr['xrd']['alias'])) {
1732                 $alias = $arr['xrd']['alias'];
1733                 if(! isset($alias[0]))
1734                         $aliases = array($alias);
1735                 else
1736                         $aliases = $alias;
1737                 foreach($aliases as $alias) {
1738                         $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
1739                 }
1740         }
1741
1742         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
1743
1744         return $links;
1745
1746 }}
1747
1748 // Convert an ACL array to a storable string
1749
1750 if(! function_exists('perms2str')) {
1751 function perms2str($p) {
1752         $ret = '';
1753         $tmp = $p;
1754         if(is_array($tmp)) {
1755                 array_walk($tmp,'sanitise_acl');
1756                 $ret = implode('',$tmp);
1757         }
1758         return $ret;
1759 }}
1760
1761 // generate a guaranteed unique (for this domain) item ID for ATOM
1762 // safe from birthday paradox
1763
1764 if(! function_exists('item_new_uri')) {
1765 function item_new_uri($hostname,$uid) {
1766
1767         do {
1768                 $dups = false;
1769                 $hash = random_string();
1770
1771                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1772
1773                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1774                         dbesc($uri));
1775                 if(count($r))
1776                         $dups = true;
1777         } while($dups == true);
1778         return $uri;
1779 }}
1780
1781 // Generate a guaranteed unique photo ID.
1782 // safe from birthday paradox
1783
1784 if(! function_exists('photo_new_resource')) {
1785 function photo_new_resource() {
1786
1787         do {
1788                 $found = false;
1789                 $resource = hash('md5',uniqid(mt_rand(),true));
1790                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1791                         dbesc($resource)
1792                 );
1793                 if(count($r))
1794                         $found = true;
1795         } while($found == true);
1796         return $resource;
1797 }}
1798
1799
1800 // Take a URL from the wild, prepend http:// if necessary
1801 // and check DNS to see if it's real
1802 // return true if it's OK, false if something is wrong with it
1803
1804 if(! function_exists('validate_url')) {
1805 function validate_url(&$url) {
1806         if(substr($url,0,4) != 'http')
1807                 $url = 'http://' . $url;
1808         $h = @parse_url($url);
1809
1810         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1811                 return true;
1812         }
1813         return false;
1814 }}
1815
1816 // checks that email is an actual resolvable internet address
1817
1818 if(! function_exists('validate_email')) {
1819 function validate_email($addr) {
1820
1821         if(! strpos($addr,'@'))
1822                 return false;
1823         $h = substr($addr,strpos($addr,'@') + 1);
1824
1825         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1826                 return true;
1827         }
1828         return false;
1829 }}
1830
1831 // Check $url against our list of allowed sites,
1832 // wildcards allowed. If allowed_sites is unset return true;
1833 // If url is allowed, return true.
1834 // otherwise, return false
1835
1836 if(! function_exists('allowed_url')) {
1837 function allowed_url($url) {
1838
1839         $h = @parse_url($url);
1840
1841         if(! $h) {
1842                 return false;
1843         }
1844
1845         $str_allowed = get_config('system','allowed_sites');
1846         if(! $str_allowed)
1847                 return true;
1848
1849         $found = false;
1850
1851         $host = strtolower($h['host']);
1852
1853         // always allow our own site
1854
1855         if($host == strtolower($_SERVER['SERVER_NAME']))
1856                 return true;
1857
1858         $fnmatch = function_exists('fnmatch');
1859         $allowed = explode(',',$str_allowed);
1860
1861         if(count($allowed)) {
1862                 foreach($allowed as $a) {
1863                         $pat = strtolower(trim($a));
1864                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1865                                 $found = true; 
1866                                 break;
1867                         }
1868                 }
1869         }
1870         return $found;
1871 }}
1872
1873 // check if email address is allowed to register here.
1874 // Compare against our list (wildcards allowed).
1875 // Returns false if not allowed, true if allowed or if
1876 // allowed list is not configured.
1877
1878 if(! function_exists('allowed_email')) {
1879 function allowed_email($email) {
1880
1881
1882         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1883         if(! $domain)
1884                 return false;
1885
1886         $str_allowed = get_config('system','allowed_email');
1887         if(! $str_allowed)
1888                 return true;
1889
1890         $found = false;
1891
1892         $fnmatch = function_exists('fnmatch');
1893         $allowed = explode(',',$str_allowed);
1894
1895         if(count($allowed)) {
1896                 foreach($allowed as $a) {
1897                         $pat = strtolower(trim($a));
1898                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
1899                                 $found = true; 
1900                                 break;
1901                         }
1902                 }
1903         }
1904         return $found;
1905 }}
1906
1907
1908
1909 // wrapper to load a view template, checking for alternate
1910 // languages before falling back to the default
1911
1912 // obsolete, deprecated.
1913
1914 if(! function_exists('load_view_file')) {
1915 function load_view_file($s) {
1916         global $lang, $a;
1917         if(! isset($lang))
1918                 $lang = 'en';
1919         $b = basename($s);
1920         $d = dirname($s);
1921         if(file_exists("$d/$lang/$b"))
1922                 return file_get_contents("$d/$lang/$b");
1923         
1924         $theme = current_theme();
1925         
1926         if(file_exists("$d/theme/$theme/$b"))
1927                 return file_get_contents("$d/theme/$theme/$b");
1928                         
1929         return file_get_contents($s);
1930 }}
1931
1932 if(! function_exists('get_intltext_template')) {
1933 function get_intltext_template($s) {
1934         global $lang;
1935
1936         if(! isset($lang))
1937                 $lang = 'en';
1938
1939         if(file_exists("view/$lang/$s"))
1940                 return file_get_contents("view/$lang/$s");
1941         elseif(file_exists("view/en/$s"))
1942                 return file_get_contents("view/en/$s");
1943         else
1944                 return file_get_contents("view/$s");
1945 }}
1946
1947 if(! function_exists('get_markup_template')) {
1948 function get_markup_template($s) {
1949
1950         $theme = current_theme();
1951         
1952         if(file_exists("view/theme/$theme/$s"))
1953                 return file_get_contents("view/theme/$theme/$s");
1954         else
1955                 return file_get_contents("view/$s");
1956
1957 }}
1958
1959
1960
1961
1962
1963 // for html,xml parsing - let's say you've got
1964 // an attribute foobar="class1 class2 class3"
1965 // and you want to find out if it contains 'class3'.
1966 // you can't use a normal sub string search because you
1967 // might match 'notclass3' and a regex to do the job is 
1968 // possible but a bit complicated. 
1969 // pass the attribute string as $attr and the attribute you 
1970 // are looking for as $s - returns true if found, otherwise false
1971
1972 if(! function_exists('attribute_contains')) {
1973 function attribute_contains($attr,$s) {
1974         $a = explode(' ', $attr);
1975         if(count($a) && in_array($s,$a))
1976                 return true;
1977         return false;
1978 }}
1979
1980 if(! function_exists('logger')) {
1981 function logger($msg,$level = 0) {
1982         $debugging = get_config('system','debugging');
1983         $loglevel  = intval(get_config('system','loglevel'));
1984         $logfile   = get_config('system','logfile');
1985
1986         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1987                 return;
1988         
1989         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1990         return;
1991 }}
1992
1993
1994 if(! function_exists('activity_match')) {
1995 function activity_match($haystack,$needle) {
1996         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1997                 return true;
1998         return false;
1999 }}
2000
2001
2002 // Pull out all #hashtags and @person tags from $s;
2003 // We also get @person@domain.com - which would make 
2004 // the regex quite complicated as tags can also
2005 // end a sentence. So we'll run through our results
2006 // and strip the period from any tags which end with one.
2007 // Returns array of tags found, or empty array.
2008
2009
2010 if(! function_exists('get_tags')) {
2011 function get_tags($s) {
2012         $ret = array();
2013
2014         // ignore anything in a code block
2015
2016         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
2017
2018         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
2019                 foreach($match[1] as $mtch) {
2020                         if(strstr($mtch,"]")) {
2021                                 // we might be inside a bbcode color tag - leave it alone
2022                                 continue;
2023                         }
2024                         if(substr($mtch,-1,1) === '.')
2025                                 $ret[] = substr($mtch,0,-1);
2026                         else
2027                                 $ret[] = $mtch;
2028                 }
2029         }
2030
2031         return $ret;
2032 }}
2033
2034
2035 // quick and dirty quoted_printable encoding
2036
2037 if(! function_exists('qp')) {
2038 function qp($s) {
2039 return str_replace ("%","=",rawurlencode($s));
2040 }} 
2041
2042
2043
2044 if(! function_exists('get_mentions')) {
2045 function get_mentions($item) {
2046         $o = '';
2047         if(! strlen($item['tag']))
2048                 return $o;
2049
2050         $arr = explode(',',$item['tag']);
2051         foreach($arr as $x) {
2052                 $matches = null;
2053                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
2054                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
2055                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
2056                 }
2057         }
2058         return $o;
2059 }}
2060
2061 if(! function_exists('contact_block')) {
2062 function contact_block() {
2063         $o = '';
2064         $a = get_app();
2065
2066         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
2067         if(! $shown)
2068                 $shown = 24;
2069
2070         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
2071                 return $o;
2072         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
2073                         intval($a->profile['uid'])
2074         );
2075         if(count($r)) {
2076                 $total = intval($r[0]['total']);
2077         }
2078         if(! $total) {
2079                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
2080                 return $o;
2081         }
2082         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
2083                         intval($a->profile['uid']),
2084                         intval($shown)
2085         );
2086         if(count($r)) {
2087                 $o .= '<h4 class="contact-h4">' .  sprintf( tt('%d Contact','%d Contacts', $total),$total) . '</h4><div id="contact-block">';
2088                 foreach($r as $rr) {
2089                         $o .= micropro($rr,true,'mpfriend');
2090                 }
2091                 $o .= '</div><div id="contact-block-end"></div>';
2092                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
2093                 
2094         }
2095
2096         $arr = array('contacts' => $r, 'output' => $o);
2097
2098         call_hooks('contact_block_end', $arr);
2099         return $o;
2100
2101 }}
2102
2103 if(! function_exists('micropro')) {
2104 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
2105
2106         if($class)
2107                 $class = ' ' . $class;
2108
2109         $url = $contact['url'];
2110         $sparkle = '';
2111
2112         if($redirect) {
2113                 $a = get_app();
2114                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
2115                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
2116                         $url = $redirect_url;
2117                         $sparkle = ' sparkle';
2118                 }
2119         }
2120         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
2121         if($click)
2122                 $url = '';
2123         if($textmode) {
2124                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle 
2125                         . (($click) ? ' fakelink' : '') . '" '
2126                         . (($url) ? ' href="' . $url . '"' : '') . $click
2127                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
2128                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
2129         }
2130         else {
2131                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle 
2132                         . (($click) ? ' fakelink' : '') . '" '
2133                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="' 
2134                         . $contact['micro'] . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
2135                         . '" /></a></div>' . "\r\n";
2136         }
2137 }}
2138
2139
2140
2141 if(! function_exists('search')) {
2142 function search($s,$id='search-box',$url='/search') {
2143         $a = get_app();
2144         $o  = '<div id="' . $id . '">';
2145         $o .= '<form action="' . $a->get_baseurl() . $url . '" method="get" >';
2146         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2147         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2148         $o .= '</form></div>';
2149         return $o;
2150 }}
2151
2152 if(! function_exists('valid_email')) {
2153 function valid_email($x){
2154         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2155                 return true;
2156         return false;
2157 }}
2158
2159
2160 if(! function_exists('gravatar_img')) {
2161 function gravatar_img($email) {
2162         $size = 175;
2163         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2164         $rating = 'pg';
2165         $hash = md5(trim(strtolower($email)));
2166         
2167         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2168                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2169
2170         logger('gravatar: ' . $email . ' ' . $url);
2171         return $url;
2172 }}
2173
2174 if(! function_exists('aes_decrypt')) {
2175 function aes_decrypt($val,$ky)
2176 {
2177     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2178     for($a=0;$a<strlen($ky);$a++)
2179       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2180     $mode = MCRYPT_MODE_ECB;
2181     $enc = MCRYPT_RIJNDAEL_128;
2182     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2183     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));
2184 }}
2185
2186
2187 if(! function_exists('aes_encrypt')) {
2188 function aes_encrypt($val,$ky)
2189 {
2190     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2191     for($a=0;$a<strlen($ky);$a++)
2192       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2193     $mode=MCRYPT_MODE_ECB;
2194     $enc=MCRYPT_RIJNDAEL_128;
2195     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2196     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2197 }} 
2198
2199
2200 /**
2201  *
2202  * Function: linkify
2203  *
2204  * Replace naked text hyperlink with HTML formatted hyperlink
2205  *
2206  */
2207
2208 if(! function_exists('linkify')) {
2209 function linkify($s) {
2210         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2211         return($s);
2212 }}
2213
2214
2215 /**
2216  * 
2217  * Function: smilies
2218  *
2219  * Description:
2220  * Replaces text emoticons with graphical images
2221  *
2222  * @Parameter: string $s
2223  *
2224  * Returns string
2225  */
2226
2227 if(! function_exists('smilies')) {
2228 function smilies($s) {
2229         $a = get_app();
2230
2231         return str_replace(
2232         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ':)', ';-)', ':-(', ':(', ':-P', ':P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2233         array(
2234                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2235                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2236                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2237                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2238                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":)" />',
2239                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2240                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2241                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2242                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2243                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":P" />',
2244                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2245                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2246                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2247                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2248                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2249                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2250         ), $s);
2251 }}
2252
2253
2254 /**
2255  *
2256  * Function : profile_load
2257  * @parameter App    $a
2258  * @parameter string $nickname
2259  * @parameter int    $profile
2260  *
2261  * Summary: Loads a profile into the page sidebar. 
2262  * The function requires a writeable copy of the main App structure, and the nickname
2263  * of a registered local account.
2264  *
2265  * If the viewer is an authenticated remote viewer, the profile displayed is the
2266  * one that has been configured for his/her viewing in the Contact manager.
2267  * Passing a non-zero profile ID can also allow a preview of a selected profile
2268  * by the owner.
2269  *
2270  * Profile information is placed in the App structure for later retrieval.
2271  * Honours the owner's chosen theme for display. 
2272  *
2273  */
2274
2275 if(! function_exists('profile_load')) {
2276 function profile_load(&$a, $nickname, $profile = 0) {
2277         if(remote_user()) {
2278                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2279                         intval($_SESSION['visitor_id']));
2280                 if(count($r))
2281                         $profile = $r[0]['profile-id'];
2282         } 
2283
2284         $r = null;
2285
2286         if($profile) {
2287                 $profile_int = intval($profile);
2288                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2289                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2290                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2291                         dbesc($nickname),
2292                         intval($profile_int)
2293                 );
2294         }
2295         if(! count($r)) {       
2296                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2297                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2298                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2299                         dbesc($nickname)
2300                 );
2301         }
2302
2303         if(($r === false) || (! count($r))) {
2304                 notice( t('No profile') . EOL );
2305                 $a->error = 404;
2306                 return;
2307         }
2308
2309         $a->profile = $r[0];
2310
2311
2312         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2313         $_SESSION['theme'] = $a->profile['theme'];
2314
2315         if(! (x($a->page,'aside')))
2316                 $a->page['aside'] = '';
2317
2318         $a->page['aside'] .= profile_sidebar($a->profile);
2319         $a->page['aside'] .= contact_block();
2320
2321         return;
2322 }}
2323
2324
2325 /**
2326  *
2327  * Function: profile_sidebar
2328  *
2329  * Formats a profile for display in the sidebar.
2330  * It is very difficult to templatise the HTML completely
2331  * because of all the conditional logic.
2332  *
2333  * @parameter: array $profile
2334  *
2335  * Returns HTML string stuitable for sidebar inclusion
2336  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2337  *
2338  */
2339
2340
2341 if(! function_exists('profile_sidebar')) {
2342 function profile_sidebar($profile) {
2343
2344         $o = '';
2345         $location = '';
2346         $address = false;
2347
2348         if((! is_array($profile)) && (! count($profile)))
2349                 return $o;
2350
2351         call_hooks('profile_sidebar_enter', $profile);
2352
2353         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2354
2355         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2356
2357         $tabs = '';
2358
2359         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2360
2361         // don't show connect link to yourself
2362         
2363         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2364
2365         // don't show connect link to authenticated visitors either
2366
2367         if((remote_user()) && ($_SESSION['visitor_visiting'] == $profile['uid']))
2368                 $connect = ''; 
2369
2370         if((x($profile,'address') == 1) 
2371                 || (x($profile,'locality') == 1) 
2372                 || (x($profile,'region') == 1) 
2373                 || (x($profile,'postal-code') == 1) 
2374                 || (x($profile,'country-name') == 1))
2375                 $address = true;
2376
2377         if($address) {
2378                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2379                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2380                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2381                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2382                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2383                         . '<span class="region">' . $profile['region'] . '</span>'
2384                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2385                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2386                 $location .= '</div></div><div class="profile-clear"></div>';
2387
2388         }
2389
2390         $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>' : '');
2391
2392         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2393
2394         $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>' : '');
2395
2396         $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>' : '');
2397
2398         if($profile['hidewall'] && (! local_user()) && (! remote_user())) {
2399                 $location = $gender = $marital = $homepage = '';
2400         }
2401
2402         $tpl = get_markup_template('profile_vcard.tpl');
2403
2404         $o .= replace_macros($tpl, array(
2405                 '$fullname' => $fullname,
2406                 '$pdesc'    => $pdesc,
2407                 '$tabs'     => $tabs,
2408                 '$photo'    => $photo,
2409                 '$connect'  => $connect,                
2410                 '$location' => $location,
2411                 '$gender'   => $gender,
2412                 '$pubkey'   => $pubkey,
2413                 '$marital'  => $marital,
2414                 '$homepage' => $homepage
2415         ));
2416
2417
2418         $arr = array('profile' => $profile, 'entry' => $o);
2419
2420         call_hooks('profile_sidebar', $arr);
2421
2422         return $o;
2423 }}
2424
2425
2426 if(! function_exists('register_hook')) {
2427 function register_hook($hook,$file,$function) {
2428
2429         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2430                 dbesc($hook),
2431                 dbesc($file),
2432                 dbesc($function)
2433         );
2434         if(count($r))
2435                 return true;
2436
2437         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2438                 dbesc($hook),
2439                 dbesc($file),
2440                 dbesc($function)
2441         );
2442         return $r;
2443 }}
2444
2445 if(! function_exists('unregister_hook')) {
2446 function unregister_hook($hook,$file,$function) {
2447
2448         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2449                 dbesc($hook),
2450                 dbesc($file),
2451                 dbesc($function)
2452         );
2453         return $r;
2454 }}
2455
2456
2457 if(! function_exists('load_hooks')) {
2458 function load_hooks() {
2459         $a = get_app();
2460         $a->hooks = array();
2461         $r = q("SELECT * FROM `hook` WHERE 1");
2462         if(count($r)) {
2463                 foreach($r as $rr) {
2464                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2465                 }
2466         }
2467 }}
2468
2469
2470 if(! function_exists('call_hooks')) {
2471 function call_hooks($name, &$data = null) {
2472         $a = get_app();
2473
2474         if(count($a->hooks)) {
2475                 foreach($a->hooks as $hook) {
2476                         if($hook[HOOK_HOOK] === $name) {
2477                                 @include_once($hook[HOOK_FILE]);
2478                                 if(function_exists($hook[HOOK_FUNCTION])) {
2479                                         $func = $hook[HOOK_FUNCTION];
2480                                         $func($a,$data);
2481                                 }
2482                         }
2483                 }
2484         }
2485 }}
2486
2487
2488 if(! function_exists('day_translate')) {
2489 function day_translate($s) {
2490         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2491                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2492                 $s);
2493
2494         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2495                 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')),
2496                 $ret);
2497
2498         return $ret;
2499 }}
2500
2501 if(! function_exists('get_birthdays')) {
2502 function get_birthdays() {
2503
2504         $a = get_app();
2505         $o = '';
2506
2507         if(! local_user())
2508                 return $o;
2509
2510         $bd_format = t('g A l F d') ; // 8 AM Friday January 18
2511
2512         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2513                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2514                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2515                 ORDER BY `start` DESC ",
2516                 intval(local_user()),
2517                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2518                 dbesc(datetime_convert('UTC','UTC','now'))
2519         );
2520
2521         if($r && count($r)) {
2522                 $total = 0;
2523                 foreach($r as $rr)
2524                         if(strlen($rr['name']))
2525                                 $total ++;
2526
2527                 if($total) {
2528                         $o .= '<div id="birthday-notice" class="birthday-notice fakelink" onclick=openClose(\'birthday-wrapper\'); >' . t('Birthday Reminders') . ' ' . '(' . $total . ')' . '</div>'; 
2529                         $o .= '<div id="birthday-wrapper" style="display: none;" ><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2530                         $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2531                         $o .= '<div id="birthday-title-end"></div>';
2532
2533                         foreach($r as $rr) {
2534                                 if(! strlen($rr['name']))
2535                                         continue;
2536                                 $now = strtotime('now');
2537                                 $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2538         
2539                                 $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2540                                 . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2541                                 . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2542                                 . '</div>' ;
2543                         }
2544                         $o .= '</div></div>';
2545                 }
2546         }
2547         return $o;
2548 }}
2549
2550
2551 if(! function_exists('normalise_link')) {
2552 function normalise_link($url) {
2553         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
2554         return(rtrim($ret,'/'));
2555 }}
2556
2557 /**
2558  *
2559  * Compare two URLs to see if they are the same, but ignore
2560  * slight but hopefully insignificant differences such as if one 
2561  * is https and the other isn't, or if one is www.something and 
2562  * the other isn't - and also ignore case differences.
2563  *
2564  * Return true if the URLs match, otherwise false.
2565  *
2566  */
2567
2568 if(! function_exists('link_compare')) {
2569 function link_compare($a,$b) {
2570         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
2571                 return true;
2572         return false;
2573 }}
2574
2575
2576 if(! function_exists('prepare_body')) {
2577 function prepare_body($item,$attach = false) {
2578
2579         $s = prepare_text($item['body']);
2580         if(! $attach)
2581                 return $s;
2582
2583         $arr = explode(',',$item['attach']);
2584         if(count($arr)) {
2585                 $s .= '<div class="body-attach">';
2586                 foreach($arr as $r) {
2587                         $matches = false;
2588                         $icon = '';
2589                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
2590                         if($cnt) {
2591                                 $icontype = strtolower(substr($matches[3],0,strpos($matches[3],'/')));
2592                                 switch($icontype) {
2593                                         case 'video':
2594                                         case 'audio':
2595                                         case 'image':
2596                                         case 'text':
2597                                                 $icon = '<div class="attachtype type-' . $icontype . '"></div>';
2598                                                 break;
2599                                         default:
2600                                                 $icon = '<div class="attachtype type-unkn"></div>';
2601                                                 break;
2602                                 }
2603                                 $title = ((strlen(trim($matches[4]))) ? escape_tags(trim($matches[4])) : escape_tags($matches[1]));
2604                                 $title .= ' ' . $matches[2] . ' ' . t('bytes');
2605
2606                                 $s .= '<a href="' . strip_tags($matches[1]) . '" title="' . $title . '" class="attachlink" target="external-link" >' . $icon . '</a>';
2607                         }
2608                 }
2609                 $s .= '<div class="clear"></div></div>';
2610         }
2611         return $s;
2612 }}
2613
2614 if(! function_exists('prepare_text')) {
2615 function prepare_text($text) {
2616
2617         require_once('include/bbcode.php');
2618
2619         $s = smilies(bbcode($text));
2620
2621         return $s;
2622 }}
2623
2624 /**
2625  * 
2626  * Wrap calls to proc_close(proc_open()) and call hook
2627  * so plugins can take part in process :)
2628  * 
2629  * args:
2630  * $cmd program to run
2631  *  next args are passed as $cmd command line
2632  * 
2633  * e.g.: proc_run("ls","-la","/tmp");
2634  * 
2635  * $cmd and string args are surrounded with ""
2636  */
2637
2638 if(! function_exists('proc_run')) {
2639 function proc_run($cmd){
2640
2641         $a = get_app();
2642
2643         $args = func_get_args();
2644         call_hooks("proc_run", $args);
2645
2646         if(count($args) && $args[0] === 'php')
2647         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2648         
2649         foreach ($args as $arg){
2650                 $arg = escapeshellarg($arg);
2651         }
2652         $cmdline = implode($args," ");
2653         proc_close(proc_open($cmdline." &",array(),$foo));
2654 }}
2655
2656 if(! function_exists('current_theme')) {
2657 function current_theme(){
2658         $app_base_themes = array('duepuntozero', 'loozah');
2659         
2660         $a = get_app();
2661         
2662         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2663         $theme_name = ((is_array($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2664         
2665         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2666                 return($theme_name);
2667         
2668         foreach($app_base_themes as $t) {
2669                 if(file_exists('view/theme/' . $t . '/style.css'))
2670                         return($t);
2671         }
2672         
2673         $fallback = glob('view/theme/*/style.css');
2674         if(count($fallback))
2675                 return (str_replace('view/theme/','', str_replace("/style.css","",$fallback[0])));
2676
2677 }}
2678
2679 /*
2680 * Return full URL to theme which is currently in effect.
2681 * Provide a sane default if nothing is chosen or the specified theme does not exist.
2682 */
2683 if(! function_exists('current_theme_url')) {
2684 function current_theme_url() {
2685         global $a;
2686         $t = current_theme();
2687         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css');
2688 }}
2689
2690 if(! function_exists('feed_birthday')) {
2691 function feed_birthday($uid,$tz) {
2692
2693         /**
2694          *
2695          * Determine the next birthday, but only if the birthday is published
2696          * in the default profile. We _could_ also look for a private profile that the
2697          * recipient can see, but somebody could get mad at us if they start getting
2698          * public birthday greetings when they haven't made this info public. 
2699          *
2700          * Assuming we are able to publish this info, we are then going to convert
2701          * the start time from the owner's timezone to UTC. 
2702          *
2703          * This will potentially solve the problem found with some social networks
2704          * where birthdays are converted to the viewer's timezone and salutations from
2705          * elsewhere in the world show up on the wrong day. We will convert it to the
2706          * viewer's timezone also, but first we are going to convert it from the birthday
2707          * person's timezone to GMT - so the viewer may find the birthday starting at
2708          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2709          *
2710          */
2711
2712         $birthday = '';
2713
2714         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2715                 intval($uid)
2716         );
2717
2718         if($p && count($p)) {
2719                 $tmp_dob = substr($p[0]['dob'],5);
2720                 if(intval($tmp_dob)) {
2721                         $y = datetime_convert($tz,$tz,'now','Y');
2722                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2723                         $t_dob = strtotime($bd);
2724                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2725                         if($t_dob < $now)
2726                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2727                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2728                 }
2729         }
2730
2731         return $birthday;
2732 }}
2733
2734 /**
2735  * return atom link elements for all of our hubs
2736  */
2737
2738 if(! function_exists('feed_hublinks')) {
2739 function feed_hublinks() {
2740
2741         $hub = get_config('system','huburl');
2742
2743         $hubxml = '';
2744         if(strlen($hub)) {
2745                 $hubs = explode(',', $hub);
2746                 if(count($hubs)) {
2747                         foreach($hubs as $h) {
2748                                 $h = trim($h);
2749                                 if(! strlen($h))
2750                                         continue;
2751                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2752                         }
2753                 }
2754         }
2755         return $hubxml;
2756 }}
2757
2758 /* return atom link elements for salmon endpoints */
2759
2760 if(! function_exists('feed_salmonlinks')) {
2761 function feed_salmonlinks($nick) {
2762
2763         $a = get_app();
2764
2765         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2766
2767         // old style links that status.net still needed as of 12/2010 
2768
2769         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2770         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2771         return $salmon;
2772 }}
2773
2774 if(! function_exists('get_plink')) {
2775 function get_plink($item) {
2776         $a = get_app(); 
2777         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2778                         . $item['plink'] . '" title="' . t('link to source') . '" target="external-link" class="icon remote-link"></a></div>' : '');
2779         return $plink;
2780 }}
2781
2782 if(! function_exists('unamp')) {
2783 function unamp($s) {
2784         return str_replace('&amp;', '&', $s);
2785 }}
2786
2787
2788
2789
2790 if(! function_exists('lang_selector')) {
2791 function lang_selector() {
2792         global $lang;
2793         $o = '<div id="lang-select-icon" class="icon language" title="' . t('Select an alternate language') . '" onclick="openClose(\'language-selector\');" ></div>';
2794         $o .= '<div id="language-selector" style="display: none;" >';
2795         $o .= '<form action="" method="post" ><select name="system_language" onchange="this.form.submit();" >';
2796         $langs = glob('view/*/strings.php');
2797         if(is_array($langs) && count($langs)) {
2798                 if(! in_array('view/en/strings.php',$langs))
2799                         $langs[] = 'view/en/';
2800                 asort($langs);
2801                 foreach($langs as $l) {
2802                         $ll = substr($l,5);
2803                         $ll = substr($ll,0,strrpos($ll,'/'));
2804                         $selected = (($ll === $lang) ? ' selected="selected" ' : '');
2805                         $o .= '<option value="' . $ll . '"' . $selected . '>' . $ll . '</option>';
2806                 }
2807         }
2808         $o .= '</select></form></div>';
2809         return $o;
2810 }}
2811
2812
2813 if(! function_exists('parse_xml_string')) {
2814 function parse_xml_string($s,$strict = true) {
2815         if($strict) {
2816                 if(! strstr($s,'<?xml'))
2817                         return false;
2818                 $s2 = substr($s,strpos($s,'<?xml'));
2819         }
2820         else
2821                 $s2 = $s;
2822         libxml_use_internal_errors(true);
2823
2824         $x = @simplexml_load_string($s2);
2825         if(! $x) {
2826                 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
2827                 foreach(libxml_get_errors() as $err)
2828                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
2829                 libxml_clear_errors();
2830         }
2831         return $x;
2832 }}
2833
2834 if(! function_exists('is_site_admin')) {
2835 function is_site_admin() {
2836         $a = get_app();
2837         if(local_user() && x($a->user,'email') && x($a->config,'admin_email') && ($a->user['email'] === $a->config['admin_email']))
2838                 return true;
2839         return false;
2840 }}
2841
2842 /*
2843  * parse plugin comment in search of plugin infos.
2844  * like
2845  *      
2846  *       * Name: Plugin
2847  *   * Description: A plugin which plugs in
2848  *       * Version: 1.2.3
2849  *   * Author: John <profile url>
2850  *   * Author: Jane <email>
2851  *   *
2852  */
2853
2854 if (! function_exists('get_plugin_info')){
2855 function get_plugin_info($plugin){
2856         if (!is_file("addon/$plugin/$plugin.php")) return false;
2857         
2858         $f = file_get_contents("addon/$plugin/$plugin.php");
2859         $r = preg_match("|/\*.*\*/|msU", $f, $m);
2860         
2861         $info=Array(
2862                 'name' => $plugin,
2863                 'description' => "",
2864                 'author' => array(),
2865                 'version' => ""
2866         );
2867         
2868         if ($r){
2869                 $ll = explode("\n", $m[0]);
2870                 foreach( $ll as $l ) {
2871                         $l = trim($l,"\t\n\r */");
2872                         if ($l!=""){
2873                                 list($k,$v) = array_map("trim", explode(":",$l,2));
2874                                 $k= strtolower($k);
2875                                 if ($k=="author"){
2876                                         $r=preg_match("|([^<]+)<([^>]+)>|", $v, $m);
2877                                         if ($r) {
2878                                                 $info['author'][] = array('name'=>$m[1], 'link'=>$m[2]);
2879                                         } else {
2880                                                 $info['author'][] = array('name'=>$v);
2881                                         }
2882                                 } else {
2883                                         if (array_key_exists($k,$info)){
2884                                                 $info[$k]=$v;
2885                                         }
2886                                 }
2887                                 
2888                         }
2889                 }
2890                 
2891         }
2892         return $info;
2893 }}