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