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