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