]> git.mxchange.org Git - friendica.git/blob - boot.php
4a0acef936bb4e848057dd43950b8be62c775525
[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         }
1947         return $o;
1948 }}
1949
1950 if(! function_exists('contact_block')) {
1951 function contact_block() {
1952         $o = '';
1953         $a = get_app();
1954
1955         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
1956         if(! $shown)
1957                 $shown = 24;
1958
1959         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1960                 return $o;
1961         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1962                         intval($a->profile['uid'])
1963         );
1964         if(count($r)) {
1965                 $total = intval($r[0]['total']);
1966         }
1967         if(! $total) {
1968                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1969                 return $o;
1970         }
1971         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
1972                         intval($a->profile['uid']),
1973                         intval($shown)
1974         );
1975         if(count($r)) {
1976                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1977                 foreach($r as $rr) {
1978                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1979                         if(local_user() && ($rr['uid'] == local_user())
1980                                 && ($rr['network'] === 'dfrn')) {
1981                                 $url = $redirect_url;
1982                                 $sparkle = ' sparkle';
1983                         }
1984                         else {
1985                                 $url = $rr['url'];
1986                                 $sparkle = '';
1987                         }
1988
1989                         $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";
1990                 }
1991                 $o .= '</div><div id="contact-block-end"></div>';
1992                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1993                 
1994         }
1995
1996         $arr = array('contacts' => $r, 'output' => $o);
1997
1998         call_hooks('contact_block_end', $arr);
1999         return $o;
2000
2001 }}
2002
2003 if(! function_exists('search')) {
2004 function search($s) {
2005         $a = get_app();
2006         $o  = '<div id="search-box">';
2007         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
2008         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2009         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2010         $o .= '</form></div>';
2011         return $o;
2012 }}
2013
2014 if(! function_exists('valid_email')) {
2015 function valid_email($x){
2016         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2017                 return true;
2018         return false;
2019 }}
2020
2021
2022 if(! function_exists('gravatar_img')) {
2023 function gravatar_img($email) {
2024         $size = 175;
2025         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2026         $rating = 'pg';
2027         $hash = md5(trim(strtolower($email)));
2028         
2029         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2030                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2031
2032         logger('gravatar: ' . $email . ' ' . $url);
2033         return $url;
2034 }}
2035
2036 if(! function_exists('aes_decrypt')) {
2037 function aes_decrypt($val,$ky)
2038 {
2039     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2040     for($a=0;$a<strlen($ky);$a++)
2041       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2042     $mode = MCRYPT_MODE_ECB;
2043     $enc = MCRYPT_RIJNDAEL_128;
2044     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2045     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));
2046 }}
2047
2048
2049 if(! function_exists('aes_encrypt')) {
2050 function aes_encrypt($val,$ky)
2051 {
2052     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2053     for($a=0;$a<strlen($ky);$a++)
2054       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2055     $mode=MCRYPT_MODE_ECB;
2056     $enc=MCRYPT_RIJNDAEL_128;
2057     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2058     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2059 }} 
2060
2061
2062 /**
2063  *
2064  * Function: linkify
2065  *
2066  * Replace naked text hyperlink with HTML formatted hyperlink
2067  *
2068  */
2069
2070 if(! function_exists('linkify')) {
2071 function linkify($s) {
2072         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2073         return($s);
2074 }}
2075
2076
2077 /**
2078  * 
2079  * Function: smilies
2080  *
2081  * Description:
2082  * Replaces text emoticons with graphical images
2083  *
2084  * @Parameter: string $s
2085  *
2086  * Returns string
2087  */
2088
2089 if(! function_exists('smilies')) {
2090 function smilies($s) {
2091         $a = get_app();
2092
2093         return str_replace(
2094         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2095         array(
2096                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2097                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2098                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2099                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2100                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2101                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2102                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2103                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2104                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2105                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2106                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2107                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2108                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2109                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2110         ), $s);
2111 }}
2112
2113
2114 /**
2115  *
2116  * Function : profile_load
2117  * @parameter App    $a
2118  * @parameter string $nickname
2119  * @parameter int    $profile
2120  *
2121  * Summary: Loads a profile into the page sidebar. 
2122  * The function requires a writeable copy of the main App structure, and the nickname
2123  * of a registered local account.
2124  *
2125  * If the viewer is an authenticated remote viewer, the profile displayed is the
2126  * one that has been configured for his/her viewing in the Contact manager.
2127  * Passing a non-zero profile ID can also allow a preview of a selected profile
2128  * by the owner.
2129  *
2130  * Profile information is placed in the App structure for later retrieval.
2131  * Honours the owner's chosen theme for display. 
2132  *
2133  */
2134
2135 if(! function_exists('profile_load')) {
2136 function profile_load(&$a, $nickname, $profile = 0) {
2137         if(remote_user()) {
2138                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2139                         intval($_SESSION['visitor_id']));
2140                 if(count($r))
2141                         $profile = $r[0]['profile-id'];
2142         } 
2143
2144         $r = null;
2145
2146         if($profile) {
2147                 $profile_int = intval($profile);
2148                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2149                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2150                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2151                         dbesc($nickname),
2152                         intval($profile_int)
2153                 );
2154         }
2155         if(! count($r)) {       
2156                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2157                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2158                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2159                         dbesc($nickname)
2160                 );
2161         }
2162
2163         if(($r === false) || (! count($r))) {
2164                 notice( t('No profile') . EOL );
2165                 $a->error = 404;
2166                 return;
2167         }
2168
2169         $a->profile = $r[0];
2170
2171
2172         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2173         $_SESSION['theme'] = $a->profile['theme'];
2174
2175         if(! (x($a->page,'aside')))
2176                 $a->page['aside'] = '';
2177
2178         $a->page['aside'] .= profile_sidebar($a->profile);
2179         $a->page['aside'] .= contact_block();
2180
2181         return;
2182 }}
2183
2184
2185 /**
2186  *
2187  * Function: profile_sidebar
2188  *
2189  * Formats a profile for display in the sidebar.
2190  * It is very difficult to templatise the HTML completely
2191  * because of all the conditional logic.
2192  *
2193  * @parameter: array $profile
2194  *
2195  * Returns HTML string stuitable for sidebar inclusion
2196  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2197  *
2198  */
2199
2200
2201 if(! function_exists('profile_sidebar')) {
2202 function profile_sidebar($profile) {
2203
2204         $o = '';
2205         $location = '';
2206         $address = false;
2207
2208         if((! is_array($profile)) && (! count($profile)))
2209                 return $o;
2210
2211         call_hooks('profile_sidebar_enter', $profile);
2212
2213         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2214
2215         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2216
2217         $tabs = '';
2218
2219         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2220
2221         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2222  
2223         if((x($profile,'address') == 1) 
2224                 || (x($profile,'locality') == 1) 
2225                 || (x($profile,'region') == 1) 
2226                 || (x($profile,'postal-code') == 1) 
2227                 || (x($profile,'country-name') == 1))
2228                 $address = true;
2229
2230         if($address) {
2231                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2232                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2233                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2234                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2235                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2236                         . '<span class="region">' . $profile['region'] . '</span>'
2237                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2238                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2239                 $location .= '</div></div><div class="profile-clear"></div>';
2240
2241         }
2242
2243         $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>' : '');
2244
2245         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2246
2247         $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>' : '');
2248
2249         $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>' : '');
2250
2251         $tpl = load_view_file('view/profile_vcard.tpl');
2252
2253         $o .= replace_macros($tpl, array(
2254                 '$fullname' => $fullname,
2255                 '$pdesc'    => $pdesc,
2256                 '$tabs'     => $tabs,
2257                 '$photo'    => $photo,
2258                 '$connect'  => $connect,                
2259                 '$location' => $location,
2260                 '$gender'   => $gender,
2261                 '$pubkey'   => $pubkey,
2262                 '$marital'  => $marital,
2263                 '$homepage' => $homepage
2264         ));
2265
2266
2267         $arr = array('profile' => $profile, 'entry' => $o);
2268
2269         call_hooks('profile_sidebar', $arr);
2270
2271         return $o;
2272 }}
2273
2274
2275 if(! function_exists('register_hook')) {
2276 function register_hook($hook,$file,$function) {
2277
2278         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2279                 dbesc($hook),
2280                 dbesc($file),
2281                 dbesc($function)
2282         );
2283         if(count($r))
2284                 return true;
2285
2286         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2287                 dbesc($hook),
2288                 dbesc($file),
2289                 dbesc($function)
2290         );
2291         return $r;
2292 }}
2293
2294 if(! function_exists('unregister_hook')) {
2295 function unregister_hook($hook,$file,$function) {
2296
2297         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2298                 dbesc($hook),
2299                 dbesc($file),
2300                 dbesc($function)
2301         );
2302         return $r;
2303 }}
2304
2305
2306 if(! function_exists('load_hooks')) {
2307 function load_hooks() {
2308         $a = get_app();
2309         $a->hooks = array();
2310         $r = q("SELECT * FROM `hook` WHERE 1");
2311         if(count($r)) {
2312                 foreach($r as $rr) {
2313                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2314                 }
2315         }
2316 }}
2317
2318
2319 if(! function_exists('call_hooks')) {
2320 function call_hooks($name, &$data = null) {
2321         $a = get_app();
2322
2323         if(count($a->hooks)) {
2324                 foreach($a->hooks as $hook) {
2325                         if($hook[HOOK_HOOK] === $name) {
2326                                 @include_once($hook[HOOK_FILE]);
2327                                 if(function_exists($hook[HOOK_FUNCTION])) {
2328                                         $func = $hook[HOOK_FUNCTION];
2329                                         $func($a,$data);
2330                                 }
2331                         }
2332                 }
2333         }
2334 }}
2335
2336
2337 if(! function_exists('day_translate')) {
2338 function day_translate($s) {
2339         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2340                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2341                 $s);
2342
2343         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2344                 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')),
2345                 $ret);
2346
2347         return $ret;
2348 }}
2349
2350 if(! function_exists('get_birthdays')) {
2351 function get_birthdays() {
2352
2353         $a = get_app();
2354         $o = '';
2355
2356         if(! local_user())
2357                 return $o;
2358
2359         $bd_format = get_config('system','birthday_format');
2360         if(! $bd_format)
2361                 $bd_format = 'g A l F d' ; // 8 AM Friday January 18
2362
2363         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2364                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2365                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2366                 ORDER BY `start` DESC ",
2367                 intval(local_user()),
2368                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2369                 dbesc(datetime_convert('UTC','UTC','now'))
2370         );
2371
2372         if($r && count($r)) {
2373                 $o .= '<div id="birthday-wrapper"><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2374                 $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2375                 $o .= '<div id="birthday-title-end"></div>';
2376
2377                 foreach($r as $rr) {
2378                         $now = strtotime('now');
2379                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2380
2381                         $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2382                         . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2383                         . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2384                         . '</div>' ;
2385                 }
2386
2387                 $o .= '</div>';
2388         }
2389
2390   return $o;
2391
2392 }}
2393
2394 /**
2395  *
2396  * Compare two URLs to see if they are the same, but ignore
2397  * slight but hopefully insignificant differences such as if one 
2398  * is https and the other isn't, or if one is www.something and 
2399  * the other isn't - and also ignore case differences.
2400  *
2401  * Return true if the URLs match, otherwise false.
2402  *
2403  */
2404
2405 if(! function_exists('link_compare')) {
2406 function link_compare($a,$b) {
2407         $a1 = str_replace(array('https:','//www.'), array('http:','//'), $a);
2408         $b1 = str_replace(array('https:','//www.'), array('http:','//'), $b);
2409         if(strcasecmp($a1,$b1) === 0)
2410                 return true;
2411         return false;
2412 }}
2413
2414
2415 if(! function_exists('prepare_body')) {
2416 function prepare_body($item) {
2417
2418         require_once('include/bbcode.php');
2419
2420         $s = smilies(bbcode($item['body']));
2421
2422         return $s;
2423 }}
2424
2425 /**
2426  * 
2427  * Wrap calls to proc_close(proc_open()) and call hook
2428  * so plugins can take part in process :)
2429  * 
2430  * args:
2431  * $cmd program to run
2432  *  next args are passed as $cmd command line
2433  * 
2434  * e.g.: proc_run("ls","-la","/tmp");
2435  * 
2436  * $cmd and string args are surrounded with ""
2437  */
2438
2439 if(! function_exists('proc_run')) {
2440 function proc_run($cmd){
2441
2442         $a = get_app();
2443
2444         $args = func_get_args();
2445         call_hooks("proc_run", $args);
2446
2447         if(count($args) && $args[0] === 'php')
2448         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2449         
2450         foreach ($args as $arg){
2451                 $arg = escapeshellarg($arg);
2452         }
2453         $cmdline = implode($args," ");
2454         proc_close(proc_open($cmdline." &",array(),$foo));
2455 }}
2456
2457 /*
2458  * Return full URL to theme which is currently in effect.
2459  * Provide a sane default if nothing is chosen or the specified theme does not exist.
2460  */
2461
2462 if(! function_exists('current_theme_url')) {
2463 function current_theme_url() {
2464
2465         $app_base_themes = array('duepuntozero', 'loozah');
2466
2467         $a = get_app();
2468
2469         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2470         $theme_name = ((x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2471
2472         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2473                 return($a->get_baseurl() . '/view/theme/' . $theme_name . '/style.css'); 
2474
2475         foreach($app_base_themes as $t) {
2476                 if(file_exists('view/theme/' . $t . '/style.css'))
2477                         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css'); 
2478         }       
2479
2480         $fallback = glob('view/theme/*/style.css');
2481         if(count($fallback))
2482                 return($a->get_baseurl() . $fallback[0]);
2483
2484         
2485 }}
2486
2487 if(! function_exists('feed_birthday')) {
2488 function feed_birthday($uid,$tz) {
2489
2490         /**
2491          *
2492          * Determine the next birthday, but only if the birthday is published
2493          * in the default profile. We _could_ also look for a private profile that the
2494          * recipient can see, but somebody could get mad at us if they start getting
2495          * public birthday greetings when they haven't made this info public. 
2496          *
2497          * Assuming we are able to publish this info, we are then going to convert
2498          * the start time from the owner's timezone to UTC. 
2499          *
2500          * This will potentially solve the problem found with some social networks
2501          * where birthdays are converted to the viewer's timezone and salutations from
2502          * elsewhere in the world show up on the wrong day. We will convert it to the
2503          * viewer's timezone also, but first we are going to convert it from the birthday
2504          * person's timezone to GMT - so the viewer may find the birthday starting at
2505          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2506          *
2507          */
2508
2509         $birthday = '';
2510
2511         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2512                 intval($uid)
2513         );
2514
2515         if($p && count($p)) {
2516                 $tmp_dob = substr($p[0]['dob'],5);
2517                 if(intval($tmp_dob)) {
2518                         $y = datetime_convert($tz,$tz,'now','Y');
2519                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2520                         $t_dob = strtotime($bd);
2521                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2522                         if($t_dob < $now)
2523                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2524                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2525                 }
2526         }
2527
2528         return $birthday;
2529 }}
2530
2531 /**
2532  * return atom link elements for all of our hubs
2533  */
2534
2535 if(! function_exists('feed_hublinks')) {
2536 function feed_hublinks() {
2537
2538         $hub = get_config('system','huburl');
2539
2540         $hubxml = '';
2541         if(strlen($hub)) {
2542                 $hubs = explode(',', $hub);
2543                 if(count($hubs)) {
2544                         foreach($hubs as $h) {
2545                                 $h = trim($h);
2546                                 if(! strlen($h))
2547                                         continue;
2548                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2549                         }
2550                 }
2551         }
2552         return $hubxml;
2553 }}
2554
2555 /* return atom link elements for salmon endpoints */
2556
2557 if(! function_exists('feed_salmonlinks')) {
2558 function feed_salmonlinks($nick) {
2559
2560         $a = get_app();
2561
2562         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2563
2564         // old style links that status.net still needed as of 12/2010 
2565
2566         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2567         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2568         return $salmon;
2569 }}
2570
2571 if(! function_exists('get_plink')) {
2572 function get_plink($item) {
2573         $a = get_app(); 
2574         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2575                         . $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>' : '');
2576         return $plink;
2577 }}
2578
2579 if(! function_exists('unamp')) {
2580 function unamp($s) {
2581         return str_replace('&amp;', '&', $s);
2582 }}
2583