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