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