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 | |
---|