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