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