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