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