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