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