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