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