]> git.mxchange.org Git - friendica.git/blob - boot.php
change activitystreams unfollow to stop-follow but also send out OStatus unfollow
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'BUILD_ID',               1030   );
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         $a->config[$family][$key] = $value;
1100
1101         if(get_config($family,$key,true) === false) {
1102                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1103                         dbesc($family),
1104                         dbesc($key),
1105                         dbesc($value)
1106                 );
1107                 if($ret) 
1108                         return $value;
1109                 return $ret;
1110         }
1111         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1112                 dbesc($value),
1113                 dbesc($family),
1114                 dbesc($key)
1115         );
1116         if($ret)
1117                 return $value;
1118         return $ret;
1119 }}
1120
1121
1122 if(! function_exists('load_pconfig')) {
1123 function load_pconfig($uid,$family) {
1124         global $a;
1125         $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
1126                 dbesc($family),
1127                 intval($uid)
1128         );
1129         if(count($r)) {
1130                 foreach($r as $rr) {
1131                         $k = $rr['k'];
1132                         $a->config[$uid][$family][$k] = $rr['v'];
1133                 }
1134         }
1135 }}
1136
1137
1138
1139 if(! function_exists('get_pconfig')) {
1140 function get_pconfig($uid,$family, $key, $instore = false) {
1141
1142         global $a;
1143
1144         if(! $instore) {
1145                 if(isset($a->config[$uid][$family][$key])) {
1146                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1147                                 return false;
1148                         }
1149                         return $a->config[$uid][$family][$key];
1150                 }
1151         }
1152         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1153                 intval($uid),
1154                 dbesc($family),
1155                 dbesc($key)
1156         );
1157         if(count($ret)) {
1158                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1159                 return $ret[0]['v'];
1160         }
1161         else {
1162                 $a->config[$uid][$family][$key] = '!<unset>!';
1163         }
1164         return false;
1165 }}
1166
1167 if(! function_exists('del_config')) {
1168 function del_config($family,$key) {
1169
1170         global $a;
1171         if(x($a->config[$family],$key))
1172                 unset($a->config[$family][$key]);
1173         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1174                 dbesc($cat),
1175                 dbesc($key)
1176         );
1177         return $ret;
1178 }}
1179
1180
1181
1182 // Same as above functions except these are for personal config storage and take an
1183 // additional $uid argument.
1184
1185 if(! function_exists('set_pconfig')) {
1186 function set_pconfig($uid,$family,$key,$value) {
1187
1188         global $a;
1189         $a->config[$uid][$family][$key] = $value;
1190
1191         if(get_pconfig($uid,$family,$key,true) === false) {
1192                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1193                         intval($uid),
1194                         dbesc($family),
1195                         dbesc($key),
1196                         dbesc($value)
1197                 );
1198                 if($ret) 
1199                         return $value;
1200                 return $ret;
1201         }
1202         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1203                 dbesc($value),
1204                 intval($uid),
1205                 dbesc($family),
1206                 dbesc($key)
1207         );
1208
1209         if($ret)
1210                 return $value;
1211         return $ret;
1212 }}
1213
1214 if(! function_exists('del_pconfig')) {
1215 function del_pconfig($uid,$family,$key) {
1216
1217         global $a;
1218         if(x($a->config[$uid][$family],$key))
1219                 unset($a->config[$uid][$family][$key]);
1220         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1221                 intval($uid),
1222                 dbesc($cat),
1223                 dbesc($key)
1224         );
1225         return $ret;
1226 }}
1227
1228
1229 // convert an XML document to a normalised, case-corrected array
1230 // used by webfinger
1231
1232 if(! function_exists('convert_xml_element_to_array')) {
1233 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1234
1235         // If we're getting too deep, bail out
1236         if ($recursion_depth > 512) {
1237                 return(null);
1238         }
1239
1240         if (!is_string($xml_element) &&
1241         !is_array($xml_element) &&
1242         (get_class($xml_element) == 'SimpleXMLElement')) {
1243                 $xml_element_copy = $xml_element;
1244                 $xml_element = get_object_vars($xml_element);
1245         }
1246
1247         if (is_array($xml_element)) {
1248                 $result_array = array();
1249                 if (count($xml_element) <= 0) {
1250                         return (trim(strval($xml_element_copy)));
1251                 }
1252
1253                 foreach($xml_element as $key=>$value) {
1254
1255                         $recursion_depth++;
1256                         $result_array[strtolower($key)] =
1257                 convert_xml_element_to_array($value, $recursion_depth);
1258                         $recursion_depth--;
1259                 }
1260                 if ($recursion_depth == 0) {
1261                         $temp_array = $result_array;
1262                         $result_array = array(
1263                                 strtolower($xml_element_copy->getName()) => $temp_array,
1264                         );
1265                 }
1266
1267                 return ($result_array);
1268
1269         } else {
1270                 return (trim(strval($xml_element)));
1271         }
1272 }}
1273
1274 // Given an email style address, perform webfinger lookup and 
1275 // return the resulting DFRN profile URL, or if no DFRN profile URL
1276 // is located, returns an OStatus subscription template (prefixed 
1277 // with the string 'stat:' to identify it as on OStatus template).
1278 // If this isn't an email style address just return $s.
1279 // Return an empty string if email-style addresses but webfinger fails,
1280 // or if the resultant personal XRD doesn't contain a supported 
1281 // subscription/friend-request attribute.
1282
1283 if(! function_exists('webfinger_dfrn')) {
1284 function webfinger_dfrn($s) {
1285         if(! strstr($s,'@')) {
1286                 return $s;
1287         }
1288         $links = webfinger($s);
1289         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1290         if(count($links)) {
1291                 foreach($links as $link)
1292                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1293                                 return $link['@attributes']['href'];
1294                 foreach($links as $link)
1295                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1296                                 return 'stat:' . $link['@attributes']['template'];              
1297         }
1298         return '';
1299 }}
1300
1301 // Given an email style address, perform webfinger lookup and 
1302 // return the array of link attributes from the personal XRD file.
1303 // On error/failure return an empty array.
1304
1305
1306 if(! function_exists('webfinger')) {
1307 function webfinger($s) {
1308         $host = '';
1309         if(strstr($s,'@')) {
1310                 $host = substr($s,strpos($s,'@') + 1);
1311         }
1312         if(strlen($host)) {
1313                 $tpl = fetch_lrdd_template($host);
1314                 logger('webfinger: lrdd template: ' . $tpl);
1315                 if(strlen($tpl)) {
1316                         $pxrd = str_replace('{uri}', urlencode('acct:'.$s), $tpl);
1317                         $links = fetch_xrd_links($pxrd);
1318                         if(! count($links)) {
1319                                 // try with double slashes
1320                                 $pxrd = str_replace('{uri}', urlencode('acct://'.$s), $tpl);
1321                                 $links = fetch_xrd_links($pxrd);
1322                         }
1323                         return $links;
1324                 }
1325         }
1326         return array();
1327 }}
1328
1329 if(! function_exists('lrdd')) {
1330 function lrdd($uri) {
1331
1332         $a = get_app();
1333
1334         if(strstr($uri,'@')) {  
1335                 return(webfinger($uri));
1336         }
1337         else {
1338                 $html = fetch_url($uri);
1339                 $headers = $a->get_curl_headers();
1340                 $lines = explode("\n",$headers);
1341                 if(count($lines)) {
1342                         foreach($lines as $line) {                              
1343                                 // TODO alter the following regex to support multiple relations (space separated)
1344                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1345                                         $link = $matches[1];
1346                                         break;
1347                                 }
1348                         }
1349                 }
1350                 if(! isset($link)) {
1351                         // parse the page of the supplied URL looking for rel links
1352
1353                         require_once('library/HTML5/Parser.php');
1354                         $dom = HTML5_Parser::parse($html);
1355
1356                         if($dom) {
1357                                 $items = $dom->getElementsByTagName('link');
1358
1359                                 foreach($items as $item) {
1360                                         $x = $item->getAttribute('rel');
1361                                         if($x == "lrdd") {
1362                                                 $link = $item->getAttribute('href');
1363                                                 break;
1364                                         }
1365                                 }
1366                         }
1367                 }
1368
1369                 if(isset($link))
1370                         return(fetch_xrd_links($link));
1371         }
1372         return array();
1373 }}
1374
1375
1376
1377 // Given a host name, locate the LRDD template from that
1378 // host. Returns the LRDD template or an empty string on
1379 // error/failure.
1380
1381 if(! function_exists('fetch_lrdd_template')) {
1382 function fetch_lrdd_template($host) {
1383         $tpl = '';
1384         $url = 'http://' . $host . '/.well-known/host-meta' ;
1385         $links = fetch_xrd_links($url);
1386         if(count($links)) {
1387                 foreach($links as $link)
1388                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1389                                 $tpl = $link['@attributes']['template'];
1390         }
1391         if(! strpos($tpl,'{uri}'))
1392                 $tpl = '';
1393         return $tpl;
1394 }}
1395
1396 // Given a URL, retrieve the page as an XRD document.
1397 // Return an array of links.
1398 // on error/failure return empty array.
1399
1400 if(! function_exists('fetch_xrd_links')) {
1401 function fetch_xrd_links($url) {
1402
1403
1404         $xml = fetch_url($url);
1405         if (! $xml)
1406                 return array();
1407
1408         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1409         $h = simplexml_load_string($xml);
1410         $arr = convert_xml_element_to_array($h);
1411
1412         if(isset($arr['xrd']['link'])) {
1413                 $link = $arr['xrd']['link'];
1414                 if(! isset($link[0]))
1415                         $links = array($link);
1416                 else
1417                         $links = $link;
1418                 return $links;
1419         }
1420         return array();
1421 }}
1422
1423 // Convert an ACL array to a storable string
1424
1425 if(! function_exists('perms2str')) {
1426 function perms2str($p) {
1427         $ret = '';
1428         $tmp = $p;
1429         if(is_array($tmp)) {
1430                 array_walk($tmp,'sanitise_acl');
1431                 $ret = implode('',$tmp);
1432         }
1433         return $ret;
1434 }}
1435
1436 // generate a guaranteed unique (for this domain) item ID for ATOM
1437 // safe from birthday paradox
1438
1439 if(! function_exists('item_new_uri')) {
1440 function item_new_uri($hostname,$uid) {
1441
1442         do {
1443                 $dups = false;
1444                 $hash = random_string();
1445
1446                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1447
1448                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1449                         dbesc($uri));
1450                 if(count($r))
1451                         $dups = true;
1452         } while($dups == true);
1453         return $uri;
1454 }}
1455
1456 // Generate a guaranteed unique photo ID.
1457 // safe from birthday paradox
1458
1459 if(! function_exists('photo_new_resource')) {
1460 function photo_new_resource() {
1461
1462         do {
1463                 $found = false;
1464                 $resource = hash('md5',uniqid(mt_rand(),true));
1465                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1466                         dbesc($resource)
1467                 );
1468                 if(count($r))
1469                         $found = true;
1470         } while($found == true);
1471         return $resource;
1472 }}
1473
1474
1475 // Take a URL from the wild, prepend http:// if necessary
1476 // and check DNS to see if it's real
1477 // return true if it's OK, false if something is wrong with it
1478
1479 if(! function_exists('validate_url')) {
1480 function validate_url(&$url) {
1481         if(substr($url,0,4) != 'http')
1482                 $url = 'http://' . $url;
1483         $h = parse_url($url);
1484
1485         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1486                 return true;
1487         }
1488         return false;
1489 }}
1490
1491 // checks that email is an actual resolvable internet address
1492
1493 if(! function_exists('validate_email')) {
1494 function validate_email($addr) {
1495
1496         if(! strpos($addr,'@'))
1497                 return false;
1498         $h = substr($addr,strpos($addr,'@') + 1);
1499
1500         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1501                 return true;
1502         }
1503         return false;
1504 }}
1505
1506 // Check $url against our list of allowed sites,
1507 // wildcards allowed. If allowed_sites is unset return true;
1508 // If url is allowed, return true.
1509 // otherwise, return false
1510
1511 if(! function_exists('allowed_url')) {
1512 function allowed_url($url) {
1513
1514         $h = parse_url($url);
1515
1516         if(! $h) {
1517                 return false;
1518         }
1519
1520         $str_allowed = get_config('system','allowed_sites');
1521         if(! $str_allowed)
1522                 return true;
1523
1524         $found = false;
1525
1526         $host = strtolower($h['host']);
1527
1528         // always allow our own site
1529
1530         if($host == strtolower($_SERVER['SERVER_NAME']))
1531                 return true;
1532
1533         $fnmatch = function_exists('fnmatch');
1534         $allowed = explode(',',$str_allowed);
1535
1536         if(count($allowed)) {
1537                 foreach($allowed as $a) {
1538                         $pat = strtolower(trim($a));
1539                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1540                                 $found = true; 
1541                                 break;
1542                         }
1543                 }
1544         }
1545         return $found;
1546 }}
1547
1548 // check if email address is allowed to register here.
1549 // Compare against our list (wildcards allowed).
1550 // Returns false if not allowed, true if allowed or if
1551 // allowed list is not configured.
1552
1553 if(! function_exists('allowed_email')) {
1554 function allowed_email($email) {
1555
1556
1557         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1558         if(! $domain)
1559                 return false;
1560
1561         $str_allowed = get_config('system','allowed_email');
1562         if(! $str_allowed)
1563                 return true;
1564
1565         $found = false;
1566
1567         $fnmatch = function_exists('fnmatch');
1568         $allowed = explode(',',$str_allowed);
1569
1570         if(count($allowed)) {
1571                 foreach($allowed as $a) {
1572                         $pat = strtolower(trim($a));
1573                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1574                                 $found = true; 
1575                                 break;
1576                         }
1577                 }
1578         }
1579         return $found;
1580 }}
1581
1582 // Format the like/dislike text for a profile item
1583 // $cnt = number of people who like/dislike the item
1584 // $arr = array of pre-linked names of likers/dislikers
1585 // $type = one of 'like, 'dislike'
1586 // $id  = item id
1587 // returns formatted text
1588
1589 if(! function_exists('format_like')) {
1590 function format_like($cnt,$arr,$type,$id) {
1591         $o = '';
1592         if($cnt == 1)
1593                 $o .= $arr[0] . (($type === 'like') ? t(' likes this.') : t(' doesn\'t like this.')) . EOL ;
1594         else {
1595                 $o .= '<span class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');" >' 
1596                         . $cnt . ' ' . t('people') . '</span> ' . (($type === 'like') ? t('like this.') : t('don\'t like this.')) . EOL ;
1597                 $total = count($arr);
1598                 if($total >= MAX_LIKERS)
1599                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1600                 if($total < MAX_LIKERS)
1601                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1602                 $str = implode(', ', $arr);
1603                 if($total >= MAX_LIKERS)
1604                         $str .= t(', and ') . $total - MAX_LIKERS . t(' other people');
1605                 $str .= (($type === 'like') ? t(' like this.') : t(' don\'t like this.'));
1606                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1607         }
1608         return $o;
1609 }}
1610
1611
1612 // wrapper to load a view template, checking for alternate
1613 // languages before falling back to the default
1614
1615 if(! function_exists('load_view_file')) {
1616 function load_view_file($s) {
1617         $b = basename($s);
1618         $d = dirname($s);
1619         $lang = get_config('system','language');
1620         if($lang === false)
1621                 $lang = 'en';
1622         if(file_exists("$d/$lang/$b"))
1623                 return file_get_contents("$d/$lang/$b");
1624         return file_get_contents($s);
1625 }}
1626
1627 // for html,xml parsing - let's say you've got
1628 // an attribute foobar="class1 class2 class3"
1629 // and you want to find out if it contains 'class3'.
1630 // you can't use a normal sub string search because you
1631 // might match 'notclass3' and a regex to do the job is 
1632 // possible but a bit complicated. 
1633 // pass the attribute string as $attr and the attribute you 
1634 // are looking for as $s - returns true if found, otherwise false
1635
1636 if(! function_exists('attribute_contains')) {
1637 function attribute_contains($attr,$s) {
1638         $a = explode(' ', $attr);
1639         if(count($a) && in_array($s,$a))
1640                 return true;
1641         return false;
1642 }}
1643
1644 if(! function_exists('logger')) {
1645 function logger($msg,$level = 0) {
1646
1647         $debugging = get_config('system','debugging');
1648         $loglevel  = intval(get_config('system','loglevel'));
1649         $logfile   = get_config('system','logfile');
1650
1651         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1652                 return;
1653         
1654         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1655         return;
1656 }}
1657
1658
1659 if(! function_exists('activity_match')) {
1660 function activity_match($haystack,$needle) {
1661         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1662                 return true;
1663         return false;
1664 }}
1665
1666
1667 // Pull out all #hashtags and @person tags from $s;
1668 // We also get @person@domain.com - which would make 
1669 // the regex quite complicated as tags can also
1670 // end a sentence. So we'll run through our results
1671 // and strip the period from any tags which end with one.
1672 // Returns array of tags found, or empty array.
1673
1674
1675 if(! function_exists('get_tags')) {
1676 function get_tags($s) {
1677         $ret = array();
1678         if(preg_match_all('/([@#][^ \x0D\x0A,:?]*)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1679                 foreach($match[1] as $match) {
1680                         if(strstr($match,"]")) {
1681                                 // we might be inside a bbcode color tag - leave it alone
1682                                 continue;
1683                         }
1684                         if(substr($match,-1,1) === '.')
1685                                 $ret[] = substr($match,0,-1);
1686                         else
1687                                 $ret[] = $match;
1688                 }
1689         }
1690
1691         return $ret;
1692 }}
1693
1694
1695 // quick and dirty quoted_printable encoding
1696
1697 if(! function_exists('qp')) {
1698 function qp($s) {
1699 return str_replace ("%","=",rawurlencode($s));
1700 }} 
1701
1702
1703 if(! function_exists('like_puller')) {
1704 function like_puller($a,$item,&$arr,$mode) {
1705
1706         $url = '';
1707         $sparkle = '';
1708         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1709
1710         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1711                 $url = $item['author-link'];
1712                 if(($item['network'] === 'dfrn') && (! $item['self']) && ($item['author-link'] == $item['url'])) {
1713                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1714                         $sparkle = ' class="sparkle" ';
1715                 }
1716                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1717                         $arr[$item['parent'] . '-l'] = array();
1718                 if(! isset($arr[$item['parent']]))
1719                         $arr[$item['parent']] = 1;
1720                 else    
1721                         $arr[$item['parent']] ++;
1722                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1723         }
1724         return;
1725 }}
1726
1727 if(! function_exists('get_mentions')) {
1728 function get_mentions($item) {
1729         $o = '';
1730         if(! strlen($item['tag']))
1731                 return $o;
1732
1733         $arr = explode(',',$item['tag']);
1734         foreach($arr as $x) {
1735                 $matches = null;
1736                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches))
1737                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1738         }
1739         return $o;
1740 }}
1741
1742 if(! function_exists('contact_block')) {
1743 function contact_block() {
1744         $o = '';
1745         $a = get_app();
1746
1747         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
1748         if(! $shown)
1749                 $shown = 24;
1750
1751         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
1752                 return $o;
1753         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
1754                         intval($a->profile['uid'])
1755         );
1756         if(count($r)) {
1757                 $total = intval($r[0]['total']);
1758         }
1759         if(! $total) {
1760                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
1761                 return $o;
1762         }
1763         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
1764                         intval($a->profile['uid']),
1765                         intval($shown)
1766         );
1767         if(count($r)) {
1768                 $o .= '<h4 class="contact-h4">' . $total . ' ' . t('Contacts') . '</h4><div id="contact-block">';
1769                 foreach($r as $rr) {
1770                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
1771                         if(local_user() && ($rr['uid'] == local_user())
1772                                 && ($rr['network'] === 'dfrn')) {
1773                                 $url = $redirect_url;
1774                                 $sparkle = ' sparkle';
1775                         }
1776                         else {
1777                                 $url = $rr['url'];
1778                                 $sparkle = '';
1779                         }
1780
1781                         $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";
1782                 }
1783                 $o .= '</div><div id="contact-block-end"></div>';
1784                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
1785                 
1786         }
1787
1788         $arr = array('contacts' => $r, 'output' => $o);
1789
1790         call_hooks('contact_block_end', $arr);
1791         return $o;
1792
1793 }}
1794
1795 if(! function_exists('search')) {
1796 function search($s) {
1797         $a = get_app();
1798         $o  = '<div id="search-box">';
1799         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
1800         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
1801         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
1802         $o .= '</form></div>';
1803         return $o;
1804 }}
1805
1806 if(! function_exists('valid_email')) {
1807 function valid_email($x){
1808         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
1809                 return true;
1810         return false;
1811 }}
1812
1813
1814 if(! function_exists('gravatar_img')) {
1815 function gravatar_img($email) {
1816         $size = 175;
1817         $opt = 'identicon';   // psuedo-random geometric pattern if not found
1818         $rating = 'pg';
1819         $hash = md5(trim(strtolower($email)));
1820         
1821         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
1822                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
1823
1824         logger('gravatar: ' . $email . ' ' . $url);
1825         return $url;
1826 }}
1827
1828 if(! function_exists('aes_decrypt')) {
1829 function aes_decrypt($val,$ky)
1830 {
1831     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1832     for($a=0;$a<strlen($ky);$a++)
1833       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1834     $mode = MCRYPT_MODE_ECB;
1835     $enc = MCRYPT_RIJNDAEL_128;
1836     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
1837     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));
1838 }}
1839
1840
1841 if(! function_exists('aes_encrypt')) {
1842 function aes_encrypt($val,$ky)
1843 {
1844     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1845     for($a=0;$a<strlen($ky);$a++)
1846       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
1847     $mode=MCRYPT_MODE_ECB;
1848     $enc=MCRYPT_RIJNDAEL_128;
1849     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
1850     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
1851 }} 
1852
1853
1854 /**
1855  *
1856  * Function: linkify
1857  *
1858  * Replace naked text hyperlink with HTML formatted hyperlink
1859  *
1860  */
1861
1862 if(! function_exists('linkify')) {
1863 function linkify($s) {
1864         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%]*)/", ' <a href="$1" >$1</a>', $s);
1865         return($s);
1866 }}
1867
1868
1869 /**
1870  * 
1871  * Function: smilies
1872  *
1873  * Description:
1874  * Replaces text emoticons with graphical images
1875  *
1876  * @Parameter: string $s
1877  *
1878  * Returns string
1879  */
1880
1881 if(! function_exists('smilies')) {
1882 function smilies($s) {
1883         $a = get_app();
1884
1885         return str_replace(
1886         array( ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
1887         array(
1888                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
1889                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
1890                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
1891                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
1892                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
1893                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
1894                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
1895                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
1896                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
1897                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
1898                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
1899         ), $s);
1900 }}
1901
1902
1903 /**
1904  *
1905  * Function : profile_load
1906  * @parameter App    $a
1907  * @parameter string $nickname
1908  * @parameter int    $profile
1909  *
1910  * Summary: Loads a profile into the page sidebar. 
1911  * The function requires a writeable copy of the main App structure, and the nickname
1912  * of a registered local account.
1913  *
1914  * If the viewer is an authenticated remote viewer, the profile displayed is the
1915  * one that has been configured for his/her viewing in the Contact manager.
1916  * Passing a non-zero profile ID can also allow a preview of a selected profile
1917  * by the owner.
1918  *
1919  * Profile information is placed in the App structure for later retrieval.
1920  * Honours the owner's chosen theme for display. 
1921  *
1922  */
1923
1924 if(! function_exists('profile_load')) {
1925 function profile_load(&$a, $nickname, $profile = 0) {
1926         if(remote_user()) {
1927                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
1928                         intval($_SESSION['visitor_id']));
1929                 if(count($r))
1930                         $profile = $r[0]['profile-id'];
1931         } 
1932
1933         $r = null;
1934
1935         if($profile) {
1936                 $profile_int = intval($profile);
1937                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1938                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1939                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
1940                         dbesc($nickname),
1941                         intval($profile_int)
1942                 );
1943         }
1944         if(! count($r)) {       
1945                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
1946                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
1947                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
1948                         dbesc($nickname)
1949                 );
1950         }
1951
1952         if(($r === false) || (! count($r))) {
1953                 notice( t('No profile') . EOL );
1954                 $a->error = 404;
1955                 return;
1956         }
1957
1958         $a->profile = $r[0];
1959
1960
1961         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
1962         $_SESSION['theme'] = $a->profile['theme'];
1963
1964         if(! (x($a->page,'aside')))
1965                 $a->page['aside'] = '';
1966
1967         $a->page['aside'] .= profile_sidebar($a->profile);
1968         $a->page['aside'] .= contact_block();
1969
1970         return;
1971 }}
1972
1973
1974 /**
1975  *
1976  * Function: profile_sidebar
1977  *
1978  * Formats a profile for display in the sidebar.
1979  * It is very difficult to templatise the HTML completely
1980  * because of all the conditional logic.
1981  *
1982  * @parameter: array $profile
1983  *
1984  * Returns HTML string stuitable for sidebar inclusion
1985  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
1986  *
1987  */
1988
1989
1990 if(! function_exists('profile_sidebar')) {
1991 function profile_sidebar($profile) {
1992
1993         $o = '';
1994         $location = '';
1995         $address = false;
1996
1997         if((! is_array($profile)) && (! count($profile)))
1998                 return $o;
1999
2000         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2001
2002         $tabs = '';
2003
2004         $photo = '<div id="profile=photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2005
2006         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2007  
2008         if((x($profile,'address') == 1) 
2009                 || (x($profile,'locality') == 1) 
2010                 || (x($profile,'region') == 1) 
2011                 || (x($profile,'postal-code') == 1) 
2012                 || (x($profile,'country-name') == 1))
2013                 $address = true;
2014
2015         if($address) {
2016                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2017                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2018                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2019                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2020                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2021                         . '<span class="region">' . $profile['region'] . '</span>'
2022                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2023                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2024                 $location .= '</div></div><div class="profile-clear"></div>';
2025
2026         }
2027
2028         $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>' : '');
2029
2030         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2031
2032         $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>' : '');
2033
2034         $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>' : '');
2035
2036         $tpl = load_view_file('view/profile_vcard.tpl');
2037
2038         $o .= replace_macros($tpl, array(
2039                 '$fullname' => $fullname,
2040                 '$tabs'     => $tabs,
2041                 '$photo'    => $photo,
2042                 '$connect'  => $connect,                
2043                 '$location' => $location,
2044                 '$gender'   => $gender,
2045                 '$pubkey'   => $pubkey,
2046                 '$marital'  => $marital,
2047                 '$homepage' => $homepage
2048         ));
2049
2050         call_hooks('profile_sidebar', $o);
2051
2052         return $o;
2053 }}
2054
2055
2056 if(! function_exists('register_hook')) {
2057 function register_hook($hook,$file,$function) {
2058
2059         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2060                 dbesc($hook),
2061                 dbesc($file),
2062                 dbesc($function)
2063         );
2064         if(count($r))
2065                 return true;
2066
2067         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2068                 dbesc($hook),
2069                 dbesc($file),
2070                 dbesc($function)
2071         );
2072         return $r;
2073 }}
2074
2075 if(! function_exists('unregister_hook')) {
2076 function unregister_hook($hook,$file,$function) {
2077
2078         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2079                 dbesc($hook),
2080                 dbesc($file),
2081                 dbesc($function)
2082         );
2083         return $r;
2084 }}
2085
2086
2087 if(! function_exists('load_hooks')) {
2088 function load_hooks() {
2089         $a = get_app();
2090         $r = q("SELECT * FROM `hook` WHERE 1");
2091         if(count($r)) {
2092                 foreach($r as $rr) {
2093                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2094                 }
2095         }
2096 }}
2097
2098
2099 if(! function_exists('call_hooks')) {
2100 function call_hooks($name, &$data = null) {
2101         $a = get_app();
2102
2103         if(count($a->hooks)) {
2104                 foreach($a->hooks as $hook) {
2105                         if($hook[HOOK_HOOK] === $name) {
2106                                 @include_once($hook[HOOK_FILE]);
2107                                 if(function_exists($hook[HOOK_FUNCTION])) {
2108                                         $func = $hook[HOOK_FUNCTION];
2109                                         $func($a,$data);
2110                                 }
2111                         }
2112                 }
2113         }
2114 }}
2115