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