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