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