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