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