[3733] | 1 | <?php |
---|
| 2 | /** |
---|
| 3 | * CalDAV Server - handle PROPFIND method |
---|
| 4 | * |
---|
| 5 | * @package davical |
---|
| 6 | * @subpackage propfind |
---|
| 7 | * @author Andrew McMillan <andrew@catalyst.net.nz> |
---|
| 8 | * @copyright Catalyst .Net Ltd, Andrew McMillan |
---|
| 9 | * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later |
---|
| 10 | */ |
---|
| 11 | dbg_error_log('PROPFIND', 'method handler'); |
---|
| 12 | include_once("drivers_ldap.php"); |
---|
| 13 | $request->NeedPrivilege( array('DAV::read', 'urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read-current-user-privilege-set') ); |
---|
| 14 | |
---|
| 15 | require_once('iCalendar.php'); |
---|
| 16 | require_once('XMLDocument.php'); |
---|
| 17 | require_once('DAVResource.php'); |
---|
| 18 | |
---|
| 19 | $reply = new XMLDocument( array( 'DAV:' => '' ) ); |
---|
| 20 | |
---|
| 21 | if ( !isset($request->xml_tags) ) { |
---|
| 22 | $request->DoResponse( 403, translate("Request body contains no XML data!") ); |
---|
| 23 | } |
---|
| 24 | $position = 0; |
---|
| 25 | $xmltree = BuildXMLTree( $request->xml_tags, $position); |
---|
| 26 | if ( !is_object($xmltree) ) { |
---|
| 27 | $request->DoResponse( 403, translate("Request body is not valid XML data!") ); |
---|
| 28 | } |
---|
| 29 | $allprop = $xmltree->GetPath('/DAV::propfind/*'); |
---|
| 30 | $property_list = array(); |
---|
| 31 | foreach( $allprop AS $k1 => $propwrap ) { |
---|
| 32 | switch ( $propwrap->GetTag() ) { |
---|
| 33 | case 'DAV::allprop': |
---|
| 34 | $property_list[] = 'DAV::allprop'; |
---|
| 35 | break; |
---|
| 36 | case 'DAV::propname': |
---|
| 37 | $property_list[] = 'DAV::propname'; |
---|
| 38 | break; |
---|
| 39 | default: // prop, include |
---|
| 40 | $subprop = $propwrap->GetElements(); |
---|
| 41 | foreach( $subprop AS $k => $v ) { |
---|
| 42 | $property_list[] = $v->GetTag(); |
---|
| 43 | } |
---|
| 44 | } |
---|
| 45 | } |
---|
| 46 | |
---|
| 47 | |
---|
| 48 | /** |
---|
| 49 | * Add the calendar-proxy-read/write pseudocollections |
---|
| 50 | * @param responses array of responses to which to add the collections |
---|
| 51 | */ |
---|
| 52 | function add_proxy_response( $which, $parent_path ) { |
---|
| 53 | global $request, $reply, $c, $session, $property_list; |
---|
| 54 | |
---|
| 55 | if ($parent_path != $request->principal->dav_name()) { |
---|
| 56 | dbg_error_log( 'PROPFIND', 'Not returning proxy response since "%s" != "%s"', $parent_path, $request->principal->dav_name() ); |
---|
| 57 | return null; // Nothing to proxy for |
---|
| 58 | } |
---|
| 59 | |
---|
| 60 | $collection = (object) ''; |
---|
| 61 | if ( $which == 'read' ) { |
---|
| 62 | $proxy_group = $request->principal->ReadProxyGroup(); |
---|
| 63 | } else if ( $which == 'write' ) { |
---|
| 64 | $proxy_group = $request->principal->WriteProxyGroup(); |
---|
| 65 | } |
---|
| 66 | |
---|
| 67 | dbg_error_log( 'PROPFIND', 'Returning proxy response to "%s" for "%s"', $which, $parent_path ); |
---|
| 68 | |
---|
| 69 | $collection->parent_container = $parent_path; |
---|
| 70 | $collection->dav_name = $parent_path.'calendar-proxy-'.$which.'/'; |
---|
| 71 | $collection->is_calendar = 'f'; |
---|
| 72 | $collection->is_addressbook = 'f'; |
---|
| 73 | $collection->is_principal = 't'; |
---|
| 74 | $collection->is_proxy = 't'; |
---|
| 75 | $collection->proxy_type = $which; |
---|
| 76 | $collection->type = 'proxy'; |
---|
| 77 | $collection->dav_displayname = $collection->dav_name; |
---|
| 78 | $collection->collection_id = 0; |
---|
| 79 | $collection->user_no = $session->user_no; |
---|
| 80 | $collection->username = $session->username; |
---|
| 81 | $collection->email = $session->email; |
---|
| 82 | $collection->created = date('Ymd\THis'); |
---|
| 83 | $collection->dav_etag = md5($c->system_name . $collection->dav_name . implode($proxy_group) ); |
---|
| 84 | $collection->proxy_for = $proxy_group; |
---|
| 85 | $collection->resourcetypes = sprintf('<DAV::collection/><http://calendarserver.org/ns/:calendar-proxy-%s/>', $which); |
---|
| 86 | $collection->in_freebusy_set = 'f'; |
---|
| 87 | $collection->schedule_transp = 'transp'; |
---|
| 88 | $collection->timezone = null; |
---|
| 89 | $collection->description = ''; |
---|
| 90 | |
---|
| 91 | $resource = new DAVResource($collection); |
---|
| 92 | $resource->FetchPrincipal(); |
---|
| 93 | return $resource->RenderAsXML($property_list, $reply); |
---|
| 94 | |
---|
| 95 | } |
---|
| 96 | |
---|
| 97 | |
---|
| 98 | /** |
---|
| 99 | * Get XML response for items in the collection |
---|
| 100 | * If '/' is requested, a list of visible users is given, otherwise |
---|
| 101 | * a list of calendars for the user which are parented by this path. |
---|
| 102 | */ |
---|
| 103 | function get_collection_contents( $depth, $collection, $parent_path = null ) { |
---|
| 104 | global $c, $session, $request, $reply, $property_list; |
---|
| 105 | |
---|
| 106 | $bound_from = $collection->bound_from(); |
---|
| 107 | $bound_to = $collection->dav_name(); |
---|
| 108 | if ( !isset($parent_path) ) $parent_path = $collection->dav_name(); |
---|
| 109 | dbg_error_log('PROPFIND','Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', |
---|
| 110 | $depth, $collection->dav_name(), $bound_from, $bound_to ); |
---|
| 111 | |
---|
| 112 | $date_format = iCalendar::HttpDateFormat(); |
---|
| 113 | $responses = array(); |
---|
| 114 | if ( ! $collection->IsCalendar() && ! $collection->IsAddressbook() ) { |
---|
| 115 | /** |
---|
| 116 | * Calendar/Addressbook collections may not contain collections, so we won't look |
---|
| 117 | */ |
---|
| 118 | $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); |
---|
| 119 | if ( $bound_from == '/' ) { |
---|
| 120 | $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, "; |
---|
| 121 | $sql .= "to_char(joined at time zone 'GMT',$date_format) AS created, "; |
---|
| 122 | $sql .= "to_char(updated at time zone 'GMT',$date_format) AS modified, "; |
---|
| 123 | $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, '; |
---|
| 124 | $sql .= 'principal_id AS collection_id, '; |
---|
| 125 | $sql .= 'principal.* '; |
---|
| 126 | $sql .= 'FROM usr JOIN principal USING (user_no) '; |
---|
| 127 | $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; |
---|
| 128 | $sql .= 'ORDER BY usr.user_no'; |
---|
| 129 | } |
---|
| 130 | else { |
---|
| 131 | $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', |
---|
| 132 | array(':this_dav_name' => $bound_from)); |
---|
| 133 | if( $qry->Exec('PROPFIND',__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 134 | while( $binding = $qry->Fetch() ) { |
---|
| 135 | $resource = new DAVResource($binding->dav_name); |
---|
| 136 | if ( $resource->HavePrivilegeTo('DAV::read', false) ) { |
---|
| 137 | //if( true){ |
---|
| 138 | $resource->set_bind_location( str_replace($bound_from,$bound_to,$binding->dav_name)); |
---|
| 139 | $responses[] = $resource->RenderAsXML($property_list, $reply); |
---|
| 140 | if ( $depth > 0 ) { |
---|
| 141 | $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource, $binding->dav_name ) ); |
---|
| 142 | } |
---|
| 143 | } |
---|
| 144 | } |
---|
| 145 | } |
---|
| 146 | |
---|
| 147 | $sql = 'SELECT principal.*, collection.*, \'collection\' AS type '; |
---|
| 148 | $sql .= 'FROM collection LEFT JOIN principal USING (user_no) '; |
---|
| 149 | $sql .= 'WHERE parent_container = :this_dav_name '; |
---|
| 150 | $sql .= "AND (path_privs(:session_principal::int8,collection.dav_name,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; |
---|
| 151 | $sql .= ' ORDER BY collection_id'; |
---|
| 152 | $params[':this_dav_name'] = $bound_from; |
---|
| 153 | } |
---|
| 154 | $qry = new AwlQuery($sql, $params); |
---|
| 155 | |
---|
| 156 | if( $qry->Exec('PROPFIND',__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 157 | while( $subcollection = $qry->Fetch() ) { |
---|
| 158 | $resource = new DAVResource($subcollection); |
---|
| 159 | $resource->set_bind_location( str_replace($bound_from,$bound_to,$subcollection->dav_name)); |
---|
| 160 | $responses[] = $resource->RenderAsXML($property_list, $reply); |
---|
| 161 | if ( $depth > 0 ) { |
---|
| 162 | $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource, |
---|
| 163 | str_replace($resource->parent_path(), $parent_path, $resource->dav_name() ) ) ); |
---|
| 164 | } |
---|
| 165 | } |
---|
| 166 | } |
---|
| 167 | |
---|
| 168 | if ( $collection->IsPrincipal() ) { |
---|
| 169 | // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write) |
---|
| 170 | dbg_error_log('PROPFIND','Adding calendar-proxy-read and write. Path: %s', $bound_from ); |
---|
| 171 | $response = add_proxy_response('read', $bound_from ); |
---|
| 172 | if ( isset($response) ) $responses[] = $response; |
---|
| 173 | $response = add_proxy_response('write', $bound_from ); |
---|
| 174 | if ( isset($response) ) $responses[] = $response; |
---|
| 175 | } |
---|
| 176 | } |
---|
| 177 | |
---|
| 178 | /** |
---|
| 179 | * freebusy permission is not allowed to see the items in a collection. Must have at least read permission. |
---|
| 180 | */ |
---|
| 181 | if ( $collection->HavePrivilegeTo('DAV::read', false) ) { |
---|
| 182 | dbg_error_log('PROPFIND','Getting collection items: Depth %d, Path: %s', $depth, $bound_from ); |
---|
| 183 | $privacy_clause = ' '; |
---|
| 184 | $time_limit_clause = ' '; |
---|
| 185 | if ( $collection->IsCalendar() ) { |
---|
| 186 | if ( ! $collection->HavePrivilegeTo('all', false) ) { |
---|
| 187 | $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | if ( isset($c->hide_older_than) && intval($c->hide_older_than > 0) ) { |
---|
| 191 | $time_limit_clause = " AND calendar_item.dtstart > (now() - interval '".intval($c->hide_older_than)." days') "; |
---|
| 192 | } |
---|
| 193 | } |
---|
| 194 | /****************************** |
---|
| 195 | ***************** Melhorar as consultas |
---|
| 196 | */ |
---|
| 197 | $path = preg_replace("/^\//","",$request->path); |
---|
| 198 | $nome = $collection->GetProperty('user_no'); |
---|
| 199 | if ( $collection->IsCalendar() ) |
---|
| 200 | { |
---|
| 201 | $sqlp = "SELECT cal_id FROM phpgw_cal_user WHERE cal_type = 'u' AND cal_status != 'R' AND cal_login = :nome"; |
---|
| 202 | $qryp = new AwlQuery( $sqlp, array( ':nome' => $nome) ); |
---|
| 203 | if ( $qryp->Exec("PROPFIND",__LINE__,__FILE__) && $qryp->rows() > 0 ) { |
---|
| 204 | while( $part = $qryp->Fetch() ) { |
---|
| 205 | $sql = "SELECT * FROM phpgw_cal WHERE cal_id = $part->cal_id AND cal_type = 'E'"; |
---|
| 206 | $qry = new AwlQuery( $sql); |
---|
| 207 | if ( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 208 | while( $item = $qry->Fetch() ) { |
---|
| 209 | $resource = new DAVResource($item); |
---|
| 210 | //$resource->set_bind_location( str_replace($bound_from,$bound_to,$item->owner)); |
---|
| 211 | $resource->set_bind_location( $path."/".$item->cal_id."@".$item->owner.".ics"); |
---|
| 212 | $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path ); |
---|
| 213 | } |
---|
| 214 | } |
---|
| 215 | $sql = "SELECT * FROM phpgw_cal INNER JOIN phpgw_cal_repeats USING(cal_id) WHERE cal_id = $part->cal_id"; |
---|
| 216 | $qry = new AwlQuery( $sql); |
---|
| 217 | if ( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 218 | while( $item = $qry->Fetch() ) { |
---|
| 219 | $resource = new DAVResource($item); |
---|
| 220 | $resource->set_bind_location( $path."/".$item->cal_id."@".$item->owner.".ics"); |
---|
| 221 | $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path ); |
---|
| 222 | } |
---|
| 223 | } |
---|
| 224 | } |
---|
| 225 | } |
---|
| 226 | } |
---|
| 227 | else if ( $collection->IsAddressbook()) { |
---|
| 228 | $sql = "SELECT * FROM phpgw_cc_contact WHERE id_owner = :nome"; |
---|
| 229 | $qry = new AwlQuery( $sql,array(':nome' => $nome )); |
---|
| 230 | if ( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 231 | while( $item = $qry->Fetch() ) { |
---|
| 232 | $resource = new DAVResource($item); |
---|
| 233 | $resource->set_bind_location( $path."/".$item->id_contact."@".$item->id_owner.".vcf"); |
---|
| 234 | $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path ); |
---|
| 235 | } |
---|
| 236 | } |
---|
| 237 | |
---|
| 238 | } |
---|
| 239 | //else |
---|
| 240 | // { |
---|
| 241 | // dbg_error_log( "PROPFIND", "Responding with propfind error: uUknow collection type"); |
---|
| 242 | // $request->DoResponse( 501, 'Collection error'); |
---|
| 243 | // } |
---|
| 244 | //$sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, '; |
---|
| 245 | //$sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',$date_format) AS created, "; |
---|
| 246 | //$sql .= "to_char(last_modified at time zone 'GMT',$date_format) AS modified, "; |
---|
| 247 | //$sql .= 'summary AS dav_displayname '; |
---|
| 248 | //$sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) '; |
---|
| 249 | //$sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) '; |
---|
| 250 | //$sql .= 'WHERE collection.dav_name = :collection_dav_name '.$time_limit_clause.' '.$privacy_clause; |
---|
| 251 | //if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY caldav_data.dav_id"; |
---|
| 252 | //$qry = new AwlQuery( $sql, array( ':collection_dav_name' => $bound_from) ); |
---|
| 253 | //if( $qry->Exec('PROPFIND',__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
| 254 | // while( $item = $qry->Fetch() ) { |
---|
| 255 | // $resource = new DAVResource($item); |
---|
| 256 | // $resource->set_bind_location( str_replace($bound_from,$bound_to,$item->dav_name)); |
---|
| 257 | // $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path ); |
---|
| 258 | // } |
---|
| 259 | // } |
---|
| 260 | } |
---|
| 261 | |
---|
| 262 | return $responses; |
---|
| 263 | } |
---|
| 264 | |
---|
| 265 | |
---|
| 266 | |
---|
| 267 | /** |
---|
| 268 | * Something that we can handle, at least roughly correctly. |
---|
| 269 | */ |
---|
| 270 | $responses = array(); |
---|
| 271 | if ( $request->IsProxyRequest() ) { |
---|
| 272 | $response = add_proxy_response($request->proxy_type, $request->principal->dav_name() ); |
---|
| 273 | if ( isset($response) ) $responses[] = $response; |
---|
| 274 | } |
---|
| 275 | else { |
---|
| 276 | $resource = new DAVResource($request->path); |
---|
| 277 | if ( ! $resource->Exists() ) { |
---|
| 278 | $request->PreconditionFailed( 404, 'must-exist', translate('That resource is not present on this server.') ); |
---|
| 279 | } |
---|
| 280 | $resource->NeedPrivilege('DAV::read'); |
---|
| 281 | if ( $resource->IsCollection() ) { |
---|
| 282 | dbg_error_log('PROPFIND','Getting collection contents: Depth %d, Path: %s', $request->depth, $resource->dav_name() ); |
---|
| 283 | $responses[] = $resource->RenderAsXML($property_list, $reply); |
---|
| 284 | if ( $request->depth > 0 ) { |
---|
| 285 | $responses = array_merge($responses, get_collection_contents( $request->depth - 1, $resource ) ); |
---|
| 286 | } |
---|
| 287 | } |
---|
| 288 | elseif ( $request->HavePrivilegeTo('DAV::read',false) ){ |
---|
| 289 | $responses[] = $resource->RenderAsXML($property_list, $reply); |
---|
| 290 | } |
---|
| 291 | |
---|
| 292 | } |
---|
| 293 | $xmldoc = $reply->Render('multistatus', $responses); |
---|
| 294 | $etag = md5($xmldoc); |
---|
| 295 | header('ETag: "'.$etag.'"'); |
---|
| 296 | $request->DoResponse( 207, $xmldoc, 'text/xml; charset="utf-8"' ); |
---|
| 297 | |
---|