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