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