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