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