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