]> git.mxchange.org Git - friendica.git/blob - boot.php
Merge branch 'master' of git://github.com/friendika/friendika
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1033   );
6 define ( 'FRIENDIKA_VERSION',      '2.01.1006' );
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
1691         // ignore anything in a code block
1692
1693         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
1694
1695         if(preg_match_all('/([@#][^ \x0D\x0A,:?]*)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1696                 foreach($match[1] as $match) {
1697                         if(strstr($match,"]")) {
1698                                 // we might be inside a bbcode color tag - leave it alone
1699                                 continue;
1700                         }
1701                         if(substr($match,-1,1) === '.')
1702                                 $ret[] = substr($match,0,-1);
1703                         else
1704                                 $ret[] = $match;
1705                 }
1706         }
1707
1708         return $ret;
1709 }}
1710
1711
1712 // quick and dirty quoted_printable encoding
1713
1714 if(! function_exists('qp')) {
1715 function qp($s) {
1716 return str_replace ("%","=",rawurlencode($s));
1717 }} 
1718
1719
1720 if(! function_exists('like_puller')) {
1721 function like_puller($a,$item,&$arr,$mode) {
1722
1723         $url = '';
1724         $sparkle = '';
1725         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1726
1727         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1728                 $url = $item['author-link'];
1729                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1730                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1731                         $sparkle = ' class="sparkle" ';
1732                 }
1733                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1734                         $arr[$item['parent'] . '-l'] = array();
1735                 if(! isset($arr[$item['parent']]))
1736                         $arr[$item['parent']] = 1;
1737                 else    
1738                         $arr[$item['parent']] ++;
1739                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1740         }
1741         return;
1742 }}
1743
1744 if(! function_exists('get_mentions')) {
1745 function get_mentions($item) {
1746         $o = '';
1747         if(! strlen($item['tag']))
1748                 return $o;
1749
1750         $arr = explode(',',$item['tag']);
1751         foreach($arr as $x) {
1752                 $matches = null;
1753                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1754                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1755         }
1756         return $o;
1757 }}
1758
1759 if(! function_exists('contact_block')) {
1760 function contact_block() {
1761         $o = '';
1762         $a = get_app();
1763
1764         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
1765         if(! $shown)
1766                 $shown = 24;
1767
1768         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1769                 return $o;
1770         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1771                         intval($a->profile['uid'])
1772         );
1773         if(count($r)) {
1774                 $total = intval($r[0]['total']);
1775         }
1776         if(! $total) {
1777                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1778                 return $o;
1779         }
1780         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
1781                         intval($a->profile['uid']),
1782                         intval($shown)
1783         );
1784         if(count($r)) {
1785                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1786                 foreach($r as $rr) {
1787                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1788                         if(local_user() && ($rr['uid'] == local_user())
1789                                 && ($rr['network'] === 'dfrn')) {
1790                                 $url = $redirect_url;
1791                                 $sparkle = ' sparkle';
1792                         }
1793                         else {
1794                                 $url = $rr['url'];
1795                                 $sparkle = '';
1796                         }
1797
1798                         $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";
1799                 }
1800                 $o .= '</div><div id="contact-block-end"></div>';
1801                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1802                 
1803         }
1804
1805         $arr = array('contacts' => $r, 'output' => $o);
1806
1807         call_hooks('contact_block_end', $arr);
1808         return $o;
1809
1810 }}
1811
1812 if(! function_exists('search')) {
1813 function search($s) {
1814         $a = get_app();
1815         $o  = '<div id="search-box">';
1816         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1817         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1818         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1819         $o .= '</form></div>';
1820         return $o;
1821 }}
1822
1823 if(! function_exists('valid_email')) {
1824 function valid_email($x){
1825         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1826                 return true;
1827         return false;
1828 }}
1829
1830
1831 if(! function_exists('gravatar_img')) {
1832 function gravatar_img($email) {
1833         $size = 175;
1834         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1835         $rating = 'pg';
1836         $hash = md5(trim(strtolower($email)));
1837         
1838         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1839                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1840
1841         logger('gravatar: ' . $email . ' ' . $url);
1842         return $url;
1843 }}
1844
1845 if(! function_exists('aes_decrypt')) {
1846 function aes_decrypt($val,$ky)
1847 {
1848     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1849     for($a=0;$a<strlen($ky);$a++)
1850       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1851     $mode = MCRYPT_MODE_ECB;
1852     $enc = MCRYPT_RIJNDAEL_128;
1853     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
1854     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));
1855 }}
1856
1857
1858 if(! function_exists('aes_encrypt')) {
1859 function aes_encrypt($val,$ky)
1860 {
1861     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1862     for($a=0;$a<strlen($ky);$a++)
1863       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1864     $mode=MCRYPT_MODE_ECB;
1865     $enc=MCRYPT_RIJNDAEL_128;
1866     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
1867     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
1868 }} 
1869
1870
1871 /**
1872  *
1873  * Function: linkify
1874  *
1875  * Replace naked text hyperlink with HTML formatted hyperlink
1876  *
1877  */
1878
1879 if(! function_exists('linkify')) {
1880 function linkify($s) {
1881         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%]*)/", ' <a href="$1" >$1</a>', $s);
1882         return($s);
1883 }}
1884
1885
1886 /**
1887  * 
1888  * Function: smilies
1889  *
1890  * Description:
1891  * Replaces text emoticons with graphical images
1892  *
1893  * @Parameter: string $s
1894  *
1895  * Returns string
1896  */
1897
1898 if(! function_exists('smilies')) {
1899 function smilies($s) {
1900         $a = get_app();
1901
1902         return str_replace(
1903         array( ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
1904         array(
1905                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1906                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1907                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1908                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
1909                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1910                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1911                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1912                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1913                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1914                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1915                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
1916         ), $s);
1917 }}
1918
1919
1920 /**
1921  *
1922  * Function : profile_load
1923  * @parameter App    $a
1924  * @parameter string $nickname
1925  * @parameter int    $profile
1926  *
1927  * Summary: Loads a profile into the page sidebar. 
1928  * The function requires a writeable copy of the main App structure, and the nickname
1929  * of a registered local account.
1930  *
1931  * If the viewer is an authenticated remote viewer, the profile displayed is the
1932  * one that has been configured for his/her viewing in the Contact manager.
1933  * Passing a non-zero profile ID can also allow a preview of a selected profile
1934  * by the owner.
1935  *
1936  * Profile information is placed in the App structure for later retrieval.
1937  * Honours the owner's chosen theme for display. 
1938  *
1939  */
1940
1941 if(! function_exists('profile_load')) {
1942 function profile_load(&$a, $nickname, $profile = 0) {
1943         if(remote_user()) {
1944                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
1945                         intval($_SESSION['visitor_id']));
1946                 if(count($r))
1947                         $profile = $r[0]['profile-id'];
1948         } 
1949
1950         $r = null;
1951
1952         if($profile) {
1953                 $profile_int = intval($profile);
1954                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1955                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1956                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
1957                         dbesc($nickname),
1958                         intval($profile_int)
1959                 );
1960         }
1961         if(! count($r)) {       
1962                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1963                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1964                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
1965                         dbesc($nickname)
1966                 );
1967         }
1968
1969         if(($r === false) || (! count($r))) {
1970                 notice( t('No profile') . EOL );
1971                 $a->error = 404;
1972                 return;
1973         }
1974
1975         $a->profile = $r[0];
1976
1977
1978         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
1979         $_SESSION['theme'] = $a->profile['theme'];
1980
1981         if(! (x($a->page,'aside')))
1982                 $a->page['aside'] = '';
1983
1984         $a->page['aside'] .= profile_sidebar($a->profile);
1985         $a->page['aside'] .= contact_block();
1986
1987         return;
1988 }}
1989
1990
1991 /**
1992  *
1993  * Function: profile_sidebar
1994  *
1995  * Formats a profile for display in the sidebar.
1996  * It is very difficult to templatise the HTML completely
1997  * because of all the conditional logic.
1998  *
1999  * @parameter: array $profile
2000  *
2001  * Returns HTML string stuitable for sidebar inclusion
2002  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2003  *
2004  */
2005
2006
2007 if(! function_exists('profile_sidebar')) {
2008 function profile_sidebar($profile) {
2009
2010         $o = '';
2011         $location = '';
2012         $address = false;
2013
2014         if((! is_array($profile)) && (! count($profile)))
2015                 return $o;
2016
2017         call_hooks('profile_sidebar_enter', $profile);
2018
2019         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2020
2021         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2022
2023         $tabs = '';
2024
2025         $photo = '<div id="profile=photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2026
2027         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2028  
2029         if((x($profile,'address') == 1) 
2030                 || (x($profile,'locality') == 1) 
2031                 || (x($profile,'region') == 1) 
2032                 || (x($profile,'postal-code') == 1) 
2033                 || (x($profile,'country-name') == 1))
2034                 $address = true;
2035
2036         if($address) {
2037                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2038                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2039                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2040                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2041                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2042                         . '<span class="region">' . $profile['region'] . '</span>'
2043                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2044                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2045                 $location .= '</div></div><div class="profile-clear"></div>';
2046
2047         }
2048
2049         $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>' : '');
2050
2051         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2052
2053         $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>' : '');
2054
2055         $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>' : '');
2056
2057         $tpl = load_view_file('view/profile_vcard.tpl');
2058
2059         $o .= replace_macros($tpl, array(
2060                 '$fullname' => $fullname,
2061                 '$pdesc'    => $pdesc,
2062                 '$tabs'     => $tabs,
2063                 '$photo'    => $photo,
2064                 '$connect'  => $connect,                
2065                 '$location' => $location,
2066                 '$gender'   => $gender,
2067                 '$pubkey'   => $pubkey,
2068                 '$marital'  => $marital,
2069                 '$homepage' => $homepage
2070         ));
2071
2072
2073         $arr = array('profile' => $profile, 'entry' => $o);
2074
2075         call_hooks('profile_sidebar', $arr);
2076
2077         return $o;
2078 }}
2079
2080
2081 if(! function_exists('register_hook')) {
2082 function register_hook($hook,$file,$function) {
2083
2084         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2085                 dbesc($hook),
2086                 dbesc($file),
2087                 dbesc($function)
2088         );
2089         if(count($r))
2090                 return true;
2091
2092         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2093                 dbesc($hook),
2094                 dbesc($file),
2095                 dbesc($function)
2096         );
2097         return $r;
2098 }}
2099
2100 if(! function_exists('unregister_hook')) {
2101 function unregister_hook($hook,$file,$function) {
2102
2103         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2104                 dbesc($hook),
2105                 dbesc($file),
2106                 dbesc($function)
2107         );
2108         return $r;
2109 }}
2110
2111
2112 if(! function_exists('load_hooks')) {
2113 function load_hooks() {
2114         $a = get_app();
2115         $r = q("SELECT * FROM `hook` WHERE 1");
2116         if(count($r)) {
2117                 foreach($r as $rr) {
2118                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2119                 }
2120         }
2121 }}
2122
2123
2124 if(! function_exists('call_hooks')) {
2125 function call_hooks($name, &$data = null) {
2126         $a = get_app();
2127
2128         if(count($a->hooks)) {
2129                 foreach($a->hooks as $hook) {
2130                         if($hook[HOOK_HOOK] === $name) {
2131                                 @include_once($hook[HOOK_FILE]);
2132                                 if(function_exists($hook[HOOK_FUNCTION])) {
2133                                         $func = $hook[HOOK_FUNCTION];
2134                                         $func($a,$data);
2135                                 }
2136                         }
2137                 }
2138         }
2139 }}
2140
2141
2142 if(! function_exists('day_translate')) {
2143 function day_translate($s) {
2144         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2145                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2146                 $s);
2147
2148         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2149                 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')),
2150                 $ret);
2151
2152         return $ret;
2153 }}
2154
2155 if(! function_exists('get_birthdays')) {
2156 function get_birthdays() {
2157
2158         $a = get_app();
2159         $o = '';
2160
2161         if(! local_user())
2162                 return $o;
2163
2164         $bd_format = get_config('system','birthday_format');
2165         if(! $bd_format)
2166                 $bd_format = 'g A l F d' ; // 8 AM Friday January 18
2167
2168         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2169                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2170                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2171                 ORDER BY `start` DESC ",
2172                 intval(local_user()),
2173                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2174                 dbesc(datetime_convert('UTC','UTC','now'))
2175         );
2176
2177         if($r && count($r)) {
2178                 $o .= '<div id="birthday-wrapper"><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2179                 $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2180                 $o .= '<div id="birthday-title-end"></div>';
2181
2182                 foreach($r as $rr) {
2183                         $now = strtotime('now');
2184                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2185
2186                         $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2187                         . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2188                         . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2189                         . '</div>' ;
2190                 }
2191
2192                 $o .= '</div>';
2193         }
2194
2195   return $o;
2196
2197 }}
2198
2199 /**
2200  *
2201  * Compare two URLs to see if they are the same, but ignore
2202  * slight but hopefully insignificant differences such as if one 
2203  * is https and the other isn't, or if one is www.something and 
2204  * the other isn't - and also ignore case differences.
2205  *
2206  * Return true if the URLs match, otherwise false.
2207  *
2208  */
2209
2210 if(! function_exists('link_compare')) {
2211 function link_compare($a,$b) {
2212         $a1 = str_replace(array('https:','//www.'), array('http:','//'), $a);
2213         $b1 = str_replace(array('https:','//www.'), array('http:','//'), $b);
2214         if(strcasecmp($a1,$b1) === 0)
2215                 return true;
2216         return false;
2217 }}