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