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