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