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