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