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