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