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