]> git.mxchange.org Git - friendica.git/blob - boot.php
ae7d94dac6a1c5888c0cf2ab4b153af3ba62ebe4
[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.1038' );
8 define ( 'DFRN_PROTOCOL_VERSION',  '2.21'    );
9 define ( 'DB_UPDATE_VERSION',      1075      );
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                 $headers = array('Expect:');
761         } else {
762                 if(!in_array('Expect:', $headers)) {
763                         array_push($headers, 'Expect:');
764                 }
765         }
766
767         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
768
769         $check_cert = get_config('system','verifyssl');
770         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
771         $prx = get_config('system','proxy');
772         if(strlen($prx)) {
773                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
774                 curl_setopt($ch, CURLOPT_PROXY, $prx);
775                 $prxusr = get_config('system','proxyuser');
776                 if(strlen($prxusr))
777                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
778         }
779
780         $a->set_curl_code(0);
781
782         // don't let curl abort the entire application
783         // if it throws any errors.
784
785         $s = @curl_exec($ch);
786
787         $base = $s;
788         $curl_info = curl_getinfo($ch);
789         $http_code = $curl_info['http_code'];
790
791         $header = '';
792
793         // Pull out multiple headers, e.g. proxy and continuation headers
794         // allow for HTTP/2.x without fixing code
795
796         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
797                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
798                 $header .= $chunk;
799                 $base = substr($base,strlen($chunk));
800         }
801
802         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
803         $matches = array();
804         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
805         $url = trim(array_pop($matches));
806         $url_parsed = @parse_url($url);
807         if (isset($url_parsed)) {
808             $redirects++;
809             return post_url($url,$binary,$headers,$redirects);
810         }
811     }
812         $a->set_curl_code($http_code);
813         $body = substr($s,strlen($header));
814
815         $a->set_curl_headers($header);
816
817         curl_close($ch);
818         return($body);
819 }}
820
821 // random hash, 64 chars
822
823 if(! function_exists('random_string')) {
824 function random_string() {
825         return(hash('sha256',uniqid(rand(),true)));
826 }}
827
828 /**
829  * This is our primary input filter. 
830  *
831  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
832  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
833  * after cleansing, and angle chars with the high bit set could get through as markup.
834  * 
835  * This is now disabled because it was interfering with some legitimate unicode sequences 
836  * and hopefully there aren't a lot of those browsers left. 
837  *
838  * Use this on any text input where angle chars are not valid or permitted
839  * They will be replaced with safer brackets. This may be filtered further
840  * if these are not allowed either.   
841  *
842  */
843
844 if(! function_exists('notags')) {
845 function notags($string) {
846
847         return(str_replace(array("<",">"), array('[',']'), $string));
848
849 //  High-bit filter no longer used
850 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
851 }}
852
853 // use this on "body" or "content" input where angle chars shouldn't be removed,
854 // and allow them to be safely displayed.
855
856 if(! function_exists('escape_tags')) {
857 function escape_tags($string) {
858
859         return(htmlspecialchars($string));
860 }}
861
862 // wrapper for adding a login box. If $register == true provide a registration
863 // link. This will most always depend on the value of $a->config['register_policy'].
864 // returns the complete html for inserting into the page
865
866 if(! function_exists('login')) {
867 function login($register = false) {
868         $o = "";
869         $register_tpl = (($register) ? get_markup_template("register-link.tpl") : "");
870         
871         $register_html = replace_macros($register_tpl,array(
872                 '$title' => t('Create a New Account'),
873                 '$desc' => t('Register')
874         ));
875
876         $noid = get_config('system','no_openid');
877         if($noid) {
878                 $classname = 'no-openid';
879                 $namelabel = t('Nickname or Email address: ');
880                 $passlabel = t('Password: ');
881                 $login     = t('Login');
882         }
883         else {
884                 $classname = 'openid';
885                 $namelabel = t('Nickname/Email/OpenID: ');
886                 $passlabel = t("Password \x28if not OpenID\x29: ");
887                 $login     = t('Login');
888         }
889         $lostpass = t('Forgot your password?');
890         $lostlink = t('Password Reset');
891
892         if(local_user()) {
893                 $tpl = get_markup_template("logout.tpl");
894         }
895         else {
896                 $tpl = get_markup_template("login.tpl");
897
898         }
899
900         $o = '<script type="text/javascript"> $(document).ready(function() { $("#login-name").focus();} );</script>';   
901
902         $o .= replace_macros($tpl,array(
903                 '$logout'        => t('Logout'),
904                 '$register_html' => $register_html, 
905                 '$classname'     => $classname,
906                 '$namelabel'     => $namelabel,
907                 '$passlabel'     => $passlabel,
908                 '$login'         => $login,
909                 '$lostpass'      => $lostpass,
910                 '$lostlink'      => $lostlink 
911         ));
912
913         return $o;
914 }}
915
916 // generate a string that's random, but usually pronounceable. 
917 // used to generate initial passwords
918
919 if(! function_exists('autoname')) {
920 function autoname($len) {
921
922         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
923         if(mt_rand(0,5) == 4)
924                 $vowels[] = 'y';
925
926         $cons = array(
927                         'b','bl','br',
928                         'c','ch','cl','cr',
929                         'd','dr',
930                         'f','fl','fr',
931                         'g','gh','gl','gr',
932                         'h',
933                         'j',
934                         'k','kh','kl','kr',
935                         'l',
936                         'm',
937                         'n',
938                         'p','ph','pl','pr',
939                         'qu',
940                         'r','rh',
941                         's','sc','sh','sm','sp','st',
942                         't','th','tr',
943                         'v',
944                         'w','wh',
945                         'x',
946                         'z','zh'
947                         );
948
949         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
950                                 'nd','ng','nk','nt','rn','rp','rt');
951
952         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
953                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
954
955         $start = mt_rand(0,2);
956         if($start == 0)
957                 $table = $vowels;
958         else
959                 $table = $cons;
960
961         $word = '';
962
963         for ($x = 0; $x < $len; $x ++) {
964                 $r = mt_rand(0,count($table) - 1);
965                 $word .= $table[$r];
966   
967                 if($table == $vowels)
968                         $table = array_merge($cons,$midcons);
969                 else
970                         $table = $vowels;
971
972         }
973
974         $word = substr($word,0,$len);
975
976         foreach($noend as $noe) {
977                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
978                         $word = substr($word,0,-1);
979                         break;
980                 }
981         }
982         if(substr($word,-1) == 'q')
983                 $word = substr($word,0,-1);    
984         return $word;
985 }}
986
987 // Used to end the current process, after saving session state. 
988
989 if(! function_exists('killme')) {
990 function killme() {
991         session_write_close();
992         exit;
993 }}
994
995 // redirect to another URL and terminate this process.
996
997 if(! function_exists('goaway')) {
998 function goaway($s) {
999         header("Location: $s");
1000         killme();
1001 }}
1002
1003 // Generic XML return
1004 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
1005 // of $st and an optional text <message> of $message and terminates the current process. 
1006
1007 if(! function_exists('xml_status')) {
1008 function xml_status($st, $message = '') {
1009
1010         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
1011
1012         if($st)
1013                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
1014
1015         header( "Content-type: text/xml" );
1016         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
1017         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
1018         killme();
1019 }}
1020
1021 // Returns the uid of locally logged in user or false.
1022
1023 if(! function_exists('local_user')) {
1024 function local_user() {
1025         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
1026                 return intval($_SESSION['uid']);
1027         return false;
1028 }}
1029
1030 // Returns contact id of authenticated site visitor or false
1031
1032 if(! function_exists('remote_user')) {
1033 function remote_user() {
1034         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
1035                 return intval($_SESSION['visitor_id']);
1036         return false;
1037 }}
1038
1039 // contents of $s are displayed prominently on the page the next time
1040 // a page is loaded. Usually used for errors or alerts.
1041
1042 if(! function_exists('notice')) {
1043 function notice($s) {
1044         $a = get_app();
1045         if($a->interactive)
1046                 $_SESSION['sysmsg'] .= $s;
1047 }}
1048 if(! function_exists('info')) {
1049 function info($s) {
1050         $a = get_app();
1051         if($a->interactive)
1052                 $_SESSION['sysmsg_info'] .= $s;
1053 }}
1054
1055
1056 // wrapper around config to limit the text length of an incoming message
1057
1058 if(! function_exists('get_max_import_size')) {
1059 function get_max_import_size() {
1060         global $a;
1061         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
1062 }}
1063
1064
1065 // escape text ($str) for XML transport
1066 // returns escaped text.
1067
1068 if(! function_exists('xmlify')) {
1069 function xmlify($str) {
1070         $buffer = '';
1071         
1072         for($x = 0; $x < strlen($str); $x ++) {
1073                 $char = $str[$x];
1074         
1075                 switch( $char ) {
1076
1077                         case "\r" :
1078                                 break;
1079                         case "&" :
1080                                 $buffer .= '&amp;';
1081                                 break;
1082                         case "'" :
1083                                 $buffer .= '&apos;';
1084                                 break;
1085                         case "\"" :
1086                                 $buffer .= '&quot;';
1087                                 break;
1088                         case '<' :
1089                                 $buffer .= '&lt;';
1090                                 break;
1091                         case '>' :
1092                                 $buffer .= '&gt;';
1093                                 break;
1094                         case "\n" :
1095                                 $buffer .= "\n";
1096                                 break;
1097                         default :
1098                                 $buffer .= $char;
1099                                 break;
1100                 }       
1101         }
1102         $buffer = trim($buffer);
1103         return($buffer);
1104 }}
1105
1106 // undo an xmlify
1107 // pass xml escaped text ($s), returns unescaped text
1108
1109 if(! function_exists('unxmlify')) {
1110 function unxmlify($s) {
1111         $ret = str_replace('&amp;','&', $s);
1112         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
1113         return $ret;    
1114 }}
1115
1116 // convenience wrapper, reverse the operation "bin2hex"
1117
1118 if(! function_exists('hex2bin')) {
1119 function hex2bin($s) {
1120         if(! ctype_xdigit($s)) {
1121                 logger('hex2bin: illegal input: ' . print_r(debug_backtrace(), true));
1122                 return($s);
1123         }
1124
1125         return(pack("H*",$s));
1126 }}
1127
1128 // Automatic pagination.
1129 // To use, get the count of total items.
1130 // Then call $a->set_pager_total($number_items);
1131 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
1132 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
1133 // (assuming there are enough items to paginate).
1134 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
1135 // will limit the results to the correct items for the current page. 
1136 // The actual page handling is then accomplished at the application layer. 
1137
1138 if(! function_exists('paginate')) {
1139 function paginate(&$a) {
1140         $o = '';
1141         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
1142         $stripped = str_replace('q=','',$stripped);
1143         $stripped = trim($stripped,'/');
1144         $pagenum = $a->pager['page'];
1145         $url = $a->get_baseurl() . '/' . $stripped;
1146
1147
1148           if($a->pager['total'] > $a->pager['itemspage']) {
1149                 $o .= '<div class="pager">';
1150                 if($a->pager['page'] != 1)
1151                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
1152
1153                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
1154
1155                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
1156
1157                         $numstart = 1;
1158                 $numstop = $numpages;
1159
1160                 if($numpages > 14) {
1161                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
1162                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
1163                 }
1164    
1165                 for($i = $numstart; $i <= $numstop; $i++){
1166                         if($i == $a->pager['page'])
1167                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1168                         else
1169                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1170                         $o .= '</span> ';
1171                 }
1172
1173                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
1174                         if($i == $a->pager['page'])
1175                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1176                         else
1177                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1178                         $o .= '</span> ';
1179                 }
1180
1181                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
1182                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
1183
1184                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
1185                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
1186                 $o .= '</div>'."\r\n";
1187         }
1188         return $o;
1189 }}
1190
1191 // Turn user/group ACLs stored as angle bracketed text into arrays
1192
1193 if(! function_exists('expand_acl')) {
1194 function expand_acl($s) {
1195         // turn string array of angle-bracketed elements into numeric array
1196         // e.g. "<1><2><3>" => array(1,2,3);
1197         $ret = array();
1198
1199         if(strlen($s)) {
1200                 $t = str_replace('<','',$s);
1201                 $a = explode('>',$t);
1202                 foreach($a as $aa) {
1203                         if(intval($aa))
1204                                 $ret[] = intval($aa);
1205                 }
1206         }
1207         return $ret;
1208 }}              
1209
1210 // Used to wrap ACL elements in angle brackets for storage 
1211
1212 if(! function_exists('sanitise_acl')) {
1213 function sanitise_acl(&$item) {
1214         if(intval($item))
1215                 $item = '<' . intval(notags(trim($item))) . '>';
1216         else
1217                 unset($item);
1218 }}
1219
1220 // retrieve a "family" of config variables from database to cached storage
1221
1222 if(! function_exists('load_config')) {
1223 function load_config($family) {
1224         global $a;
1225         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
1226                 dbesc($family)
1227         );
1228         if(count($r)) {
1229                 foreach($r as $rr) {
1230                         $k = $rr['k'];
1231                         if ($rr['cat'] === 'config') {
1232                                 $a->config[$k] = $rr['v'];
1233                         } else {
1234                                 $a->config[$family][$k] = $rr['v'];
1235                         }
1236                 }
1237         }
1238 }}
1239
1240 // get a particular config variable given the family name
1241 // and key. Returns false if not set.
1242 // $instore is only used by the set_config function
1243 // to determine if the key already exists in the DB
1244 // If a key is found in the DB but doesn't exist in
1245 // local config cache, pull it into the cache so we don't have
1246 // to hit the DB again for this item.
1247
1248 if(! function_exists('get_config')) {
1249 function get_config($family, $key, $instore = false) {
1250
1251         global $a;
1252
1253         if(! $instore) {
1254                 if(isset($a->config[$family][$key])) {
1255                         if($a->config[$family][$key] === '!<unset>!') {
1256                                 return false;
1257                         }
1258                         return $a->config[$family][$key];
1259                 }
1260         }
1261         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1262                 dbesc($family),
1263                 dbesc($key)
1264         );
1265         if(count($ret)) {
1266                 // manage array value
1267                 $val = (preg_match("|^a:[0-9]+:{.*}$|", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']);
1268                 $a->config[$family][$key] = $val;
1269                 return $val;
1270         }
1271         else {
1272                 $a->config[$family][$key] = '!<unset>!';
1273         }
1274         return false;
1275 }}
1276
1277 // Store a config value ($value) in the category ($family)
1278 // under the key ($key)
1279 // Return the value, or false if the database update failed
1280
1281 if(! function_exists('set_config')) {
1282 function set_config($family,$key,$value) {
1283         global $a;
1284         
1285         // manage array value
1286         $dbvalue = (is_array($value)?serialize($value):$value);
1287
1288         if(get_config($family,$key,true) === false) {
1289                 $a->config[$family][$key] = $value;
1290                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1291                         dbesc($family),
1292                         dbesc($key),
1293                         dbesc($dbvalue)
1294                 );
1295                 if($ret) 
1296                         return $value;
1297                 return $ret;
1298         }
1299         
1300         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1301                 dbesc($dbvalue),
1302                 dbesc($family),
1303                 dbesc($key)
1304         );
1305
1306         $a->config[$family][$key] = $value;
1307
1308         if($ret)
1309                 return $value;
1310         return $ret;
1311 }}
1312
1313
1314 if(! function_exists('load_pconfig')) {
1315 function load_pconfig($uid,$family) {
1316         global $a;
1317         $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
1318                 dbesc($family),
1319                 intval($uid)
1320         );
1321         if(count($r)) {
1322                 foreach($r as $rr) {
1323                         $k = $rr['k'];
1324                         $a->config[$uid][$family][$k] = $rr['v'];
1325                 }
1326         }
1327 }}
1328
1329
1330
1331 if(! function_exists('get_pconfig')) {
1332 function get_pconfig($uid,$family, $key, $instore = false) {
1333
1334         global $a;
1335
1336         if(! $instore) {
1337                 if(isset($a->config[$uid][$family][$key])) {
1338                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1339                                 return false;
1340                         }
1341                         return $a->config[$uid][$family][$key];
1342                 }
1343         }
1344
1345         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1346                 intval($uid),
1347                 dbesc($family),
1348                 dbesc($key)
1349         );
1350
1351         if(count($ret)) {
1352                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1353                 return $ret[0]['v'];
1354         }
1355         else {
1356                 $a->config[$uid][$family][$key] = '!<unset>!';
1357         }
1358         return false;
1359 }}
1360
1361 if(! function_exists('del_config')) {
1362 function del_config($family,$key) {
1363
1364         global $a;
1365         if(x($a->config[$family],$key))
1366                 unset($a->config[$family][$key]);
1367         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1368                 dbesc($cat),
1369                 dbesc($key)
1370         );
1371         return $ret;
1372 }}
1373
1374
1375
1376 // Same as above functions except these are for personal config storage and take an
1377 // additional $uid argument.
1378
1379 if(! function_exists('set_pconfig')) {
1380 function set_pconfig($uid,$family,$key,$value) {
1381
1382         global $a;
1383
1384         if(get_pconfig($uid,$family,$key,true) === false) {
1385                 $a->config[$uid][$family][$key] = $value;
1386                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1387                         intval($uid),
1388                         dbesc($family),
1389                         dbesc($key),
1390                         dbesc($value)
1391                 );
1392                 if($ret) 
1393                         return $value;
1394                 return $ret;
1395         }
1396         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1397                 dbesc($value),
1398                 intval($uid),
1399                 dbesc($family),
1400                 dbesc($key)
1401         );
1402
1403         $a->config[$uid][$family][$key] = $value;
1404
1405         if($ret)
1406                 return $value;
1407         return $ret;
1408 }}
1409
1410 if(! function_exists('del_pconfig')) {
1411 function del_pconfig($uid,$family,$key) {
1412
1413         global $a;
1414         if(x($a->config[$uid][$family],$key))
1415                 unset($a->config[$uid][$family][$key]);
1416         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1417                 intval($uid),
1418                 dbesc($family),
1419                 dbesc($key)
1420         );
1421         return $ret;
1422 }}
1423
1424
1425 // convert an XML document to a normalised, case-corrected array
1426 // used by webfinger
1427
1428 if(! function_exists('convert_xml_element_to_array')) {
1429 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1430
1431         // If we're getting too deep, bail out
1432         if ($recursion_depth > 512) {
1433                 return(null);
1434         }
1435
1436         if (!is_string($xml_element) &&
1437         !is_array($xml_element) &&
1438         (get_class($xml_element) == 'SimpleXMLElement')) {
1439                 $xml_element_copy = $xml_element;
1440                 $xml_element = get_object_vars($xml_element);
1441         }
1442
1443         if (is_array($xml_element)) {
1444                 $result_array = array();
1445                 if (count($xml_element) <= 0) {
1446                         return (trim(strval($xml_element_copy)));
1447                 }
1448
1449                 foreach($xml_element as $key=>$value) {
1450
1451                         $recursion_depth++;
1452                         $result_array[strtolower($key)] =
1453                 convert_xml_element_to_array($value, $recursion_depth);
1454                         $recursion_depth--;
1455                 }
1456                 if ($recursion_depth == 0) {
1457                         $temp_array = $result_array;
1458                         $result_array = array(
1459                                 strtolower($xml_element_copy->getName()) => $temp_array,
1460                         );
1461                 }
1462
1463                 return ($result_array);
1464
1465         } else {
1466                 return (trim(strval($xml_element)));
1467         }
1468 }}
1469
1470 // Given an email style address, perform webfinger lookup and 
1471 // return the resulting DFRN profile URL, or if no DFRN profile URL
1472 // is located, returns an OStatus subscription template (prefixed 
1473 // with the string 'stat:' to identify it as on OStatus template).
1474 // If this isn't an email style address just return $s.
1475 // Return an empty string if email-style addresses but webfinger fails,
1476 // or if the resultant personal XRD doesn't contain a supported 
1477 // subscription/friend-request attribute.
1478
1479 if(! function_exists('webfinger_dfrn')) {
1480 function webfinger_dfrn($s) {
1481         if(! strstr($s,'@')) {
1482                 return $s;
1483         }
1484         $links = webfinger($s);
1485         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1486         if(count($links)) {
1487                 foreach($links as $link)
1488                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1489                                 return $link['@attributes']['href'];
1490                 foreach($links as $link)
1491                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1492                                 return 'stat:' . $link['@attributes']['template'];              
1493         }
1494         return '';
1495 }}
1496
1497 // Given an email style address, perform webfinger lookup and 
1498 // return the array of link attributes from the personal XRD file.
1499 // On error/failure return an empty array.
1500
1501
1502 if(! function_exists('webfinger')) {
1503 function webfinger($s) {
1504         $host = '';
1505         if(strstr($s,'@')) {
1506                 $host = substr($s,strpos($s,'@') + 1);
1507         }
1508         if(strlen($host)) {
1509                 $tpl = fetch_lrdd_template($host);
1510                 logger('webfinger: lrdd template: ' . $tpl);
1511                 if(strlen($tpl)) {
1512                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
1513                         logger('webfinger: pxrd: ' . $pxrd);
1514                         $links = fetch_xrd_links($pxrd);
1515                         if(! count($links)) {
1516                                 // try with double slashes
1517                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
1518                                 logger('webfinger: pxrd: ' . $pxrd);
1519                                 $links = fetch_xrd_links($pxrd);
1520                         }
1521                         return $links;
1522                 }
1523         }
1524         return array();
1525 }}
1526
1527 if(! function_exists('lrdd')) {
1528 function lrdd($uri) {
1529
1530         $a = get_app();
1531
1532         // default priority is host priority, host-meta first
1533
1534         $priority = 'host';
1535
1536         // All we have is an email address. Resource-priority is irrelevant
1537         // because our URI isn't directly resolvable.
1538
1539         if(strstr($uri,'@')) {  
1540                 return(webfinger($uri));
1541         }
1542
1543         // get the host meta file
1544
1545         $host = @parse_url($uri);
1546
1547         if($host) {
1548                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
1549                 $url .= $host['host'] . '/.well-known/host-meta' ;
1550         }
1551         else
1552                 return array();
1553
1554         logger('lrdd: constructed url: ' . $url);
1555
1556         $xml = fetch_url($url);
1557         $headers = $a->get_curl_headers();
1558
1559         if (! $xml)
1560                 return array();
1561
1562         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
1563
1564         $h = parse_xml_string($xml);
1565         if(! $h)
1566                 return array();
1567
1568         $arr = convert_xml_element_to_array($h);
1569
1570         if(isset($arr['xrd']['property'])) {
1571                 $property = $arr['crd']['property'];
1572                 if(! isset($property[0]))
1573                         $properties = array($property);
1574                 else
1575                         $properties = $property;
1576                 foreach($properties as $prop)
1577                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
1578                                 $priority = 'resource';
1579         } 
1580
1581         // save the links in case we need them
1582
1583         $links = array();
1584
1585         if(isset($arr['xrd']['link'])) {
1586                 $link = $arr['xrd']['link'];
1587                 if(! isset($link[0]))
1588                         $links = array($link);
1589                 else
1590                         $links = $link;
1591         }
1592
1593         // do we have a template or href?
1594
1595         if(count($links)) {
1596                 foreach($links as $link) {
1597                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
1598                                 if(x($link['@attributes'],'template'))
1599                                         $tpl = $link['@attributes']['template'];
1600                                 elseif(x($link['@attributes'],'href'))
1601                                         $href = $link['@attributes']['href'];
1602                         }
1603                 }               
1604         }
1605
1606         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
1607                 $tpl = '';
1608
1609         if($priority === 'host') {
1610                 if(strlen($tpl)) 
1611                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
1612                 elseif(isset($href))
1613                         $pxrd = $href;
1614                 if(isset($pxrd)) {
1615                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
1616                         $links = fetch_xrd_links($pxrd);
1617                         return $links;
1618                 }
1619
1620                 $lines = explode("\n",$headers);
1621                 if(count($lines)) {
1622                         foreach($lines as $line) {                              
1623                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1624                                         return(fetch_xrd_links($matches[1]));
1625                                         break;
1626                                 }
1627                         }
1628                 }
1629         }
1630
1631
1632         // priority 'resource'
1633
1634
1635         $html = fetch_url($uri);
1636         $headers = $a->get_curl_headers();
1637         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
1638
1639         // don't try and parse raw xml as html
1640         if(! strstr($html,'<?xml')) {
1641                 require_once('library/HTML5/Parser.php');
1642                 $dom = @HTML5_Parser::parse($html);
1643
1644                 if($dom) {
1645                         $items = $dom->getElementsByTagName('link');
1646                         foreach($items as $item) {
1647                                 $x = $item->getAttribute('rel');
1648                                 if($x == "lrdd") {
1649                                         $pagelink = $item->getAttribute('href');
1650                                         break;
1651                                 }
1652                         }
1653                 }
1654         }
1655
1656         if(isset($pagelink))
1657                 return(fetch_xrd_links($pagelink));
1658
1659         // next look in HTTP headers
1660
1661         $lines = explode("\n",$headers);
1662         if(count($lines)) {
1663                 foreach($lines as $line) {                              
1664                         // TODO alter the following regex to support multiple relations (space separated)
1665                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1666                                 $pagelink = $matches[1];
1667                                 break;
1668                         }
1669                         // don't try and run feeds through the html5 parser
1670                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
1671                                 return array();
1672                         if(stristr($html,'<rss') || stristr($html,'<feed'))
1673                                 return array();
1674                 }
1675         }
1676
1677         if(isset($pagelink))
1678                 return(fetch_xrd_links($pagelink));
1679
1680         // If we haven't found any links, return the host xrd links (which we have already fetched)
1681
1682         if(isset($links))
1683                 return $links;
1684
1685         return array();
1686
1687 }}
1688
1689
1690
1691 // Given a host name, locate the LRDD template from that
1692 // host. Returns the LRDD template or an empty string on
1693 // error/failure.
1694
1695 if(! function_exists('fetch_lrdd_template')) {
1696 function fetch_lrdd_template($host) {
1697         $tpl = '';
1698
1699         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
1700         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
1701         $links = fetch_xrd_links($url1);
1702         logger('fetch_lrdd_template from: ' . $url1);
1703         logger('template (https): ' . print_r($links,true));
1704         if(! count($links)) {
1705                 logger('fetch_lrdd_template from: ' . $url2);
1706                 $links = fetch_xrd_links($url2);
1707                 logger('template (http): ' . print_r($links,true));
1708         }
1709         if(count($links)) {
1710                 foreach($links as $link)
1711                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1712                                 $tpl = $link['@attributes']['template'];
1713         }
1714         if(! strpos($tpl,'{uri}'))
1715                 $tpl = '';
1716         return $tpl;
1717 }}
1718
1719 // Given a URL, retrieve the page as an XRD document.
1720 // Return an array of links.
1721 // on error/failure return empty array.
1722
1723 if(! function_exists('fetch_xrd_links')) {
1724 function fetch_xrd_links($url) {
1725
1726
1727         $xml = fetch_url($url);
1728         if (! $xml)
1729                 return array();
1730
1731         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1732         $h = parse_xml_string($xml);
1733         if(! $h)
1734                 return array();
1735
1736         $arr = convert_xml_element_to_array($h);
1737
1738         $links = array();
1739
1740         if(isset($arr['xrd']['link'])) {
1741                 $link = $arr['xrd']['link'];
1742                 if(! isset($link[0]))
1743                         $links = array($link);
1744                 else
1745                         $links = $link;
1746         }
1747         if(isset($arr['xrd']['alias'])) {
1748                 $alias = $arr['xrd']['alias'];
1749                 if(! isset($alias[0]))
1750                         $aliases = array($alias);
1751                 else
1752                         $aliases = $alias;
1753                 foreach($aliases as $alias) {
1754                         $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
1755                 }
1756         }
1757
1758         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
1759
1760         return $links;
1761
1762 }}
1763
1764 // Convert an ACL array to a storable string
1765
1766 if(! function_exists('perms2str')) {
1767 function perms2str($p) {
1768         $ret = '';
1769         $tmp = $p;
1770         if(is_array($tmp)) {
1771                 array_walk($tmp,'sanitise_acl');
1772                 $ret = implode('',$tmp);
1773         }
1774         return $ret;
1775 }}
1776
1777 // generate a guaranteed unique (for this domain) item ID for ATOM
1778 // safe from birthday paradox
1779
1780 if(! function_exists('item_new_uri')) {
1781 function item_new_uri($hostname,$uid) {
1782
1783         do {
1784                 $dups = false;
1785                 $hash = random_string();
1786
1787                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1788
1789                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1790                         dbesc($uri));
1791                 if(count($r))
1792                         $dups = true;
1793         } while($dups == true);
1794         return $uri;
1795 }}
1796
1797 // Generate a guaranteed unique photo ID.
1798 // safe from birthday paradox
1799
1800 if(! function_exists('photo_new_resource')) {
1801 function photo_new_resource() {
1802
1803         do {
1804                 $found = false;
1805                 $resource = hash('md5',uniqid(mt_rand(),true));
1806                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1807                         dbesc($resource)
1808                 );
1809                 if(count($r))
1810                         $found = true;
1811         } while($found == true);
1812         return $resource;
1813 }}
1814
1815
1816 // Take a URL from the wild, prepend http:// if necessary
1817 // and check DNS to see if it's real
1818 // return true if it's OK, false if something is wrong with it
1819
1820 if(! function_exists('validate_url')) {
1821 function validate_url(&$url) {
1822         if(substr($url,0,4) != 'http')
1823                 $url = 'http://' . $url;
1824         $h = @parse_url($url);
1825
1826         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1827                 return true;
1828         }
1829         return false;
1830 }}
1831
1832 // checks that email is an actual resolvable internet address
1833
1834 if(! function_exists('validate_email')) {
1835 function validate_email($addr) {
1836
1837         if(! strpos($addr,'@'))
1838                 return false;
1839         $h = substr($addr,strpos($addr,'@') + 1);
1840
1841         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1842                 return true;
1843         }
1844         return false;
1845 }}
1846
1847 // Check $url against our list of allowed sites,
1848 // wildcards allowed. If allowed_sites is unset return true;
1849 // If url is allowed, return true.
1850 // otherwise, return false
1851
1852 if(! function_exists('allowed_url')) {
1853 function allowed_url($url) {
1854
1855         $h = @parse_url($url);
1856
1857         if(! $h) {
1858                 return false;
1859         }
1860
1861         $str_allowed = get_config('system','allowed_sites');
1862         if(! $str_allowed)
1863                 return true;
1864
1865         $found = false;
1866
1867         $host = strtolower($h['host']);
1868
1869         // always allow our own site
1870
1871         if($host == strtolower($_SERVER['SERVER_NAME']))
1872                 return true;
1873
1874         $fnmatch = function_exists('fnmatch');
1875         $allowed = explode(',',$str_allowed);
1876
1877         if(count($allowed)) {
1878                 foreach($allowed as $a) {
1879                         $pat = strtolower(trim($a));
1880                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1881                                 $found = true; 
1882                                 break;
1883                         }
1884                 }
1885         }
1886         return $found;
1887 }}
1888
1889 // check if email address is allowed to register here.
1890 // Compare against our list (wildcards allowed).
1891 // Returns false if not allowed, true if allowed or if
1892 // allowed list is not configured.
1893
1894 if(! function_exists('allowed_email')) {
1895 function allowed_email($email) {
1896
1897
1898         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1899         if(! $domain)
1900                 return false;
1901
1902         $str_allowed = get_config('system','allowed_email');
1903         if(! $str_allowed)
1904                 return true;
1905
1906         $found = false;
1907
1908         $fnmatch = function_exists('fnmatch');
1909         $allowed = explode(',',$str_allowed);
1910
1911         if(count($allowed)) {
1912                 foreach($allowed as $a) {
1913                         $pat = strtolower(trim($a));
1914                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
1915                                 $found = true; 
1916                                 break;
1917                         }
1918                 }
1919         }
1920         return $found;
1921 }}
1922
1923
1924
1925 // wrapper to load a view template, checking for alternate
1926 // languages before falling back to the default
1927
1928 // obsolete, deprecated.
1929
1930 if(! function_exists('load_view_file')) {
1931 function load_view_file($s) {
1932         global $lang, $a;
1933         if(! isset($lang))
1934                 $lang = 'en';
1935         $b = basename($s);
1936         $d = dirname($s);
1937         if(file_exists("$d/$lang/$b"))
1938                 return file_get_contents("$d/$lang/$b");
1939         
1940         $theme = current_theme();
1941         
1942         if(file_exists("$d/theme/$theme/$b"))
1943                 return file_get_contents("$d/theme/$theme/$b");
1944                         
1945         return file_get_contents($s);
1946 }}
1947
1948 if(! function_exists('get_intltext_template')) {
1949 function get_intltext_template($s) {
1950         global $lang;
1951
1952         if(! isset($lang))
1953                 $lang = 'en';
1954
1955         if(file_exists("view/$lang/$s"))
1956                 return file_get_contents("view/$lang/$s");
1957         elseif(file_exists("view/en/$s"))
1958                 return file_get_contents("view/en/$s");
1959         else
1960                 return file_get_contents("view/$s");
1961 }}
1962
1963 if(! function_exists('get_markup_template')) {
1964 function get_markup_template($s) {
1965
1966         $theme = current_theme();
1967         
1968         if(file_exists("view/theme/$theme/$s"))
1969                 return file_get_contents("view/theme/$theme/$s");
1970         else
1971                 return file_get_contents("view/$s");
1972
1973 }}
1974
1975
1976
1977
1978
1979 // for html,xml parsing - let's say you've got
1980 // an attribute foobar="class1 class2 class3"
1981 // and you want to find out if it contains 'class3'.
1982 // you can't use a normal sub string search because you
1983 // might match 'notclass3' and a regex to do the job is 
1984 // possible but a bit complicated. 
1985 // pass the attribute string as $attr and the attribute you 
1986 // are looking for as $s - returns true if found, otherwise false
1987
1988 if(! function_exists('attribute_contains')) {
1989 function attribute_contains($attr,$s) {
1990         $a = explode(' ', $attr);
1991         if(count($a) && in_array($s,$a))
1992                 return true;
1993         return false;
1994 }}
1995
1996 if(! function_exists('logger')) {
1997 function logger($msg,$level = 0) {
1998         $debugging = get_config('system','debugging');
1999         $loglevel  = intval(get_config('system','loglevel'));
2000         $logfile   = get_config('system','logfile');
2001
2002         if((! $debugging) || (! $logfile) || ($level > $loglevel))
2003                 return;
2004         
2005         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
2006         return;
2007 }}
2008
2009
2010 if(! function_exists('activity_match')) {
2011 function activity_match($haystack,$needle) {
2012         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
2013                 return true;
2014         return false;
2015 }}
2016
2017
2018 // Pull out all #hashtags and @person tags from $s;
2019 // We also get @person@domain.com - which would make 
2020 // the regex quite complicated as tags can also
2021 // end a sentence. So we'll run through our results
2022 // and strip the period from any tags which end with one.
2023 // Returns array of tags found, or empty array.
2024
2025
2026 if(! function_exists('get_tags')) {
2027 function get_tags($s) {
2028         $ret = array();
2029
2030         // ignore anything in a code block
2031
2032         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
2033
2034         // Match full names against @tags including the space between first and last
2035         // We will look these up afterward to see if they are full names or not recognisable.
2036
2037         if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
2038                 foreach($match[1] as $mtch) {
2039                         if(strstr($mtch,"]")) {
2040                                 // we might be inside a bbcode color tag - leave it alone
2041                                 continue;
2042                         }
2043                         if(substr($mtch,-1,1) === '.')
2044                                 $ret[] = substr($mtch,0,-1);
2045                         else
2046                                 $ret[] = $mtch;
2047                 }
2048         }
2049
2050         // Otherwise pull out single word tags. These can be @nickname, @first_last
2051         // and #hash tags.
2052
2053         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
2054                 foreach($match[1] as $mtch) {
2055                         if(strstr($mtch,"]")) {
2056                                 // we might be inside a bbcode color tag - leave it alone
2057                                 continue;
2058                         }
2059                         if(substr($mtch,-1,1) === '.')
2060                                 $ret[] = substr($mtch,0,-1);
2061                         else
2062                                 $ret[] = $mtch;
2063                 }
2064         }
2065         return $ret;
2066 }}
2067
2068
2069 // quick and dirty quoted_printable encoding
2070
2071 if(! function_exists('qp')) {
2072 function qp($s) {
2073 return str_replace ("%","=",rawurlencode($s));
2074 }} 
2075
2076
2077
2078 if(! function_exists('get_mentions')) {
2079 function get_mentions($item) {
2080         $o = '';
2081         if(! strlen($item['tag']))
2082                 return $o;
2083
2084         $arr = explode(',',$item['tag']);
2085         foreach($arr as $x) {
2086                 $matches = null;
2087                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
2088                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
2089                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
2090                 }
2091         }
2092         return $o;
2093 }}
2094
2095 if(! function_exists('contact_block')) {
2096 function contact_block() {
2097         $o = '';
2098         $a = get_app();
2099
2100         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
2101         if(! $shown)
2102                 $shown = 24;
2103
2104         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
2105                 return $o;
2106         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
2107                         intval($a->profile['uid'])
2108         );
2109         if(count($r)) {
2110                 $total = intval($r[0]['total']);
2111         }
2112         if(! $total) {
2113                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
2114                 return $o;
2115         }
2116         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
2117                         intval($a->profile['uid']),
2118                         intval($shown)
2119         );
2120         if(count($r)) {
2121                 $o .= '<h4 class="contact-h4">' .  sprintf( tt('%d Contact','%d Contacts', $total),$total) . '</h4><div id="contact-block">';
2122                 foreach($r as $rr) {
2123                         $o .= micropro($rr,true,'mpfriend');
2124                 }
2125                 $o .= '</div><div id="contact-block-end"></div>';
2126                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
2127                 
2128         }
2129
2130         $arr = array('contacts' => $r, 'output' => $o);
2131
2132         call_hooks('contact_block_end', $arr);
2133         return $o;
2134
2135 }}
2136
2137 if(! function_exists('micropro')) {
2138 function micropro($contact, $redirect = false, $class = '', $textmode = false) {
2139
2140         if($class)
2141                 $class = ' ' . $class;
2142
2143         $url = $contact['url'];
2144         $sparkle = '';
2145
2146         if($redirect) {
2147                 $a = get_app();
2148                 $redirect_url = $a->get_baseurl() . '/redir/' . $contact['id'];
2149                 if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === 'dfrn')) {
2150                         $url = $redirect_url;
2151                         $sparkle = ' sparkle';
2152                 }
2153         }
2154         $click = ((x($contact,'click')) ? ' onclick="' . $contact['click'] . '" ' : '');
2155         if($click)
2156                 $url = '';
2157         if($textmode) {
2158                 return '<div class="contact-block-textdiv' . $class . '"><a class="contact-block-link' . $class . $sparkle 
2159                         . (($click) ? ' fakelink' : '') . '" '
2160                         . (($url) ? ' href="' . $url . '"' : '') . $click
2161                         . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
2162                         . '" >'. $contact['name'] . '</a></div>' . "\r\n";
2163         }
2164         else {
2165                 return '<div class="contact-block-div' . $class . '"><a class="contact-block-link' . $class . $sparkle 
2166                         . (($click) ? ' fakelink' : '') . '" '
2167                         . (($url) ? ' href="' . $url . '"' : '') . $click . ' ><img class="contact-block-img' . $class . $sparkle . '" src="' 
2168                         . $contact['micro'] . '" title="' . $contact['name'] . ' [' . $contact['url'] . ']" alt="' . $contact['name'] 
2169                         . '" /></a></div>' . "\r\n";
2170         }
2171 }}
2172
2173
2174
2175 if(! function_exists('search')) {
2176 function search($s,$id='search-box',$url='/search') {
2177         $a = get_app();
2178         $o  = '<div id="' . $id . '">';
2179         $o .= '<form action="' . $a->get_baseurl() . $url . '" method="get" >';
2180         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2181         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2182         $o .= '</form></div>';
2183         return $o;
2184 }}
2185
2186 if(! function_exists('valid_email')) {
2187 function valid_email($x){
2188         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2189                 return true;
2190         return false;
2191 }}
2192
2193
2194 if(! function_exists('gravatar_img')) {
2195 function gravatar_img($email) {
2196         $size = 175;
2197         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2198         $rating = 'pg';
2199         $hash = md5(trim(strtolower($email)));
2200         
2201         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2202                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2203
2204         logger('gravatar: ' . $email . ' ' . $url);
2205         return $url;
2206 }}
2207
2208 if(! function_exists('aes_decrypt')) {
2209 function aes_decrypt($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     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2217     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));
2218 }}
2219
2220
2221 if(! function_exists('aes_encrypt')) {
2222 function aes_encrypt($val,$ky)
2223 {
2224     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2225     for($a=0;$a<strlen($ky);$a++)
2226       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2227     $mode=MCRYPT_MODE_ECB;
2228     $enc=MCRYPT_RIJNDAEL_128;
2229     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2230     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2231 }} 
2232
2233
2234 /**
2235  *
2236  * Function: linkify
2237  *
2238  * Replace naked text hyperlink with HTML formatted hyperlink
2239  *
2240  */
2241
2242 if(! function_exists('linkify')) {
2243 function linkify($s) {
2244         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2245         return($s);
2246 }}
2247
2248
2249 /**
2250  * 
2251  * Function: smilies
2252  *
2253  * Description:
2254  * Replaces text emoticons with graphical images
2255  *
2256  * @Parameter: string $s
2257  *
2258  * Returns string
2259  */
2260
2261 if(! function_exists('smilies')) {
2262 function smilies($s) {
2263         $a = get_app();
2264
2265         return str_replace(
2266         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ':)', ';-)', ':-(', ':(', ':-P', ':P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2267         array(
2268                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2269                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2270                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2271                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2272                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":)" />',
2273                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2274                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2275                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2276                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2277                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":P" />',
2278                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2279                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2280                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2281                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2282                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2283                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2284         ), $s);
2285 }}
2286
2287
2288 /**
2289  *
2290  * Function : profile_load
2291  * @parameter App    $a
2292  * @parameter string $nickname
2293  * @parameter int    $profile
2294  *
2295  * Summary: Loads a profile into the page sidebar. 
2296  * The function requires a writeable copy of the main App structure, and the nickname
2297  * of a registered local account.
2298  *
2299  * If the viewer is an authenticated remote viewer, the profile displayed is the
2300  * one that has been configured for his/her viewing in the Contact manager.
2301  * Passing a non-zero profile ID can also allow a preview of a selected profile
2302  * by the owner.
2303  *
2304  * Profile information is placed in the App structure for later retrieval.
2305  * Honours the owner's chosen theme for display. 
2306  *
2307  */
2308
2309 if(! function_exists('profile_load')) {
2310 function profile_load(&$a, $nickname, $profile = 0) {
2311         if(remote_user()) {
2312                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2313                         intval($_SESSION['visitor_id']));
2314                 if(count($r))
2315                         $profile = $r[0]['profile-id'];
2316         } 
2317
2318         $r = null;
2319
2320         if($profile) {
2321                 $profile_int = intval($profile);
2322                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2323                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2324                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2325                         dbesc($nickname),
2326                         intval($profile_int)
2327                 );
2328         }
2329         if(! count($r)) {       
2330                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2331                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2332                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2333                         dbesc($nickname)
2334                 );
2335         }
2336
2337         if(($r === false) || (! count($r))) {
2338                 notice( t('No profile') . EOL );
2339                 $a->error = 404;
2340                 return;
2341         }
2342
2343         $a->profile = $r[0];
2344
2345
2346         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2347         $_SESSION['theme'] = $a->profile['theme'];
2348
2349         if(! (x($a->page,'aside')))
2350                 $a->page['aside'] = '';
2351
2352         $a->page['aside'] .= profile_sidebar($a->profile);
2353         $a->page['aside'] .= contact_block();
2354
2355         return;
2356 }}
2357
2358
2359 /**
2360  *
2361  * Function: profile_sidebar
2362  *
2363  * Formats a profile for display in the sidebar.
2364  * It is very difficult to templatise the HTML completely
2365  * because of all the conditional logic.
2366  *
2367  * @parameter: array $profile
2368  *
2369  * Returns HTML string stuitable for sidebar inclusion
2370  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2371  *
2372  */
2373
2374
2375 if(! function_exists('profile_sidebar')) {
2376 function profile_sidebar($profile) {
2377
2378         $o = '';
2379         $location = '';
2380         $address = false;
2381
2382         if((! is_array($profile)) && (! count($profile)))
2383                 return $o;
2384
2385         call_hooks('profile_sidebar_enter', $profile);
2386
2387         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2388
2389         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2390
2391         $tabs = '';
2392
2393         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2394
2395         // don't show connect link to yourself
2396         
2397         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2398
2399         // don't show connect link to authenticated visitors either
2400
2401         if((remote_user()) && ($_SESSION['visitor_visiting'] == $profile['uid']))
2402                 $connect = ''; 
2403
2404         if((x($profile,'address') == 1) 
2405                 || (x($profile,'locality') == 1) 
2406                 || (x($profile,'region') == 1) 
2407                 || (x($profile,'postal-code') == 1) 
2408                 || (x($profile,'country-name') == 1))
2409                 $address = true;
2410
2411         if($address) {
2412                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2413                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2414                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2415                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2416                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2417                         . '<span class="region">' . $profile['region'] . '</span>'
2418                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2419                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2420                 $location .= '</div></div><div class="profile-clear"></div>';
2421
2422         }
2423
2424         $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>' : '');
2425
2426         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2427
2428         $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>' : '');
2429
2430         $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>' : '');
2431
2432         if($profile['hidewall'] && (! local_user()) && (! remote_user())) {
2433                 $location = $gender = $marital = $homepage = '';
2434         }
2435
2436         $tpl = get_markup_template('profile_vcard.tpl');
2437
2438         $o .= replace_macros($tpl, array(
2439                 '$fullname' => $fullname,
2440                 '$pdesc'    => $pdesc,
2441                 '$tabs'     => $tabs,
2442                 '$photo'    => $photo,
2443                 '$connect'  => $connect,                
2444                 '$location' => $location,
2445                 '$gender'   => $gender,
2446                 '$pubkey'   => $pubkey,
2447                 '$marital'  => $marital,
2448                 '$homepage' => $homepage
2449         ));
2450
2451
2452         $arr = array('profile' => &$profile, 'entry' => &$o);
2453
2454         call_hooks('profile_sidebar', $arr);
2455
2456         return $o;
2457 }}
2458
2459
2460 if(! function_exists('register_hook')) {
2461 function register_hook($hook,$file,$function) {
2462
2463         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2464                 dbesc($hook),
2465                 dbesc($file),
2466                 dbesc($function)
2467         );
2468         if(count($r))
2469                 return true;
2470
2471         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2472                 dbesc($hook),
2473                 dbesc($file),
2474                 dbesc($function)
2475         );
2476         return $r;
2477 }}
2478
2479 if(! function_exists('unregister_hook')) {
2480 function unregister_hook($hook,$file,$function) {
2481
2482         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2483                 dbesc($hook),
2484                 dbesc($file),
2485                 dbesc($function)
2486         );
2487         return $r;
2488 }}
2489
2490
2491 if(! function_exists('load_hooks')) {
2492 function load_hooks() {
2493         $a = get_app();
2494         $a->hooks = array();
2495         $r = q("SELECT * FROM `hook` WHERE 1");
2496         if(count($r)) {
2497                 foreach($r as $rr) {
2498                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2499                 }
2500         }
2501 }}
2502
2503
2504 if(! function_exists('call_hooks')) {
2505 function call_hooks($name, &$data = null) {
2506         $a = get_app();
2507
2508         if(count($a->hooks)) {
2509                 foreach($a->hooks as $hook) {
2510                         if($hook[HOOK_HOOK] === $name) {
2511                                 @include_once($hook[HOOK_FILE]);
2512                                 if(function_exists($hook[HOOK_FUNCTION])) {
2513                                         $func = $hook[HOOK_FUNCTION];
2514                                         $func($a,$data);
2515                                 }
2516                         }
2517                 }
2518         }
2519 }}
2520
2521
2522 if(! function_exists('day_translate')) {
2523 function day_translate($s) {
2524         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2525                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2526                 $s);
2527
2528         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2529                 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')),
2530                 $ret);
2531
2532         return $ret;
2533 }}
2534
2535 if(! function_exists('get_birthdays')) {
2536 function get_birthdays() {
2537
2538         $a = get_app();
2539         $o = '';
2540
2541         if(! local_user())
2542                 return $o;
2543
2544         $bd_format = t('g A l F d') ; // 8 AM Friday January 18
2545
2546         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2547                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2548                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2549                 ORDER BY `start` DESC ",
2550                 intval(local_user()),
2551                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2552                 dbesc(datetime_convert('UTC','UTC','now'))
2553         );
2554
2555         if($r && count($r)) {
2556                 $total = 0;
2557                 foreach($r as $rr)
2558                         if(strlen($rr['name']))
2559                                 $total ++;
2560
2561                 if($total) {
2562                         $o .= '<div id="birthday-notice" class="birthday-notice fakelink" onclick=openClose(\'birthday-wrapper\'); >' . t('Birthday Reminders') . ' ' . '(' . $total . ')' . '</div>'; 
2563                         $o .= '<div id="birthday-wrapper" style="display: none;" ><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2564                         $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2565                         $o .= '<div id="birthday-title-end"></div>';
2566
2567                         foreach($r as $rr) {
2568                                 if(! strlen($rr['name']))
2569                                         continue;
2570                                 $now = strtotime('now');
2571                                 $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2572         
2573                                 $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2574                                 . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2575                                 . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2576                                 . '</div>' ;
2577                         }
2578                         $o .= '</div></div>';
2579                 }
2580         }
2581         return $o;
2582 }}
2583
2584
2585 if(! function_exists('normalise_link')) {
2586 function normalise_link($url) {
2587         $ret = str_replace(array('https:','//www.'), array('http:','//'), $url);
2588         return(rtrim($ret,'/'));
2589 }}
2590
2591 /**
2592  *
2593  * Compare two URLs to see if they are the same, but ignore
2594  * slight but hopefully insignificant differences such as if one 
2595  * is https and the other isn't, or if one is www.something and 
2596  * the other isn't - and also ignore case differences.
2597  *
2598  * Return true if the URLs match, otherwise false.
2599  *
2600  */
2601
2602 if(! function_exists('link_compare')) {
2603 function link_compare($a,$b) {
2604         if(strcasecmp(normalise_link($a),normalise_link($b)) === 0)
2605                 return true;
2606         return false;
2607 }}
2608
2609
2610 if(! function_exists('prepare_body')) {
2611 function prepare_body($item,$attach = false) {
2612
2613         $s = prepare_text($item['body']);
2614         if(! $attach)
2615                 return $s;
2616
2617         $arr = explode(',',$item['attach']);
2618         if(count($arr)) {
2619                 $s .= '<div class="body-attach">';
2620                 foreach($arr as $r) {
2621                         $matches = false;
2622                         $icon = '';
2623                         $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
2624                         if($cnt) {
2625                                 $icontype = strtolower(substr($matches[3],0,strpos($matches[3],'/')));
2626                                 switch($icontype) {
2627                                         case 'video':
2628                                         case 'audio':
2629                                         case 'image':
2630                                         case 'text':
2631                                                 $icon = '<div class="attachtype type-' . $icontype . '"></div>';
2632                                                 break;
2633                                         default:
2634                                                 $icon = '<div class="attachtype type-unkn"></div>';
2635                                                 break;
2636                                 }
2637                                 $title = ((strlen(trim($matches[4]))) ? escape_tags(trim($matches[4])) : escape_tags($matches[1]));
2638                                 $title .= ' ' . $matches[2] . ' ' . t('bytes');
2639
2640                                 $s .= '<a href="' . strip_tags($matches[1]) . '" title="' . $title . '" class="attachlink" target="external-link" >' . $icon . '</a>';
2641                         }
2642                 }
2643                 $s .= '<div class="clear"></div></div>';
2644         }
2645         return $s;
2646 }}
2647
2648 if(! function_exists('prepare_text')) {
2649 function prepare_text($text) {
2650
2651         require_once('include/bbcode.php');
2652
2653         $s = smilies(bbcode($text));
2654
2655         return $s;
2656 }}
2657
2658 /**
2659  * 
2660  * Wrap calls to proc_close(proc_open()) and call hook
2661  * so plugins can take part in process :)
2662  * 
2663  * args:
2664  * $cmd program to run
2665  *  next args are passed as $cmd command line
2666  * 
2667  * e.g.: proc_run("ls","-la","/tmp");
2668  * 
2669  * $cmd and string args are surrounded with ""
2670  */
2671
2672 if(! function_exists('proc_run')) {
2673 function proc_run($cmd){
2674
2675         $a = get_app();
2676
2677         $args = func_get_args();
2678         call_hooks("proc_run", $args);
2679
2680         if(count($args) && $args[0] === 'php')
2681         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2682         
2683         foreach ($args as $arg){
2684                 $arg = escapeshellarg($arg);
2685         }
2686         $cmdline = implode($args," ");
2687         proc_close(proc_open($cmdline." &",array(),$foo));
2688 }}
2689
2690 if(! function_exists('current_theme')) {
2691 function current_theme(){
2692         $app_base_themes = array('duepuntozero', 'loozah');
2693         
2694         $a = get_app();
2695         
2696         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2697         $theme_name = ((is_array($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2698         
2699         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2700                 return($theme_name);
2701         
2702         foreach($app_base_themes as $t) {
2703                 if(file_exists('view/theme/' . $t . '/style.css'))
2704                         return($t);
2705         }
2706         
2707         $fallback = glob('view/theme/*/style.css');
2708         if(count($fallback))
2709                 return (str_replace('view/theme/','', str_replace("/style.css","",$fallback[0])));
2710
2711 }}
2712
2713 /*
2714 * Return full URL to theme which is currently in effect.
2715 * Provide a sane default if nothing is chosen or the specified theme does not exist.
2716 */
2717 if(! function_exists('current_theme_url')) {
2718 function current_theme_url() {
2719         global $a;
2720         $t = current_theme();
2721         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css');
2722 }}
2723
2724 if(! function_exists('feed_birthday')) {
2725 function feed_birthday($uid,$tz) {
2726
2727         /**
2728          *
2729          * Determine the next birthday, but only if the birthday is published
2730          * in the default profile. We _could_ also look for a private profile that the
2731          * recipient can see, but somebody could get mad at us if they start getting
2732          * public birthday greetings when they haven't made this info public. 
2733          *
2734          * Assuming we are able to publish this info, we are then going to convert
2735          * the start time from the owner's timezone to UTC. 
2736          *
2737          * This will potentially solve the problem found with some social networks
2738          * where birthdays are converted to the viewer's timezone and salutations from
2739          * elsewhere in the world show up on the wrong day. We will convert it to the
2740          * viewer's timezone also, but first we are going to convert it from the birthday
2741          * person's timezone to GMT - so the viewer may find the birthday starting at
2742          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2743          *
2744          */
2745
2746         $birthday = '';
2747
2748         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2749                 intval($uid)
2750         );
2751
2752         if($p && count($p)) {
2753                 $tmp_dob = substr($p[0]['dob'],5);
2754                 if(intval($tmp_dob)) {
2755                         $y = datetime_convert($tz,$tz,'now','Y');
2756                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2757                         $t_dob = strtotime($bd);
2758                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2759                         if($t_dob < $now)
2760                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2761                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2762                 }
2763         }
2764
2765         return $birthday;
2766 }}
2767
2768 /**
2769  * return atom link elements for all of our hubs
2770  */
2771
2772 if(! function_exists('feed_hublinks')) {
2773 function feed_hublinks() {
2774
2775         $hub = get_config('system','huburl');
2776
2777         $hubxml = '';
2778         if(strlen($hub)) {
2779                 $hubs = explode(',', $hub);
2780                 if(count($hubs)) {
2781                         foreach($hubs as $h) {
2782                                 $h = trim($h);
2783                                 if(! strlen($h))
2784                                         continue;
2785                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2786                         }
2787                 }
2788         }
2789         return $hubxml;
2790 }}
2791
2792 /* return atom link elements for salmon endpoints */
2793
2794 if(! function_exists('feed_salmonlinks')) {
2795 function feed_salmonlinks($nick) {
2796
2797         $a = get_app();
2798
2799         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2800
2801         // old style links that status.net still needed as of 12/2010 
2802
2803         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2804         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2805         return $salmon;
2806 }}
2807
2808 if(! function_exists('get_plink')) {
2809 function get_plink($item) {
2810         $a = get_app(); 
2811         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2812                         . $item['plink'] . '" title="' . t('link to source') . '" target="external-link" class="icon remote-link"></a></div>' : '');
2813         return $plink;
2814 }}
2815
2816 if(! function_exists('unamp')) {
2817 function unamp($s) {
2818         return str_replace('&amp;', '&', $s);
2819 }}
2820
2821
2822
2823
2824 if(! function_exists('lang_selector')) {
2825 function lang_selector() {
2826         global $lang;
2827         $o = '<div id="lang-select-icon" class="icon language" title="' . t('Select an alternate language') . '" onclick="openClose(\'language-selector\');" ></div>';
2828         $o .= '<div id="language-selector" style="display: none;" >';
2829         $o .= '<form action="" method="post" ><select name="system_language" onchange="this.form.submit();" >';
2830         $langs = glob('view/*/strings.php');
2831         if(is_array($langs) && count($langs)) {
2832                 if(! in_array('view/en/strings.php',$langs))
2833                         $langs[] = 'view/en/';
2834                 asort($langs);
2835                 foreach($langs as $l) {
2836                         $ll = substr($l,5);
2837                         $ll = substr($ll,0,strrpos($ll,'/'));
2838                         $selected = (($ll === $lang) ? ' selected="selected" ' : '');
2839                         $o .= '<option value="' . $ll . '"' . $selected . '>' . $ll . '</option>';
2840                 }
2841         }
2842         $o .= '</select></form></div>';
2843         return $o;
2844 }}
2845
2846
2847 if(! function_exists('parse_xml_string')) {
2848 function parse_xml_string($s,$strict = true) {
2849         if($strict) {
2850                 if(! strstr($s,'<?xml'))
2851                         return false;
2852                 $s2 = substr($s,strpos($s,'<?xml'));
2853         }
2854         else
2855                 $s2 = $s;
2856         libxml_use_internal_errors(true);
2857
2858         $x = @simplexml_load_string($s2);
2859         if(! $x) {
2860                 logger('libxml: parse: error: ' . $s2, LOGGER_DATA);
2861                 foreach(libxml_get_errors() as $err)
2862                         logger('libxml: parse: ' . $err->code." at ".$err->line.":".$err->column." : ".$err->message, LOGGER_DATA);
2863                 libxml_clear_errors();
2864         }
2865         return $x;
2866 }}
2867
2868 if(! function_exists('is_site_admin')) {
2869 function is_site_admin() {
2870         $a = get_app();
2871         if(local_user() && x($a->user,'email') && x($a->config,'admin_email') && ($a->user['email'] === $a->config['admin_email']))
2872                 return true;
2873         return false;
2874 }}
2875
2876 /*
2877  * parse plugin comment in search of plugin infos.
2878  * like
2879  *      
2880  *       * Name: Plugin
2881  *   * Description: A plugin which plugs in
2882  *       * Version: 1.2.3
2883  *   * Author: John <profile url>
2884  *   * Author: Jane <email>
2885  *   *
2886  */
2887
2888 if (! function_exists('get_plugin_info')){
2889 function get_plugin_info($plugin){
2890         if (!is_file("addon/$plugin/$plugin.php")) return false;
2891         
2892         $f = file_get_contents("addon/$plugin/$plugin.php");
2893         $r = preg_match("|/\*.*\*/|msU", $f, $m);
2894         
2895         $info=Array(
2896                 'name' => $plugin,
2897                 'description' => "",
2898                 'author' => array(),
2899                 'version' => ""
2900         );
2901         
2902         if ($r){
2903                 $ll = explode("\n", $m[0]);
2904                 foreach( $ll as $l ) {
2905                         $l = trim($l,"\t\n\r */");
2906                         if ($l!=""){
2907                                 list($k,$v) = array_map("trim", explode(":",$l,2));
2908                                 $k= strtolower($k);
2909                                 if ($k=="author"){
2910                                         $r=preg_match("|([^<]+)<([^>]+)>|", $v, $m);
2911                                         if ($r) {
2912                                                 $info['author'][] = array('name'=>$m[1], 'link'=>$m[2]);
2913                                         } else {
2914                                                 $info['author'][] = array('name'=>$v);
2915                                         }
2916                                 } else {
2917                                         if (array_key_exists($k,$info)){
2918                                                 $info[$k]=$v;
2919                                         }
2920                                 }
2921                                 
2922                         }
2923                 }
2924                 
2925         }
2926         return $info;
2927 }}
2928
2929 if(! function_exists('return_bytes')) {
2930 function return_bytes ($size_str) {
2931     switch (substr ($size_str, -1))
2932     {
2933         case 'M': case 'm': return (int)$size_str * 1048576;
2934         case 'K': case 'k': return (int)$size_str * 1024;
2935         case 'G': case 'g': return (int)$size_str * 1073741824;
2936         default: return $size_str;
2937     }
2938 }}
2939