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