/** * Method used to get the user's roles */ function GetRoles() { $this->roles = array(); $sql = 'SELECT role_name FROM roles JOIN role_member ON roles.role_no=role_member.role_no WHERE user_no = ' . $this->user_no; $qry = new AwlQuery($sql); if ($qry->Exec('DAViCalSession') && $qry->rows() > 0) { while ($role = $qry->Fetch()) { $this->roles[$role->role_name] = 1; } } }
function delete_collection($id) { $params = array(':collection_id' => $id); $qry = new AwlQuery('SELECT child.collection_id AS child_id FROM collection child JOIN collection parent ON (parent.dav_name = child.parent_container) WHERE parent.collection_id = :collection_id', $params); if ($qry->Exec('DELETE', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { delete_collection($row->child_id); } } if ($qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM property WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM locks WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM collection WHERE collection_id = :collection_id", $params)) { @dbg_error_log("DELETE", "DELETE (collection): User: %d, ETag: %s, Path: %s", $session->user_no, $request->etag_if_match, $request->path); return true; } return false; }
static function getInstance($name) { $qry = new AwlQuery('SELECT * FROM timezones WHERE tzid = ? ORDER BY active DESC', $name); if ($qry->Exec('VTimezone', __LINE__, __FILE__) && $qry->rows() > 0 && ($row = $qry->Fetch())) { $vtz = new vComponent($row->vtimezone); if ($vtz->GetType() == 'VTIMEZONE') { return $vtz; } $tmp = $vtz->GetComponents('VTIMEZONE'); if (count($tmp) < 1 || $tmp[0]->GetType() != 'VTIMEZONE') { return null; } $vtz = $tmp[0]; return $vtz; } return null; }
function SQLTest() { $result = ''; $sql = "SELECT event_instances::timestamp AS event_date FROM event_instances(:dtstart,:rrule) LIMIT " . $this->result_limit; $start = microtime(true); $qry = new AwlQuery($sql, array(':dtstart' => $this->dtstart, ':rrule' => $this->recur)); // printf( "%s\n", $qry->querystring); if ($qry->Exec("test") && $qry->rows() > 0) { $i = 0; while ($row = $qry->Fetch()) { if ($i++ % 4 == 0) { $result .= "\n"; } $result .= " " . $row->event_date; } } $this->SQL_time = microtime(true) - $start; return $result; }
/** * Split this out so we do it as infrequently as possible, given the cost. */ function FetchProxyGroups() { global $c; $this->read_proxy_group = array(); $this->write_proxy_group = array(); $this->write_proxy_for = array(); $this->read_proxy_for = array(); if (!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy === false) { $write_priv = privilege_to_bits(array('write')); // whom are we a proxy for? who is a proxy for us? // (as per Caldav Proxy section 5.1 Paragraph 7 and 5) $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from p_has_proxy_access_to(:request_principal,:scan_depth))'; $params = array(':request_principal' => $this->principal_id, ':scan_depth' => $c->permission_scan_depth); $qry = new AwlQuery($sql, $params); if ($qry->Exec('CalDAVPrincipal') && $qry->rows() > 0) { while ($relationship = $qry->Fetch()) { if ((bindec($relationship->pprivs) & $write_priv) != 0) { $this->write_proxy_for[] = ConstructURL('/' . $relationship->username . '/', true); $this->group_membership[] = ConstructURL('/' . $relationship->username . '/calendar-proxy-write/', true); } else { $this->read_proxy_for[] = ConstructURL('/' . $relationship->username . '/', true); $this->group_membership[] = ConstructURL('/' . $relationship->username . '/calendar-proxy-read/', true); } } } $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from grants_proxy_access_from_p(:request_principal,:scan_depth))'; $qry = new AwlQuery($sql, $params); // reuse $params assigned for earlier query if ($qry->Exec('CalDAVPrincipal') && $qry->rows() > 0) { while ($relationship = $qry->Fetch()) { if (bindec($relationship->pprivs) & $write_priv) { $this->write_proxy_group[] = ConstructURL('/' . $relationship->username . '/', true); } else { $this->read_proxy_group[] = ConstructURL('/' . $relationship->username . '/', true); } } } // @dbg_error_log( 'principal', 'Read-proxy-for: %s', implode(',',$this->read_proxy_for) ); // @dbg_error_log( 'principal', 'Write-proxy-for: %s', implode(',',$this->write_proxy_for) ); // @dbg_error_log( 'principal', 'Read-proxy-group: %s', implode(',',$this->read_proxy_group) ); // @dbg_error_log( 'principal', 'Write-proxy-group: %s', implode(',',$this->write_proxy_group) ); } }
$proplist = array(); foreach ($props as $k => $v) { $proplist[] = $v->GetTag(); } function display_status($status_code) { return sprintf('HTTP/1.1 %03d %s', intval($status_code), getStatusMessage($status_code)); } $collection = new DAVResource($request->path); if (!$collection->Exists()) { $request->DoResponse(404); } $params = array(':collection_id' => $collection->GetProperty('collection_id'), ':sync_token' => $sync_token); $sql = "SELECT new_sync_token( :sync_token, :collection_id)"; $qry = new AwlQuery($sql, $params); if (!$qry->Exec("REPORT", __LINE__, __FILE__) || $qry->rows() <= 0) { $request->DoResponse(500, translate("Database error")); } $row = $qry->Fetch(); if (!isset($row->new_sync_token)) { /** If we got a null back then they gave us a sync token we know not of, so provide a full sync */ $sync_token = 0; $params[':sync_token'] = $sync_token; if (!$qry->QDo($sql, $params) || $qry->rows() <= 0) { $request->DoResponse(500, translate("Database error")); } $row = $qry->Fetch(); } $new_token = $row->new_sync_token; if ($sync_token == $new_token) { // No change, so we just re-send the old token.
/** * Authenticate against a different PostgreSQL database which contains a usr table in * the AWL format. * * Use this as in the following example config snippet: * * require_once('auth-functions.php'); * $c->authenticate_hook = array( * 'call' => 'AuthExternalAwl', * 'config' => array( * // A PgSQL database connection string for the database containing user records * 'connection[]' => 'dbname=wrms host=otherhost port=5433 user=general', * // Which columns should be fetched from the database * 'columns' => "user_no, active, email_ok, joined, last_update AS updated, last_used, username, password, fullname, email", * // a WHERE clause to limit the records returned. * 'where' => "active AND org_code=7" * ) * ); * */ function AuthExternalAWL($username, $password) { global $c; $persistent = isset($c->authenticate_hook['config']['use_persistent']) && $c->authenticate_hook['config']['use_persistent']; if (isset($c->authenticate_hook['config']['columns'])) { $cols = $c->authenticate_hook['config']['columns']; } else { $cols = '*'; } if (isset($c->authenticate_hook['config']['where'])) { $andwhere = ' AND ' . $c->authenticate_hook['config']['where']; } else { $andwhere = ''; } $qry = new AwlQuery('SELECT ' . $cols . ' FROM usr WHERE lower(username) = :username ' . $andwhere, array(':username' => strtolower($username))); $authconn = $qry->SetConnection($c->authenticate_hook['config']['connection'], $persistent ? array(PDO::ATTR_PERSISTENT => true) : null); if (!$authconn) { echo <<<EOERRMSG <html><head><title>Database Connection Failure</title></head><body> <h1>Database Error</h1> <h3>Could not connect to PostgreSQL database</h3> </body> </html> EOERRMSG; exit(1); } if ($qry->Exec('Login', __LINE__, __FILE__) && $qry->rows() == 1) { $usr = $qry->Fetch(); if (session_validate_password($password, $usr->password)) { UpdateUserFromExternal($usr); /** * We disallow login by inactive users _after_ we have updated the local copy */ if (isset($usr->active) && $usr->active == 'f') { return false; } $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $usr->username)); if ($qry->Exec() && $qry->rows() == 1) { $principal = $qry->Fetch(); return $principal; } return $usr; // Somewhat optimistically } } return false; }
/** * Create a new CalDAVRequest object. */ function __construct($options = array()) { global $session, $c, $debugging; $this->options = $options; if (!isset($this->options['allow_by_email'])) { $this->options['allow_by_email'] = false; } if (isset($_SERVER['HTTP_PREFER'])) { $this->prefer = explode(',', $_SERVER['HTTP_PREFER']); } else { if (isset($_SERVER['HTTP_BRIEF']) && strtoupper($_SERVER['HTTP_BRIEF']) == 'T') { $this->prefer = array('return-minimal'); } else { $this->prefer = array(); } } /** * Our path is /<script name>/<user name>/<user controlled> if it ends in * a trailing '/' then it is referring to a DAV 'collection' but otherwise * it is referring to a DAV data item. * * Permissions are controlled as follows: * 1. if there is no <user name> component, the request has read privileges * 2. if the requester is an admin, the request has read/write priviliges * 3. if there is a <user name> component which matches the logged on user * then the request has read/write privileges * 4. otherwise we query the defined relationships between users and use * the minimum privileges returned from that analysis. */ if (isset($_SERVER['PATH_INFO'])) { $this->path = $_SERVER['PATH_INFO']; } else { $this->path = '/'; if (isset($_SERVER['REQUEST_URI'])) { if (preg_match('{^(.*?\\.php)(.*)$}', $_SERVER['REQUEST_URI'], $matches)) { $this->path = $matches[2]; if (substr($this->path, 0, 1) != '/') { $this->path = '/' . $this->path; } } else { if ($_SERVER['REQUEST_URI'] != '/') { dbg_error_log('LOG', 'Server is not supplying PATH_INFO and REQUEST_URI does not include a PHP program. Wildly guessing "/"!!!'); } } } } $this->path = rawurldecode($this->path); /** Allow a request for .../calendar.ics to translate into the calendar URL */ if (preg_match('#^(/[^/]+/[^/]+).ics$#', $this->path, $matches)) { $this->path = $matches[1] . '/'; } if (isset($c->replace_path) && isset($c->replace_path['from']) && isset($c->replace_path['to'])) { $this->path = preg_replace($c->replace_path['from'], $c->replace_path['to'], $this->path); } // dbg_error_log( "caldav", "Sanitising path '%s'", $this->path ); $bad_chars_regex = '/[\\^\\[\\(\\\\]/'; if (preg_match($bad_chars_regex, $this->path)) { $this->DoResponse(400, translate("The calendar path contains illegal characters.")); } if (strstr($this->path, '//')) { $this->path = preg_replace('#//+#', '/', $this->path); } if (!isset($c->raw_post)) { $c->raw_post = file_get_contents('php://input'); } if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { $encoding = $_SERVER['HTTP_CONTENT_ENCODING']; @dbg_error_log('caldav', 'Content-Encoding: %s', $encoding); $encoding = preg_replace('{[^a-z0-9-]}i', '', $encoding); if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['caldav']))) { $fh = fopen('/var/log/davical/encoded_data.debug' . $encoding, 'w'); if ($fh) { fwrite($fh, $c->raw_post); fclose($fh); } } switch ($encoding) { case 'gzip': $this->raw_post = @gzdecode($c->raw_post); break; case 'deflate': $this->raw_post = @gzinflate($c->raw_post); break; case 'compress': $this->raw_post = @gzuncompress($c->raw_post); break; default: } if (empty($this->raw_post) && !empty($c->raw_post)) { $this->PreconditionFailed(415, 'content-encoding', sprintf('Unable to decode "%s" content encoding.', $_SERVER['HTTP_CONTENT_ENCODING'])); } $c->raw_post = $this->raw_post; } else { $this->raw_post = $c->raw_post; } if (isset($debugging) && isset($_GET['method'])) { $_SERVER['REQUEST_METHOD'] = $_GET['method']; } else { if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $_SERVER['REQUEST_METHOD'] = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; } } $this->method = $_SERVER['REQUEST_METHOD']; $this->content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null; if (preg_match('{^(\\S+/\\S+)\\s*(;.*)?$}', $this->content_type, $matches)) { $this->content_type = $matches[1]; } if (strlen($c->raw_post) > 0) { if ($this->method == 'PROPFIND' || $this->method == 'REPORT' || $this->method == 'PROPPATCH' || $this->method == 'BIND' || $this->method == 'MKTICKET' || $this->method == 'ACL') { if (!preg_match('{^(text|application)/xml$}', $this->content_type)) { @dbg_error_log("LOG request", 'Request is "%s" but client set content-type to "%s". Assuming they meant XML!', $this->method, $this->content_type); $this->content_type = 'text/xml'; } } else { if ($this->method == 'PUT' || $this->method == 'POST') { $this->CoerceContentType(); } } } else { $this->content_type = 'text/plain'; } $this->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "Probably Mulberry"; /** * A variety of requests may set the "Depth" header to control recursion */ if (isset($_SERVER['HTTP_DEPTH'])) { $this->depth = $_SERVER['HTTP_DEPTH']; } else { /** * Per rfc2518, section 9.2, 'Depth' might not always be present, and if it * is not present then a reasonable request-type-dependent default should be * chosen. */ switch ($this->method) { case 'DELETE': case 'MOVE': case 'COPY': case 'LOCK': $this->depth = 'infinity'; break; case 'REPORT': $this->depth = 0; break; case 'PROPFIND': default: $this->depth = 0; } } if (!is_int($this->depth) && "infinity" == $this->depth) { $this->depth = DEPTH_INFINITY; } $this->depth = intval($this->depth); /** * MOVE/COPY use a "Destination" header and (optionally) an "Overwrite" one. */ if (isset($_SERVER['HTTP_DESTINATION'])) { $this->destination = $_SERVER['HTTP_DESTINATION']; if (preg_match('{^(https?)://([a-z.-]+)(:[0-9]+)?(/.*)$}', $this->destination, $matches)) { $this->destination = $matches[4]; } } $this->overwrite = isset($_SERVER['HTTP_OVERWRITE']) && $_SERVER['HTTP_OVERWRITE'] == 'F' ? false : true; // RFC4918, 9.8.4 says default True. /** * LOCK things use an "If" header to hold the lock in some cases, and "Lock-token" in others */ if (isset($_SERVER['HTTP_IF'])) { $this->if_clause = $_SERVER['HTTP_IF']; } if (isset($_SERVER['HTTP_LOCK_TOKEN']) && preg_match('#[<]opaquelocktoken:(.*)[>]#', $_SERVER['HTTP_LOCK_TOKEN'], $matches)) { $this->lock_token = $matches[1]; } /** * Check for an access ticket. */ if (isset($_GET['ticket'])) { $this->ticket = new DAVTicket($_GET['ticket']); } else { if (isset($_SERVER['HTTP_TICKET'])) { $this->ticket = new DAVTicket($_SERVER['HTTP_TICKET']); } } /** * LOCK things use a "Timeout" header to set a series of reducing alternative values */ if (isset($_SERVER['HTTP_TIMEOUT'])) { $timeouts = explode(',', $_SERVER['HTTP_TIMEOUT']); foreach ($timeouts as $k => $v) { if (strtolower($v) == 'infinite') { $this->timeout = isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100; break; } elseif (strtolower(substr($v, 0, 7)) == 'second-') { $this->timeout = min(intval(substr($v, 7)), isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100); break; } } if (!isset($this->timeout) || $this->timeout == 0) { $this->timeout = isset($c->default_lock_timeout) ? $c->default_lock_timeout : 900; } } $this->principal = new Principal('path', $this->path); /** * RFC2518, 5.2: URL pointing to a collection SHOULD end in '/', and if it does not then * we SHOULD return a Content-location header with the correction... * * We therefore look for a collection which matches one of the following URLs: * - The exact request. * - If the exact request, doesn't end in '/', then the request URL with a '/' appended * - The request URL truncated to the last '/' * The collection URL for this request is therefore the longest row in the result, so we * can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1" */ $sql = "SELECT * FROM collection WHERE dav_name = :exact_name"; $params = array(':exact_name' => $this->path); if (!preg_match('#/$#', $this->path)) { $sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name"; $params[':truncated_name'] = preg_replace('#[^/]*$#', '', $this->path); $params[':trailing_slash_name'] = $this->path . "/"; } $sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1"; $qry = new AwlQuery($sql, $params); if ($qry->Exec('caldav', __LINE__, __FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch())) { if ($row->dav_name == $this->path . "/") { $this->path = $row->dav_name; dbg_error_log("caldav", "Path is actually a collection - sending Content-Location header."); header("Content-Location: " . ConstructURL($this->path)); } $this->collection_id = $row->collection_id; $this->collection_path = $row->dav_name; $this->collection_type = $row->is_calendar == 't' ? 'calendar' : 'collection'; $this->collection = $row; if (preg_match('#^((/[^/]+/)\\.(in|out)/)[^/]*$#', $this->path, $matches)) { $this->collection_type = 'schedule-' . $matches[3] . 'box'; } $this->collection->type = $this->collection_type; } else { if (preg_match('{^( ( / ([^/]+) / ) \\.(in|out)/ ) [^/]*$}x', $this->path, $matches)) { // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it $params = array(':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1]); $params[':boxname'] = $matches[4] == 'in' ? ' Inbox' : ' Outbox'; $this->collection_type = 'schedule-' . $matches[4] . 'box'; $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type); $sql = <<<EOSQL INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes ) VALUES( (SELECT user_no FROM usr WHERE username = text(:username)), :parent_container, :dav_name, (SELECT fullname FROM usr WHERE username = text(:username)) || :boxname, FALSE, current_timestamp, current_timestamp, '1', :resourcetypes ) EOSQL; $qry = new AwlQuery($sql, $params); $qry->Exec('caldav', __LINE__, __FILE__); dbg_error_log('caldav', 'Created new collection as "%s".', trim($params[':boxname'])); // Uncache anything to do with the collection $cache = getCacheInstance(); $cache->delete('collection-' . $params[':dav_name'], null); $cache->delete('principal-' . $params[':parent_container'], null); $qry = new AwlQuery("SELECT * FROM collection WHERE dav_name = :dav_name", array(':dav_name' => $matches[1])); if ($qry->Exec('caldav', __LINE__, __FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch())) { $this->collection_id = $row->collection_id; $this->collection_path = $matches[1]; $this->collection = $row; $this->collection->type = $this->collection_type; } } else { if (preg_match('#^((/[^/]+/)calendar-proxy-(read|write))/?[^/]*$#', $this->path, $matches)) { $this->collection_type = 'proxy'; $this->_is_proxy_request = true; $this->proxy_type = $matches[3]; $this->collection_path = $matches[1] . '/'; // Enforce trailling '/' if ($this->collection_path == $this->path . "/") { $this->path .= '/'; dbg_error_log("caldav", "Path is actually a (proxy) collection - sending Content-Location header."); header("Content-Location: " . ConstructURL($this->path)); } } else { if ($this->options['allow_by_email'] && preg_match('#^/(\\S+@\\S+[.]\\S+)/?$#', $this->path)) { /** @todo we should deprecate this now that Evolution 2.27 can do scheduling extensions */ $this->collection_id = -1; $this->collection_type = 'email'; $this->collection_path = $this->path; $this->_is_principal = true; } else { if (preg_match('#^(/[^/?]+)/?$#', $this->path, $matches) || preg_match('#^(/principals/[^/]+/[^/]+)/?$#', $this->path, $matches)) { $this->collection_id = -1; $this->collection_path = $matches[1] . '/'; // Enforce trailling '/' $this->collection_type = 'principal'; $this->_is_principal = true; if ($this->collection_path == $this->path . "/") { $this->path .= '/'; dbg_error_log("caldav", "Path is actually a collection - sending Content-Location header."); header("Content-Location: " . ConstructURL($this->path)); } if (preg_match('#^(/principals/[^/]+/[^/]+)/?$#', $this->path, $matches)) { // Force a depth of 0 on these, which are at the wrong URL. $this->depth = 0; } } else { if ($this->path == '/') { $this->collection_id = -1; $this->collection_path = '/'; $this->collection_type = 'root'; } } } } } } if ($this->collection_path == $this->path) { $this->_is_collection = true; } dbg_error_log("caldav", " Collection '%s' is %d, type %s", $this->collection_path, $this->collection_id, $this->collection_type); /** * Extract the user whom we are accessing */ $this->principal = new DAVPrincipal(array("path" => $this->path, "options" => $this->options)); $this->user_no = $this->principal->user_no(); $this->username = $this->principal->username(); $this->by_email = $this->principal->byEmail(); $this->principal_id = $this->principal->principal_id(); if ($this->collection_type == 'principal' || $this->collection_type == 'email' || $this->collection_type == 'proxy') { $this->collection = $this->principal->AsCollection(); if ($this->collection_type == 'proxy') { $this->collection = $this->principal->AsCollection(); $this->collection->is_proxy = 't'; $this->collection->type = 'proxy'; $this->collection->proxy_type = $this->proxy_type; $this->collection->dav_displayname = sprintf('Proxy %s for %s', $this->proxy_type, $this->principal->username()); } } elseif ($this->collection_type == 'root') { $this->collection = (object) array('collection_id' => 0, 'dav_name' => '/', 'dav_etag' => md5($c->system_name), 'is_calendar' => 'f', 'is_addressbook' => 'f', 'is_principal' => 'f', 'user_no' => 0, 'dav_displayname' => $c->system_name, 'type' => 'root', 'created' => date('Ymd\\THis')); } /** * Evaluate our permissions for accessing the target */ $this->setPermissions(); $this->supported_methods = array('OPTIONS' => '', 'PROPFIND' => '', 'REPORT' => '', 'DELETE' => '', 'LOCK' => '', 'UNLOCK' => '', 'MOVE' => '', 'ACL' => ''); if ($this->IsCollection()) { switch ($this->collection_type) { case 'root': case 'email': // We just override the list completely here. $this->supported_methods = array('OPTIONS' => '', 'PROPFIND' => '', 'REPORT' => ''); break; case 'schedule-inbox': case 'schedule-outbox': $this->supported_methods = array_merge($this->supported_methods, array('POST' => '', 'GET' => '', 'PUT' => '', 'HEAD' => '', 'PROPPATCH' => '')); break; case 'calendar': $this->supported_methods['GET'] = ''; $this->supported_methods['PUT'] = ''; $this->supported_methods['HEAD'] = ''; break; case 'collection': case 'principal': $this->supported_methods['GET'] = ''; $this->supported_methods['PUT'] = ''; $this->supported_methods['HEAD'] = ''; $this->supported_methods['MKCOL'] = ''; $this->supported_methods['MKCALENDAR'] = ''; $this->supported_methods['PROPPATCH'] = ''; $this->supported_methods['BIND'] = ''; break; } } else { $this->supported_methods = array_merge($this->supported_methods, array('GET' => '', 'HEAD' => '', 'PUT' => '')); } $this->supported_reports = array('DAV::principal-property-search' => '', 'DAV::expand-property' => '', 'DAV::sync-collection' => ''); if (isset($this->collection) && $this->collection->is_calendar) { $this->supported_reports = array_merge($this->supported_reports, array('urn:ietf:params:xml:ns:caldav:calendar-query' => '', 'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '', 'urn:ietf:params:xml:ns:caldav:free-busy-query' => '')); } if (isset($this->collection) && $this->collection->is_addressbook) { $this->supported_reports = array_merge($this->supported_reports, array('urn:ietf:params:xml:ns:carddav:addressbook-query' => '', 'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => '')); } /** * If the content we are receiving is XML then we parse it here. RFC2518 says we * should reasonably expect to see either text/xml or application/xml */ if (isset($this->content_type) && preg_match('#(application|text)/xml#', $this->content_type)) { if (!isset($this->raw_post) || $this->raw_post == '') { $this->XMLResponse(400, new XMLElement('error', new XMLElement('missing-xml'), array('xmlns' => 'DAV:'))); } $xml_parser = xml_parser_create_ns('UTF-8'); $this->xml_tags = array(); xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0); $rc = xml_parse_into_struct($xml_parser, $this->raw_post, $this->xml_tags); if ($rc == false) { dbg_error_log('ERROR', 'XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser)); $this->XMLResponse(400, new XMLElement('error', new XMLElement('invalid-xml'), array('xmlns' => 'DAV:'))); } xml_parser_free($xml_parser); if (count($this->xml_tags)) { dbg_error_log("caldav", " Parsed incoming XML request body."); } else { $this->xml_tags = null; dbg_error_log("ERROR", "Incoming request sent content-type XML with no XML request body."); } } /** * Look out for If-None-Match or If-Match headers */ if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) { $this->etag_none_match = $_SERVER["HTTP_IF_NONE_MATCH"]; if ($this->etag_none_match == '') { unset($this->etag_none_match); } } if (isset($_SERVER["HTTP_IF_MATCH"])) { $this->etag_if_match = $_SERVER["HTTP_IF_MATCH"]; if ($this->etag_if_match == '') { unset($this->etag_if_match); } } }
/** * Internal function used to assign the session details to a user's new session. * @param object $u The user+session object we (probably) read from the database. */ function AssignSessionDetails($u) { if (!isset($u->principal_id)) { // If they don't have a principal_id set then we should re-read from our local database $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $u->username)); if ($qry->Exec() && $qry->rows() == 1) { $u = $qry->Fetch(); } } // Assign each field in the selected record to the object foreach ($u as $k => $v) { $this->{$k} = $v; } $this->GetRoles(); $this->logged_in = true; if (function_exists("awl_set_locale") && isset($this->locale) && $this->locale != "") { awl_set_locale($this->locale); } }
LEFT JOIN calendar_item USING (collection_id,dav_id) LEFT JOIN addressbook_resource USING (dav_id) WHERE collection.collection_id = :collection_id {$hide_older} {$hide_todo} AND sync_time >= (SELECT modification_time FROM sync_tokens WHERE sync_token = :sync_token) EOSQL; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY collection.collection_id, lower(sync_changes.dav_name), sync_changes.sync_time"; } else { $sql .= " ORDER BY collection.collection_id, sync_changes.dav_name, sync_changes.sync_time"; } } $qry = new AwlQuery($sql, $params); $last_dav_name = ''; $first_status = 0; if ($qry->Exec("REPORT", __LINE__, __FILE__)) { if ($qry->rows() > 50) { // If there are more than 50 rows to send we should not send full data in response ... $c->sync_resource_data_ok = false; } while ($object = $qry->Fetch()) { if ($request_via_binding) { $object->dav_name = str_replace($bound_from, $collection_path, $object->dav_name); } if ($object->dav_name == $last_dav_name) { /** The complex case: this is the second or subsequent for this dav_id */ if ($object->sync_status == 404) { array_pop($responses); $resultset = array(new XMLElement('href', ConstructURL($object->dav_name)), new XMLElement('status', display_status($object->sync_status))); $responses[] = new XMLElement('response', $resultset); $first_status = 404; } else {
function handle_freebusy_request($ic) { global $c, $session, $request, $ical; $request->NeedPrivilege('CALDAV:schedule-send-freebusy'); $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C")); $responses = array(); $fbq_start = $ic->GetPValue('DTSTART'); $fbq_end = $ic->GetPValue('DTEND'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); $attendees = $ic->GetProperties('ATTENDEE'); if (preg_match('# iCal/\\d#', $_SERVER['HTTP_USER_AGENT'])) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Responding with free/busy for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Calculating free/busy for %s", $attendee_email); /** @todo Refactor this so we only do one query here and loop through the results */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email); $qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); if (!$qry->Exec('POST', __LINE__, __FILE__)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() > 1) { // Unlikely, but if we get more than one result we'll do an exact match instead. if (!$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() == 0) { /** Sigh... Go back to the original case-insensitive match */ $qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); } } $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); if ($qry->rows() == 0) { $remote = new iSchedule(); $answer = $remote->sendRequest($attendee->Value(), 'VFREEBUSY/REQUEST', $ical->Render()); if ($answer === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } foreach ($answer as $a) { if ($a === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); } elseif (substr($a, 0, 1) >= 1) { $reply->CalDAVElement($response, "request-status", $a); $reply->CalDAVElement($response, "calendar-data"); } else { $reply->CalDAVElement($response, "request-status", "2.0;Success"); $reply->CalDAVElement($response, "calendar-data", $a); } $responses[] = $response; } continue; } if (!($attendee_usr = $qry->Fetch())) { $request->DoResponse(501, 'Database error'); } if ((privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0) { $reply->CalDAVElement($response, "request-status", "3.8;No authority"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } $attendee_path_match = '^/' . $attendee_usr->username . '/'; $fb = get_freebusy($attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p)); $fb->AddProperty('UID', $ic->GetPValue('UID')); $fb->SetProperties($ic->GetProperties('ORGANIZER'), 'ORGANIZER'); $fb->AddProperty($attendee); $vcal = new vCalendar(array('METHOD' => 'REPLY')); $vcal->AddComponent($fb); $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); $reply->CalDAVElement($response, "request-status", "2.0;Success"); // Cargo-cult setting $reply->CalDAVElement($response, "calendar-data", $vcal->Render()); $responses[] = $response; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav'); $request->XMLResponse(200, $response); }
/** * Get the addressbook_home_set, as lazily as possible */ function addressbook_home_set() { if (!isset($this->addressbook_home_set)) { $this->addressbook_home_set = array(); $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_addressbook AND dav_name ~ :dav_name_start', array(':dav_name_start' => '^' . $this->dav_name)); if ($qry->Exec('principal', __LINE__, __FILE__)) { if ($qry->rows() > 0) { while ($addressbook = $qry->Fetch()) { $this->addressbook_home_set[] = ConstructURL($addressbook->parent_container, true); } } else { $this->addressbook_home_set[] = $this->url; } } } return $this->addressbook_home_set; }
break; case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': if ($dav_resource->IsCollection() && $dav_resource->IsCalendar() && !$dav_resource->IsBinding()) { $tzcomponent = $setting->GetPath('urn:ietf:params:xml:ns:caldav:calendar-timezone'); $tzstring = $tzcomponent[0]->GetContent(); $calendar = new vCalendar($tzstring); $timezones = $calendar->GetComponents('VTIMEZONE'); if (count($timezones) == 0) { break; } $tz = $timezones[0]; // Backward compatibility $tzid = $tz->GetPValue('TZID'); $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':olson_name'] = $calendar->GetOlsonName($tz); $params[':vtimezone'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params); } $qry->QDo('UPDATE collection SET timezone = :tzid WHERE dav_name = :dav_name', array(':tzid' => $tzid, ':dav_name' => $dav_resource->dav_name())); } else { add_failure('set', $tag, 'HTTP/1.1 409 Conflict', translate("calendar-timezone property is only valid for a calendar.")); } break; /** * The following properties are read-only, so they will cause the request to fail */ /** * The following properties are read-only, so they will cause the request to fail */
/** * Return general server-related properties for this URL */ function ResourceProperty($tag, $prop, &$reply, &$denied) { global $c, $session, $request; // dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name ); if ($reply === null) { $reply = $GLOBALS['reply']; } switch ($tag) { case 'DAV::allprop': $property_list = $this->DAV_AllProperties(); $discarded = array(); foreach ($property_list as $k => $v) { $this->ResourceProperty($v, $prop, $reply, $discarded); } break; case 'DAV::href': $prop->NewElement('href', ConstructURL($this->dav_name)); break; case 'DAV::resource-id': if ($this->resource_id > 0) { $reply->DAVElement($prop, 'resource-id', $reply->href(ConstructURL('/.resources/' . $this->resource_id))); } else { return false; } break; case 'DAV::parent-set': $sql = <<<EOQRY SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id) WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from EOQRY; $qry = new AwlQuery($sql, array(':bound_from' => $this->bound_from())); $parents = array(); if ($qry->Exec('DAVResource', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { $parents[$row->parent_container] = true; } } $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->bound_from())] = true; $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->dav_name())] = true; $parent_set = $reply->DAVElement($prop, 'parent-set'); foreach ($parents as $parent => $v) { if (preg_match('{^(.*)?/([^/]+)/?$}', $parent, $matches)) { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', ConstructURL($matches[1])), new XMLElement('segment', $matches[2]))); } else { if ($parent == '/') { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', '/'), new XMLElement('segment', ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))); } } } break; case 'DAV::getcontenttype': if (!isset($this->contenttype) && !$this->_is_collection && !isset($this->resource)) { $this->FetchResource(); } $prop->NewElement('getcontenttype', $this->contenttype); break; case 'DAV::resourcetype': $resourcetypes = $prop->NewElement('resourcetype'); if ($this->_is_collection) { $type_list = $this->GetProperty('resourcetype'); if (!is_array($type_list)) { return true; } // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) ); foreach ($type_list as $k => $v) { if ($v == '') { continue; } $reply->NSElement($resourcetypes, $v); } if ($this->_is_binding) { $reply->NSElement($resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding'); } } break; case 'DAV::getlastmodified': /** getlastmodified is HTTP Date format: i.e. the Last-Modified header in response to a GET */ $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified'))); break; case 'DAV::creationdate': /** creationdate is ISO8601 format */ $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true)); break; case 'DAV::getcontentlength': if ($this->_is_collection) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } if (isset($this->resource)) { $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data)); } break; case 'DAV::getcontentlanguage': $locale = isset($c->current_locale) ? $c->current_locale : ''; if (isset($this->locale) && $this->locale != '') { $locale = $this->locale; } $reply->NSElement($prop, $tag, $locale); break; case 'DAV::acl-restrictions': $reply->NSElement($prop, $tag, array(new XMLElement('grant-only'), new XMLElement('no-invert'))); break; case 'DAV::inherited-acl-set': $inherited_acls = array(); if (!$this->_is_collection) { $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name)); } $reply->NSElement($prop, $tag, $inherited_acls); break; case 'DAV::owner': // The principal-URL of the owner if ($this->IsExternal()) { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL($this->collection->bound_from))); } else { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL(DeconstructURL($this->principal_url())))); } break; case 'DAV::add-member': if (!$this->_is_collection) { return false; } if (isset($c->post_add_member) && $c->post_add_member === false) { return false; } $reply->DAVElement($prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())) . '?add-member')); break; // Empty tag responses. // Empty tag responses. case 'DAV::group': case 'DAV::alternate-URI-set': $reply->NSElement($prop, $tag); break; case 'DAV::getetag': if ($this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'http://calendarserver.org/ns/:getctag': if (!$this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'DAV::sync-token': if (!$this->_is_collection) { return false; } $sync_token = $this->sync_token(); if (empty($sync_token)) { return false; } $reply->NSElement($prop, $tag, $sync_token); break; case 'http://calendarserver.org/ns/:calendar-proxy-read-for': $proxy_type = 'read'; case 'http://calendarserver.org/ns/:calendar-proxy-write-for': if (isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy) { return false; } if (!isset($proxy_type)) { $proxy_type = 'write'; } // ProxyFor is an already constructed URL $reply->CalendarserverElement($prop, 'calendar-proxy-' . $proxy_type . '-for', $reply->href($this->principal->ProxyFor($proxy_type))); break; case 'DAV::current-user-privilege-set': if ($this->HavePrivilegeTo('DAV::read-current-user-privilege-set')) { $reply->NSElement($prop, $tag, $this->BuildPrivileges()); } else { $denied[] = $tag; } break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': if (!$this->IsCalendar() && !$this->IsSchedulingCollection()) { return false; } $reply->NSElement($prop, $tag, 'text/calendar'); break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set': if (!$this->_is_collection) { return false; } if ($this->IsCalendar()) { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $set_of_components = $this->dead_properties[$tag]; foreach ($set_of_components as $k => $v) { if (preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches)) { $set_of_components[$k] = $matches[1]; } else { unset($set_of_components[$k]); } } } else { if (isset($c->default_calendar_components) && is_array($c->default_calendar_components)) { $set_of_components = $c->default_calendar_components; } else { $set_of_components = array('VEVENT', 'VTODO', 'VJOURNAL'); } } } else { if ($this->IsSchedulingCollection()) { $set_of_components = array('VEVENT', 'VTODO', 'VFREEBUSY'); } else { return false; } } $components = array(); foreach ($set_of_components as $v) { $components[] = $reply->NewXMLElement('comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav'); } $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components); break; case 'DAV::supported-method-set': $prop->NewElement('supported-method-set', $this->BuildSupportedMethods()); break; case 'DAV::supported-report-set': $prop->NewElement('supported-report-set', $this->BuildSupportedReports($reply)); break; case 'DAV::supportedlock': $prop->NewElement('supportedlock', new XMLElement('lockentry', array(new XMLElement('lockscope', new XMLElement('exclusive')), new XMLElement('locktype', new XMLElement('write'))))); break; case 'DAV::supported-privilege-set': $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply)); break; case 'DAV::principal-collection-set': $prop->NewElement('principal-collection-set', $reply->href(ConstructURL('/'))); break; case 'DAV::current-user-principal': $prop->NewElement('current-user-principal', $reply->href(ConstructURL(DeconstructURL($request->principal->url())))); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $reply->Tag($tag); break; case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': if (!$this->_is_collection) { return false; } if (!isset($this->collection->vtimezone) || $this->collection->vtimezone == '') { return false; } $cal = new iCalComponent(); $cal->VCalendar(); $cal->AddComponent(new iCalComponent($this->collection->vtimezone)); $reply->NSElement($prop, $tag, $cal->Render()); break; case 'urn:ietf:params:xml:ns:carddav:address-data': case 'urn:ietf:params:xml:ns:caldav:calendar-data': if ($this->_is_collection) { return false; } if (!isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } $reply->NSElement($prop, $tag, $this->resource->caldav_data); break; case 'urn:ietf:params:xml:ns:carddav:max-resource-size': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $reply->NSElement($prop, $tag, 65500); break; case 'urn:ietf:params:xml:ns:carddav:supported-address-data': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $address_data = $reply->NewXMLElement('address-data', false, array('content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav'); $reply->NSElement($prop, $tag, $address_data); break; case 'DAV::acl': if ($this->HavePrivilegeTo('DAV::read-acl')) { $reply->NSElement($prop, $tag, $this->GetACL($reply)); } else { $denied[] = $tag; } break; case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery': case 'DAV::ticketdiscovery': $reply->NSElement($prop, 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply)); break; default: $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag)); if (isset($property_value)) { $reply->NSElement($prop, $tag, $property_value); } else { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $reply->NSElement($prop, $tag, $this->dead_properties[$tag]); } else { // dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name ); return false; } } } return true; }
/** * To read the record from the database. * If we don't have any keys then the record will be blank. * @return boolean Whether we actually read a record. */ function Read() { $i_read_the_record = false; $values = (object) array(); $this->EditMode = true; $where = $this->_BuildWhereClause(true); if ("" != $where) { // $fieldlist = $this->_BuildFieldList(); $fieldlist = "*"; // $join = $this->_BuildJoinClause(true); $sql = "SELECT {$fieldlist} FROM {$this->Table} {$where}"; $qry = new AwlQuery($sql); if ($qry->Exec("DBRecord", __LINE__, __FILE__) && $qry->rows() > 0) { $i_read_the_record = true; $values = $qry->Fetch(); $this->EditMode = false; // Default to not editing if we read the record. dbg_error_log("DBRecord", ":Read: Read %s record from table.", $this->Table, $this->WriteType); } } $this->Values =& $values; $this->WriteType = $i_read_the_record ? "update" : "insert"; dbg_error_log("DBRecord", ":Read: Record %s write type is %s.", $this->Table, $this->WriteType); return $i_read_the_record; }
/** * Authentication has already happened. We know the username, we just need * to do the authorisation / access control. The password is ignored. * * @package awl */ function auth_external($username, $password) { global $c; $qry = new AwlQuery("SELECT * FROM usr WHERE active AND lower(username) = ? ", strtolower($username)); if ($qry->Exec('Login', __LINE, __FILE__) && $qry->rows() == 1) { $usr = $qry->Fetch(); return $usr; } return false; }
} $sql = 'SELECT calendar_item.*, addressbook_resource.*, caldav_data.* FROM caldav_data LEFT JOIN calendar_item USING(dav_id, user_no, dav_name, collection_id) LEFT JOIN addressbook_resource USING(dav_id) LEFT JOIN collection USING(collection_id)'; /** * @todo: Add stanzas for missing rows, so we don't just return a blank multistatus but * actually return <response> stanzas with a 404 for each absent href. We could do * this relatively easily with an array_flip($params) and remove each matching dav_name * as we process it. */ if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $where .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql . $where, $params); if ($qry->Exec('REPORT', __LINE__, __FILE__) && $qry->rows() > 0) { while ($dav_object = $qry->Fetch()) { if ($bound_from != $collection->dav_name()) { $dav_object->dav_name = str_replace($bound_from, $collection->dav_name(), $dav_object->dav_name); } //if ( $need_expansion ) { $vResource = new vComponent($dav_object->caldav_data); $expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end); // $event = $expanded->GetComponents("VEVENT")[0]; // // $attendeeName = "ATTENDEE"; // // $event->ClearProperties($attendeeName); // // $attendeeQry = new AwlQuery("SELECT params, attendee FROM calendar_attendee WHERE dav_id = :dav_id", array(':dav_id' => $dav_object->dav_id)); // $attendeeQry->Execute();
function get_freebusy($path_match, $range_start, $range_end, $bin_privs = null) { global $request, $c; $debugging = false; // if ( $debugging ) { // printf( "Path: %s\n", $path_match ); // print_r( $range_start ); // print_r( $range_end ); // } if (!isset($bin_privs)) { $bin_privs = $request->Privileges(); } if (!isset($range_start) || !isset($range_end)) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a time-range filter'); } $params = array(':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC()); $where = ' WHERE caldav_data.dav_name ~ :path_match '; $where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) '; $where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) "; $where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) "; $where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) "; $where .= "AND collection.is_calendar AND collection.schedule_transp = 'opaque' "; if ($bin_privs != privilege_to_bits('all')) { $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } $fbtimes = array(); $sql = 'SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, '; $sql .= "to_char(calendar_item.dtstart at time zone 'GMT'," . AWLDatabase::SqlUTCFormat . ') AS start, '; $sql .= "to_char(calendar_item.dtend at time zone 'GMT'," . AWLDatabase::SqlUTCFormat . ') AS finish, '; $sql .= "calendar_item.class, calendar_item.dav_id "; $sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) '; $sql .= 'INNER JOIN collection USING(collection_id)'; $sql .= $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= ' ORDER BY dav_id'; } $qry = new AwlQuery($sql, $params); if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) { while ($calendar_object = $qry->Fetch()) { $extra = ''; if ($calendar_object->status == 'TENTATIVE') { $extra = ';BUSY-TENTATIVE'; } else { if (isset($c->_workaround_client_freebusy_bug) && $c->_workaround_client_freebusy_bug) { $extra = ';BUSY'; } } // if ( $debugging ) { // $extra = ';'.$calendar_object->dav_id; // } // dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s, %s", $calendar_object->start, $calendar_object->finish, $calendar_object->class ); $ics = new vComponent($calendar_object->caldav_data); $expanded = expand_event_instances($ics, $range_start, $range_end); $expansion = $expanded->GetComponents(array('VEVENT' => true, 'VTODO' => true, 'VJOURNAL' => true)); // if ( $debugging ) echo "=================== $calendar_object->dav_id ========================\n"; $dtstart_type = 'DTSTART'; foreach ($expansion as $k => $v) { // if ( $debugging ) print $k."\n".$v->Render(); $start_date = $v->GetProperty($dtstart_type); if (!isset($start_date) && $v->GetType() != 'VTODO') { $dtstart_type = 'DUE'; $start_date = $v->GetProperty($dtstart_type); } $start_date = new RepeatRuleDateTime($start_date); $duration = $v->GetProperty('DURATION'); $duration = !isset($duration) ? 'P1D' : $duration->Value(); $end_date = clone $start_date; $end_date->modify($duration); if ($end_date == $start_date || $end_date < $range_start || $start_date > $range_end) { // if ( $debugging ) // echo "-----------------------------------------------------\n"; continue; } // if ( $debugging ) // echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; $thisfb = $start_date->UTC() . '/' . $end_date->UTC() . $extra; array_push($fbtimes, $thisfb); } } } $freebusy = new vComponent(); $freebusy->setType('VFREEBUSY'); $freebusy->AddProperty('DTSTAMP', date('Ymd\\THis\\Z')); $freebusy->AddProperty('DTSTART', $range_start->UTC()); $freebusy->AddProperty('DTEND', $range_end->UTC()); sort($fbtimes); foreach ($fbtimes as $k => $v) { $text = explode(';', $v, 2); $freebusy->AddProperty('FREEBUSY', $text[0], isset($text[1]) ? array('FBTYPE' => $text[1]) : null); } return $freebusy; }
/** * Actually write the resource to the database. All checking of whether this is reasonable * should be done before this is called. * * @param DAVResource $resource The resource being written * @param string $caldav_data The actual data to be written * @param DAVResource $collection The collection containing the resource being written * @param int $author The user_no who wants to put this resource on the server * @param string $etag An etag unique for this event * @param string $put_action_type INSERT or UPDATE depending on what we are to do * @param boolean $caldav_context True, if we are responding via CalDAV, false for other ways of calling this * @param string Either 'INSERT' or 'UPDATE': the type of action we are doing * @param boolean $log_action Whether to log the fact that we are writing this into an action log (if configured) * @param string $weak_etag An etag that is NOT modified on ATTENDEE changes for this event * * @return boolean True for success, false for failure. */ function write_resource(DAVResource $resource, $caldav_data, DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action = true, $weak_etag = null) { global $tz_regex, $session; $path = $resource->bound_from(); $user_no = $collection->user_no(); $vcal = new vCalendar($caldav_data); $resources = $vcal->GetComponents('VTIMEZONE', false); // Not matching VTIMEZONE if (!isset($resources[0])) { $resource_type = 'Unknown'; /** @todo Handle writing non-calendar resources, like address book entries or random file data */ rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412); return false; } else { $first = $resources[0]; if (!$first instanceof vComponent) { print $vcal->Render(); fatal('This is not a vComponent!'); } $resource_type = $first->GetType(); } $collection_id = $collection->collection_id(); $qry = new AwlQuery(); $qry->Begin(); $dav_params = array(':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag); $calitem_params = array(':etag' => $etag); if ($put_action_type == 'INSERT') { $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data'); } else { $qry->QDo('SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path)); } if ($qry->rows() != 1 || !($row = $qry->Fetch())) { // No dav_id? => We're toast! trace_bug('No dav_id for "%s" on %s!!!', $path, $create_resource ? 'create' : 'update'); rollback_on_error($caldav_context, $user_no, $path); return false; } $dav_id = $row->dav_id; $old_dav_data = $row->caldav_data; $dav_params[':dav_id'] = $dav_id; $calitem_params[':dav_id'] = $dav_id; $due = null; if ($first->GetType() == 'VTODO') { $due = $first->GetPValue('DUE'); } $calitem_params[':due'] = $due; $dtstart = $first->GetPValue('DTSTART'); if (empty($dtstart)) { $dtstart = $due; } $calitem_params[':dtstart'] = $dtstart; $dtend = $first->GetPValue('DTEND'); if (isset($dtend) && $dtend != '') { dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION')); $calitem_params[':dtend'] = $dtend; $dtend = ':dtend'; } else { // In this case we'll construct the SQL directly as a calculation relative to :dtstart $dtend = 'NULL'; if ($first->GetPValue('DURATION') != '' and $dtstart != '') { $duration = trim(preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION'))); if ($duration == '') { $duration = '0 seconds'; } $dtend = '(:dtstart::timestamp with time zone + :duration::interval)'; $calitem_params[':duration'] = $duration; } elseif ($first->GetType() == 'VEVENT') { /** * From RFC2445 4.6.1: * For cases where a "VEVENT" calendar component specifies a "DTSTART" * property with a DATE data type but no "DTEND" property, the events * non-inclusive end is the end of the calendar date specified by the * "DTSTART" property. For cases where a "VEVENT" calendar component specifies * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property, * the event ends on the same calendar date and time of day specified by the * "DTSTART" property. * * So we're looking for 'VALUE=DATE', to identify the duration, effectively. * */ $dtstart_prop = $first->GetProperty('DTSTART'); $value_type = $dtstart_prop->GetParameterValue('VALUE'); dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type); if (isset($value_type) && $value_type == 'DATE') { $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)'; } else { $dtend = ':dtstart'; } } } $dtstamp = $first->GetPValue('DTSTAMP'); if (!isset($dtstamp) || $dtstamp == '') { // Strictly, we're dealing with an out of spec component here, but we'll try and survive $dtstamp = gmdate('Ymd\\THis\\Z'); } $calitem_params[':dtstamp'] = $dtstamp; $last_modified = $first->GetPValue('LAST-MODIFIED'); if (!isset($last_modified) || $last_modified == '') { $last_modified = $dtstamp; } $dav_params[':modified'] = $last_modified; $calitem_params[':modified'] = $last_modified; $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $class = $first->GetPValue('CLASS'); /* Check and see if we should over ride the class. */ /** @todo is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */ if (public_events_only($user_no, $path)) { $class = 'PUBLIC'; } /* * It seems that some calendar clients don't set a class... * RFC2445, 4.8.1.3: * Default is PUBLIC */ if (!isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $last_olson = 'Turkmenikikamukau'; // I really hope this location doesn't exist! $tzid = GetTZID($first); if (!empty($tzid)) { $timezones = $vcal->GetComponents('VTIMEZONE'); foreach ($timezones as $k => $tz) { if ($tz->GetPValue('TZID') != $tzid) { /** * We'll skip any tz definitions that are for a TZID other than the DTSTART/DUE on the first VEVENT/VTODO */ dbg_error_log('ERROR', ' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue('TZID'), $tzid); continue; } $olson = olson_from_tzstring($tzid); if (empty($olson)) { $olson = $tz->GetPValue('X-LIC-LOCATION'); if (!empty($olson)) { $olson = olson_from_tzstring($olson); } } } dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($olson) ? $olson : ''); if (!empty($olson) && $olson != $last_olson && preg_match($tz_regex, $olson)) { dbg_error_log('PUT', ' Setting timezone to %s', $olson); if ($olson != '') { $qry->QDo('SET TIMEZONE TO \'' . $olson . "'"); } $last_olson = $olson; } $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':olson_name'] = $olson; $params[':vtimezone'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params); } if (!isset($olson) || $olson == '') { $olson = $tzid; } } $qry->QDo('SELECT new_sync_token(0,' . $collection_id . ')'); $calitem_params[':tzid'] = $tzid; $calitem_params[':uid'] = $first->GetPValue('UID'); $calitem_params[':summary'] = $first->GetPValue('SUMMARY'); $calitem_params[':location'] = $first->GetPValue('LOCATION'); $calitem_params[':transp'] = $first->GetPValue('TRANSP'); $calitem_params[':description'] = $first->GetPValue('DESCRIPTION'); $calitem_params[':rrule'] = $first->GetPValue('RRULE'); $calitem_params[':url'] = $first->GetPValue('URL'); $calitem_params[':priority'] = $first->GetPValue('PRIORITY'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':status'] = $first->GetPValue('STATUS'); if (!$collection->IsSchedulingCollection()) { if (do_scheduling_requests($vcal, $put_action_type == 'INSERT', $old_dav_data, true)) { $dav_params[':dav_data'] = $vcal->Render(null, true); $etag = null; } } if (!isset($dav_params[':modified'])) { $dav_params[':modified'] = 'now'; } if ($put_action_type == 'INSERT') { $sql = 'INSERT INTO caldav_data ( dav_id, user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag ) VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )'; $dav_params[':collection_id'] = $collection_id; $dav_params[':user_no'] = $user_no; $dav_params[':dav_name'] = $path; $dav_params[':created'] = isset($created) && $created != '' ? $created : $dtstamp; } else { $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id'; } $qry = new AwlQuery($sql, $dav_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { fatal('Insert into calendar_item failed...'); rollback_on_error($caldav_context, $user_no, $path); return false; } if ($put_action_type == 'INSERT') { $sql = <<<EOSQL INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp, description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id ) VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp, :dtstart, {$dtend}, :summary, :location, :class, :transp, :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, :collection_id ) EOSQL; $sync_change = 201; $calitem_params[':collection_id'] = $collection_id; $calitem_params[':user_no'] = $user_no; $calitem_params[':dav_name'] = $path; $calitem_params[':created'] = $dav_params[':created']; } else { $sql = <<<EOSQL UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, due=:due, percent_complete=:percent_complete, status=:status WHERE dav_id=:dav_id EOSQL; $sync_change = 200; } write_alarms($dav_id, $first); write_attendees($dav_id, $vcal); if ($log_action && function_exists('log_caldav_action')) { log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } else { if ($log_action) { dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } } $qry = new AwlQuery($sql, $calitem_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path)); $qry->Commit(); if (function_exists('post_commit_action')) { post_commit_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } // Uncache anything to do with the collection $cache = getCacheInstance(); $cache_ns = 'collection-' . preg_replace('{/[^/]*$}', '/', $path); $cache->delete($cache_ns, null); dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return true; // Success! }
function ticket_row_editor() { global $c, $id, $editor, $can_write_principal, $privilege_names; $ticketrow = new Editor("Tickets", "access_ticket"); $ticketrow->SetSubmitName('ticketrow'); if ($can_write_principal && $ticketrow->IsSubmit()) { $username = $editor->Value('username'); $ugly_path = $_POST['target']; if ($ugly_path == '/' . $username || $ugly_path == '/' . $username . '/') { $target_collection = $id; } else { $username_len = strlen($username) + 2; $sql = "SELECT collection_id FROM collection WHERE dav_name = :exact_name"; $sql .= " AND substring(dav_name FROM 1 FOR {$username_len}) = '/{$username}/'"; $params = array(':exact_name' => $ugly_path); if (!preg_match('#/$#', $ugly_path)) { $sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name"; $params[':truncated_name'] = preg_replace('#[^/]*$#', '', $ugly_path); $params[':trailing_slash_name'] = $ugly_path . "/"; } $sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1"; $qry = new AwlQuery($sql, $params); if ($qry->Exec() && $qry->rows() > 0) { $row = $qry->Fetch(); $target_collection = $row->collection_id; } else { $c->messages[] = translate('Can only add tickets for existing collection paths which you own'); return $ticketrow; } } $_POST['dav_owner_id'] = $id; $_POST['target_collection_id'] = $target_collection; $ticket_id = check_by_regex($_POST['ticket_id'], '/[A-Za-z0-9]+/'); $ticketrow->SetWhere('dav_owner_id=' . $id . ' AND ticket_id=' . AwlQuery::quote($ticket_id)); if (isset($_POST['ticket_privileges'])) { $privilege_bitpos = array_flip($privilege_names); $priv_names = array_keys($_POST['ticket_privileges']); $privs_dec = privilege_to_bits($priv_names); $_POST['privileges'] = sprintf('%024s', decbin($privs_dec)); $ticketrow->Assign('privileges', $privs_dec); } $c->messages[] = translate('Creating new ticket granting privileges to this Principal'); $ticketrow->Write(); } return $ticketrow; }
dbg_log_array('cardquery', 'qry_filters', $qry_filters, true); $components = array(); $filter_fragment = SqlFilterCardDAV($qry_filters, $components); if ($filter_fragment !== false) { $where .= ' ' . $filter_fragment['sql']; $params = $filter_fragment['params']; } } else { dbg_error_log('cardquery', 'No query filters'); } $sql = 'SELECT * FROM caldav_data INNER JOIN addressbook_resource USING(dav_id)' . $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY dav_id"; } $qry = new AwlQuery($sql, $params); if ($qry->Exec("cardquery", __LINE__, __FILE__) && $qry->rows() > 0) { while ($address_object = $qry->Fetch()) { if (!$need_post_filter || apply_filter($qry_filters, $address_object)) { if ($bound_from != $target_collection->dav_name()) { $address_object->dav_name = str_replace($bound_from, $target_collection->dav_name(), $address_object->dav_name); } if (count($address_data_properties) > 0) { $vcard = new VCard($address_object->caldav_data); $vcard->MaskProperties($address_data_properties); $address_object->caldav_data = $vcard->Render(); } $responses[] = component_to_xml($properties, $address_object); } } } $multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray());
/** * A slightly simpler version of write_resource which will make more sense for calling from * an external program. This makes assumptions that the collection and user do exist * and bypasses all checks for whether it is reasonable to write this here. * @param string $path The path to the resource being written * @param string $caldav_data The actual resource to be written * @param string $put_action_type INSERT or UPDATE depending on what we are to do * @return boolean True for success, false for failure. */ function simple_write_resource($path, $caldav_data, $put_action_type, $write_action_log = false) { $etag = md5($caldav_data); $ic = new iCalComponent($caldav_data); /** * We pull the user_no & collection_id out of the collection table, based on the resource path */ $collection_path = preg_replace('#/[^/]*$#', '/', $path); $qry = new AwlQuery('SELECT user_no, collection_id FROM collection WHERE dav_name = :dav_name ', array(':dav_name' => $collection_path)); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 1) { $collection = $qry->Fetch(); $user_no = $collection->user_no; return write_resource($user_no, $path, $caldav_data, $collection->collection_id, $user_no, $etag, $ic, $put_action_type, false, $write_action_log); } return false; }
/** * Do what must be done with time zones from on file. Attempt to turn * them into something that PostgreSQL can understand... * * @DEPRECATED: This class will be removed soon. * @todo Remove this function. */ function DealWithTimeZones() { global $c; deprecated('iCalendar::DealWithTimeZones'); $tzid = $this->Get('TZID'); if (isset($c->save_time_zone_defs) && $c->save_time_zone_defs) { $qry = new AwlQuery("SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $tzid); if ($qry->Exec('iCalendar') && $qry->rows() == 1) { $row = $qry->Fetch(); $this->tz_locn = $row->tz_locn; } dbg_error_log('iCalendar', " TZCrap2: TZID '%s', DB Rows=%d, Location '%s'", $tzid, $qry->rows(), $this->tz_locn); } if ((!isset($this->tz_locn) || $this->tz_locn == '') && $tzid != '') { /** * In case there was no X-LIC-LOCATION defined, let's hope there is something in the TZID * that we can use. We are looking for a string like "Pacific/Auckland" if possible. */ $tzname = preg_replace('#^(.*[^a-z])?([a-z]+/[a-z]+)$#i', '$1', $tzid); /** * Unfortunately this kind of thing will never work well :-( * if ( strstr( $tzname, ' ' ) ) { $words = preg_split('/\s/', $tzname ); $tzabbr = ''; foreach( $words AS $i => $word ) { $tzabbr .= substr( $word, 0, 1); } $this->tz_locn = $tzabbr; } */ if (preg_match('#\\S+/\\S+#', $tzname)) { $this->tz_locn = $tzname; } dbg_error_log('iCalendar', " TZCrap3: TZID '%s', Location '%s', Perhaps: %s", $tzid, $this->tz_locn, $tzname); } if ($tzid != '' && isset($c->save_time_zone_defs) && $c->save_time_zone_defs && $qry->rows() != 1 && isset($this->vtimezone) && $this->vtimezone != "") { $qry2 = new AwlQuery("INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );", $tzid, $this->tz_locn, $this->vtimezone); $qry2->Exec('iCalendar'); } if ((!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid)) { $this->tz_locn = $c->local_tzid; } }
function write_zone_aliases($tzid, $aliases) { global $added_aliases; foreach ($aliases as $alias_node) { $alias = $alias_node->GetContent(); $params = array(':tzid' => $tzid, ':alias' => $alias); $qry = new AwlQuery('SELECT * FROM tz_aliases JOIN timezones USING(our_tzno) WHERE tzid=:tzid AND tzalias=:alias', $params); if ($qry->Exec('tz/update', __LINE__, __FILE__) && $qry->rows() < 1) { $qry->QDo('INSERT INTO tz_aliases(our_tzno,tzalias) SELECT our_tzno, :alias FROM timezones WHERE tzid = :tzid', $params); $added_aliases++; } } }
/** * Writes the data to a member in the collection and returns the segment_name of the resource in our internal namespace. * @param $data iCalendar The resource to be written. * @param $create_resource boolean True if this is a new resource. * @param $segment_name The name of the resource within the collection. */ function WriteCalendarMember($data, $create_resource, $segment_name = null) { if (!$this->IsSchedulingCollection() && !$this->IsCalendar()) { return false; } // function write_resource( $user_no, $path, $caldav_data, $collection_id, $author, $etag, $ic, $put_action_type, $caldav_context, $log_action=true, $weak_etag=null ) { global $tz_regex; $resources = $ic->GetComponents('VTIMEZONE', false); // Not matching VTIMEZONE if (!isset($resources[0])) { $resource_type = 'Unknown'; /** @TODO: Handle writing non-calendar resources, like address book entries or random file data */ rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412); return false; } else { $first = $resources[0]; $resource_type = $first->GetType(); } $qry = new AwlQuery(); $qry->Begin(); $params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag); if ($put_action_type == 'INSERT') { create_scheduling_requests($vcal); $sql = 'INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag ) VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )'; $params[':collection_id'] = $collection_id; } else { update_scheduling_requests($vcal); $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=current_timestamp, weak_etag=:weak_etag WHERE user_no=:user_no AND dav_name=:dav_name'; } if (!$qry->QDo($sql, $params)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path)); if ($qry->rows() == 1 && ($row = $qry->Fetch())) { $dav_id = $row->dav_id; } $calitem_params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag); $dtstart = $first->GetPValue('DTSTART'); $calitem_params[':dtstart'] = $dtstart; if ((!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '') { $dtstart = $first->GetPValue('DUE'); } $dtend = $first->GetPValue('DTEND'); if (isset($dtend) && $dtend != '') { dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION')); $calitem_params[':dtend'] = $dtend; $dtend = ':dtend'; } else { $dtend = 'NULL'; if ($first->GetPValue('DURATION') != '' and $dtstart != '') { $duration = preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION')); $dtend = '(:dtstart::timestamp with time zone + :duration::interval)'; $calitem_params[':duration'] = $duration; } elseif ($first->GetType() == 'VEVENT') { /** * From RFC2445 4.6.1: * For cases where a "VEVENT" calendar component specifies a "DTSTART" * property with a DATE data type but no "DTEND" property, the events * non-inclusive end is the end of the calendar date specified by the * "DTSTART" property. For cases where a "VEVENT" calendar component specifies * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property, * the event ends on the same calendar date and time of day specified by the * "DTSTART" property. * * So we're looking for 'VALUE=DATE', to identify the duration, effectively. * */ $value_type = $first->GetPParamValue('DTSTART', 'VALUE'); dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type); if (isset($value_type) && $value_type == 'DATE') { $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)'; } else { $dtend = ':dtstart'; } } } $last_modified = $first->GetPValue('LAST-MODIFIED'); if (!isset($last_modified) || $last_modified == '') { $last_modified = gmdate('Ymd\\THis\\Z'); } $calitem_params[':modified'] = $last_modified; $dtstamp = $first->GetPValue('DTSTAMP'); if (!isset($dtstamp) || $dtstamp == '') { $dtstamp = $last_modified; } $calitem_params[':dtstamp'] = $dtstamp; $class = $first->GetPValue('CLASS'); /* Check and see if we should over ride the class. */ /** @TODO: is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */ if (public_events_only($user_no, $path)) { $class = 'PUBLIC'; } /* * It seems that some calendar clients don't set a class... * RFC2445, 4.8.1.3: * Default is PUBLIC */ if (!isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $last_tz_locn = 'Turkmenikikamukau'; // I really hope this location doesn't exist! $tzid = $first->GetPParamValue('DTSTART', 'TZID'); if (!isset($tzid) || $tzid == '') { $tzid = $first->GetPParamValue('DUE', 'TZID'); } $timezones = $ic->GetComponents('VTIMEZONE'); foreach ($timezones as $k => $tz) { if ($tz->GetPValue('TZID') != $tzid) { /** * We'll pretend they didn't forget to give us a TZID and that they * really hope the server is running in the timezone they supplied... but be noisy about it. */ dbg_error_log('ERROR', ' Event includes TZID[%s] but uses TZID[%s]!', $tz->GetPValue('TZID'), $tzid); $tzid = $tz->GetPValue('TZID'); } // This is the one $tz_locn = $tz->GetPValue('X-LIC-LOCATION'); if (!isset($tz_locn)) { if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) { $tz_locn = $matches[1]; } else { if (isset($tzid) && $tzid != '') { dbg_error_log('ERROR', ' Couldn\'t guess Olsen TZ from TZID[%s]. This may end in tears...', $tzid); } } } else { if (!preg_match($tz_regex, $tz_locn)) { if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) { $tz_locn = $matches[1]; } } } dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($tz_locn) ? $tz_locn : ''); if (isset($tz_locn) && $tz_locn != $last_tz_locn && preg_match($tz_regex, $tz_locn)) { dbg_error_log('PUT', ' Setting timezone to %s', $tz_locn); if ($tz_locn != '') { $qry->QDo('SET TIMEZONE TO \'' . $tz_locn . "'"); } $last_tz_locn = $tz_locn; } $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':tzlocn'] = $tz_locn; $params[':tzspec'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params); } if (!isset($tz_locn) || $tz_locn == '') { $tz_locn = $tzid; } } $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $calitem_params[':created'] = $created; $calitem_params[':tzid'] = $tzid; $calitem_params[':uid'] = $first->GetPValue('UID'); $calitem_params[':summary'] = $first->GetPValue('SUMMARY'); $calitem_params[':location'] = $first->GetPValue('LOCATION'); $calitem_params[':transp'] = $first->GetPValue('TRANSP'); $calitem_params[':description'] = $first->GetPValue('DESCRIPTION'); $calitem_params[':rrule'] = $first->GetPValue('RRULE'); $calitem_params[':url'] = $first->GetPValue('URL'); $calitem_params[':priority'] = $first->GetPValue('PRIORITY'); $calitem_params[':due'] = $first->GetPValue('DUE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':status'] = $first->GetPValue('STATUS'); if ($put_action_type == 'INSERT') { $sql = <<<EOSQL INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp, description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id ) VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, :dtstart, {$dtend}, :summary, :location, :class, :transp, :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, {$collection_id} ) EOSQL; $sync_change = 201; } else { $sql = <<<EOSQL UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, created=:created, due=:due, percent_complete=:percent_complete, status=:status WHERE user_no=:user_no AND dav_name=:dav_name EOSQL; $sync_change = 200; } write_alarms($dav_id, $first); write_attendees($dav_id, $first); if ($log_action && function_exists('log_caldav_action')) { log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } else { if ($log_action) { dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } } $qry = new AwlQuery($sql, $calitem_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path)); $qry->Commit(); dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return $segment_name; }
$status[] = new XMLElement('propstat', array(new XMLElement('prop', $props), new XMLElement('status', 'HTTP/1.1 424 Failed Dependency'))); if ($request_type == 'extended-mkcol') { $request->DoResponse($failure_code, $reply->Render('mkcol-response', array_merge($status, $failure), 'text/xml; charset="utf-8"')); } else { array_unshift($failure, $reply->href(ConstructURL($request->path))); $failure[] = new XMLElement('responsedescription', translate('Some properties were not able to be set.')); $request->DoResponse(207, $reply->Render('multistatus', new XMLElement('response', $failure)), 'text/xml; charset="utf-8"'); } } } $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name'; $qry = new AwlQuery($sql, array(':dav_name' => $request->path)); if (!$qry->Exec('MKCOL', __LINE__, __FILE__)) { $request->DoResponse(500, translate('Error querying database.')); } if ($qry->rows() != 0) { $request->DoResponse(405, translate('A collection already exists at that location.')); } $qry = new AwlQuery(); $qry->Begin(); if (!$qry->QDo('INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, is_addressbook, resourcetypes, created, modified ) VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname, :is_calendar, :is_addressbook, :resourcetypes, current_timestamp, current_timestamp )', array(':user_no' => $request->user_no, ':parent_container' => $parent_container, ':dav_name' => $request->path, ':dav_etag' => md5($request->user_no . $request->path), ':dav_displayname' => $displayname, ':is_calendar' => $is_calendar ? 't' : 'f', ':is_addressbook' => $is_addressbook ? 't' : 'f', ':resourcetypes' => $resourcetypes))) { $request->DoResponse(500, translate('Error writing calendar details to database.')); } foreach ($dav_properties as $k => $v) { if (!$qry->QDo('SELECT set_dav_property( :dav_name, :user_no::integer, :tag::text, :value::text )', array(':dav_name' => $request->path, ':user_no' => $request->user_no, ':tag' => $k, ':value' => $v))) { $request->DoResponse(500, translate('Error writing calendar properties to database.')); } }
/** * Get XML response for items in the collection * If '/' is requested, a list of visible users is given, otherwise * a list of calendars for the user which are parented by this path. */ function get_collection_contents($depth, $collection, $parent_path = null) { global $c, $session, $request, $reply, $property_list; $bound_from = $collection->bound_from(); $bound_to = $collection->dav_name(); if (!isset($parent_path)) { $parent_path = $collection->dav_name(); } dbg_error_log('PROPFIND', 'Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', $depth, $collection->dav_name(), $bound_from, $bound_to); $date_format = AwlDatabase::HttpDateFormat; $responses = array(); if (!$collection->IsCalendar() && !$collection->IsAddressbook()) { /** * Calendar/Addressbook collections may not contain collections, so we are only looking in the other ones */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth); if ($bound_from == '/') { $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, "; $sql .= "to_char(joined at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(updated at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, '; $sql .= 'principal_id AS collection_id, '; $sql .= 'principal.* '; $sql .= 'FROM usr JOIN principal USING (user_no) '; $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; $sql .= 'ORDER BY usr.user_no'; } else { $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', array(':this_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($binding = $qry->Fetch()) { $resource = new DAVResource($binding->dav_name); if ($resource->IsExternal()) { require_once "external-fetch.php"; update_external($resource); } if ($resource->HavePrivilegeTo('DAV::read', false)) { $resource->set_bind_location(str_replace($bound_from, $bound_to, $binding->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, $binding->dav_name)); } } } } $sql = 'SELECT principal.*, collection.*, \'collection\' AS type '; $sql .= 'FROM collection LEFT JOIN principal USING (user_no) '; $sql .= 'WHERE parent_container = :this_dav_name '; $sql .= ' ORDER BY collection_id'; $params[':this_dav_name'] = $bound_from; unset($params[':session_principal']); unset($params[':scan_depth']); } $qry = new AwlQuery($sql, $params); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($subcollection = $qry->Fetch()) { $resource = new DAVResource($subcollection); if (!$resource->HavePrivilegeTo('DAV::read')) { continue; } $resource->set_bind_location(str_replace($bound_from, $bound_to, $subcollection->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, str_replace($resource->parent_path(), $parent_path, $resource->dav_name()))); } } } if ((!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy == false) && $collection->IsPrincipal()) { // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write) dbg_error_log('PROPFIND', 'Adding calendar-proxy-read and write. Path: %s', $bound_from); $response = add_proxy_response('read', $bound_from); if (isset($response)) { $responses[] = $response; } $response = add_proxy_response('write', $bound_from); if (isset($response)) { $responses[] = $response; } } } /** * freebusy permission is not allowed to see the items in a collection. Must have at least read permission. */ if ($collection->HavePrivilegeTo('DAV::read', false)) { dbg_error_log('PROPFIND', 'Getting collection items: Depth %d, Path: %s', $depth, $bound_from); $privacy_clause = ' '; $todo_clause = ' '; $time_limit_clause = ' '; if ($collection->IsCalendar()) { if (!$collection->HavePrivilegeTo('all', false)) { $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) { $todo_clause = " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) { $time_limit_clause = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END) "; } } $sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, '; $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(coalesce(calendar_item.last_modified, caldav_data.modified) at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'summary AS dav_displayname '; $sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) '; $sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) '; $sql .= 'WHERE collection.dav_name = :collection_dav_name ' . $time_limit_clause . ' ' . $todo_clause . ' ' . $privacy_clause; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql, array(':collection_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($item = $qry->Fetch()) { if ($bound_from != $bound_to) { $item->bound_from = $item->dav_name; $item->dav_name = str_replace($bound_from, $bound_to, $item->dav_name); } $resource = new DAVResource($item); $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path); } } } return $responses; }
$dav_id = $row->collection_id; } else { create_external('/.external/' . md5($href), true, false); $qry->QDo('SELECT collection_id FROM collection WHERE dav_name = :dav_name ', array(':dav_name' => '/.external/' . md5($href))); if ($qry->rows() != 1 || !($row = $qry->Fetch())) { $request->DoResponse(500, translate('Database Error')); } $dav_id = $row->collection_id; } $sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname, external_url, type ) VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname, :external_url, :external_type )'; $params = array(':target_id' => $dav_id, ':ticket_id' => null, ':parent_container' => $parent->dav_name(), ':session_principal' => $session->principal_id, ':dav_name' => $destination_path, ':displayname' => $segment, ':external_url' => $href, ':external_type' => 'calendar'); $qry = new AwlQuery($sql, $params); if ($qry->Exec('BIND', __LINE__, __FILE__)) { $qry = new AwlQuery('SELECT bind_id from dav_binding where dav_name = :dav_name', array(':dav_name' => $destination_path)); if (!$qry->Exec('BIND', __LINE__, __FILE__) || $qry->rows() != 1 || !($row = $qry->Fetch())) { $request->DoResponse(500, translate('Database Error')); } fetch_external($row->bind_id, ''); $request->DoResponse(201); } else { $request->DoResponse(500, translate('Database Error')); } } else { $source = new DAVResource($href); if (!$source->Exists()) { $request->PreconditionFailed(403, 'DAV::bind-source-exists', translate('The BIND Request MUST identify an existing resource.')); } if ($source->IsPrincipal() || !$source->IsCollection()) { $request->PreconditionFailed(403, 'DAV::binding-allowed', translate('DAViCal only allows BIND requests for collections at present.')); }
break; default: /** * @todo We should handle a lot more properties here. principal-URL seems a likely one to be used. * @todo We should catch the unsupported properties in the query and fire back an error indicating so. */ dbg_error_log("principal", "Unhandled tag '%s' to match '%s'\n", $v1->GetNSTag(), $match); } } if ($subwhere != "") { $where .= sprintf("%s(%s)", $where == "" ? "" : $clause_joiner, $subwhere); } } if ($where != "") { $where = "WHERE {$where}"; } $sql = "SELECT * FROM dav_principal {$where} ORDER BY principal_id LIMIT 100"; $qry = new AwlQuery($sql, $params); $get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*'); $properties = array(); foreach ($get_props as $k1 => $v1) { $properties[] = $v1->GetNSTag(); } if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { $principal = new DAVResource($row); $responses[] = $principal->RenderAsXML($properties, $reply); } } $multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray()); $request->XMLResponse(207, $multistatus);
/** * Render the user's administrative roles * * @return string The string of html to be output */ function RenderRoles($ef, $title = null) { global $session; $html = ""; if ($title == null) { $title = i18n("User Roles"); } $html = $title == "" ? "" : $ef->BreakLine(translate($title)); $html .= '<tr><th class="prompt">' . translate("User Roles") . '</th><td class="entry">'; if ($ef->EditMode) { $sql = "SELECT role_name FROM roles "; if (!$session->AllowedTo('Admin')) { $sql .= "NATURAL JOIN role_member WHERE user_no={$session->user_no} "; } $sql .= "ORDER BY roles.role_no"; $ef->record->roles = array(); // Select the records $q = new AwlQuery($sql); if ($q && $q->Exec("User") && $q->rows()) { $i = 0; while ($row = $q->Fetch()) { @dbg_error_log("User", ":RenderRoles: Is a member of '%s': %s", $row->role_name, $this->roles[$row->role_name]); $ef->record->roles[$row->role_name] = isset($this->roles[$row->role_name]) ? $this->roles[$row->role_name] : 'f'; $html .= $ef->DataEntryField("", "checkbox", "roles[{$row->role_name}]", array("title" => translate("Does the user have the right to perform this role?"), "_label" => translate($row->role_name))); } } } else { $i = 0; foreach ($this->roles as $k => $v) { if ($i++ > 0) { $html .= ", "; } $html .= $k; } } $html .= '</td></tr>' . "\n"; return $html; }