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