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