]> git.mxchange.org Git - friendica.git/blob - boot.php
3636288b282e2576df84d07af0def2a9725d1f91
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'FRIENDIKA_VERSION',      '2.1.921' );
6 define ( 'DFRN_PROTOCOL_VERSION',  '2.1'  );
7 define ( 'DB_UPDATE_VERSION',      1043   );
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 .= (($type === 'like') ? sprintf( t('%s likes this.'), $arr[0]) : sprintf( t('%s doesn\'t like this.'), $arr[0])) . EOL ;
1832         else {
1833                 $spanatts = 'class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');"';
1834                 $o .= (($type === 'like') ? 
1835                                         sprintf( t('<span  %1$s>%2$d people</span> like this.'), $spanatts, $cnt)
1836                                          : 
1837                                         sprintf( t('<span  %1$s>%2$d people</span> don\'t like this.'), $spanatts, $cnt) ); 
1838                 $o .= EOL ;
1839                 $total = count($arr);
1840                 if($total >= MAX_LIKERS)
1841                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1842                 if($total < MAX_LIKERS)
1843                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1844                 $str = implode(', ', $arr);
1845                 if($total >= MAX_LIKERS)
1846                         $str .= sprintf( t(', and %d other people'), $total - MAX_LIKERS );
1847                 $str = (($type === 'like') ? sprintf( t('%s like this.'), $str) : sprintf( t('%s don\'t like this.'), $str));
1848                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1849         }
1850         return $o;
1851 }}
1852
1853
1854 // wrapper to load a view template, checking for alternate
1855 // languages before falling back to the default
1856
1857 if(! function_exists('load_view_file')) {
1858 function load_view_file($s) {
1859         $b = basename($s);
1860         $d = dirname($s);
1861         $lang = get_config('system','language');
1862         if($lang === false)
1863                 $lang = 'en';
1864         if(file_exists("$d/$lang/$b"))
1865                 return file_get_contents("$d/$lang/$b");
1866         return file_get_contents($s);
1867 }}
1868
1869 // for html,xml parsing - let's say you've got
1870 // an attribute foobar="class1 class2 class3"
1871 // and you want to find out if it contains 'class3'.
1872 // you can't use a normal sub string search because you
1873 // might match 'notclass3' and a regex to do the job is 
1874 // possible but a bit complicated. 
1875 // pass the attribute string as $attr and the attribute you 
1876 // are looking for as $s - returns true if found, otherwise false
1877
1878 if(! function_exists('attribute_contains')) {
1879 function attribute_contains($attr,$s) {
1880         $a = explode(' ', $attr);
1881         if(count($a) && in_array($s,$a))
1882                 return true;
1883         return false;
1884 }}
1885
1886 if(! function_exists('logger')) {
1887 function logger($msg,$level = 0) {
1888         $debugging = get_config('system','debugging');
1889         $loglevel  = intval(get_config('system','loglevel'));
1890         $logfile   = get_config('system','logfile');
1891
1892         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1893                 return;
1894         
1895         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1896         return;
1897 }}
1898
1899
1900 if(! function_exists('activity_match')) {
1901 function activity_match($haystack,$needle) {
1902         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1903                 return true;
1904         return false;
1905 }}
1906
1907
1908 // Pull out all #hashtags and @person tags from $s;
1909 // We also get @person@domain.com - which would make 
1910 // the regex quite complicated as tags can also
1911 // end a sentence. So we'll run through our results
1912 // and strip the period from any tags which end with one.
1913 // Returns array of tags found, or empty array.
1914
1915
1916 if(! function_exists('get_tags')) {
1917 function get_tags($s) {
1918         $ret = array();
1919
1920         // ignore anything in a code block
1921
1922         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
1923
1924         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1925                 foreach($match[1] as $match) {
1926                         if(strstr($match,"]")) {
1927                                 // we might be inside a bbcode color tag - leave it alone
1928                                 continue;
1929                         }
1930                         if(substr($match,-1,1) === '.')
1931                                 $ret[] = substr($match,0,-1);
1932                         else
1933                                 $ret[] = $match;
1934                 }
1935         }
1936
1937         return $ret;
1938 }}
1939
1940
1941 // quick and dirty quoted_printable encoding
1942
1943 if(! function_exists('qp')) {
1944 function qp($s) {
1945 return str_replace ("%","=",rawurlencode($s));
1946 }} 
1947
1948
1949 if(! function_exists('like_puller')) {
1950 function like_puller($a,$item,&$arr,$mode) {
1951
1952         $url = '';
1953         $sparkle = '';
1954         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1955
1956         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1957                 $url = $item['author-link'];
1958                 if((local_user()) && (local_user() == $item['uid']) && ($item['network'] === 'dfrn') && (! $item['self']) && (link_compare($item['author-link'],$item['url']))) {
1959                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1960                         $sparkle = ' class="sparkle" ';
1961                 }
1962                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1963                         $arr[$item['parent'] . '-l'] = array();
1964                 if(! isset($arr[$item['parent']]))
1965                         $arr[$item['parent']] = 1;
1966                 else    
1967                         $arr[$item['parent']] ++;
1968                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1969         }
1970         return;
1971 }}
1972
1973 if(! function_exists('get_mentions')) {
1974 function get_mentions($item) {
1975         $o = '';
1976         if(! strlen($item['tag']))
1977                 return $o;
1978
1979         $arr = explode(',',$item['tag']);
1980         foreach($arr as $x) {
1981                 $matches = null;
1982                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
1983                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1984                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
1985                 }
1986         }
1987         return $o;
1988 }}
1989
1990 if(! function_exists('contact_block')) {
1991 function contact_block() {
1992         $o = '';
1993         $a = get_app();
1994
1995         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
1996         if(! $shown)
1997                 $shown = 24;
1998
1999         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
2000                 return $o;
2001         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
2002                         intval($a->profile['uid'])
2003         );
2004         if(count($r)) {
2005                 $total = intval($r[0]['total']);
2006         }
2007         if(! $total) {
2008                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
2009                 return $o;
2010         }
2011         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
2012                         intval($a->profile['uid']),
2013                         intval($shown)
2014         );
2015         if(count($r)) {
2016                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
2017                 foreach($r as $rr) {
2018                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
2019                         if(local_user() && ($rr['uid'] == local_user())
2020                                 && ($rr['network'] === 'dfrn')) {
2021                                 $url = $redirect_url;
2022                                 $sparkle = ' sparkle';
2023                         }
2024                         else {
2025                                 $url = $rr['url'];
2026                                 $sparkle = '';
2027                         }
2028
2029                         $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";
2030                 }
2031                 $o .= '</div><div id="contact-block-end"></div>';
2032                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
2033                 
2034         }
2035
2036         $arr = array('contacts' => $r, 'output' => $o);
2037
2038         call_hooks('contact_block_end', $arr);
2039         return $o;
2040
2041 }}
2042
2043 if(! function_exists('search')) {
2044 function search($s) {
2045         $a = get_app();
2046         $o  = '<div id="search-box">';
2047         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
2048         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2049         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2050         $o .= '</form></div>';
2051         return $o;
2052 }}
2053
2054 if(! function_exists('valid_email')) {
2055 function valid_email($x){
2056         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2057                 return true;
2058         return false;
2059 }}
2060
2061
2062 if(! function_exists('gravatar_img')) {
2063 function gravatar_img($email) {
2064         $size = 175;
2065         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2066         $rating = 'pg';
2067         $hash = md5(trim(strtolower($email)));
2068         
2069         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2070                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2071
2072         logger('gravatar: ' . $email . ' ' . $url);
2073         return $url;
2074 }}
2075
2076 if(! function_exists('aes_decrypt')) {
2077 function aes_decrypt($val,$ky)
2078 {
2079     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2080     for($a=0;$a<strlen($ky);$a++)
2081       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2082     $mode = MCRYPT_MODE_ECB;
2083     $enc = MCRYPT_RIJNDAEL_128;
2084     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2085     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));
2086 }}
2087
2088
2089 if(! function_exists('aes_encrypt')) {
2090 function aes_encrypt($val,$ky)
2091 {
2092     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2093     for($a=0;$a<strlen($ky);$a++)
2094       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2095     $mode=MCRYPT_MODE_ECB;
2096     $enc=MCRYPT_RIJNDAEL_128;
2097     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2098     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2099 }} 
2100
2101
2102 /**
2103  *
2104  * Function: linkify
2105  *
2106  * Replace naked text hyperlink with HTML formatted hyperlink
2107  *
2108  */
2109
2110 if(! function_exists('linkify')) {
2111 function linkify($s) {
2112         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2113         return($s);
2114 }}
2115
2116
2117 /**
2118  * 
2119  * Function: smilies
2120  *
2121  * Description:
2122  * Replaces text emoticons with graphical images
2123  *
2124  * @Parameter: string $s
2125  *
2126  * Returns string
2127  */
2128
2129 if(! function_exists('smilies')) {
2130 function smilies($s) {
2131         $a = get_app();
2132
2133         return str_replace(
2134         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2135         array(
2136                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2137                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2138                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2139                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2140                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2141                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2142                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2143                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2144                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2145                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2146                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2147                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2148                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2149                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2150         ), $s);
2151 }}
2152
2153
2154 /**
2155  *
2156  * Function : profile_load
2157  * @parameter App    $a
2158  * @parameter string $nickname
2159  * @parameter int    $profile
2160  *
2161  * Summary: Loads a profile into the page sidebar. 
2162  * The function requires a writeable copy of the main App structure, and the nickname
2163  * of a registered local account.
2164  *
2165  * If the viewer is an authenticated remote viewer, the profile displayed is the
2166  * one that has been configured for his/her viewing in the Contact manager.
2167  * Passing a non-zero profile ID can also allow a preview of a selected profile
2168  * by the owner.
2169  *
2170  * Profile information is placed in the App structure for later retrieval.
2171  * Honours the owner's chosen theme for display. 
2172  *
2173  */
2174
2175 if(! function_exists('profile_load')) {
2176 function profile_load(&$a, $nickname, $profile = 0) {
2177         if(remote_user()) {
2178                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2179                         intval($_SESSION['visitor_id']));
2180                 if(count($r))
2181                         $profile = $r[0]['profile-id'];
2182         } 
2183
2184         $r = null;
2185
2186         if($profile) {
2187                 $profile_int = intval($profile);
2188                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2189                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2190                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2191                         dbesc($nickname),
2192                         intval($profile_int)
2193                 );
2194         }
2195         if(! count($r)) {       
2196                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2197                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2198                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2199                         dbesc($nickname)
2200                 );
2201         }
2202
2203         if(($r === false) || (! count($r))) {
2204                 notice( t('No profile') . EOL );
2205                 $a->error = 404;
2206                 return;
2207         }
2208
2209         $a->profile = $r[0];
2210
2211
2212         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2213         $_SESSION['theme'] = $a->profile['theme'];
2214
2215         if(! (x($a->page,'aside')))
2216                 $a->page['aside'] = '';
2217
2218         $a->page['aside'] .= profile_sidebar($a->profile);
2219         $a->page['aside'] .= contact_block();
2220
2221         return;
2222 }}
2223
2224
2225 /**
2226  *
2227  * Function: profile_sidebar
2228  *
2229  * Formats a profile for display in the sidebar.
2230  * It is very difficult to templatise the HTML completely
2231  * because of all the conditional logic.
2232  *
2233  * @parameter: array $profile
2234  *
2235  * Returns HTML string stuitable for sidebar inclusion
2236  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2237  *
2238  */
2239
2240
2241 if(! function_exists('profile_sidebar')) {
2242 function profile_sidebar($profile) {
2243
2244         $o = '';
2245         $location = '';
2246         $address = false;
2247
2248         if((! is_array($profile)) && (! count($profile)))
2249                 return $o;
2250
2251         call_hooks('profile_sidebar_enter', $profile);
2252
2253         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2254
2255         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2256
2257         $tabs = '';
2258
2259         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2260
2261         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2262  
2263         if((x($profile,'address') == 1) 
2264                 || (x($profile,'locality') == 1) 
2265                 || (x($profile,'region') == 1) 
2266                 || (x($profile,'postal-code') == 1) 
2267                 || (x($profile,'country-name') == 1))
2268                 $address = true;
2269
2270         if($address) {
2271                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2272                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2273                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2274                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2275                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2276                         . '<span class="region">' . $profile['region'] . '</span>'
2277                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2278                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2279                 $location .= '</div></div><div class="profile-clear"></div>';
2280
2281         }
2282
2283         $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>' : '');
2284
2285         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2286
2287         $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>' : '');
2288
2289         $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>' : '');
2290
2291         $tpl = load_view_file('view/profile_vcard.tpl');
2292
2293         $o .= replace_macros($tpl, array(
2294                 '$fullname' => $fullname,
2295                 '$pdesc'    => $pdesc,
2296                 '$tabs'     => $tabs,
2297                 '$photo'    => $photo,
2298                 '$connect'  => $connect,                
2299                 '$location' => $location,
2300                 '$gender'   => $gender,
2301                 '$pubkey'   => $pubkey,
2302                 '$marital'  => $marital,
2303                 '$homepage' => $homepage
2304         ));
2305
2306
2307         $arr = array('profile' => $profile, 'entry' => $o);
2308
2309         call_hooks('profile_sidebar', $arr);
2310
2311         return $o;
2312 }}
2313
2314
2315 if(! function_exists('register_hook')) {
2316 function register_hook($hook,$file,$function) {
2317
2318         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2319                 dbesc($hook),
2320                 dbesc($file),
2321                 dbesc($function)
2322         );
2323         if(count($r))
2324                 return true;
2325
2326         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2327                 dbesc($hook),
2328                 dbesc($file),
2329                 dbesc($function)
2330         );
2331         return $r;
2332 }}
2333
2334 if(! function_exists('unregister_hook')) {
2335 function unregister_hook($hook,$file,$function) {
2336
2337         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2338                 dbesc($hook),
2339                 dbesc($file),
2340                 dbesc($function)
2341         );
2342         return $r;
2343 }}
2344
2345
2346 if(! function_exists('load_hooks')) {
2347 function load_hooks() {
2348         $a = get_app();
2349         $a->hooks = array();
2350         $r = q("SELECT * FROM `hook` WHERE 1");
2351         if(count($r)) {
2352                 foreach($r as $rr) {
2353                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2354                 }
2355         }
2356 }}
2357
2358
2359 if(! function_exists('call_hooks')) {
2360 function call_hooks($name, &$data = null) {
2361         $a = get_app();
2362
2363         if(count($a->hooks)) {
2364                 foreach($a->hooks as $hook) {
2365                         if($hook[HOOK_HOOK] === $name) {
2366                                 @include_once($hook[HOOK_FILE]);
2367                                 if(function_exists($hook[HOOK_FUNCTION])) {
2368                                         $func = $hook[HOOK_FUNCTION];
2369                                         $func($a,$data);
2370                                 }
2371                         }
2372                 }
2373         }
2374 }}
2375
2376
2377 if(! function_exists('day_translate')) {
2378 function day_translate($s) {
2379         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2380                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2381                 $s);
2382
2383         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2384                 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')),
2385                 $ret);
2386
2387         return $ret;
2388 }}
2389
2390 if(! function_exists('get_birthdays')) {
2391 function get_birthdays() {
2392
2393         $a = get_app();
2394         $o = '';
2395
2396         if(! local_user())
2397                 return $o;
2398
2399         $bd_format = get_config('system','birthday_format');
2400         if(! $bd_format)
2401                 $bd_format = 'g A l F d' ; // 8 AM Friday January 18
2402
2403         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2404                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2405                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2406                 ORDER BY `start` DESC ",
2407                 intval(local_user()),
2408                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2409                 dbesc(datetime_convert('UTC','UTC','now'))
2410         );
2411
2412         if($r && count($r)) {
2413                 $total = 0;
2414                 foreach($r as $rr)
2415                         if(strlen($rr['name']))
2416                                 $total ++;
2417
2418                 $o .= '<div id="birthday-notice" class="birthday-notice fakelink" onclick=openClose(\'birthday-wrapper\'); >' . t('Birthday Reminders') . ' ' . '(' . $total . ')' . '</div>'; 
2419                 $o .= '<div id="birthday-wrapper" style="display: none;" ><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2420                 $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2421                 $o .= '<div id="birthday-title-end"></div>';
2422
2423                 foreach($r as $rr) {
2424                         if(! strlen($rr['name']))
2425                                 continue;
2426                         $now = strtotime('now');
2427                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2428
2429                         $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2430                         . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2431                         . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2432                         . '</div>' ;
2433                 }
2434
2435                 $o .= '</div></div>';
2436         }
2437
2438   return $o;
2439
2440 }}
2441
2442 /**
2443  *
2444  * Compare two URLs to see if they are the same, but ignore
2445  * slight but hopefully insignificant differences such as if one 
2446  * is https and the other isn't, or if one is www.something and 
2447  * the other isn't - and also ignore case differences.
2448  *
2449  * Return true if the URLs match, otherwise false.
2450  *
2451  */
2452
2453 if(! function_exists('link_compare')) {
2454 function link_compare($a,$b) {
2455         $a1 = str_replace(array('https:','//www.'), array('http:','//'), $a);
2456         $b1 = str_replace(array('https:','//www.'), array('http:','//'), $b);
2457         if(strcasecmp($a1,$b1) === 0)
2458                 return true;
2459         return false;
2460 }}
2461
2462
2463 if(! function_exists('prepare_body')) {
2464 function prepare_body($item) {
2465         return prepare_text($item['body']);
2466 }}
2467
2468 if(! function_exists('prepare_text')) {
2469 function prepare_text($text) {
2470
2471         require_once('include/bbcode.php');
2472
2473         $s = smilies(bbcode($text));
2474
2475         return $s;
2476 }}
2477
2478 /**
2479  * 
2480  * Wrap calls to proc_close(proc_open()) and call hook
2481  * so plugins can take part in process :)
2482  * 
2483  * args:
2484  * $cmd program to run
2485  *  next args are passed as $cmd command line
2486  * 
2487  * e.g.: proc_run("ls","-la","/tmp");
2488  * 
2489  * $cmd and string args are surrounded with ""
2490  */
2491
2492 if(! function_exists('proc_run')) {
2493 function proc_run($cmd){
2494
2495         $a = get_app();
2496
2497         $args = func_get_args();
2498         call_hooks("proc_run", $args);
2499
2500         if(count($args) && $args[0] === 'php')
2501         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2502         
2503         foreach ($args as $arg){
2504                 $arg = escapeshellarg($arg);
2505         }
2506         $cmdline = implode($args," ");
2507         proc_close(proc_open($cmdline." &",array(),$foo));
2508 }}
2509
2510 /*
2511  * Return full URL to theme which is currently in effect.
2512  * Provide a sane default if nothing is chosen or the specified theme does not exist.
2513  */
2514
2515 if(! function_exists('current_theme_url')) {
2516 function current_theme_url() {
2517
2518         $app_base_themes = array('duepuntozero', 'loozah');
2519
2520         $a = get_app();
2521
2522         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2523         $theme_name = ((x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2524
2525         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2526                 return($a->get_baseurl() . '/view/theme/' . $theme_name . '/style.css'); 
2527
2528         foreach($app_base_themes as $t) {
2529                 if(file_exists('view/theme/' . $t . '/style.css'))
2530                         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css'); 
2531         }       
2532
2533         $fallback = glob('view/theme/*/style.css');
2534         if(count($fallback))
2535                 return($a->get_baseurl() . $fallback[0]);
2536
2537         
2538 }}
2539
2540 if(! function_exists('feed_birthday')) {
2541 function feed_birthday($uid,$tz) {
2542
2543         /**
2544          *
2545          * Determine the next birthday, but only if the birthday is published
2546          * in the default profile. We _could_ also look for a private profile that the
2547          * recipient can see, but somebody could get mad at us if they start getting
2548          * public birthday greetings when they haven't made this info public. 
2549          *
2550          * Assuming we are able to publish this info, we are then going to convert
2551          * the start time from the owner's timezone to UTC. 
2552          *
2553          * This will potentially solve the problem found with some social networks
2554          * where birthdays are converted to the viewer's timezone and salutations from
2555          * elsewhere in the world show up on the wrong day. We will convert it to the
2556          * viewer's timezone also, but first we are going to convert it from the birthday
2557          * person's timezone to GMT - so the viewer may find the birthday starting at
2558          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2559          *
2560          */
2561
2562         $birthday = '';
2563
2564         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2565                 intval($uid)
2566         );
2567
2568         if($p && count($p)) {
2569                 $tmp_dob = substr($p[0]['dob'],5);
2570                 if(intval($tmp_dob)) {
2571                         $y = datetime_convert($tz,$tz,'now','Y');
2572                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2573                         $t_dob = strtotime($bd);
2574                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2575                         if($t_dob < $now)
2576                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2577                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2578                 }
2579         }
2580
2581         return $birthday;
2582 }}
2583
2584 /**
2585  * return atom link elements for all of our hubs
2586  */
2587
2588 if(! function_exists('feed_hublinks')) {
2589 function feed_hublinks() {
2590
2591         $hub = get_config('system','huburl');
2592
2593         $hubxml = '';
2594         if(strlen($hub)) {
2595                 $hubs = explode(',', $hub);
2596                 if(count($hubs)) {
2597                         foreach($hubs as $h) {
2598                                 $h = trim($h);
2599                                 if(! strlen($h))
2600                                         continue;
2601                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2602                         }
2603                 }
2604         }
2605         return $hubxml;
2606 }}
2607
2608 /* return atom link elements for salmon endpoints */
2609
2610 if(! function_exists('feed_salmonlinks')) {
2611 function feed_salmonlinks($nick) {
2612
2613         $a = get_app();
2614
2615         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2616
2617         // old style links that status.net still needed as of 12/2010 
2618
2619         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2620         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2621         return $salmon;
2622 }}
2623
2624 if(! function_exists('get_plink')) {
2625 function get_plink($item) {
2626         $a = get_app(); 
2627         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2628                         . $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>' : '');
2629         return $plink;
2630 }}
2631
2632 if(! function_exists('unamp')) {
2633 function unamp($s) {
2634         return str_replace('&amp;', '&', $s);
2635 }}
2636
2637 if(! function_exists('extract_item_authors')) {
2638 function extract_item_authors($arr,$uid) {
2639
2640         if((! $uid) || (! is_array($arr)) || (! count($arr)))
2641                 return array();
2642         $urls = array();
2643         foreach($arr as $rr) {
2644                 if(! in_array("'" . dbesc($rr['author-link']) . "'",$urls))
2645                         $urls[] = "'" . dbesc($rr['author-link']) . "'";
2646         }
2647
2648         // pre-quoted, don't put quotes on %s
2649         if(count($urls)) {
2650                 $r = q("SELECT `id`,`url` FROM `contact` WHERE `uid` = %d AND `url` IN ( %s ) AND `network` = 'dfrn' AND `self` = 0 AND `blocked` = 0 ",
2651                         intval($uid),
2652                         implode(',',$urls)
2653                 );
2654                 if(count($r)) {
2655                         $ret = array();
2656                         foreach($r as $rr)
2657                                 $ret[$rr['url']] = $rr['id'];
2658                         return $ret;
2659                 }
2660         }
2661         return array();         
2662 }}