]> git.mxchange.org Git - friendica.git/blob - boot.php
dbg info for photo uploads
[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.1025' );
8 define ( 'DFRN_PROTOCOL_VERSION',  '2.21'    );
9 define ( 'DB_UPDATE_VERSION',      1070      );
10
11 define ( 'EOL',                    "<br />\r\n"     );
12 define ( 'ATOM_TIME',              'Y-m-d\TH:i:s\Z' );
13 define ( 'DOWN_ARROW',             '&#x21e9;'       );
14
15 /**
16  *
17  * Image storage quality. Lower numbers save space at cost of image detail.
18  * For ease of upgrade, please do not change here. Change jpeg quality with 
19  * set_config('system','jpeg_quality',n) in .htconfig.php
20  * where n is netween 1 and 100, and with very poor results below about 50 
21  *
22  */
23
24 define ( 'JPEG_QUALITY',            100  );         
25
26 /**
27  * SSL redirection policies
28  */
29
30 define ( 'SSL_POLICY_NONE',         0 );
31 define ( 'SSL_POLICY_FULL',         1 );
32 define ( 'SSL_POLICY_SELFSIGN',     2 );
33
34
35 /**
36  * log levels
37  */
38
39 define ( 'LOGGER_NORMAL',          0 );
40 define ( 'LOGGER_TRACE',           1 );
41 define ( 'LOGGER_DEBUG',           2 );
42 define ( 'LOGGER_DATA',            3 );
43 define ( 'LOGGER_ALL',             4 );
44
45 /**
46  * registration policies
47  */
48
49 define ( 'REGISTER_CLOSED',        0 );
50 define ( 'REGISTER_APPROVE',       1 );
51 define ( 'REGISTER_OPEN',          2 );
52
53 /**
54  * relationship types
55  * When used in contact records, this indicates that 'uid' has 
56  * this relationship with contact['name']
57  */
58
59 define ( 'REL_VIP',        1);
60 define ( 'REL_FAN',        2);
61 define ( 'REL_BUD',        3);
62
63 /**
64  * Hook array order
65  */
66  
67 define ( 'HOOK_HOOK',      0);
68 define ( 'HOOK_FILE',      1);
69 define ( 'HOOK_FUNCTION',  2);
70
71 /**
72  *
73  * page/profile types
74  *
75  * PAGE_NORMAL is a typical personal profile account
76  * PAGE_SOAPBOX automatically approves all friend requests as REL_FAN, (readonly)
77  * PAGE_COMMUNITY automatically approves all friend requests as REL_FAN, but with 
78  *      write access to wall and comments (no email and not included in page owner's ACL lists)
79  * PAGE_FREELOVE automatically approves all friend requests as full friends (REL_BUD). 
80  *
81  */
82
83 define ( 'PAGE_NORMAL',            0 );
84 define ( 'PAGE_SOAPBOX',           1 );
85 define ( 'PAGE_COMMUNITY',         2 );
86 define ( 'PAGE_FREELOVE',          3 );
87
88 /**
89  * Network and protocol family types 
90  */
91
92 define ( 'NETWORK_DFRN',             'dfrn');    // Friendika, Mistpark, other DFRN implementations
93 define ( 'NETWORK_OSTATUS',          'stat');    // status.net, identi.ca, GNU-social, other OStatus implementations
94 define ( 'NETWORK_FEED',             'feed');    // RSS/Atom feeds with no known "post/notify" protocol
95 define ( 'NETWORK_DIASPORA',         'dspr');    // Diaspora
96 define ( 'NETWORK_MAIL',             'mail');    // IMAP/POP
97 define ( 'NETWORK_FACEBOOK',         'face');    // Facebook API     
98
99
100 /**
101  * Maximum number of "people who like (or don't like) this"  that we will list by name
102  */
103
104 define ( 'MAX_LIKERS',    75);
105
106 /**
107  * email notification options
108  */
109
110 define ( 'NOTIFY_INTRO',   0x0001 );
111 define ( 'NOTIFY_CONFIRM', 0x0002 );
112 define ( 'NOTIFY_WALL',    0x0004 );
113 define ( 'NOTIFY_COMMENT', 0x0008 );
114 define ( 'NOTIFY_MAIL',    0x0010 );
115
116 /**
117  * various namespaces we may need to parse
118  */
119
120 define ( 'NAMESPACE_DFRN' ,           'http://purl.org/macgirvin/dfrn/1.0' ); 
121 define ( 'NAMESPACE_THREAD' ,         'http://purl.org/syndication/thread/1.0' );
122 define ( 'NAMESPACE_TOMB' ,           'http://purl.org/atompub/tombstones/1.0' );
123 define ( 'NAMESPACE_ACTIVITY',        'http://activitystrea.ms/spec/1.0/' );
124 define ( 'NAMESPACE_ACTIVITY_SCHEMA', 'http://activitystrea.ms/schema/1.0/' );
125 define ( 'NAMESPACE_MEDIA',           'http://purl.org/syndication/atommedia' );
126 define ( 'NAMESPACE_SALMON_ME',       'http://salmon-protocol.org/ns/magic-env' );
127 define ( 'NAMESPACE_OSTATUSSUB',      'http://ostatus.org/schema/1.0/subscribe' );
128 define ( 'NAMESPACE_GEORSS',          'http://www.georss.org/georss' );
129 define ( 'NAMESPACE_POCO',            'http://portablecontacts.net/spec/1.0' );
130 define ( 'NAMESPACE_FEED',            'http://schemas.google.com/g/2010#updates-from' );
131 define ( 'NAMESPACE_OSTATUS',         'http://ostatus.org/schema/1.0' );
132 define ( 'NAMESPACE_STATUSNET',       'http://status.net/schema/api/1/' );
133
134 /**
135  * activity stream defines
136  */
137
138 define ( 'ACTIVITY_LIKE',        NAMESPACE_ACTIVITY_SCHEMA . 'like' );
139 define ( 'ACTIVITY_DISLIKE',     NAMESPACE_DFRN            . '/dislike' );
140 define ( 'ACTIVITY_OBJ_HEART',   NAMESPACE_DFRN            . '/heart' );
141
142 define ( 'ACTIVITY_FRIEND',      NAMESPACE_ACTIVITY_SCHEMA . 'make-friend' );
143 define ( 'ACTIVITY_FOLLOW',      NAMESPACE_ACTIVITY_SCHEMA . 'follow' );
144 define ( 'ACTIVITY_UNFOLLOW',    NAMESPACE_ACTIVITY_SCHEMA . 'stop-following' );
145 define ( 'ACTIVITY_POST',        NAMESPACE_ACTIVITY_SCHEMA . 'post' );
146 define ( 'ACTIVITY_UPDATE',      NAMESPACE_ACTIVITY_SCHEMA . 'update' );
147 define ( 'ACTIVITY_TAG',         NAMESPACE_ACTIVITY_SCHEMA . 'tag' );
148
149 define ( 'ACTIVITY_OBJ_COMMENT', NAMESPACE_ACTIVITY_SCHEMA . 'comment' );
150 define ( 'ACTIVITY_OBJ_NOTE',    NAMESPACE_ACTIVITY_SCHEMA . 'note' );
151 define ( 'ACTIVITY_OBJ_PERSON',  NAMESPACE_ACTIVITY_SCHEMA . 'person' );
152 define ( 'ACTIVITY_OBJ_PHOTO',   NAMESPACE_ACTIVITY_SCHEMA . 'photo' );
153 define ( 'ACTIVITY_OBJ_P_PHOTO', NAMESPACE_ACTIVITY_SCHEMA . 'profile-photo' );
154 define ( 'ACTIVITY_OBJ_ALBUM',   NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' );
155 define ( 'ACTIVITY_OBJ_EVENT',   NAMESPACE_ACTIVITY_SCHEMA . 'event' );
156
157 /**
158  * item weight for query ordering
159  */
160
161 define ( 'GRAVITY_PARENT',       0);
162 define ( 'GRAVITY_LIKE',         3);
163 define ( 'GRAVITY_COMMENT',      6);
164
165 /**
166  *
167  * Reverse the effect of magic_quotes_gpc if it is enabled.
168  * Please disable magic_quotes_gpc so we don't have to do this.
169  * See http://php.net/manual/en/security.magicquotes.disabling.php
170  *
171  */
172
173 if (get_magic_quotes_gpc()) {
174     $process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
175     while (list($key, $val) = each($process)) {
176         foreach ($val as $k => $v) {
177             unset($process[$key][$k]);
178             if (is_array($v)) {
179                 $process[$key][stripslashes($k)] = $v;
180                 $process[] = &$process[$key][stripslashes($k)];
181             } else {
182                 $process[$key][stripslashes($k)] = stripslashes($v);
183             }
184         }
185     }
186     unset($process);
187 }
188
189 /*
190  * translation system
191  */
192 require_once("include/pgettext.php");
193
194
195 /**
196  *
197  * class: App
198  *
199  * Our main application structure for the life of this page
200  * Primarily deals with the URL that got us here
201  * and tries to make some sense of it, and 
202  * stores our page contents and config storage
203  * and anything else that might need to be passed around 
204  * before we spit the page out. 
205  *
206  */
207
208 if(! class_exists('App')) {
209 class App {
210
211         public  $module_loaded = false;
212         public  $query_string;
213         public  $config;
214         public  $page;
215         public  $profile;
216         public  $user;
217         public  $cid;
218         public  $contact;
219         public  $contacts;
220         public  $page_contact;
221         public  $content;
222         public  $data;
223         public  $error = false;
224         public  $cmd;
225         public  $argv;
226         public  $argc;
227         public  $module;
228         public  $pager;
229         public  $strings;   
230         public  $path;
231         public  $hooks;
232         public  $timezone;
233         public  $interactive = true;
234         public  $plugins;
235         public  $apps;
236         public  $identities;
237
238         private $scheme;
239         private $hostname;
240         private $baseurl;
241         private $db;
242
243         private $curl_code;
244         private $curl_headers;
245
246         function __construct() {
247
248                 $this->config = array();
249                 $this->page = array();
250                 $this->pager= array();
251
252                 $this->query_string = '';
253
254                 $this->scheme = ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']))      ?  'https' : 'http' );
255
256                 if(x($_SERVER,'SERVER_NAME')) {
257                         $this->hostname = $_SERVER['SERVER_NAME'];
258
259                         /** 
260                          * Figure out if we are running at the top of a domain
261                          * or in a sub-directory and adjust accordingly
262                          */
263
264                         $path = trim(dirname($_SERVER['SCRIPT_NAME']),'/\\');
265                         if(isset($path) && strlen($path) && ($path != $this->path))
266                                 $this->path = $path;
267                 }
268
269                 set_include_path("include/$this->hostname" . PATH_SEPARATOR . 'include' . PATH_SEPARATOR . '.' );
270
271                 if((x($_SERVER,'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'],0,2) === "q=")
272                         $this->query_string = substr($_SERVER['QUERY_STRING'],2);
273                 if(x($_GET,'q'))
274                         $this->cmd = trim($_GET['q'],'/\\');
275
276
277
278                 /**
279                  *
280                  * Break the URL path into C style argc/argv style arguments for our
281                  * modules. Given "http://example.com/module/arg1/arg2", $this->argc
282                  * will be 3 (integer) and $this->argv will contain:
283                  *   [0] => 'module'
284                  *   [1] => 'arg1'
285                  *   [2] => 'arg2'
286                  *
287                  *
288                  * There will always be one argument. If provided a naked domain
289                  * URL, $this->argv[0] is set to "home".
290                  *
291                  */
292
293                 $this->argv = explode('/',$this->cmd);
294                 $this->argc = count($this->argv);
295                 if((array_key_exists('0',$this->argv)) && strlen($this->argv[0])) {
296                         $this->module = str_replace(".", "_", $this->argv[0]);
297                 }
298                 else {
299                         $this->module = 'home';
300                 }
301
302                 /**
303                  * Special handling for the webfinger/lrdd host XRD file
304                  * Just spit out the contents and exit.
305                  */
306
307                 if($this->cmd === '.well-known/host-meta') {
308                         require_once('include/hostxrd.php');
309                         hostxrd($this->get_baseurl());
310                         // NOTREACHED
311                 }
312
313                 /**
314                  * See if there is any page number information, and initialise 
315                  * pagination
316                  */
317
318                 $this->pager['page'] = ((x($_GET,'page')) ? $_GET['page'] : 1);
319                 $this->pager['itemspage'] = 50;
320                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
321                 $this->pager['total'] = 0;
322         }
323
324         function get_baseurl($ssl = false) {
325
326                 $scheme = $this->scheme;
327
328                 if(x($this->config,'ssl_policy')) {
329                         if(($ssl) || ($this->config['ssl_policy'] == SSL_POLICY_FULL)) 
330                                 $scheme = 'https';
331                         if(($this->config['ssl_policy'] == SSL_POLICY_SELFSIGN) && (local_user() || x($_POST,'auth-params')))
332                                 $scheme = 'https';
333                 }
334
335                 $this->baseurl = $scheme . "://" . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );
336                 return $this->baseurl;
337         }
338
339         function set_baseurl($url) {
340                 $parsed = @parse_url($url);
341
342                 $this->baseurl = $url;
343
344                 if($parsed) {           
345                         $this->scheme = $parsed['scheme'];
346
347                         $this->hostname = $parsed['host'];
348                         if(x($parsed,'port'))
349                                 $this->hostname .= ':' . $parsed['port'];
350                         if(x($parsed,'path'))
351                                 $this->path = trim($parsed['path'],'\\/');
352                 }
353
354         }
355
356         function get_hostname() {
357                 return $this->hostname;
358         }
359
360         function set_hostname($h) {
361                 $this->hostname = $h;
362         }
363
364         function set_path($p) {
365                 $this->path = trim(trim($p),'/');
366         } 
367
368         function get_path() {
369                 return $this->path;
370         }
371
372         function set_pager_total($n) {
373                 $this->pager['total'] = intval($n);
374         }
375
376         function set_pager_itemspage($n) {
377                 $this->pager['itemspage'] = intval($n);
378                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
379
380         } 
381
382         function init_pagehead() {
383                 $this->page['title'] = $this->config['sitename'];
384                 $tpl = file_get_contents('view/head.tpl');
385                 $this->page['htmlhead'] = replace_macros($tpl,array(
386                         '$baseurl' => $this->get_baseurl(),
387                         '$generator' => 'Friendika' . ' ' . FRIENDIKA_VERSION,
388                         '$delitem' => t('Delete this item?'),
389                         '$comment' => t('Comment')
390                 ));
391         }
392
393         function set_curl_code($code) {
394                 $this->curl_code = $code;
395         }
396
397         function get_curl_code() {
398                 return $this->curl_code;
399         }
400
401         function set_curl_headers($headers) {
402                 $this->curl_headers = $headers;
403         }
404
405         function get_curl_headers() {
406                 return $this->curl_headers;
407         }
408
409
410 }}
411
412 // retrieve the App structure
413 // useful in functions which require it but don't get it passed to them
414
415 if(! function_exists('get_app')) {
416 function get_app() {
417         global $a;
418         return $a;
419 }};
420
421
422 // Multi-purpose function to check variable state.
423 // Usage: x($var) or $x($array,'key')
424 // returns false if variable/key is not set
425 // if variable is set, returns 1 if has 'non-zero' value, otherwise returns 0.
426 // e.g. x('') or x(0) returns 0;
427
428 if(! function_exists('x')) {
429 function x($s,$k = NULL) {
430         if($k != NULL) {
431                 if((is_array($s)) && (array_key_exists($k,$s))) {
432                         if($s[$k])
433                                 return (int) 1;
434                         return (int) 0;
435                 }
436                 return false;
437         }
438         else {          
439                 if(isset($s)) {
440                         if($s) {
441                                 return (int) 1;
442                         }
443                         return (int) 0;
444                 }
445                 return false;
446         }
447 }}
448
449 // called from db initialisation if db is dead.
450
451 if(! function_exists('system_unavailable')) {
452 function system_unavailable() {
453         include('system_unavailable.php');
454         system_down();
455         killme();
456 }}
457
458
459 // install and uninstall plugin
460 if (! function_exists('uninstall_plugin')){
461 function uninstall_plugin($plugin){
462         logger("Addons: uninstalling " . $plugin);
463         q("DELETE FROM `addon` WHERE `name` = '%s' LIMIT 1",
464                 dbesc($plugin)
465         );
466
467         @include_once('addon/' . $plugin . '/' . $plugin . '.php');
468         if(function_exists($plugin . '_uninstall')) {
469                 $func = $plugin . '_uninstall';
470                 $func();
471         }
472 }}
473
474 if (! function_exists('install_plugin')){
475 function install_plugin($plugin){
476         logger("Addons: installing " . $plugin);
477         $t = filemtime('addon/' . $plugin . '/' . $plugin . '.php');
478         @include_once('addon/' . $plugin . '/' . $plugin . '.php');
479         if(function_exists($plugin . '_install')) {
480                 $func = $plugin . '_install';
481                 $func();
482                 
483                 $plugin_admin = (function_exists($plugin."_plugin_admin")?1:0);
484                 
485                 $r = q("INSERT INTO `addon` (`name`, `installed`, `timestamp`, `plugin_admin`) VALUES ( '%s', 1, %d , %d ) ",
486                         dbesc($plugin),
487                         intval($t),
488                         $plugin_admin
489                 );
490         }
491 }}
492
493 // Primarily involved with database upgrade, but also sets the 
494 // base url for use in cmdline programs which don't have
495 // $_SERVER variables, and synchronising the state of installed plugins.
496
497
498 if(! function_exists('check_config')) {
499 function check_config(&$a) {
500
501
502         load_config('system');
503
504         $build = get_config('system','build');
505         if(! x($build))
506                 $build = set_config('system','build',DB_UPDATE_VERSION);
507
508         $url = get_config('system','url');
509
510         // if the url isn't set or the stored url is radically different 
511         // than the currently visited url, store the current value accordingly.
512         // "Radically different" ignores common variations such as http vs https 
513         // and www.example.com vs example.com.
514
515         if((! x($url)) || (! link_compare($url,$a->get_baseurl())))
516                 $url = set_config('system','url',$a->get_baseurl());
517
518         if($build != DB_UPDATE_VERSION) {
519                 $stored = intval($build);
520                 $current = intval(DB_UPDATE_VERSION);
521                 if(($stored < $current) && file_exists('update.php')) {
522
523                         // We're reporting a different version than what is currently installed.
524                         // Run any existing update scripts to bring the database up to current.
525
526                         require_once('update.php');
527
528                         // make sure that boot.php and update.php are the same release, we might be
529                         // updating right this very second and the correct version of the update.php
530                         // file may not be here yet. This can happen on a very busy site.
531
532                         if(DB_UPDATE_VERSION == UPDATE_VERSION) {
533
534                                 for($x = $stored; $x < $current; $x ++) {
535                                         if(function_exists('update_' . $x)) {
536                                                 $func = 'update_' . $x;
537                                                 $func($a);
538                                         }
539                                 }
540                                 set_config('system','build', DB_UPDATE_VERSION);
541                         }
542                 }
543         }
544
545         /**
546          *
547          * Synchronise plugins:
548          *
549          * $a->config['system']['addon'] contains a comma-separated list of names
550          * of plugins/addons which are used on this system. 
551          * Go through the database list of already installed addons, and if we have
552          * an entry, but it isn't in the config list, call the uninstall procedure
553          * and mark it uninstalled in the database (for now we'll remove it).
554          * Then go through the config list and if we have a plugin that isn't installed,
555          * call the install procedure and add it to the database.
556          *
557          */
558
559         $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
560         if(count($r))
561                 $installed = $r;
562         else
563                 $installed = array();
564
565         $plugins = get_config('system','addon');
566         $plugins_arr = array();
567
568         if($plugins)
569                 $plugins_arr = explode(',',str_replace(' ', '',$plugins));
570
571         $a->plugins = $plugins_arr;
572
573         $installed_arr = array();
574
575         if(count($installed)) {
576                 foreach($installed as $i) {
577                         if(! in_array($i['name'],$plugins_arr)) {
578                                 uninstall_plugin($i['name']);
579                         }
580                         else
581                                 $installed_arr[] = $i['name'];
582                 }
583         }
584
585         if(count($plugins_arr)) {
586                 foreach($plugins_arr as $p) {
587                         if(! in_array($p,$installed_arr)) {
588                                 install_plugin($p);
589                         }
590                 }
591         }
592
593
594         load_hooks();
595
596         return;
597 }}
598
599 // reload all updated plugins
600
601 if(! function_exists('reload_plugins')) {
602 function reload_plugins() {
603         $plugins = get_config('system','addon');
604         if(strlen($plugins)) {
605
606                 $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
607                 if(count($r))
608                         $installed = $r;
609                 else
610                         $installed = array();
611
612                 $parr = explode(',',$plugins);
613                 if(count($parr)) {
614                         foreach($parr as $pl) {
615                                 $pl = trim($pl);
616                                 
617                                 $t = filemtime('addon/' . $pl . '/' . $pl . '.php');
618                                 foreach($installed as $i) {
619                                         if(($i['name'] == $pl) && ($i['timestamp'] != $t)) {    
620                                                 logger('Reloading plugin: ' . $i['name']);
621                                                 @include_once('addon/' . $pl . '/' . $pl . '.php');
622
623                                                 if(function_exists($pl . '_uninstall')) {
624                                                         $func = $pl . '_uninstall';
625                                                         $func();
626                                                 }
627                                                 if(function_exists($pl . '_install')) {
628                                                         $func = $pl . '_install';
629                                                         $func();
630                                                 }
631                                                 q("UPDATE `addon` SET `timestamp` = %d WHERE `id` = %d LIMIT 1",
632                                                         intval($t),
633                                                         intval($i['id'])
634                                                 );
635                                         }
636                                 }
637                         }
638                 }
639         }
640 }}
641                                 
642
643
644 // This is our template processor.
645 // $s is the string requiring macro substitution.
646 // $r is an array of key value pairs (search => replace)
647 // returns substituted string.
648 // WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other.
649 // For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, 
650 // depending on the order in which they were declared in the array.   
651
652 require_once("include/template_processor.php");
653
654 if(! function_exists('replace_macros')) {  
655 function replace_macros($s,$r) {
656         global $t;
657         
658         return $t->replace($s,$r);
659
660 }}
661
662
663 // curl wrapper. If binary flag is true, return binary
664 // results. 
665
666 if(! function_exists('fetch_url')) {
667 function fetch_url($url,$binary = false, &$redirects = 0) {
668
669         $a = get_app();
670
671         $ch = curl_init($url);
672         if(($redirects > 8) || (! $ch)) 
673                 return false;
674
675         curl_setopt($ch, CURLOPT_HEADER, true);
676         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
677
678
679         $curl_time = intval(get_config('system','curl_timeout'));
680         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
681
682         // by default we will allow self-signed certs
683         // but you can override this
684
685         $check_cert = get_config('system','verifyssl');
686         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
687
688         $prx = get_config('system','proxy');
689         if(strlen($prx)) {
690                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
691                 curl_setopt($ch, CURLOPT_PROXY, $prx);
692                 $prxusr = get_config('system','proxyuser');
693                 if(strlen($prxusr))
694                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
695         }
696         if($binary)
697                 curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
698
699         $a->set_curl_code(0);
700
701         // don't let curl abort the entire application
702         // if it throws any errors.
703
704         $s = @curl_exec($ch);
705
706         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
707         $header = substr($s,0,strpos($s,"\r\n\r\n"));
708         if(stristr($header,'100') && (strlen($header) < 30)) {
709                 // 100 Continue has two headers, get the real one
710                 $s = substr($s,strlen($header)+4);
711                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
712         }
713         if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
714         $matches = array();
715         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
716         $url = trim(array_pop($matches));
717         $url_parsed = @parse_url($url);
718         if (isset($url_parsed)) {
719             $redirects++;
720             return fetch_url($url,$binary,$redirects);
721         }
722     }
723         $a->set_curl_code($http_code);
724
725         $body = substr($s,strlen($header)+4);
726
727         /* one more try to make sure there are no more headers */
728
729         if(strpos($body,'HTTP/') === 0) {
730                 $header = substr($body,0,strpos($body,"\r\n\r\n"));
731                 $body = substr($body,strlen($header)+4);
732         }
733
734         $a->set_curl_headers($header);
735
736         curl_close($ch);
737         return($body);
738 }}
739
740 // post request to $url. $params is an array of post variables.
741
742 if(! function_exists('post_url')) {
743 function post_url($url,$params, $headers = null, &$redirects = 0) {
744         $a = get_app();
745         $ch = curl_init($url);
746         if(($redirects > 8) || (! $ch)) 
747                 return false;
748
749         curl_setopt($ch, CURLOPT_HEADER, true);
750         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
751         curl_setopt($ch, CURLOPT_POST,1);
752         curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
753
754         $curl_time = intval(get_config('system','curl_timeout'));
755         curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
756
757         if(is_array($headers))
758                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
759
760         $check_cert = get_config('system','verifyssl');
761         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
762         $prx = get_config('system','proxy');
763         if(strlen($prx)) {
764                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
765                 curl_setopt($ch, CURLOPT_PROXY, $prx);
766                 $prxusr = get_config('system','proxyuser');
767                 if(strlen($prxusr))
768                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
769         }
770
771         $a->set_curl_code(0);
772
773         // don't let curl abort the entire application
774         // if it throws any errors.
775
776         $s = @curl_exec($ch);
777
778         $http_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
779         $header = substr($s,0,strpos($s,"\r\n\r\n"));
780         if(stristr($header,'100') && (strlen($header) < 30)) {
781                 // 100 Continue has two headers, get the real one
782                 $s = substr($s,strlen($header)+4);
783                 $header = substr($s,0,strpos($s,"\r\n\r\n"));
784         }
785         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
786         $matches = array();
787         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
788         $url = trim(array_pop($matches));
789         $url_parsed = @parse_url($url);
790         if (isset($url_parsed)) {
791             $redirects++;
792             return post_url($url,$binary,$headers,$redirects);
793         }
794     }
795         $a->set_curl_code($http_code);
796         $body = substr($s,strlen($header)+4);
797
798         /* one more try to make sure there are no more headers */
799
800         if(strpos($body,'HTTP/') === 0) {
801                 $header = substr($body,0,strpos($body,"\r\n\r\n"));
802                 $body = substr($body,strlen($header)+4);
803         }
804
805         $a->set_curl_headers($header);
806
807         curl_close($ch);
808         return($body);
809 }}
810
811 // random hash, 64 chars
812
813 if(! function_exists('random_string')) {
814 function random_string() {
815         return(hash('sha256',uniqid(rand(),true)));
816 }}
817
818 /**
819  * This is our primary input filter. 
820  *
821  * The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
822  * that had an XSS attack vector due to stripping the high-bit on an 8-bit character
823  * after cleansing, and angle chars with the high bit set could get through as markup.
824  * 
825  * This is now disabled because it was interfering with some legitimate unicode sequences 
826  * and hopefully there aren't a lot of those browsers left. 
827  *
828  * Use this on any text input where angle chars are not valid or permitted
829  * They will be replaced with safer brackets. This may be filtered further
830  * if these are not allowed either.   
831  *
832  */
833
834 if(! function_exists('notags')) {
835 function notags($string) {
836
837         return(str_replace(array("<",">"), array('[',']'), $string));
838
839 //  High-bit filter no longer used
840 //      return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string));
841 }}
842
843 // use this on "body" or "content" input where angle chars shouldn't be removed,
844 // and allow them to be safely displayed.
845
846 if(! function_exists('escape_tags')) {
847 function escape_tags($string) {
848
849         return(htmlspecialchars($string));
850 }}
851
852 // wrapper for adding a login box. If $register == true provide a registration
853 // link. This will most always depend on the value of $a->config['register_policy'].
854 // returns the complete html for inserting into the page
855
856 if(! function_exists('login')) {
857 function login($register = false) {
858         $o = "";
859         $register_tpl = (($register) ? get_markup_template("register-link.tpl") : "");
860         
861         $register_html = replace_macros($register_tpl,array(
862                 '$title' => t('Create a New Account'),
863                 '$desc' => t('Register')
864         ));
865
866         $noid = get_config('system','no_openid');
867         if($noid) {
868                 $classname = 'no-openid';
869                 $namelabel = t('Nickname or Email address: ');
870                 $passlabel = t('Password: ');
871                 $login     = t('Login');
872         }
873         else {
874                 $classname = 'openid';
875                 $namelabel = t('Nickname/Email/OpenID: ');
876                 $passlabel = t("Password \x28if not OpenID\x29: ");
877                 $login     = t('Login');
878         }
879         $lostpass = t('Forgot your password?');
880         $lostlink = t('Password Reset');
881
882         if(local_user()) {
883                 $tpl = get_markup_template("logout.tpl");
884         }
885         else {
886                 $tpl = get_markup_template("login.tpl");
887
888         }
889
890         $o = '<script type="text/javascript"> $(document).ready(function() { $("#login-name").focus();} );</script>';   
891
892         $o .= replace_macros($tpl,array(
893                 '$logout'        => t('Logout'),
894                 '$register_html' => $register_html, 
895                 '$classname'     => $classname,
896                 '$namelabel'     => $namelabel,
897                 '$passlabel'     => $passlabel,
898                 '$login'         => $login,
899                 '$lostpass'      => $lostpass,
900                 '$lostlink'      => $lostlink 
901         ));
902
903         return $o;
904 }}
905
906 // generate a string that's random, but usually pronounceable. 
907 // used to generate initial passwords
908
909 if(! function_exists('autoname')) {
910 function autoname($len) {
911
912         $vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'); 
913         if(mt_rand(0,5) == 4)
914                 $vowels[] = 'y';
915
916         $cons = array(
917                         'b','bl','br',
918                         'c','ch','cl','cr',
919                         'd','dr',
920                         'f','fl','fr',
921                         'g','gh','gl','gr',
922                         'h',
923                         'j',
924                         'k','kh','kl','kr',
925                         'l',
926                         'm',
927                         'n',
928                         'p','ph','pl','pr',
929                         'qu',
930                         'r','rh',
931                         's','sc','sh','sm','sp','st',
932                         't','th','tr',
933                         'v',
934                         'w','wh',
935                         'x',
936                         'z','zh'
937                         );
938
939         $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
940                                 'nd','ng','nk','nt','rn','rp','rt');
941
942         $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
943                                 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
944
945         $start = mt_rand(0,2);
946         if($start == 0)
947                 $table = $vowels;
948         else
949                 $table = $cons;
950
951         $word = '';
952
953         for ($x = 0; $x < $len; $x ++) {
954                 $r = mt_rand(0,count($table) - 1);
955                 $word .= $table[$r];
956   
957                 if($table == $vowels)
958                         $table = array_merge($cons,$midcons);
959                 else
960                         $table = $vowels;
961
962         }
963
964         $word = substr($word,0,$len);
965
966         foreach($noend as $noe) {
967                 if((strlen($word) > 2) && (substr($word,-2) == $noe)) {
968                         $word = substr($word,0,-1);
969                         break;
970                 }
971         }
972         if(substr($word,-1) == 'q')
973                 $word = substr($word,0,-1);    
974         return $word;
975 }}
976
977 // Used to end the current process, after saving session state. 
978
979 if(! function_exists('killme')) {
980 function killme() {
981         session_write_close();
982         exit;
983 }}
984
985 // redirect to another URL and terminate this process.
986
987 if(! function_exists('goaway')) {
988 function goaway($s) {
989         header("Location: $s");
990         killme();
991 }}
992
993 // Generic XML return
994 // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable 
995 // of $st and an optional text <message> of $message and terminates the current process. 
996
997 if(! function_exists('xml_status')) {
998 function xml_status($st, $message = '') {
999
1000         $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : '');
1001
1002         if($st)
1003                 logger('xml_status returning non_zero: ' . $st . " message=" . $message);
1004
1005         header( "Content-type: text/xml" );
1006         echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n";
1007         echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n";
1008         killme();
1009 }}
1010
1011 // Returns the uid of locally logged in user or false.
1012
1013 if(! function_exists('local_user')) {
1014 function local_user() {
1015         if((x($_SESSION,'authenticated')) && (x($_SESSION,'uid')))
1016                 return intval($_SESSION['uid']);
1017         return false;
1018 }}
1019
1020 // Returns contact id of authenticated site visitor or false
1021
1022 if(! function_exists('remote_user')) {
1023 function remote_user() {
1024         if((x($_SESSION,'authenticated')) && (x($_SESSION,'visitor_id')))
1025                 return intval($_SESSION['visitor_id']);
1026         return false;
1027 }}
1028
1029 // contents of $s are displayed prominently on the page the next time
1030 // a page is loaded. Usually used for errors or alerts.
1031
1032 if(! function_exists('notice')) {
1033 function notice($s) {
1034         $a = get_app();
1035         if($a->interactive)
1036                 $_SESSION['sysmsg'] .= $s;
1037 }}
1038 if(! function_exists('info')) {
1039 function info($s) {
1040         $a = get_app();
1041         if($a->interactive)
1042                 $_SESSION['sysmsg_info'] .= $s;
1043 }}
1044
1045
1046 // wrapper around config to limit the text length of an incoming message
1047
1048 if(! function_exists('get_max_import_size')) {
1049 function get_max_import_size() {
1050         global $a;
1051         return ((x($a->config,'max_import_size')) ? $a->config['max_import_size'] : 0 );
1052 }}
1053
1054
1055 // escape text ($str) for XML transport
1056 // returns escaped text.
1057
1058 if(! function_exists('xmlify')) {
1059 function xmlify($str) {
1060         $buffer = '';
1061         
1062         for($x = 0; $x < strlen($str); $x ++) {
1063                 $char = $str[$x];
1064         
1065                 switch( $char ) {
1066
1067                         case "\r" :
1068                                 break;
1069                         case "&" :
1070                                 $buffer .= '&amp;';
1071                                 break;
1072                         case "'" :
1073                                 $buffer .= '&apos;';
1074                                 break;
1075                         case "\"" :
1076                                 $buffer .= '&quot;';
1077                                 break;
1078                         case '<' :
1079                                 $buffer .= '&lt;';
1080                                 break;
1081                         case '>' :
1082                                 $buffer .= '&gt;';
1083                                 break;
1084                         case "\n" :
1085                                 $buffer .= "\n";
1086                                 break;
1087                         default :
1088                                 $buffer .= $char;
1089                                 break;
1090                 }       
1091         }
1092         $buffer = trim($buffer);
1093         return($buffer);
1094 }}
1095
1096 // undo an xmlify
1097 // pass xml escaped text ($s), returns unescaped text
1098
1099 if(! function_exists('unxmlify')) {
1100 function unxmlify($s) {
1101         $ret = str_replace('&amp;','&', $s);
1102         $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
1103         return $ret;    
1104 }}
1105
1106 // convenience wrapper, reverse the operation "bin2hex"
1107
1108 if(! function_exists('hex2bin')) {
1109 function hex2bin($s) {
1110         if(! ctype_xdigit($s)) {
1111                 logger('hex2bin: illegal input: ' . print_r(debug_backtrace(), true));
1112                 return($s);
1113         }
1114
1115         return(pack("H*",$s));
1116 }}
1117
1118 // Automatic pagination.
1119 // To use, get the count of total items.
1120 // Then call $a->set_pager_total($number_items);
1121 // Optionally call $a->set_pager_itemspage($n) to the number of items to display on each page
1122 // Then call paginate($a) after the end of the display loop to insert the pager block on the page
1123 // (assuming there are enough items to paginate).
1124 // When using with SQL, the setting LIMIT %d, %d => $a->pager['start'],$a->pager['itemspage']
1125 // will limit the results to the correct items for the current page. 
1126 // The actual page handling is then accomplished at the application layer. 
1127
1128 if(! function_exists('paginate')) {
1129 function paginate(&$a) {
1130         $o = '';
1131         $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string);
1132         $stripped = str_replace('q=','',$stripped);
1133         $stripped = trim($stripped,'/');
1134         $pagenum = $a->pager['page'];
1135         $url = $a->get_baseurl() . '/' . $stripped;
1136
1137
1138           if($a->pager['total'] > $a->pager['itemspage']) {
1139                 $o .= '<div class="pager">';
1140                 if($a->pager['page'] != 1)
1141                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
1142
1143                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
1144
1145                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
1146
1147                         $numstart = 1;
1148                 $numstop = $numpages;
1149
1150                 if($numpages > 14) {
1151                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
1152                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
1153                 }
1154    
1155                 for($i = $numstart; $i <= $numstop; $i++){
1156                         if($i == $a->pager['page'])
1157                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1158                         else
1159                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1160                         $o .= '</span> ';
1161                 }
1162
1163                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
1164                         if($i == $a->pager['page'])
1165                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1166                         else
1167                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1168                         $o .= '</span> ';
1169                 }
1170
1171                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
1172                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
1173
1174                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
1175                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
1176                 $o .= '</div>'."\r\n";
1177         }
1178         return $o;
1179 }}
1180
1181 // Turn user/group ACLs stored as angle bracketed text into arrays
1182
1183 if(! function_exists('expand_acl')) {
1184 function expand_acl($s) {
1185         // turn string array of angle-bracketed elements into numeric array
1186         // e.g. "<1><2><3>" => array(1,2,3);
1187         $ret = array();
1188
1189         if(strlen($s)) {
1190                 $t = str_replace('<','',$s);
1191                 $a = explode('>',$t);
1192                 foreach($a as $aa) {
1193                         if(intval($aa))
1194                                 $ret[] = intval($aa);
1195                 }
1196         }
1197         return $ret;
1198 }}              
1199
1200 // Used to wrap ACL elements in angle brackets for storage 
1201
1202 if(! function_exists('sanitise_acl')) {
1203 function sanitise_acl(&$item) {
1204         if(intval($item))
1205                 $item = '<' . intval(notags(trim($item))) . '>';
1206         else
1207                 unset($item);
1208 }}
1209
1210 // retrieve a "family" of config variables from database to cached storage
1211
1212 if(! function_exists('load_config')) {
1213 function load_config($family) {
1214         global $a;
1215         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
1216                 dbesc($family)
1217         );
1218         if(count($r)) {
1219                 foreach($r as $rr) {
1220                         $k = $rr['k'];
1221                         $a->config[$family][$k] = $rr['v'];
1222                 }
1223         }
1224 }}
1225
1226 // get a particular config variable given the family name
1227 // and key. Returns false if not set.
1228 // $instore is only used by the set_config function
1229 // to determine if the key already exists in the DB
1230 // If a key is found in the DB but doesn't exist in
1231 // local config cache, pull it into the cache so we don't have
1232 // to hit the DB again for this item.
1233
1234 if(! function_exists('get_config')) {
1235 function get_config($family, $key, $instore = false) {
1236
1237         global $a;
1238
1239         if(! $instore) {
1240                 if(isset($a->config[$family][$key])) {
1241                         if($a->config[$family][$key] === '!<unset>!') {
1242                                 return false;
1243                         }
1244                         return $a->config[$family][$key];
1245                 }
1246         }
1247         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1248                 dbesc($family),
1249                 dbesc($key)
1250         );
1251         if(count($ret)) {
1252                 // manage array value
1253                 $val = (preg_match("|^a:[0-9]+:{.*}$|", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']);
1254                 $a->config[$family][$key] = $val;
1255                 return $val;
1256         }
1257         else {
1258                 $a->config[$family][$key] = '!<unset>!';
1259         }
1260         return false;
1261 }}
1262
1263 // Store a config value ($value) in the category ($family)
1264 // under the key ($key)
1265 // Return the value, or false if the database update failed
1266
1267 if(! function_exists('set_config')) {
1268 function set_config($family,$key,$value) {
1269         global $a;
1270         
1271         // manage array value
1272         $dbvalue = (is_array($value)?serialize($value):$value);
1273
1274         if(get_config($family,$key,true) === false) {
1275                 $a->config[$family][$key] = $value;
1276                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1277                         dbesc($family),
1278                         dbesc($key),
1279                         dbesc($dbvalue)
1280                 );
1281                 if($ret) 
1282                         return $value;
1283                 return $ret;
1284         }
1285         
1286         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1287                 dbesc($dbvalue),
1288                 dbesc($family),
1289                 dbesc($key)
1290         );
1291
1292         $a->config[$family][$key] = $value;
1293
1294         if($ret)
1295                 return $value;
1296         return $ret;
1297 }}
1298
1299
1300 if(! function_exists('load_pconfig')) {
1301 function load_pconfig($uid,$family) {
1302         global $a;
1303         $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
1304                 dbesc($family),
1305                 intval($uid)
1306         );
1307         if(count($r)) {
1308                 foreach($r as $rr) {
1309                         $k = $rr['k'];
1310                         $a->config[$uid][$family][$k] = $rr['v'];
1311                 }
1312         }
1313 }}
1314
1315
1316
1317 if(! function_exists('get_pconfig')) {
1318 function get_pconfig($uid,$family, $key, $instore = false) {
1319
1320         global $a;
1321
1322         if(! $instore) {
1323                 if(isset($a->config[$uid][$family][$key])) {
1324                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1325                                 return false;
1326                         }
1327                         return $a->config[$uid][$family][$key];
1328                 }
1329         }
1330
1331         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1332                 intval($uid),
1333                 dbesc($family),
1334                 dbesc($key)
1335         );
1336
1337         if(count($ret)) {
1338                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1339                 return $ret[0]['v'];
1340         }
1341         else {
1342                 $a->config[$uid][$family][$key] = '!<unset>!';
1343         }
1344         return false;
1345 }}
1346
1347 if(! function_exists('del_config')) {
1348 function del_config($family,$key) {
1349
1350         global $a;
1351         if(x($a->config[$family],$key))
1352                 unset($a->config[$family][$key]);
1353         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1354                 dbesc($cat),
1355                 dbesc($key)
1356         );
1357         return $ret;
1358 }}
1359
1360
1361
1362 // Same as above functions except these are for personal config storage and take an
1363 // additional $uid argument.
1364
1365 if(! function_exists('set_pconfig')) {
1366 function set_pconfig($uid,$family,$key,$value) {
1367
1368         global $a;
1369
1370         if(get_pconfig($uid,$family,$key,true) === false) {
1371                 $a->config[$uid][$family][$key] = $value;
1372                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1373                         intval($uid),
1374                         dbesc($family),
1375                         dbesc($key),
1376                         dbesc($value)
1377                 );
1378                 if($ret) 
1379                         return $value;
1380                 return $ret;
1381         }
1382         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1383                 dbesc($value),
1384                 intval($uid),
1385                 dbesc($family),
1386                 dbesc($key)
1387         );
1388
1389         $a->config[$uid][$family][$key] = $value;
1390
1391         if($ret)
1392                 return $value;
1393         return $ret;
1394 }}
1395
1396 if(! function_exists('del_pconfig')) {
1397 function del_pconfig($uid,$family,$key) {
1398
1399         global $a;
1400         if(x($a->config[$uid][$family],$key))
1401                 unset($a->config[$uid][$family][$key]);
1402         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1403                 intval($uid),
1404                 dbesc($family),
1405                 dbesc($key)
1406         );
1407         return $ret;
1408 }}
1409
1410
1411 // convert an XML document to a normalised, case-corrected array
1412 // used by webfinger
1413
1414 if(! function_exists('convert_xml_element_to_array')) {
1415 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1416
1417         // If we're getting too deep, bail out
1418         if ($recursion_depth > 512) {
1419                 return(null);
1420         }
1421
1422         if (!is_string($xml_element) &&
1423         !is_array($xml_element) &&
1424         (get_class($xml_element) == 'SimpleXMLElement')) {
1425                 $xml_element_copy = $xml_element;
1426                 $xml_element = get_object_vars($xml_element);
1427         }
1428
1429         if (is_array($xml_element)) {
1430                 $result_array = array();
1431                 if (count($xml_element) <= 0) {
1432                         return (trim(strval($xml_element_copy)));
1433                 }
1434
1435                 foreach($xml_element as $key=>$value) {
1436
1437                         $recursion_depth++;
1438                         $result_array[strtolower($key)] =
1439                 convert_xml_element_to_array($value, $recursion_depth);
1440                         $recursion_depth--;
1441                 }
1442                 if ($recursion_depth == 0) {
1443                         $temp_array = $result_array;
1444                         $result_array = array(
1445                                 strtolower($xml_element_copy->getName()) => $temp_array,
1446                         );
1447                 }
1448
1449                 return ($result_array);
1450
1451         } else {
1452                 return (trim(strval($xml_element)));
1453         }
1454 }}
1455
1456 // Given an email style address, perform webfinger lookup and 
1457 // return the resulting DFRN profile URL, or if no DFRN profile URL
1458 // is located, returns an OStatus subscription template (prefixed 
1459 // with the string 'stat:' to identify it as on OStatus template).
1460 // If this isn't an email style address just return $s.
1461 // Return an empty string if email-style addresses but webfinger fails,
1462 // or if the resultant personal XRD doesn't contain a supported 
1463 // subscription/friend-request attribute.
1464
1465 if(! function_exists('webfinger_dfrn')) {
1466 function webfinger_dfrn($s) {
1467         if(! strstr($s,'@')) {
1468                 return $s;
1469         }
1470         $links = webfinger($s);
1471         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1472         if(count($links)) {
1473                 foreach($links as $link)
1474                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1475                                 return $link['@attributes']['href'];
1476                 foreach($links as $link)
1477                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1478                                 return 'stat:' . $link['@attributes']['template'];              
1479         }
1480         return '';
1481 }}
1482
1483 // Given an email style address, perform webfinger lookup and 
1484 // return the array of link attributes from the personal XRD file.
1485 // On error/failure return an empty array.
1486
1487
1488 if(! function_exists('webfinger')) {
1489 function webfinger($s) {
1490         $host = '';
1491         if(strstr($s,'@')) {
1492                 $host = substr($s,strpos($s,'@') + 1);
1493         }
1494         if(strlen($host)) {
1495                 $tpl = fetch_lrdd_template($host);
1496                 logger('webfinger: lrdd template: ' . $tpl);
1497                 if(strlen($tpl)) {
1498                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
1499                         logger('webfinger: pxrd: ' . $pxrd);
1500                         $links = fetch_xrd_links($pxrd);
1501                         if(! count($links)) {
1502                                 // try with double slashes
1503                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
1504                                 logger('webfinger: pxrd: ' . $pxrd);
1505                                 $links = fetch_xrd_links($pxrd);
1506                         }
1507                         return $links;
1508                 }
1509         }
1510         return array();
1511 }}
1512
1513 if(! function_exists('lrdd')) {
1514 function lrdd($uri) {
1515
1516         $a = get_app();
1517
1518         // default priority is host priority, host-meta first
1519
1520         $priority = 'host';
1521
1522         // All we have is an email address. Resource-priority is irrelevant
1523         // because our URI isn't directly resolvable.
1524
1525         if(strstr($uri,'@')) {  
1526                 return(webfinger($uri));
1527         }
1528
1529         // get the host meta file
1530
1531         $host = @parse_url($uri);
1532
1533         if($host) {
1534                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
1535                 $url .= $host['host'] . '/.well-known/host-meta' ;
1536         }
1537         else
1538                 return array();
1539
1540         logger('lrdd: constructed url: ' . $url);
1541
1542         $xml = fetch_url($url);
1543         $headers = $a->get_curl_headers();
1544
1545         if (! $xml)
1546                 return array();
1547
1548         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
1549
1550         $h = parse_xml_string($xml);
1551         if(! $h)
1552                 return array();
1553
1554         $arr = convert_xml_element_to_array($h);
1555
1556         if(isset($arr['xrd']['property'])) {
1557                 $property = $arr['crd']['property'];
1558                 if(! isset($property[0]))
1559                         $properties = array($property);
1560                 else
1561                         $properties = $property;
1562                 foreach($properties as $prop)
1563                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
1564                                 $priority = 'resource';
1565         } 
1566
1567         // save the links in case we need them
1568
1569         $links = array();
1570
1571         if(isset($arr['xrd']['link'])) {
1572                 $link = $arr['xrd']['link'];
1573                 if(! isset($link[0]))
1574                         $links = array($link);
1575                 else
1576                         $links = $link;
1577         }
1578
1579         // do we have a template or href?
1580
1581         if(count($links)) {
1582                 foreach($links as $link) {
1583                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
1584                                 if(x($link['@attributes'],'template'))
1585                                         $tpl = $link['@attributes']['template'];
1586                                 elseif(x($link['@attributes'],'href'))
1587                                         $href = $link['@attributes']['href'];
1588                         }
1589                 }               
1590         }
1591
1592         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
1593                 $tpl = '';
1594
1595         if($priority === 'host') {
1596                 if(strlen($tpl)) 
1597                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
1598                 elseif(isset($href))
1599                         $pxrd = $href;
1600                 if(isset($pxrd)) {
1601                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
1602                         $links = fetch_xrd_links($pxrd);
1603                         return $links;
1604                 }
1605
1606                 $lines = explode("\n",$headers);
1607                 if(count($lines)) {
1608                         foreach($lines as $line) {                              
1609                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1610                                         return(fetch_xrd_links($matches[1]));
1611                                         break;
1612                                 }
1613                         }
1614                 }
1615         }
1616
1617
1618         // priority 'resource'
1619
1620
1621         $html = fetch_url($uri);
1622         $headers = $a->get_curl_headers();
1623         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
1624
1625         // don't try and parse raw xml as html
1626         if(! strstr($html,'<?xml')) {
1627                 require_once('library/HTML5/Parser.php');
1628                 $dom = @HTML5_Parser::parse($html);
1629
1630                 if($dom) {
1631                         $items = $dom->getElementsByTagName('link');
1632                         foreach($items as $item) {
1633                                 $x = $item->getAttribute('rel');
1634                                 if($x == "lrdd") {
1635                                         $pagelink = $item->getAttribute('href');
1636                                         break;
1637                                 }
1638                         }
1639                 }
1640         }
1641
1642         if(isset($pagelink))
1643                 return(fetch_xrd_links($pagelink));
1644
1645         // next look in HTTP headers
1646
1647         $lines = explode("\n",$headers);
1648         if(count($lines)) {
1649                 foreach($lines as $line) {                              
1650                         // TODO alter the following regex to support multiple relations (space separated)
1651                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1652                                 $pagelink = $matches[1];
1653                                 break;
1654                         }
1655                         // don't try and run feeds through the html5 parser
1656                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
1657                                 return array();
1658                         if(stristr($html,'<rss') || stristr($html,'<feed'))
1659                                 return array();
1660                 }
1661         }
1662
1663         if(isset($pagelink))
1664                 return(fetch_xrd_links($pagelink));
1665
1666         // If we haven't found any links, return the host xrd links (which we have already fetched)
1667
1668         if(isset($links))
1669                 return $links;
1670
1671         return array();
1672
1673 }}
1674
1675
1676
1677 // Given a host name, locate the LRDD template from that
1678 // host. Returns the LRDD template or an empty string on
1679 // error/failure.
1680
1681 if(! function_exists('fetch_lrdd_template')) {
1682 function fetch_lrdd_template($host) {
1683         $tpl = '';
1684
1685         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
1686         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
1687         $links = fetch_xrd_links($url1);
1688         logger('fetch_lrdd_template from: ' . $url1);
1689         logger('template (https): ' . print_r($links,true));
1690         if(! count($links)) {
1691                 logger('fetch_lrdd_template from: ' . $url2);
1692                 $links = fetch_xrd_links($url2);
1693                 logger('template (http): ' . print_r($links,true));
1694         }
1695         if(count($links)) {
1696                 foreach($links as $link)
1697                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1698                                 $tpl = $link['@attributes']['template'];
1699         }
1700         if(! strpos($tpl,'{uri}'))
1701                 $tpl = '';
1702         return $tpl;
1703 }}
1704
1705 // Given a URL, retrieve the page as an XRD document.
1706 // Return an array of links.
1707 // on error/failure return empty array.
1708
1709 if(! function_exists('fetch_xrd_links')) {
1710 function fetch_xrd_links($url) {
1711
1712
1713         $xml = fetch_url($url);
1714         if (! $xml)
1715                 return array();
1716
1717         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1718         $h = parse_xml_string($xml);
1719         if(! $h)
1720                 return array();
1721
1722         $arr = convert_xml_element_to_array($h);
1723
1724         $links = array();
1725
1726         if(isset($arr['xrd']['link'])) {
1727                 $link = $arr['xrd']['link'];
1728                 if(! isset($link[0]))
1729                         $links = array($link);
1730                 else
1731                         $links = $link;
1732         }
1733         if(isset($arr['xrd']['alias'])) {
1734                 $alias = $arr['xrd']['alias'];
1735                 if(! isset($alias[0]))
1736                         $aliases = array($alias);
1737                 else
1738                         $aliases = $alias;
1739                 foreach($aliases as $alias) {
1740                         $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
1741                 }
1742         }
1743
1744         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
1745
1746         return $links;
1747
1748 }}
1749
1750 // Convert an ACL array to a storable string
1751
1752 if(! function_exists('perms2str')) {
1753 function perms2str($p) {
1754         $ret = '';
1755         $tmp = $p;
1756         if(is_array($tmp)) {
1757                 array_walk($tmp,'sanitise_acl');
1758                 $ret = implode('',$tmp);
1759         }
1760         return $ret;
1761 }}
1762
1763 // generate a guaranteed unique (for this domain) item ID for ATOM
1764 // safe from birthday paradox
1765
1766 if(! function_exists('item_new_uri')) {
1767 function item_new_uri($hostname,$uid) {
1768
1769         do {
1770                 $dups = false;
1771                 $hash = random_string();
1772
1773                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1774
1775                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1776                         dbesc($uri));
1777                 if(count($r))
1778                         $dups = true;
1779         } while($dups == true);
1780         return $uri;
1781 }}
1782
1783 // Generate a guaranteed unique photo ID.
1784 // safe from birthday paradox
1785
1786 if(! function_exists('photo_new_resource')) {
1787 function photo_new_resource() {
1788
1789         do {
1790                 $found = false;
1791                 $resource = hash('md5',uniqid(mt_rand(),true));
1792                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1793                         dbesc($resource)
1794                 );
1795                 if(count($r))
1796                         $found = true;
1797         } while($found == true);
1798         return $resource;
1799 }}
1800
1801
1802 // Take a URL from the wild, prepend http:// if necessary
1803 // and check DNS to see if it's real
1804 // return true if it's OK, false if something is wrong with it
1805
1806 if(! function_exists('validate_url')) {
1807 function validate_url(&$url) {
1808         if(substr($url,0,4) != 'http')
1809                 $url = 'http://' . $url;
1810         $h = @parse_url($url);
1811
1812         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1813                 return true;
1814         }
1815         return false;
1816 }}
1817
1818 // checks that email is an actual resolvable internet address
1819
1820 if(! function_exists('validate_email')) {
1821 function validate_email($addr) {
1822
1823         if(! strpos($addr,'@'))
1824                 return false;
1825         $h = substr($addr,strpos($addr,'@') + 1);
1826
1827         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1828                 return true;
1829         }
1830         return false;
1831 }}
1832
1833 // Check $url against our list of allowed sites,
1834 // wildcards allowed. If allowed_sites is unset return true;
1835 // If url is allowed, return true.
1836 // otherwise, return false
1837
1838 if(! function_exists('allowed_url')) {
1839 function allowed_url($url) {
1840
1841         $h = @parse_url($url);
1842
1843         if(! $h) {
1844                 return false;
1845         }
1846
1847         $str_allowed = get_config('system','allowed_sites');
1848         if(! $str_allowed)
1849                 return true;
1850
1851         $found = false;
1852
1853         $host = strtolower($h['host']);
1854
1855         // always allow our own site
1856
1857         if($host == strtolower($_SERVER['SERVER_NAME']))
1858                 return true;
1859
1860         $fnmatch = function_exists('fnmatch');
1861         $allowed = explode(',',$str_allowed);
1862
1863         if(count($allowed)) {
1864                 foreach($allowed as $a) {
1865                         $pat = strtolower(trim($a));
1866                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1867                                 $found = true; 
1868                                 break;
1869                         }
1870                 }
1871         }
1872         return $found;
1873 }}
1874
1875 // check if email address is allowed to register here.
1876 // Compare against our list (wildcards allowed).
1877 // Returns false if not allowed, true if allowed or if
1878 // allowed list is not configured.
1879
1880 if(! function_exists('allowed_email')) {
1881 function allowed_email($email) {
1882
1883
1884         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1885         if(! $domain)
1886                 return false;
1887
1888         $str_allowed = get_config('system','allowed_email');
1889         if(! $str_allowed)
1890                 return true;
1891
1892         $found = false;
1893
1894         $fnmatch = function_exists('fnmatch');
1895         $allowed = explode(',',$str_allowed);
1896
1897         if(count($allowed)) {
1898                 foreach($allowed as $a) {
1899                         $pat = strtolower(trim($a));
1900                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
1901                                 $found = true; 
1902                                 break;
1903                         }
1904                 }
1905         }
1906         return $found;
1907 }}
1908
1909
1910
1911 // wrapper to load a view template, checking for alternate
1912 // languages before falling back to the default
1913
1914 // obsolete, deprecated.
1915
1916 if(! function_exists('load_view_file')) {
1917 function load_view_file($s) {
1918         global $lang, $a;
1919         if(! isset($lang))
1920                 $lang = 'en';
1921         $b = basename($s);
1922         $d = dirname($s);
1923         if(file_exists("$d/$lang/$b"))
1924                 return file_get_contents("$d/$lang/$b");
1925         
1926         $theme = current_theme();
1927         
1928         if(file_exists("$d/theme/$theme/$b"))
1929                 return file_get_contents("$d/theme/$theme/$b");
1930                         
1931         return file_get_contents($s);
1932 }}
1933
1934 if(! function_exists('get_intltext_template')) {
1935 function get_intltext_template($s) {
1936         global $lang;
1937
1938         if(! isset($lang))
1939                 $lang = 'en';
1940
1941         if(file_exists("view/$lang/$s"))
1942                 return file_get_contents("view/$lang/$s");
1943         elseif(file_exists("view/en/$s"))
1944                 return file_get_contents("view/en/$s");
1945         else
1946                 return file_get_contents("view/$s");
1947 }}
1948
1949 if(! function_exists('get_markup_template')) {
1950 function get_markup_template($s) {
1951
1952         $theme = current_theme();
1953         
1954         if(file_exists("view/theme/$theme/$s"))
1955                 return file_get_contents("view/theme/$theme/$s");
1956         else
1957                 return file_get_contents("view/$s");
1958
1959 }}
1960
1961
1962
1963
1964
1965 // for html,xml parsing - let's say you've got
1966 // an attribute foobar="class1 class2 class3"
1967 // and you want to find out if it contains 'class3'.
1968 // you can't use a normal sub string search because you
1969 // might match 'notclass3' and a regex to do the job is 
1970 // possible but a bit complicated. 
1971 // pass the attribute string as $attr and the attribute you 
1972 // are looking for as $s - returns true if found, otherwise false
1973
1974 if(! function_exists('attribute_contains')) {
1975 function attribute_contains($attr,$s) {
1976         $a = explode(' ', $attr);
1977         if(count($a) && in_array($s,$a))
1978                 return true;
1979         return false;
1980 }}
1981
1982 if(! function_exists('logger')) {
1983 function logger($msg,$level = 0) {
1984         $debugging = get_config('system','debugging');
1985         $loglevel  = intval(get_config('system','loglevel'));
1986         $logfile   = get_config('system','logfile');
1987
1988         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1989                 return;
1990         
1991         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1992         return;
1993 }}
1994
1995
1996 if(! function_exists('activity_match')) {
1997 function activity_match($haystack,$needle) {
1998         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1999                 return true;
2000         return false;
2001 }}
2002
2003
2004 // Pull out all #hashtags and @person tags from $s;
2005 // We also get @person@domain.com - which would make 
2006 // the regex quite complicated as tags can also
2007 // end a sentence. So we'll run through our results
2008 // and strip the period from any tags which end with one.
2009 // Returns array of tags found, or empty array.
2010
2011
2012 if(! function_exists('get_tags')) {
2013 function get_tags($s) {
2014         $ret = array();
2015
2016         // ignore anything in a code block
2017
2018         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
2019
2020         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
2021                 foreach($match[1] as $mtch) {
2022                         if(strstr($mtch,"]")) {
2023                                 // we might be inside a bbcode color tag - leave it alone
2024                                 continue;
2025                         }
2026                         if(substr($mtch,-1,1) === '.')
2027                                 $ret[] = substr($mtch,0,-1);
2028                         else
2029                                 $ret[] = $mtch;
2030                 }
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 }}