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