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