]> git.mxchange.org Git - friendica.git/blob - boot.php
Some more templates for french translation
[friendica.git] / boot.php
1 <?php
2
3 set_time_limit(0);
4
5 define ( 'FRIENDIKA_VERSION',      '2.1.931' );
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         $pagenum = $a->pager['page'];
1072         $url = $a->get_baseurl() . '/' . $stripped;
1073
1074
1075           if($a->pager['total'] > $a->pager['itemspage']) {
1076                 $o .= '<div class="pager">';
1077                 if($a->pager['page'] != 1)
1078                         $o .= '<span class="pager_prev">'."<a href=\"$url".'&page='.($a->pager['page'] - 1).'">' . t('prev') . '</a></span> ';
1079
1080                 $o .=  "<span class=\"pager_first\"><a href=\"$url"."&page=1\">" . t('first') . "</a></span> ";
1081
1082                 $numpages = $a->pager['total'] / $a->pager['itemspage'];
1083
1084                         $numstart = 1;
1085                 $numstop = $numpages;
1086
1087                 if($numpages > 14) {
1088                         $numstart = (($pagenum > 7) ? ($pagenum - 7) : 1);
1089                         $numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 14));
1090                 }
1091    
1092                 for($i = $numstart; $i <= $numstop; $i++){
1093                         if($i == $a->pager['page'])
1094                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1095                         else
1096                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1097                         $o .= '</span> ';
1098                 }
1099
1100                 if(($a->pager['total'] % $a->pager['itemspage']) != 0) {
1101                         if($i == $a->pager['page'])
1102                                 $o .= '<span class="pager_current">'.(($i < 10) ? '&nbsp;'.$i : $i);
1103                         else
1104                                 $o .= "<span class=\"pager_n\"><a href=\"$url"."&page=$i\">".(($i < 10) ? '&nbsp;'.$i : $i)."</a>";
1105                         $o .= '</span> ';
1106                 }
1107
1108                 $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
1109                 $o .= "<span class=\"pager_last\"><a href=\"$url"."&page=$lastpage\">" . t('last') . "</a></span> ";
1110
1111                 if(($a->pager['total'] - ($a->pager['itemspage'] * $a->pager['page'])) > 0)
1112                         $o .= '<span class="pager_next">'."<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('next') . '</a></span>';
1113                 $o .= '</div>'."\r\n";
1114         }
1115         return $o;
1116 }}
1117
1118 // Turn user/group ACLs stored as angle bracketed text into arrays
1119
1120 if(! function_exists('expand_acl')) {
1121 function expand_acl($s) {
1122         // turn string array of angle-bracketed elements into numeric array
1123         // e.g. "<1><2><3>" => array(1,2,3);
1124         $ret = array();
1125
1126         if(strlen($s)) {
1127                 $t = str_replace('<','',$s);
1128                 $a = explode('>',$t);
1129                 foreach($a as $aa) {
1130                         if(intval($aa))
1131                                 $ret[] = intval($aa);
1132                 }
1133         }
1134         return $ret;
1135 }}              
1136
1137 // Used to wrap ACL elements in angle brackets for storage 
1138
1139 if(! function_exists('sanitise_acl')) {
1140 function sanitise_acl(&$item) {
1141         if(intval($item))
1142                 $item = '<' . intval(notags(trim($item))) . '>';
1143         else
1144                 unset($item);
1145 }}
1146
1147 // retrieve a "family" of config variables from database to cached storage
1148
1149 if(! function_exists('load_config')) {
1150 function load_config($family) {
1151         global $a;
1152         $r = q("SELECT * FROM `config` WHERE `cat` = '%s'",
1153                 dbesc($family)
1154         );
1155         if(count($r)) {
1156                 foreach($r as $rr) {
1157                         $k = $rr['k'];
1158                         $a->config[$family][$k] = $rr['v'];
1159                 }
1160         }
1161 }}
1162
1163 // get a particular config variable given the family name
1164 // and key. Returns false if not set.
1165 // $instore is only used by the set_config function
1166 // to determine if the key already exists in the DB
1167 // If a key is found in the DB but doesn't exist in
1168 // local config cache, pull it into the cache so we don't have
1169 // to hit the DB again for this item.
1170
1171 if(! function_exists('get_config')) {
1172 function get_config($family, $key, $instore = false) {
1173
1174         global $a;
1175
1176         if(! $instore) {
1177                 if(isset($a->config[$family][$key])) {
1178                         if($a->config[$family][$key] === '!<unset>!') {
1179                                 return false;
1180                         }
1181                         return $a->config[$family][$key];
1182                 }
1183         }
1184         $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1185                 dbesc($family),
1186                 dbesc($key)
1187         );
1188         if(count($ret)) {
1189                 $a->config[$family][$key] = $ret[0]['v'];
1190                 return $ret[0]['v'];
1191         }
1192         else {
1193                 $a->config[$family][$key] = '!<unset>!';
1194         }
1195         return false;
1196 }}
1197
1198 // Store a config value ($value) in the category ($family)
1199 // under the key ($key)
1200 // Return the value, or false if the database update failed
1201
1202 if(! function_exists('set_config')) {
1203 function set_config($family,$key,$value) {
1204
1205         global $a;
1206
1207         if(get_config($family,$key,true) === false) {
1208                 $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ",
1209                         dbesc($family),
1210                         dbesc($key),
1211                         dbesc($value)
1212                 );
1213                 if($ret) 
1214                         return $value;
1215                 return $ret;
1216         }
1217         $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1218                 dbesc($value),
1219                 dbesc($family),
1220                 dbesc($key)
1221         );
1222
1223         $a->config[$family][$key] = $value;
1224
1225         if($ret)
1226                 return $value;
1227         return $ret;
1228 }}
1229
1230
1231 if(! function_exists('load_pconfig')) {
1232 function load_pconfig($uid,$family) {
1233         global $a;
1234         $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
1235                 dbesc($family),
1236                 intval($uid)
1237         );
1238         if(count($r)) {
1239                 foreach($r as $rr) {
1240                         $k = $rr['k'];
1241                         $a->config[$uid][$family][$k] = $rr['v'];
1242                 }
1243         }
1244 }}
1245
1246
1247
1248 if(! function_exists('get_pconfig')) {
1249 function get_pconfig($uid,$family, $key, $instore = false) {
1250
1251         global $a;
1252
1253         if(! $instore) {
1254                 if(isset($a->config[$uid][$family][$key])) {
1255                         if($a->config[$uid][$family][$key] === '!<unset>!') {
1256                                 return false;
1257                         }
1258                         return $a->config[$uid][$family][$key];
1259                 }
1260         }
1261
1262         $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1263                 intval($uid),
1264                 dbesc($family),
1265                 dbesc($key)
1266         );
1267
1268         if(count($ret)) {
1269                 $a->config[$uid][$family][$key] = $ret[0]['v'];
1270                 return $ret[0]['v'];
1271         }
1272         else {
1273                 $a->config[$uid][$family][$key] = '!<unset>!';
1274         }
1275         return false;
1276 }}
1277
1278 if(! function_exists('del_config')) {
1279 function del_config($family,$key) {
1280
1281         global $a;
1282         if(x($a->config[$family],$key))
1283                 unset($a->config[$family][$key]);
1284         $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
1285                 dbesc($cat),
1286                 dbesc($key)
1287         );
1288         return $ret;
1289 }}
1290
1291
1292
1293 // Same as above functions except these are for personal config storage and take an
1294 // additional $uid argument.
1295
1296 if(! function_exists('set_pconfig')) {
1297 function set_pconfig($uid,$family,$key,$value) {
1298
1299         global $a;
1300
1301         if(get_pconfig($uid,$family,$key,true) === false) {
1302                 $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ",
1303                         intval($uid),
1304                         dbesc($family),
1305                         dbesc($key),
1306                         dbesc($value)
1307                 );
1308                 if($ret) 
1309                         return $value;
1310                 return $ret;
1311         }
1312         $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1313                 dbesc($value),
1314                 intval($uid),
1315                 dbesc($family),
1316                 dbesc($key)
1317         );
1318
1319         $a->config[$uid][$family][$key] = $value;
1320
1321         if($ret)
1322                 return $value;
1323         return $ret;
1324 }}
1325
1326 if(! function_exists('del_pconfig')) {
1327 function del_pconfig($uid,$family,$key) {
1328
1329         global $a;
1330         if(x($a->config[$uid][$family],$key))
1331                 unset($a->config[$uid][$family][$key]);
1332         $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1",
1333                 intval($uid),
1334                 dbesc($family),
1335                 dbesc($key)
1336         );
1337         return $ret;
1338 }}
1339
1340
1341 // convert an XML document to a normalised, case-corrected array
1342 // used by webfinger
1343
1344 if(! function_exists('convert_xml_element_to_array')) {
1345 function convert_xml_element_to_array($xml_element, &$recursion_depth=0) {
1346
1347         // If we're getting too deep, bail out
1348         if ($recursion_depth > 512) {
1349                 return(null);
1350         }
1351
1352         if (!is_string($xml_element) &&
1353         !is_array($xml_element) &&
1354         (get_class($xml_element) == 'SimpleXMLElement')) {
1355                 $xml_element_copy = $xml_element;
1356                 $xml_element = get_object_vars($xml_element);
1357         }
1358
1359         if (is_array($xml_element)) {
1360                 $result_array = array();
1361                 if (count($xml_element) <= 0) {
1362                         return (trim(strval($xml_element_copy)));
1363                 }
1364
1365                 foreach($xml_element as $key=>$value) {
1366
1367                         $recursion_depth++;
1368                         $result_array[strtolower($key)] =
1369                 convert_xml_element_to_array($value, $recursion_depth);
1370                         $recursion_depth--;
1371                 }
1372                 if ($recursion_depth == 0) {
1373                         $temp_array = $result_array;
1374                         $result_array = array(
1375                                 strtolower($xml_element_copy->getName()) => $temp_array,
1376                         );
1377                 }
1378
1379                 return ($result_array);
1380
1381         } else {
1382                 return (trim(strval($xml_element)));
1383         }
1384 }}
1385
1386 // Given an email style address, perform webfinger lookup and 
1387 // return the resulting DFRN profile URL, or if no DFRN profile URL
1388 // is located, returns an OStatus subscription template (prefixed 
1389 // with the string 'stat:' to identify it as on OStatus template).
1390 // If this isn't an email style address just return $s.
1391 // Return an empty string if email-style addresses but webfinger fails,
1392 // or if the resultant personal XRD doesn't contain a supported 
1393 // subscription/friend-request attribute.
1394
1395 if(! function_exists('webfinger_dfrn')) {
1396 function webfinger_dfrn($s) {
1397         if(! strstr($s,'@')) {
1398                 return $s;
1399         }
1400         $links = webfinger($s);
1401         logger('webfinger_dfrn: ' . $s . ':' . print_r($links,true), LOGGER_DATA);
1402         if(count($links)) {
1403                 foreach($links as $link)
1404                         if($link['@attributes']['rel'] === NAMESPACE_DFRN)
1405                                 return $link['@attributes']['href'];
1406                 foreach($links as $link)
1407                         if($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB)
1408                                 return 'stat:' . $link['@attributes']['template'];              
1409         }
1410         return '';
1411 }}
1412
1413 // Given an email style address, perform webfinger lookup and 
1414 // return the array of link attributes from the personal XRD file.
1415 // On error/failure return an empty array.
1416
1417
1418 if(! function_exists('webfinger')) {
1419 function webfinger($s) {
1420         $host = '';
1421         if(strstr($s,'@')) {
1422                 $host = substr($s,strpos($s,'@') + 1);
1423         }
1424         if(strlen($host)) {
1425                 $tpl = fetch_lrdd_template($host);
1426                 logger('webfinger: lrdd template: ' . $tpl);
1427                 if(strlen($tpl)) {
1428                         $pxrd = str_replace('{uri}', urlencode('acct:' . $s), $tpl);
1429                         logger('webfinger: pxrd: ' . $pxrd);
1430                         $links = fetch_xrd_links($pxrd);
1431                         if(! count($links)) {
1432                                 // try with double slashes
1433                                 $pxrd = str_replace('{uri}', urlencode('acct://' . $s), $tpl);
1434                                 logger('webfinger: pxrd: ' . $pxrd);
1435                                 $links = fetch_xrd_links($pxrd);
1436                         }
1437                         return $links;
1438                 }
1439         }
1440         return array();
1441 }}
1442
1443 if(! function_exists('lrdd')) {
1444 function lrdd($uri) {
1445
1446         $a = get_app();
1447
1448         // default priority is host priority, host-meta first
1449
1450         $priority = 'host';
1451
1452         // All we have is an email address. Resource-priority is irrelevant
1453         // because our URI isn't directly resolvable.
1454
1455         if(strstr($uri,'@')) {  
1456                 return(webfinger($uri));
1457         }
1458
1459         // get the host meta file
1460
1461         $host = @parse_url($uri);
1462
1463         if($host) {
1464                 $url  = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://';
1465                 $url .= $host['host'] . '/.well-known/host-meta' ;
1466         }
1467         else
1468                 return array();
1469
1470         logger('lrdd: constructed url: ' . $url);
1471
1472         $xml = fetch_url($url);
1473         $headers = $a->get_curl_headers();
1474
1475         if (! $xml)
1476                 return array();
1477
1478         logger('lrdd: host_meta: ' . $xml, LOGGER_DATA);
1479         $h = simplexml_load_string($xml);
1480         $arr = convert_xml_element_to_array($h);
1481
1482         if(isset($arr['xrd']['property'])) {
1483                 $property = $arr['crd']['property'];
1484                 if(! isset($property[0]))
1485                         $properties = array($property);
1486                 else
1487                         $properties = $property;
1488                 foreach($properties as $prop)
1489                         if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource')
1490                                 $priority = 'resource';
1491         } 
1492
1493         // save the links in case we need them
1494
1495         $links = array();
1496
1497         if(isset($arr['xrd']['link'])) {
1498                 $link = $arr['xrd']['link'];
1499                 if(! isset($link[0]))
1500                         $links = array($link);
1501                 else
1502                         $links = $link;
1503         }
1504
1505         // do we have a template or href?
1506
1507         if(count($links)) {
1508                 foreach($links as $link) {
1509                         if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) {
1510                                 if(x($link['@attributes'],'template'))
1511                                         $tpl = $link['@attributes']['template'];
1512                                 elseif(x($link['@attributes'],'href'))
1513                                         $href = $link['@attributes']['href'];
1514                         }
1515                 }               
1516         }
1517
1518         if((! isset($tpl)) || (! strpos($tpl,'{uri}')))
1519                 $tpl = '';
1520
1521         if($priority === 'host') {
1522                 if(strlen($tpl)) 
1523                         $pxrd = str_replace('{uri}', urlencode($uri), $tpl);
1524                 elseif(isset($href))
1525                         $pxrd = $href;
1526                 if(isset($pxrd)) {
1527                         logger('lrdd: (host priority) pxrd: ' . $pxrd);
1528                         $links = fetch_xrd_links($pxrd);
1529                         return $links;
1530                 }
1531
1532                 $lines = explode("\n",$headers);
1533                 if(count($lines)) {
1534                         foreach($lines as $line) {                              
1535                                 if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1536                                         return(fetch_xrd_links($matches[1]));
1537                                         break;
1538                                 }
1539                         }
1540                 }
1541         }
1542
1543
1544         // priority 'resource'
1545
1546
1547         $html = fetch_url($uri);
1548         $headers = $a->get_curl_headers();
1549         logger('lrdd: headers=' . $headers, LOGGER_DEBUG);
1550
1551         require_once('library/HTML5/Parser.php');
1552         $dom = @HTML5_Parser::parse($html);
1553
1554         if($dom) {
1555                 $items = $dom->getElementsByTagName('link');
1556                 foreach($items as $item) {
1557                         $x = $item->getAttribute('rel');
1558                         if($x == "lrdd") {
1559                                 $pagelink = $item->getAttribute('href');
1560                                 break;
1561                         }
1562                 }
1563         }
1564
1565         if(isset($pagelink))
1566                 return(fetch_xrd_links($pagelink));
1567
1568         // next look in HTTP headers
1569
1570         $lines = explode("\n",$headers);
1571         if(count($lines)) {
1572                 foreach($lines as $line) {                              
1573                         // TODO alter the following regex to support multiple relations (space separated)
1574                         if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) {
1575                                 $pagelink = $matches[1];
1576                                 break;
1577                         }
1578                         // don't try and run feeds through the html5 parser
1579                         if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
1580                                 return array();
1581                         if(stristr($html,'<rss') || stristr($html,'<feed'))
1582                                 return array();
1583                 }
1584         }
1585
1586         if(isset($pagelink))
1587                 return(fetch_xrd_links($pagelink));
1588
1589         // If we haven't found any links, return the host xrd links (which we have already fetched)
1590
1591         if(isset($links))
1592                 return $links;
1593
1594         return array();
1595
1596 }}
1597
1598
1599
1600 // Given a host name, locate the LRDD template from that
1601 // host. Returns the LRDD template or an empty string on
1602 // error/failure.
1603
1604 if(! function_exists('fetch_lrdd_template')) {
1605 function fetch_lrdd_template($host) {
1606         $tpl = '';
1607
1608         $url1 = 'https://' . $host . '/.well-known/host-meta' ;
1609         $url2 = 'http://' . $host . '/.well-known/host-meta' ;
1610         $links = fetch_xrd_links($url1);
1611         logger('template (https): ' . print_r($links,true));
1612         if(! count($links)) {
1613                 $links = fetch_xrd_links($url2);
1614                 logger('template (http): ' . print_r($links,true));
1615         }
1616         if(count($links)) {
1617                 foreach($links as $link)
1618                         if($link['@attributes']['rel'] && $link['@attributes']['rel'] === 'lrdd')
1619                                 $tpl = $link['@attributes']['template'];
1620         }
1621         if(! strpos($tpl,'{uri}'))
1622                 $tpl = '';
1623         return $tpl;
1624 }}
1625
1626 // Given a URL, retrieve the page as an XRD document.
1627 // Return an array of links.
1628 // on error/failure return empty array.
1629
1630 if(! function_exists('fetch_xrd_links')) {
1631 function fetch_xrd_links($url) {
1632
1633
1634         $xml = fetch_url($url);
1635         if (! $xml)
1636                 return array();
1637
1638         logger('fetch_xrd_links: ' . $xml, LOGGER_DATA);
1639         $h = simplexml_load_string($xml);
1640         $arr = convert_xml_element_to_array($h);
1641
1642         $links = array();
1643
1644         if(isset($arr['xrd']['link'])) {
1645                 $link = $arr['xrd']['link'];
1646                 if(! isset($link[0]))
1647                         $links = array($link);
1648                 else
1649                         $links = $link;
1650         }
1651         if(isset($arr['xrd']['alias'])) {
1652                 $alias = $arr['xrd']['alias'];
1653                 if(! isset($alias[0]))
1654                         $aliases = array($alias);
1655                 else
1656                         $aliases = $alias;
1657                 foreach($aliases as $alias) {
1658                         $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias);
1659                 }
1660         }
1661
1662         logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA);
1663
1664         return $links;
1665
1666 }}
1667
1668 // Convert an ACL array to a storable string
1669
1670 if(! function_exists('perms2str')) {
1671 function perms2str($p) {
1672         $ret = '';
1673         $tmp = $p;
1674         if(is_array($tmp)) {
1675                 array_walk($tmp,'sanitise_acl');
1676                 $ret = implode('',$tmp);
1677         }
1678         return $ret;
1679 }}
1680
1681 // generate a guaranteed unique (for this domain) item ID for ATOM
1682 // safe from birthday paradox
1683
1684 if(! function_exists('item_new_uri')) {
1685 function item_new_uri($hostname,$uid) {
1686
1687         do {
1688                 $dups = false;
1689                 $hash = random_string();
1690
1691                 $uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
1692
1693                 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1",
1694                         dbesc($uri));
1695                 if(count($r))
1696                         $dups = true;
1697         } while($dups == true);
1698         return $uri;
1699 }}
1700
1701 // Generate a guaranteed unique photo ID.
1702 // safe from birthday paradox
1703
1704 if(! function_exists('photo_new_resource')) {
1705 function photo_new_resource() {
1706
1707         do {
1708                 $found = false;
1709                 $resource = hash('md5',uniqid(mt_rand(),true));
1710                 $r = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1",
1711                         dbesc($resource)
1712                 );
1713                 if(count($r))
1714                         $found = true;
1715         } while($found == true);
1716         return $resource;
1717 }}
1718
1719
1720 // Take a URL from the wild, prepend http:// if necessary
1721 // and check DNS to see if it's real
1722 // return true if it's OK, false if something is wrong with it
1723
1724 if(! function_exists('validate_url')) {
1725 function validate_url(&$url) {
1726         if(substr($url,0,4) != 'http')
1727                 $url = 'http://' . $url;
1728         $h = @parse_url($url);
1729
1730         if(($h) && (dns_get_record($h['host'], DNS_A + DNS_CNAME + DNS_PTR))) {
1731                 return true;
1732         }
1733         return false;
1734 }}
1735
1736 // checks that email is an actual resolvable internet address
1737
1738 if(! function_exists('validate_email')) {
1739 function validate_email($addr) {
1740
1741         if(! strpos($addr,'@'))
1742                 return false;
1743         $h = substr($addr,strpos($addr,'@') + 1);
1744
1745         if(($h) && (dns_get_record($h, DNS_A + DNS_CNAME + DNS_PTR + DNS_MX))) {
1746                 return true;
1747         }
1748         return false;
1749 }}
1750
1751 // Check $url against our list of allowed sites,
1752 // wildcards allowed. If allowed_sites is unset return true;
1753 // If url is allowed, return true.
1754 // otherwise, return false
1755
1756 if(! function_exists('allowed_url')) {
1757 function allowed_url($url) {
1758
1759         $h = @parse_url($url);
1760
1761         if(! $h) {
1762                 return false;
1763         }
1764
1765         $str_allowed = get_config('system','allowed_sites');
1766         if(! $str_allowed)
1767                 return true;
1768
1769         $found = false;
1770
1771         $host = strtolower($h['host']);
1772
1773         // always allow our own site
1774
1775         if($host == strtolower($_SERVER['SERVER_NAME']))
1776                 return true;
1777
1778         $fnmatch = function_exists('fnmatch');
1779         $allowed = explode(',',$str_allowed);
1780
1781         if(count($allowed)) {
1782                 foreach($allowed as $a) {
1783                         $pat = strtolower(trim($a));
1784                         if(($fnmatch && fnmatch($pat,$host)) || ($pat == $host)) {
1785                                 $found = true; 
1786                                 break;
1787                         }
1788                 }
1789         }
1790         return $found;
1791 }}
1792
1793 // check if email address is allowed to register here.
1794 // Compare against our list (wildcards allowed).
1795 // Returns false if not allowed, true if allowed or if
1796 // allowed list is not configured.
1797
1798 if(! function_exists('allowed_email')) {
1799 function allowed_email($email) {
1800
1801
1802         $domain = strtolower(substr($email,strpos($email,'@') + 1));
1803         if(! $domain)
1804                 return false;
1805
1806         $str_allowed = get_config('system','allowed_email');
1807         if(! $str_allowed)
1808                 return true;
1809
1810         $found = false;
1811
1812         $fnmatch = function_exists('fnmatch');
1813         $allowed = explode(',',$str_allowed);
1814
1815         if(count($allowed)) {
1816                 foreach($allowed as $a) {
1817                         $pat = strtolower(trim($a));
1818                         if(($fnmatch && fnmatch($pat,$domain)) || ($pat == $domain)) {
1819                                 $found = true; 
1820                                 break;
1821                         }
1822                 }
1823         }
1824         return $found;
1825 }}
1826
1827 // Format the like/dislike text for a profile item
1828 // $cnt = number of people who like/dislike the item
1829 // $arr = array of pre-linked names of likers/dislikers
1830 // $type = one of 'like, 'dislike'
1831 // $id  = item id
1832 // returns formatted text
1833
1834 if(! function_exists('format_like')) {
1835 function format_like($cnt,$arr,$type,$id) {
1836         $o = '';
1837         if($cnt == 1)
1838                 $o .= (($type === 'like') ? sprintf( t('%s likes this.'), $arr[0]) : sprintf( t('%s doesn\'t like this.'), $arr[0])) . EOL ;
1839         else {
1840                 $spanatts = 'class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');"';
1841                 $o .= (($type === 'like') ? 
1842                                         sprintf( t('<span  %1$s>%2$d people</span> like this.'), $spanatts, $cnt)
1843                                          : 
1844                                         sprintf( t('<span  %1$s>%2$d people</span> don\'t like this.'), $spanatts, $cnt) ); 
1845                 $o .= EOL ;
1846                 $total = count($arr);
1847                 if($total >= MAX_LIKERS)
1848                         $arr = array_slice($arr, 0, MAX_LIKERS - 1);
1849                 if($total < MAX_LIKERS)
1850                         $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1];
1851                 $str = implode(', ', $arr);
1852                 if($total >= MAX_LIKERS)
1853                         $str .= sprintf( t(', and %d other people'), $total - MAX_LIKERS );
1854                 $str = (($type === 'like') ? sprintf( t('%s like this.'), $str) : sprintf( t('%s don\'t like this.'), $str));
1855                 $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>';
1856         }
1857         return $o;
1858 }}
1859
1860
1861 // wrapper to load a view template, checking for alternate
1862 // languages before falling back to the default
1863
1864 if(! function_exists('load_view_file')) {
1865 function load_view_file($s) {
1866         global $lang;
1867         if(! isset($lang))
1868                 $lang = 'en';
1869         $b = basename($s);
1870         $d = dirname($s);
1871         if(file_exists("$d/$lang/$b"))
1872                 return file_get_contents("$d/$lang/$b");
1873         return file_get_contents($s);
1874 }}
1875
1876 // for html,xml parsing - let's say you've got
1877 // an attribute foobar="class1 class2 class3"
1878 // and you want to find out if it contains 'class3'.
1879 // you can't use a normal sub string search because you
1880 // might match 'notclass3' and a regex to do the job is 
1881 // possible but a bit complicated. 
1882 // pass the attribute string as $attr and the attribute you 
1883 // are looking for as $s - returns true if found, otherwise false
1884
1885 if(! function_exists('attribute_contains')) {
1886 function attribute_contains($attr,$s) {
1887         $a = explode(' ', $attr);
1888         if(count($a) && in_array($s,$a))
1889                 return true;
1890         return false;
1891 }}
1892
1893 if(! function_exists('logger')) {
1894 function logger($msg,$level = 0) {
1895         $debugging = get_config('system','debugging');
1896         $loglevel  = intval(get_config('system','loglevel'));
1897         $logfile   = get_config('system','logfile');
1898
1899         if((! $debugging) || (! $logfile) || ($level > $loglevel))
1900                 return;
1901         
1902         @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND);
1903         return;
1904 }}
1905
1906
1907 if(! function_exists('activity_match')) {
1908 function activity_match($haystack,$needle) {
1909         if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
1910                 return true;
1911         return false;
1912 }}
1913
1914
1915 // Pull out all #hashtags and @person tags from $s;
1916 // We also get @person@domain.com - which would make 
1917 // the regex quite complicated as tags can also
1918 // end a sentence. So we'll run through our results
1919 // and strip the period from any tags which end with one.
1920 // Returns array of tags found, or empty array.
1921
1922
1923 if(! function_exists('get_tags')) {
1924 function get_tags($s) {
1925         $ret = array();
1926
1927         // ignore anything in a code block
1928
1929         $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s);
1930
1931         if(preg_match_all('/([@#][^ \x0D\x0A,:?]+)([ \x0D\x0A,:?]|$)/',$s,$match)) {
1932                 foreach($match[1] as $match) {
1933                         if(strstr($match,"]")) {
1934                                 // we might be inside a bbcode color tag - leave it alone
1935                                 continue;
1936                         }
1937                         if(substr($match,-1,1) === '.')
1938                                 $ret[] = substr($match,0,-1);
1939                         else
1940                                 $ret[] = $match;
1941                 }
1942         }
1943
1944         return $ret;
1945 }}
1946
1947
1948 // quick and dirty quoted_printable encoding
1949
1950 if(! function_exists('qp')) {
1951 function qp($s) {
1952 return str_replace ("%","=",rawurlencode($s));
1953 }} 
1954
1955
1956 if(! function_exists('like_puller')) {
1957 function like_puller($a,$item,&$arr,$mode) {
1958
1959         $url = '';
1960         $sparkle = '';
1961         $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE);
1962
1963         if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) {
1964                 $url = $item['author-link'];
1965                 if((local_user()) && (local_user() == $item['uid']) && ($item['network'] === 'dfrn') && (! $item['self']) && (link_compare($item['author-link'],$item['url']))) {
1966                         $url = $a->get_baseurl() . '/redir/' . $item['contact-id'];
1967                         $sparkle = ' class="sparkle" ';
1968                 }
1969                 if(! ((isset($arr[$item['parent'] . '-l'])) && (is_array($arr[$item['parent'] . '-l']))))
1970                         $arr[$item['parent'] . '-l'] = array();
1971                 if(! isset($arr[$item['parent']]))
1972                         $arr[$item['parent']] = 1;
1973                 else    
1974                         $arr[$item['parent']] ++;
1975                 $arr[$item['parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author-name'] . '</a>';
1976         }
1977         return;
1978 }}
1979
1980 if(! function_exists('get_mentions')) {
1981 function get_mentions($item) {
1982         $o = '';
1983         if(! strlen($item['tag']))
1984                 return $o;
1985
1986         $arr = explode(',',$item['tag']);
1987         foreach($arr as $x) {
1988                 $matches = null;
1989                 if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
1990                         $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
1991                         $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
1992                 }
1993         }
1994         return $o;
1995 }}
1996
1997 if(! function_exists('contact_block')) {
1998 function contact_block() {
1999         $o = '';
2000         $a = get_app();
2001
2002         $shown = get_pconfig($a->profile['uid'],'system','display_friend_count');
2003         if(! $shown)
2004                 $shown = 24;
2005
2006         if((! is_array($a->profile)) || ($a->profile['hide-friends']))
2007                 return $o;
2008         $r = q("SELECT COUNT(*) AS `total` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0",
2009                         intval($a->profile['uid'])
2010         );
2011         if(count($r)) {
2012                 $total = intval($r[0]['total']);
2013         }
2014         if(! $total) {
2015                 $o .= '<h4 class="contact-h4">' . t('No contacts') . '</h4>';
2016                 return $o;
2017         }
2018         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 ORDER BY RAND() LIMIT %d",
2019                         intval($a->profile['uid']),
2020                         intval($shown)
2021         );
2022         if(count($r)) {
2023                 $o .= '<h4 class="contact-h4">' .  sprintf(tt('%d Contact','%d Contacts', $total),$total) . '</h4><div id="contact-block">';
2024                 foreach($r as $rr) {
2025                         $redirect_url = $a->get_baseurl() . '/redir/' . $rr['id'];
2026                         if(local_user() && ($rr['uid'] == local_user())
2027                                 && ($rr['network'] === 'dfrn')) {
2028                                 $url = $redirect_url;
2029                                 $sparkle = ' sparkle';
2030                         }
2031                         else {
2032                                 $url = $rr['url'];
2033                                 $sparkle = '';
2034                         }
2035
2036                         $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";
2037                 }
2038                 $o .= '</div><div id="contact-block-end"></div>';
2039                 $o .=  '<div id="viewcontacts"><a id="viewcontacts-link" href="viewcontacts/' . $a->profile['nickname'] . '">' . t('View Contacts') . '</a></div>';
2040                 
2041         }
2042
2043         $arr = array('contacts' => $r, 'output' => $o);
2044
2045         call_hooks('contact_block_end', $arr);
2046         return $o;
2047
2048 }}
2049
2050 if(! function_exists('search')) {
2051 function search($s) {
2052         $a = get_app();
2053         $o  = '<div id="search-box">';
2054         $o .= '<form action="' . $a->get_baseurl() . '/search' . '" method="get" >';
2055         $o .= '<input type="text" name="search" id="search-text" value="' . $s .'" />';
2056         $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; 
2057         $o .= '</form></div>';
2058         return $o;
2059 }}
2060
2061 if(! function_exists('valid_email')) {
2062 function valid_email($x){
2063         if(preg_match('/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x))
2064                 return true;
2065         return false;
2066 }}
2067
2068
2069 if(! function_exists('gravatar_img')) {
2070 function gravatar_img($email) {
2071         $size = 175;
2072         $opt = 'identicon';   // psuedo-random geometric pattern if not found
2073         $rating = 'pg';
2074         $hash = md5(trim(strtolower($email)));
2075         
2076         $url = 'http://www.gravatar.com/avatar/' . $hash . '.jpg' 
2077                 . '?s=' . $size . '&d=' . $opt . '&r=' . $rating;
2078
2079         logger('gravatar: ' . $email . ' ' . $url);
2080         return $url;
2081 }}
2082
2083 if(! function_exists('aes_decrypt')) {
2084 function aes_decrypt($val,$ky)
2085 {
2086     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2087     for($a=0;$a<strlen($ky);$a++)
2088       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2089     $mode = MCRYPT_MODE_ECB;
2090     $enc = MCRYPT_RIJNDAEL_128;
2091     $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
2092     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));
2093 }}
2094
2095
2096 if(! function_exists('aes_encrypt')) {
2097 function aes_encrypt($val,$ky)
2098 {
2099     $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
2100     for($a=0;$a<strlen($ky);$a++)
2101       $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
2102     $mode=MCRYPT_MODE_ECB;
2103     $enc=MCRYPT_RIJNDAEL_128;
2104     $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
2105     return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
2106 }} 
2107
2108
2109 /**
2110  *
2111  * Function: linkify
2112  *
2113  * Replace naked text hyperlink with HTML formatted hyperlink
2114  *
2115  */
2116
2117 if(! function_exists('linkify')) {
2118 function linkify($s) {
2119         $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="external-link">$1</a>', $s);
2120         return($s);
2121 }}
2122
2123
2124 /**
2125  * 
2126  * Function: smilies
2127  *
2128  * Description:
2129  * Replaces text emoticons with graphical images
2130  *
2131  * @Parameter: string $s
2132  *
2133  * Returns string
2134  */
2135
2136 if(! function_exists('smilies')) {
2137 function smilies($s) {
2138         $a = get_app();
2139
2140         return str_replace(
2141         array( '&lt;3', '&lt;/3', '&lt;\\3', ':-)', ';-)', ':-(', ':(', ':-P', ':-"', ':-x', ':-X', ':-D', '8-|', '8-O'),
2142         array(
2143                 '<img src="' . $a->get_baseurl() . '/images/smiley-heart.gif" alt="<3" />',
2144                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="</3" />',
2145                 '<img src="' . $a->get_baseurl() . '/images/smiley-brokenheart.gif" alt="<\\3" />',
2146                 '<img src="' . $a->get_baseurl() . '/images/smiley-smile.gif" alt=":-)" />',
2147                 '<img src="' . $a->get_baseurl() . '/images/smiley-wink.gif" alt=";-)" />',
2148                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":-(" />',
2149                 '<img src="' . $a->get_baseurl() . '/images/smiley-frown.gif" alt=":(" />',
2150                 '<img src="' . $a->get_baseurl() . '/images/smiley-tongue-out.gif" alt=":-P" />',
2151                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-\"" />',
2152                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-x" />',
2153                 '<img src="' . $a->get_baseurl() . '/images/smiley-kiss.gif" alt=":-X" />',
2154                 '<img src="' . $a->get_baseurl() . '/images/smiley-laughing.gif" alt=":-D" />',
2155                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-|" />',
2156                 '<img src="' . $a->get_baseurl() . '/images/smiley-surprised.gif" alt="8-O" />'
2157         ), $s);
2158 }}
2159
2160
2161 /**
2162  *
2163  * Function : profile_load
2164  * @parameter App    $a
2165  * @parameter string $nickname
2166  * @parameter int    $profile
2167  *
2168  * Summary: Loads a profile into the page sidebar. 
2169  * The function requires a writeable copy of the main App structure, and the nickname
2170  * of a registered local account.
2171  *
2172  * If the viewer is an authenticated remote viewer, the profile displayed is the
2173  * one that has been configured for his/her viewing in the Contact manager.
2174  * Passing a non-zero profile ID can also allow a preview of a selected profile
2175  * by the owner.
2176  *
2177  * Profile information is placed in the App structure for later retrieval.
2178  * Honours the owner's chosen theme for display. 
2179  *
2180  */
2181
2182 if(! function_exists('profile_load')) {
2183 function profile_load(&$a, $nickname, $profile = 0) {
2184         if(remote_user()) {
2185                 $r = q("SELECT `profile-id` FROM `contact` WHERE `id` = %d LIMIT 1",
2186                         intval($_SESSION['visitor_id']));
2187                 if(count($r))
2188                         $profile = $r[0]['profile-id'];
2189         } 
2190
2191         $r = null;
2192
2193         if($profile) {
2194                 $profile_int = intval($profile);
2195                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2196                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2197                         WHERE `user`.`nickname` = '%s' AND `profile`.`id` = %d LIMIT 1",
2198                         dbesc($nickname),
2199                         intval($profile_int)
2200                 );
2201         }
2202         if(! count($r)) {       
2203                 $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` 
2204                         LEFT JOIN `user` ON `profile`.`uid` = `user`.`uid`
2205                         WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 LIMIT 1",
2206                         dbesc($nickname)
2207                 );
2208         }
2209
2210         if(($r === false) || (! count($r))) {
2211                 notice( t('No profile') . EOL );
2212                 $a->error = 404;
2213                 return;
2214         }
2215
2216         $a->profile = $r[0];
2217
2218
2219         $a->page['title'] = $a->profile['name'] . " @ " . $a->config['sitename'];
2220         $_SESSION['theme'] = $a->profile['theme'];
2221
2222         if(! (x($a->page,'aside')))
2223                 $a->page['aside'] = '';
2224
2225         $a->page['aside'] .= profile_sidebar($a->profile);
2226         $a->page['aside'] .= contact_block();
2227
2228         return;
2229 }}
2230
2231
2232 /**
2233  *
2234  * Function: profile_sidebar
2235  *
2236  * Formats a profile for display in the sidebar.
2237  * It is very difficult to templatise the HTML completely
2238  * because of all the conditional logic.
2239  *
2240  * @parameter: array $profile
2241  *
2242  * Returns HTML string stuitable for sidebar inclusion
2243  * Exceptions: Returns empty string if passed $profile is wrong type or not populated
2244  *
2245  */
2246
2247
2248 if(! function_exists('profile_sidebar')) {
2249 function profile_sidebar($profile) {
2250
2251         $o = '';
2252         $location = '';
2253         $address = false;
2254
2255         if((! is_array($profile)) && (! count($profile)))
2256                 return $o;
2257
2258         call_hooks('profile_sidebar_enter', $profile);
2259
2260         $fullname = '<div class="fn">' . $profile['name'] . '</div>';
2261
2262         $pdesc = '<div class="title">' . $profile['pdesc'] . '</div>';
2263
2264         $tabs = '';
2265
2266         $photo = '<div id="profile-photo-wrapper"><img class="photo" src="' . $profile['photo'] . '" alt="' . $profile['name'] . '" /></div>';
2267
2268         $connect = (($profile['uid'] != local_user()) ? '<li><a id="dfrn-request-link" href="dfrn_request/' . $profile['nickname'] . '">' . t('Connect') . '</a></li>' : '');
2269  
2270         if((x($profile,'address') == 1) 
2271                 || (x($profile,'locality') == 1) 
2272                 || (x($profile,'region') == 1) 
2273                 || (x($profile,'postal-code') == 1) 
2274                 || (x($profile,'country-name') == 1))
2275                 $address = true;
2276
2277         if($address) {
2278                 $location .= '<div class="location"><span class="location-label">' . t('Location:') . '</span> <div class="adr">';
2279                 $location .= ((x($profile,'address') == 1) ? '<div class="street-address">' . $profile['address'] . '</div>' : '');
2280                 $location .= (((x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1)) 
2281                         ? '<span class="city-state-zip"><span class="locality">' . $profile['locality'] . '</span>' 
2282                         . ((x($profile['locality']) == 1) ? t(', ') : '') 
2283                         . '<span class="region">' . $profile['region'] . '</span>'
2284                         . ' <span class="postal-code">' . $profile['postal-code'] . '</span></span>' : '');
2285                 $location .= ((x($profile,'country-name') == 1) ? ' <span class="country-name">' . $profile['country-name'] . '</span>' : '');  
2286                 $location .= '</div></div><div class="profile-clear"></div>';
2287
2288         }
2289
2290         $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>' : '');
2291
2292         $pubkey = ((x($profile,'pubkey') == 1) ? '<div class="key" style="display:none;">' . $profile['pubkey'] . '</div>' : '');
2293
2294         $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>' : '');
2295
2296         $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>' : '');
2297
2298         $tpl = load_view_file('view/profile_vcard.tpl');
2299
2300         $o .= replace_macros($tpl, array(
2301                 '$fullname' => $fullname,
2302                 '$pdesc'    => $pdesc,
2303                 '$tabs'     => $tabs,
2304                 '$photo'    => $photo,
2305                 '$connect'  => $connect,                
2306                 '$location' => $location,
2307                 '$gender'   => $gender,
2308                 '$pubkey'   => $pubkey,
2309                 '$marital'  => $marital,
2310                 '$homepage' => $homepage
2311         ));
2312
2313
2314         $arr = array('profile' => $profile, 'entry' => $o);
2315
2316         call_hooks('profile_sidebar', $arr);
2317
2318         return $o;
2319 }}
2320
2321
2322 if(! function_exists('register_hook')) {
2323 function register_hook($hook,$file,$function) {
2324
2325         $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2326                 dbesc($hook),
2327                 dbesc($file),
2328                 dbesc($function)
2329         );
2330         if(count($r))
2331                 return true;
2332
2333         $r = q("INSERT INTO `hook` (`hook`, `file`, `function`) VALUES ( '%s', '%s', '%s' ) ",
2334                 dbesc($hook),
2335                 dbesc($file),
2336                 dbesc($function)
2337         );
2338         return $r;
2339 }}
2340
2341 if(! function_exists('unregister_hook')) {
2342 function unregister_hook($hook,$file,$function) {
2343
2344         $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1",
2345                 dbesc($hook),
2346                 dbesc($file),
2347                 dbesc($function)
2348         );
2349         return $r;
2350 }}
2351
2352
2353 if(! function_exists('load_hooks')) {
2354 function load_hooks() {
2355         $a = get_app();
2356         $a->hooks = array();
2357         $r = q("SELECT * FROM `hook` WHERE 1");
2358         if(count($r)) {
2359                 foreach($r as $rr) {
2360                         $a->hooks[] = array($rr['hook'], $rr['file'], $rr['function']);
2361                 }
2362         }
2363 }}
2364
2365
2366 if(! function_exists('call_hooks')) {
2367 function call_hooks($name, &$data = null) {
2368         $a = get_app();
2369
2370         if(count($a->hooks)) {
2371                 foreach($a->hooks as $hook) {
2372                         if($hook[HOOK_HOOK] === $name) {
2373                                 @include_once($hook[HOOK_FILE]);
2374                                 if(function_exists($hook[HOOK_FUNCTION])) {
2375                                         $func = $hook[HOOK_FUNCTION];
2376                                         $func($a,$data);
2377                                 }
2378                         }
2379                 }
2380         }
2381 }}
2382
2383
2384 if(! function_exists('day_translate')) {
2385 function day_translate($s) {
2386         $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
2387                 array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
2388                 $s);
2389
2390         $ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
2391                 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')),
2392                 $ret);
2393
2394         return $ret;
2395 }}
2396
2397 if(! function_exists('get_birthdays')) {
2398 function get_birthdays() {
2399
2400         $a = get_app();
2401         $o = '';
2402
2403         if(! local_user())
2404                 return $o;
2405
2406         $bd_format = get_config('system','birthday_format');
2407         if(! $bd_format)
2408                 $bd_format = 'g A l F d' ; // 8 AM Friday January 18
2409
2410         $r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event` 
2411                 LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid` 
2412                 WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s' 
2413                 ORDER BY `start` DESC ",
2414                 intval(local_user()),
2415                 dbesc(datetime_convert('UTC','UTC','now + 6 days')),
2416                 dbesc(datetime_convert('UTC','UTC','now'))
2417         );
2418
2419         if($r && count($r)) {
2420                 $total = 0;
2421                 foreach($r as $rr)
2422                         if(strlen($rr['name']))
2423                                 $total ++;
2424
2425                 $o .= '<div id="birthday-notice" class="birthday-notice fakelink" onclick=openClose(\'birthday-wrapper\'); >' . t('Birthday Reminders') . ' ' . '(' . $total . ')' . '</div>'; 
2426                 $o .= '<div id="birthday-wrapper" style="display: none;" ><div id="birthday-title">' . t('Birthdays this week:') . '</div>'; 
2427                 $o .= '<div id="birthday-adjust">' . t("\x28Adjusted for local time\x29") . '</div>';
2428                 $o .= '<div id="birthday-title-end"></div>';
2429
2430                 foreach($r as $rr) {
2431                         if(! strlen($rr['name']))
2432                                 continue;
2433                         $now = strtotime('now');
2434                         $today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false); 
2435
2436                         $o .= '<div class="birthday-list" id="birthday-' . $rr['eid'] . '"><a class="sparkle" href="' 
2437                         . $a->get_baseurl() . '/redir/'  . $rr['cid'] . '">' . $rr['name'] . '</a> ' 
2438                         . day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $bd_format)) . (($today) ?  ' ' . t('[today]') : '')
2439                         . '</div>' ;
2440                 }
2441
2442                 $o .= '</div></div>';
2443         }
2444
2445   return $o;
2446
2447 }}
2448
2449 /**
2450  *
2451  * Compare two URLs to see if they are the same, but ignore
2452  * slight but hopefully insignificant differences such as if one 
2453  * is https and the other isn't, or if one is www.something and 
2454  * the other isn't - and also ignore case differences.
2455  *
2456  * Return true if the URLs match, otherwise false.
2457  *
2458  */
2459
2460 if(! function_exists('link_compare')) {
2461 function link_compare($a,$b) {
2462         $a1 = str_replace(array('https:','//www.'), array('http:','//'), $a);
2463         $b1 = str_replace(array('https:','//www.'), array('http:','//'), $b);
2464         if(strcasecmp($a1,$b1) === 0)
2465                 return true;
2466         return false;
2467 }}
2468
2469
2470 if(! function_exists('prepare_body')) {
2471 function prepare_body($item) {
2472         return prepare_text($item['body']);
2473 }}
2474
2475 if(! function_exists('prepare_text')) {
2476 function prepare_text($text) {
2477
2478         require_once('include/bbcode.php');
2479
2480         $s = smilies(bbcode($text));
2481
2482         return $s;
2483 }}
2484
2485 /**
2486  * 
2487  * Wrap calls to proc_close(proc_open()) and call hook
2488  * so plugins can take part in process :)
2489  * 
2490  * args:
2491  * $cmd program to run
2492  *  next args are passed as $cmd command line
2493  * 
2494  * e.g.: proc_run("ls","-la","/tmp");
2495  * 
2496  * $cmd and string args are surrounded with ""
2497  */
2498
2499 if(! function_exists('proc_run')) {
2500 function proc_run($cmd){
2501
2502         $a = get_app();
2503
2504         $args = func_get_args();
2505         call_hooks("proc_run", $args);
2506
2507         if(count($args) && $args[0] === 'php')
2508         $args[0] = ((x($a->config,'php_path')) && (strlen($a->config['php_path'])) ? $a->config['php_path'] : 'php');
2509         
2510         foreach ($args as $arg){
2511                 $arg = escapeshellarg($arg);
2512         }
2513         $cmdline = implode($args," ");
2514         proc_close(proc_open($cmdline." &",array(),$foo));
2515 }}
2516
2517 /*
2518  * Return full URL to theme which is currently in effect.
2519  * Provide a sane default if nothing is chosen or the specified theme does not exist.
2520  */
2521
2522 if(! function_exists('current_theme_url')) {
2523 function current_theme_url() {
2524
2525         $app_base_themes = array('duepuntozero', 'loozah');
2526
2527         $a = get_app();
2528
2529         $system_theme = ((isset($a->config['system']['theme'])) ? $a->config['system']['theme'] : '');
2530         $theme_name = ((x($_SESSION,'theme')) ? $_SESSION['theme'] : $system_theme);
2531
2532         if($theme_name && file_exists('view/theme/' . $theme_name . '/style.css'))
2533                 return($a->get_baseurl() . '/view/theme/' . $theme_name . '/style.css'); 
2534
2535         foreach($app_base_themes as $t) {
2536                 if(file_exists('view/theme/' . $t . '/style.css'))
2537                         return($a->get_baseurl() . '/view/theme/' . $t . '/style.css'); 
2538         }       
2539
2540         $fallback = glob('view/theme/*/style.css');
2541         if(count($fallback))
2542                 return($a->get_baseurl() . $fallback[0]);
2543
2544         
2545 }}
2546
2547 if(! function_exists('feed_birthday')) {
2548 function feed_birthday($uid,$tz) {
2549
2550         /**
2551          *
2552          * Determine the next birthday, but only if the birthday is published
2553          * in the default profile. We _could_ also look for a private profile that the
2554          * recipient can see, but somebody could get mad at us if they start getting
2555          * public birthday greetings when they haven't made this info public. 
2556          *
2557          * Assuming we are able to publish this info, we are then going to convert
2558          * the start time from the owner's timezone to UTC. 
2559          *
2560          * This will potentially solve the problem found with some social networks
2561          * where birthdays are converted to the viewer's timezone and salutations from
2562          * elsewhere in the world show up on the wrong day. We will convert it to the
2563          * viewer's timezone also, but first we are going to convert it from the birthday
2564          * person's timezone to GMT - so the viewer may find the birthday starting at
2565          * 6:00PM the day before, but that will correspond to midnight to the birthday person.
2566          *
2567          */
2568
2569         $birthday = '';
2570
2571         $p = q("SELECT `dob` FROM `profile` WHERE `is-default` = 1 AND `uid` = %d LIMIT 1",
2572                 intval($uid)
2573         );
2574
2575         if($p && count($p)) {
2576                 $tmp_dob = substr($p[0]['dob'],5);
2577                 if(intval($tmp_dob)) {
2578                         $y = datetime_convert($tz,$tz,'now','Y');
2579                         $bd = $y . '-' . $tmp_dob . ' 00:00';
2580                         $t_dob = strtotime($bd);
2581                         $now = strtotime(datetime_convert($tz,$tz,'now'));
2582                         if($t_dob < $now)
2583                                 $bd = $y + 1 . '-' . $tmp_dob . ' 00:00';
2584                         $birthday = datetime_convert($tz,'UTC',$bd,ATOM_TIME); 
2585                 }
2586         }
2587
2588         return $birthday;
2589 }}
2590
2591 /**
2592  * return atom link elements for all of our hubs
2593  */
2594
2595 if(! function_exists('feed_hublinks')) {
2596 function feed_hublinks() {
2597
2598         $hub = get_config('system','huburl');
2599
2600         $hubxml = '';
2601         if(strlen($hub)) {
2602                 $hubs = explode(',', $hub);
2603                 if(count($hubs)) {
2604                         foreach($hubs as $h) {
2605                                 $h = trim($h);
2606                                 if(! strlen($h))
2607                                         continue;
2608                                 $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
2609                         }
2610                 }
2611         }
2612         return $hubxml;
2613 }}
2614
2615 /* return atom link elements for salmon endpoints */
2616
2617 if(! function_exists('feed_salmonlinks')) {
2618 function feed_salmonlinks($nick) {
2619
2620         $a = get_app();
2621
2622         $salmon  = '<link rel="salmon" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ;
2623
2624         // old style links that status.net still needed as of 12/2010 
2625
2626         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2627         $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; 
2628         return $salmon;
2629 }}
2630
2631 if(! function_exists('get_plink')) {
2632 function get_plink($item) {
2633         $a = get_app(); 
2634         $plink = (((x($item,'plink')) && (! $item['private'])) ? '<div class="wall-item-links-wrapper"><a href="' 
2635                         . $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>' : '');
2636         return $plink;
2637 }}
2638
2639 if(! function_exists('unamp')) {
2640 function unamp($s) {
2641         return str_replace('&amp;', '&', $s);
2642 }}
2643
2644 if(! function_exists('extract_item_authors')) {
2645 function extract_item_authors($arr,$uid) {
2646
2647         if((! $uid) || (! is_array($arr)) || (! count($arr)))
2648                 return array();
2649         $urls = array();
2650         foreach($arr as $rr) {
2651                 if(! in_array("'" . dbesc($rr['author-link']) . "'",$urls))
2652                         $urls[] = "'" . dbesc($rr['author-link']) . "'";
2653         }
2654
2655         // pre-quoted, don't put quotes on %s
2656         if(count($urls)) {
2657                 $r = q("SELECT `id`,`url` FROM `contact` WHERE `uid` = %d AND `url` IN ( %s ) AND `network` = 'dfrn' AND `self` = 0 AND `blocked` = 0 ",
2658                         intval($uid),
2659                         implode(',',$urls)
2660                 );
2661                 if(count($r)) {
2662                         $ret = array();
2663                         foreach($r as $rr)
2664                                 $ret[$rr['url']] = $rr['id'];
2665                         return $ret;
2666                 }
2667         }
2668         return array();         
2669 }}