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