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