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