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