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