Release 0.9.9.1 for testing.
[davical.git] / htdocs / always.php
blob13e273543e18b3cf356f58655dc41498b104c189
1 <?php
2 /**
3 * @package davical
4 * @author Andrew McMillan <andrew@mcmillan.net.nz>
5 * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/>
6 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
7 */
9 if ( preg_match('{/always.php$}', $_SERVER['SCRIPT_NAME'] ) ) header('Location: index.php');
11 // Ensure the configuration starts out as an empty object.
12 $c = (object) array();
13 $c->script_start_time = microtime(true);
15 // Ditto for a few other global things
16 unset($session); unset($request); unset($dbconn); unset($_awl_dbconn); unset($include_properties);
18 // An ultra-simple exception handler to catch errors that occur
19 // before we get a more functional exception handler in place...
20 function early_exception_handler($e) {
21 echo "Uncaught early exception: ", $e->getMessage(), "\nAt line ", $e->getLine(), " of ", $e->getFile(), "\n";
23 $trace = array_reverse($e->getTrace());
24 foreach( $trace AS $k => $v ) {
25 printf( "=====================================================\n%s[%d] %s%s%s()\n", $v['file'], $v['line'], (isset($v['class'])?$v['class']:''), (isset($v['type'])?$v['type']:''), (isset($v['function'])?$v['function']:'') );
28 set_exception_handler('early_exception_handler');
30 // Default some of the configurable values
31 $c->sysabbr = 'davical';
32 $c->admin_email = 'admin@davical.example.com';
33 $c->system_name = 'DAViCal CalDAV Server';
34 $c->domain_name = (isset($_SERVER['SERVER_NAME'])?$_SERVER['SERVER_NAME']:$_SERVER['SERVER_ADDR']);
35 $c->save_time_zone_defs = true;
36 $c->collections_always_exist = false;
37 $c->allow_get_email_visibility = false;
38 $c->permission_scan_depth = 2;
39 $c->expand_pdo_parameters = true;
40 $c->home_calendar_name = 'home';
41 $c->enable_row_linking = true;
42 $c->enable_scheduling = false;
43 $c->http_auth_mode = 'Basic';
44 // $c->default_locale = array('es_MX', 'es_AR', 'es', 'pt'); // An array of locales to try, or just a single locale
45 // $c->local_tzid = 'Pacific/Auckland'; // Perhaps we should read from /etc/timezone - I wonder how standard that is?
46 $c->default_locale = 'en';
47 $c->locale_path = '../locale';
48 $c->base_url = preg_replace('#/[^/]+\.php.*$#', '', $_SERVER['SCRIPT_NAME']);
49 $c->base_directory = preg_replace('#/[^/]*$#', '', $_SERVER['DOCUMENT_ROOT']);
50 $c->default_privileges = array('read-free-busy', 'schedule-deliver');
52 $c->stylesheets = array( $c->base_url.'/davical.css' );
53 $c->images = $c->base_url . '/images';
55 // Add a default for newly created users
56 $c->template_usr = array( 'active' => true,
57 'locale' => 'en_GB',
58 'date_format_type' => 'E',
59 'email_ok' => date('Y-m-d')
62 $c->hide_TODO = true; // VTODO only visible to collection owner
63 $c->readonly_webdav_collections = true; // WebDAV access is readonly
65 // Kind of private configuration values
66 $c->total_query_time = 0;
68 $c->dbg = array();
71 // Utilities
72 if ( ! @include_once('AWLUtilities.php') ) {
73 $try_paths = array(
74 '../../awl/inc'
75 , '/usr/share/awl/inc'
76 , '/usr/local/share/awl/inc'
78 foreach( $try_paths AS $awl_include_path ) {
79 if ( @file_exists($awl_include_path.'/AWLUtilities.php') ) {
80 set_include_path( $awl_include_path. PATH_SEPARATOR. get_include_path());
81 break;
84 if ( ! @include_once('AWLUtilities.php') ) {
85 echo "Could not find the AWL libraries. Are they installed? Check your include_path in php.ini!\n";
86 exit;
90 // Ensure that ../inc is in our included paths as early as possible
91 set_include_path( '../inc'. PATH_SEPARATOR. get_include_path());
94 /** We actually discovered this and worked around it earlier, but we can't log it until the utilties are loaded */
95 if ( !isset($_SERVER['SERVER_NAME']) ) {
96 @dbg_error_log( 'WARN', "Your webserver is not setting the SERVER_NAME parameter. You may need to set \$c->domain_name in your configuration. Using IP address meanhwhile..." );
99 /**
100 * Calculate the simplest form of reference to this page, excluding the PATH_INFO following the script name.
102 $c->protocol_server_port = sprintf( '%s://%s%s',
103 (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'? 'https' : 'http'),
104 $_SERVER['SERVER_NAME'],
106 ( (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') && $_SERVER['SERVER_PORT'] == 80 )
107 || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && $_SERVER['SERVER_PORT'] == 443 )
108 ? ''
109 : ':'.$_SERVER['SERVER_PORT']
110 ) );
111 $c->protocol_server_port_script = $c->protocol_server_port . ($_SERVER['SCRIPT_NAME'] == '/index.php' ? '' : $_SERVER['SCRIPT_NAME']);
115 * We use @file_exists because things like open_basedir might noisily deny
116 * access which could break DAViCal completely by causing output to start
117 * too early.
119 ob_start( );
120 if ( @file_exists('/etc/davical/'.$_SERVER['SERVER_NAME'].'-conf.php') ) {
121 include('/etc/davical/'.$_SERVER['SERVER_NAME'].'-conf.php');
123 else if ( @file_exists('/etc/davical/config.php') ) {
124 include('/etc/davical/config.php');
126 else if ( @file_exists('/usr/local/etc/davical/'.$_SERVER['SERVER_NAME'].'-conf.php') ) {
127 include('/usr/local/etc/davical/'.$_SERVER['SERVER_NAME'].'-conf.php');
129 else if ( @file_exists('/usr/local/etc/davical/config.php') ) {
130 include('/usr/local/etc/davical/config.php');
132 else if ( @file_exists('../config/config.php') ) {
133 include('../config/config.php');
135 else if ( @file_exists('config/config.php') ) {
136 include('config/config.php');
138 else {
139 include('davical_configuration_missing.php');
140 exit;
142 $config_warnings = trim(ob_get_contents());
143 ob_end_clean();
145 if ( !isset($c->page_title) ) $c->page_title = $c->system_name;
147 if ( isset($_SERVER['HTTP_X_DAVICAL_TESTCASE']) ) {
148 @dbg_error_log( 'LOG', '==========> Test case =%s=', $_SERVER['HTTP_X_DAVICAL_TESTCASE'] );
150 else if ( isset($c->dbg['script_start']) && $c->dbg['script_start'] ) {
151 // Only log this if more than a little debugging of some sort is turned on, somewhere
152 @dbg_error_log( 'LOG', '==========> method =%s= =%s= =%s= =%s= =%s=',
153 $_SERVER['REQUEST_METHOD'], $c->protocol_server_port_script, $_SERVER['PATH_INFO'], $c->base_url, $c->base_directory );
157 * Now that we have loaded the configuration file we can switch to a
158 * default site locale. This may be overridden by each user.
160 putenv("LANG=". $c->default_locale);
161 awl_set_locale($c->default_locale);
162 init_gettext( 'davical', $c->locale_path );
165 * Work out our version
168 $c->code_version = 0;
169 $c->version_string = '0.9.9.1'; // The actual version # is replaced into that during the build /release process
170 if ( isset($c->version_string) && preg_match( '/(\d+)\.(\d+)\.(\d+)(.*)/', $c->version_string, $matches) ) {
171 $c->code_major = $matches[1];
172 $c->code_minor = $matches[2];
173 $c->code_patch = $matches[3];
174 $c->code_version = (($c->code_major * 1000) + $c->code_minor).'.'.$c->code_patch;
175 dbg_error_log('caldav', 'Version (%d.%d.%d) == %s', $c->code_major, $c->code_minor, $c->code_patch, $c->code_version);
176 header( sprintf('Server: %d.%d', $c->code_major, $c->code_minor) );
180 * Force the domain name to what was in the configuration file
182 $_SERVER['SERVER_NAME'] = $c->domain_name;
184 require_once('AwlQuery.php');
186 $c->want_dbversion = array(1,2,9);
187 $c->schema_version = 0;
188 $qry = new AwlQuery( 'SELECT schema_major, schema_minor, schema_patch FROM awl_db_revision ORDER BY schema_id DESC LIMIT 1;' );
189 if ( $qry->Exec('always',__LINE__,__FILE__) && $row = $qry->Fetch() ) {
190 $c->schema_version = doubleval( sprintf( '%d%03d.%03d', $row->schema_major, $row->schema_minor, $row->schema_patch) );
191 $c->wanted_version = doubleval( sprintf( '%d%03d.%03d', $c->want_dbversion[0], $c->want_dbversion[1], $c->want_dbversion[2]) );
192 $c->schema_major = $row->schema_major;
193 $c->schema_minor = $row->schema_minor;
194 $c->schema_patch = $row->schema_patch;
195 if ( $c->schema_version < $c->wanted_version ) {
196 $c->messages[] = sprintf( 'Database schema needs upgrading. Current: %d.%d.%d, Desired: %d.%d.%d',
197 $row->schema_major, $row->schema_minor, $row->schema_patch, $c->want_dbversion[0], $c->want_dbversion[1], $c->want_dbversion[2]);
199 if ( isset($_SERVER['HTTP_X_DAVICAL_TESTCASE']) ) $qry->QDo('SET TIMEZONE TO \'Pacific/Auckland\'');
203 $_known_users_name = array();
204 $_known_users_id = array();
205 $_known_users_pid = array();
207 function _davical_get_principal_query_cached( $where, $parameter ) {
208 global $c, $session, $_known_users_name, $_known_users_id, $_known_users_pid;
210 $sql = 'SELECT *, to_char(updated at time zone \'GMT\',\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS modified, principal.*, ';
211 if ( isset($session->principal_id) ) {
212 $sql .= 'pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) AS privileges ';
213 $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
215 else {
216 $sql .= '0::BIT(24) AS privileges ';
217 $params = array( );
219 $sql .= 'FROM usr LEFT JOIN principal USING(user_no) WHERE '. $where;
220 $params[':param'] = $parameter;
222 $qry = new AwlQuery( $sql, $params );
223 if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows() == 1 && $row = $qry->Fetch() ) {
224 if ( isset($session->principal_id) ) {
225 $_known_users_name[$row->username] = $row;
226 $_known_users_id[$row->user_no] = $row;
227 $_known_users_pid[$row->principal_id] = $row;
229 return $row;
232 return false;
236 * Return a user record identified by a username, caching it for any subsequent lookup
237 * @param string $username The username of the record to retrieve
238 * @param boolean $use_cache Whether or not to use the cache (default: yes)
240 function getUserByName( $username, $use_cache = true ) {
241 global $_known_users_name;
243 if ( $use_cache && isset( $_known_users_name[$username] ) ) return $_known_users_name[$username];
244 return _davical_get_principal_query_cached( 'lower(username) = lower(:param)', $username );
249 * Return a user record identified by e-mail address, caching it for any subsequent lookup
250 * @param string $email The email address of the user record to retrieve
251 * @param boolean $use_cache Whether or not to use the cache (default: yes)
253 function getUserByEMail( $email, $use_cache = true ) {
254 global $_known_users_name;
256 if ( $use_cache ) {
257 /** We don't actually maintain a cache-access by e-mail, since it's rare */
258 foreach( $_known_users_name AS $k => $v ) {
259 if ( strtolower($email) == strtolower($v->email) ) return $v;
262 return _davical_get_principal_query_cached( 'lower(email) = lower(:param)', $email );
267 * Return a user record identified by a user_no, caching it for any subsequent lookup
268 * @param int $user_no The ID of the record to retrieve
269 * @param boolean $use_cache Whether or not to use the cache (default: yes)
271 function getUserByID( $user_no, $use_cache = true ) {
272 global $c, $session, $_known_users_id;
274 if ( $use_cache && isset( $_known_users_id[$user_no] ) ) return $_known_users_id[$user_no];
275 return _davical_get_principal_query_cached( 'user_no = :param', $user_no );
280 * Return a user record identified by a user_no, caching it for any subsequent lookup
281 * @param int $user_no The ID of the record to retrieve
282 * @param boolean $use_cache Whether or not to use the cache (default: yes)
284 function getPrincipalByID( $principal_id, $use_cache = true ) {
285 global $c, $session, $_known_users_pid;
287 if ( $use_cache && isset( $_known_users_pid[$principal_id] ) ) return $_known_users_pid[$principal_id];
288 return _davical_get_principal_query_cached( 'principal_id = :param', $principal_id );
293 * Return the HTTP status code description for a given code. Hopefully
294 * this is an efficient way to code this.
295 * @return string The text for a give HTTP status code, in english
297 function getStatusMessage($status) {
298 switch( $status ) {
299 case 100: $ans = 'Continue'; break;
300 case 101: $ans = 'Switching Protocols'; break;
301 case 200: $ans = 'OK'; break;
302 case 201: $ans = 'Created'; break;
303 case 202: $ans = 'Accepted'; break;
304 case 203: $ans = 'Non-Authoritative Information'; break;
305 case 204: $ans = 'No Content'; break;
306 case 205: $ans = 'Reset Content'; break;
307 case 206: $ans = 'Partial Content'; break;
308 case 207: $ans = 'Multi-Status'; break;
309 case 300: $ans = 'Multiple Choices'; break;
310 case 301: $ans = 'Moved Permanently'; break;
311 case 302: $ans = 'Found'; break;
312 case 303: $ans = 'See Other'; break;
313 case 304: $ans = 'Not Modified'; break;
314 case 305: $ans = 'Use Proxy'; break;
315 case 307: $ans = 'Temporary Redirect'; break;
316 case 400: $ans = 'Bad Request'; break;
317 case 401: $ans = 'Unauthorized'; break;
318 case 402: $ans = 'Payment Required'; break;
319 case 403: $ans = 'Forbidden'; break;
320 case 404: $ans = 'Not Found'; break;
321 case 405: $ans = 'Method Not Allowed'; break;
322 case 406: $ans = 'Not Acceptable'; break;
323 case 407: $ans = 'Proxy Authentication Required'; break;
324 case 408: $ans = 'Request Timeout'; break;
325 case 409: $ans = 'Conflict'; break;
326 case 410: $ans = 'Gone'; break;
327 case 411: $ans = 'Length Required'; break;
328 case 412: $ans = 'Precondition Failed'; break;
329 case 413: $ans = 'Request Entity Too Large'; break;
330 case 414: $ans = 'Request-URI Too Long'; break;
331 case 415: $ans = 'Unsupported Media Type'; break;
332 case 416: $ans = 'Requested Range Not Satisfiable'; break;
333 case 417: $ans = 'Expectation Failed'; break;
334 case 422: $ans = 'Unprocessable Entity'; break;
335 case 423: $ans = 'Locked'; break;
336 case 424: $ans = 'Failed Dependency'; break;
337 case 500: $ans = 'Internal Server Error'; break;
338 case 501: $ans = 'Not Implemented'; break;
339 case 502: $ans = 'Bad Gateway'; break;
340 case 503: $ans = 'Service Unavailable'; break;
341 case 504: $ans = 'Gateway Timeout'; break;
342 case 505: $ans = 'HTTP Version Not Supported'; break;
343 default: $ans = 'Unknown HTTP Status Code '.$status;
345 return $ans;
350 * Construct a URL from the supplied dav_name. The URL will be urlencoded,
351 * except for any '/' characters in it.
352 * @param string $partial_path The part of the path after the script name
354 function ConstructURL( $partial_path, $force_script = false ) {
355 global $c;
357 $partial_path = rawurlencode($partial_path);
358 $partial_path = str_replace( '%2F', '/', $partial_path);
360 if ( ! isset($c->_url_script_path) ) {
361 $c->_url_script_path = (preg_match('#/$#', $c->protocol_server_port_script) ? 'caldav.php' : '');
362 $c->_url_script_path = $c->protocol_server_port_script . $c->_url_script_path;
365 $url = $c->_url_script_path;
366 if ( $force_script ) {
367 if ( ! preg_match( '#/caldav\.php$#', $url ) ) $url .= '/caldav.php';
369 $url .= $partial_path;
370 $url = preg_replace( '#^(https?://.+)//#', '$1/', $url ); // Ensure we don't double any '/'
371 $url = preg_replace('#^https?://[^/]+#', '', $url ); // Remove any protocol + hostname portion
373 return $url;
378 * Deconstruct a dav_name from the supplied URL. The dav_name will be urldecoded.
380 * @param string $partial_path The part of the path after the script name
382 function DeconstructURL( $url, $force_script = false ) {
383 global $c;
385 $dav_name = rawurldecode($url);
387 /** Allow a path like .../username/calendar.ics to translate into the calendar URL */
388 if ( preg_match( '#^(/[^/]+/[^/]+).ics$#', $dav_name, $matches ) ) {
389 $dav_name = $matches[1]. '/';
392 /** remove any leading protocol/server/port/prefix... */
393 if ( !isset($c->deconstruction_base_path) ) $c->deconstruction_base_path = ConstructURL('/');
394 if ( preg_match( '%^(.*?)'.str_replace('%', '\\%',$c->deconstruction_base_path).'(.*)$%', $dav_name, $matches ) ) {
395 if ( $matches[1] == '' || $matches[1] == $c->protocol_server_port ) {
396 $dav_name = $matches[2];
400 /** strip doubled slashes */
401 if ( strstr($dav_name,'//') ) $dav_name = preg_replace( '#//+#', '/', $dav_name);
403 if ( substr($dav_name,0,1) != '/' ) $dav_name = '/'.$dav_name;
405 return $dav_name;
410 * Convert a date from ISO format into the sad old HTTP format.
411 * @param string $isodate The date to convert
413 function ISODateToHTTPDate( $isodate ) {
414 // Use strtotime since strptime is not available on Windows platform.
415 return( gmstrftime('%a, %d %b %Y %T GMT', strtotime($isodate)) );
419 * Convert a date into ISO format into the sparkly new ISO format.
420 * @param string $indate The date to convert
422 function DateToISODate( $indate ) {
423 // Use strtotime since strptime is not available on Windows platform.
424 return( date('c', strtotime($indate)) );
428 * Given a privilege string, or an array of privilege strings, return a bit mask
429 * of the privileges.
430 * @param mixed $raw_privs The string (or array of strings) of privilege names
431 * @return integer A bit mask of the privileges.
433 define("DAVICAL_MAXPRIV", "65535");
434 define("DAVICAL_ADDRESSBOOK_MAXPRIV", "1023");
435 function privilege_to_bits( $raw_privs ) {
436 $out_priv = 0;
438 if ( gettype($raw_privs) == 'string' ) $raw_privs = array( $raw_privs );
440 if ( ! is_array($raw_privs) ) $raw_privs = array($raw_privs);
442 foreach( $raw_privs AS $priv ) {
443 $trim_priv = trim(strtolower(preg_replace( '/^.*:/', '', $priv)));
444 switch( $trim_priv ) {
445 case 'read' : $out_priv |= 1; break;
446 case 'write-properties' : $out_priv |= 2; break;
447 case 'write-content' : $out_priv |= 4; break;
448 case 'unlock' : $out_priv |= 8; break;
449 case 'read-acl' : $out_priv |= 16; break;
450 case 'read-current-user-privilege-set' : $out_priv |= 32; break;
451 case 'bind' : $out_priv |= 64; break;
452 case 'unbind' : $out_priv |= 128; break;
453 case 'write-acl' : $out_priv |= 256; break;
454 case 'read-free-busy' : $out_priv |= 512; break;
455 case 'schedule-deliver-invite' : $out_priv |= 1024; break;
456 case 'schedule-deliver-reply' : $out_priv |= 2048; break;
457 case 'schedule-query-freebusy' : $out_priv |= 4096; break;
458 case 'schedule-send-invite' : $out_priv |= 8192; break;
459 case 'schedule-send-reply' : $out_priv |= 16384; break;
460 case 'schedule-send-freebusy' : $out_priv |= 32768; break;
462 /** Aggregates of Privileges */
463 case 'write' : $out_priv |= 198; break; // 2 + 4 + 64 + 128
464 case 'schedule-deliver' : $out_priv |= 7168; break; // 1024 + 2048 + 4096
465 case 'schedule-send' : $out_priv |= 57344; break; // 8192 + 16384 + 32768
466 case 'all' : $out_priv = DAVICAL_MAXPRIV; break;
467 default:
468 dbg_error_log( 'ERROR', 'Cannot convert privilege of "%s" into bits.', $priv );
473 // 'all' will include future privileges
474 if ( ($out_priv & DAVICAL_MAXPRIV) >= DAVICAL_MAXPRIV ) $out_priv = pow(2,24) - 1;
475 return $out_priv;
480 * Given a bit mask of the privileges, will return an array of the
481 * text values of privileges.
482 * @param integer $raw_bits A bit mask of the privileges.
483 * @return mixed The string (or array of strings) of privilege names
485 function bits_to_privilege( $raw_bits, $resourcetype = 'resource' ) {
486 $out_priv = array();
488 if ( is_string($raw_bits) ) {
489 $raw_bits = bindec($raw_bits);
492 if ( ($raw_bits & DAVICAL_MAXPRIV) == DAVICAL_MAXPRIV ) $out_priv[] = 'all';
494 if ( ($raw_bits & 1) != 0 ) $out_priv[] = 'DAV::read';
495 if ( ($raw_bits & 8) != 0 ) $out_priv[] = 'DAV::unlock';
496 if ( ($raw_bits & 16) != 0 ) $out_priv[] = 'DAV::read-acl';
497 if ( ($raw_bits & 32) != 0 ) $out_priv[] = 'DAV::read-current-user-privilege-set';
498 if ( ($raw_bits & 256) != 0 ) $out_priv[] = 'DAV::write-acl';
499 if ( ($resourcetype == 'calendar' || $resourcetype == 'proxy') && ($raw_bits & 512) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:read-free-busy';
501 if ( ($raw_bits & 198) != 0 ) {
502 if ( ($raw_bits & 198) == 198 ) $out_priv[] = 'DAV::write';
503 if ( ($raw_bits & 2) != 0 ) $out_priv[] = 'DAV::write-properties';
504 if ( ($raw_bits & 4) != 0 ) $out_priv[] = 'DAV::write-content';
505 if ( ($raw_bits & 64) != 0 ) $out_priv[] = 'DAV::bind';
506 if ( ($raw_bits & 128) != 0 ) $out_priv[] = 'DAV::unbind';
509 if ( $resourcetype == 'schedule-inbox' && ($raw_bits & 7168) != 0 ) {
510 if ( ($raw_bits & 7168) == 7168 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver';
511 if ( ($raw_bits & 1024) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-invite';
512 if ( ($raw_bits & 2048) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-reply';
513 if ( ($raw_bits & 4096) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-query-freebusy';
516 if ( $resourcetype == 'schedule-outbox' && ($raw_bits & 57344) != 0 ) {
517 if ( ($raw_bits & 57344) == 57344 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send';
518 if ( ($raw_bits & 8192) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-invite';
519 if ( ($raw_bits & 16384) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-reply';
520 if ( ($raw_bits & 32768) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-freebusy';
523 // dbg_error_log( 'DAVResource', ' Privilege bit "%s" is "%s".', $raw_bits, implode(', ', $out_priv) );
525 return $out_priv;
530 * Returns the array of privilege names converted into XMLElements
532 function privileges_to_XML( $privilege_names, &$xmldoc=null ) {
533 if ( !isset($xmldoc) && isset($GLOBALS['reply']) ) $xmldoc = $GLOBALS['reply'];
534 $privileges = array();
535 foreach( $privilege_names AS $k ) {
536 $privilege = new XMLElement('privilege');
537 if ( isset($xmldoc) )
538 $xmldoc->NSElement($privilege,$k);
539 else
540 $privilege->NewElement($k);
541 $privileges[] = $privilege;
543 return $privileges;