1 | <?php |
---|
2 | |
---|
3 | $need_expansion = false; |
---|
4 | function check_for_expansion( $calendar_data_node ) { |
---|
5 | global $need_expansion, $expand_range_start, $expand_range_end; |
---|
6 | |
---|
7 | if ( !class_exists('DateTime') ) return; /** We don't support expansion on PHP5.1 */ |
---|
8 | |
---|
9 | $expansion = $calendar_data_node->GetElements('urn:ietf:params:xml:ns:caldav:expand'); |
---|
10 | if ( isset($expansion[0]) ) { |
---|
11 | $need_expansion = true; |
---|
12 | $expand_range_start = $expansion[0]->GetAttribute('start'); |
---|
13 | $expand_range_end = $expansion[0]->GetAttribute('end'); |
---|
14 | if ( isset($expand_range_start) ) $expand_range_start = new RepeatRuleDateTime($expand_range_start); |
---|
15 | if ( isset($expand_range_end) ) $expand_range_end = new RepeatRuleDateTime($expand_range_end); |
---|
16 | } |
---|
17 | } |
---|
18 | |
---|
19 | /** |
---|
20 | * Build the array of properties to include in the report output |
---|
21 | */ |
---|
22 | $qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:calendar-query'); |
---|
23 | $proptype = $qry_content[0]->GetTag(); |
---|
24 | $properties = array(); |
---|
25 | switch( $proptype ) { |
---|
26 | case 'DAV::prop': |
---|
27 | $qry_props = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-query/'.$proptype.'/*'); |
---|
28 | foreach( $qry_content[0]->GetElements() AS $k => $v ) { |
---|
29 | $propertyname = preg_replace( '/^.*:/', '', $v->GetTag() ); |
---|
30 | $properties[$propertyname] = 1; |
---|
31 | if ( $v->GetTag() == 'urn:ietf:params:xml:ns:caldav:calendar-data' ) check_for_expansion($v); |
---|
32 | } |
---|
33 | break; |
---|
34 | |
---|
35 | case 'DAV::allprop': |
---|
36 | $properties['allprop'] = 1; |
---|
37 | if ( $qry_content[1]->GetTag() == 'DAV::include' ) { |
---|
38 | foreach( $qry_content[1]->GetElements() AS $k => $v ) { |
---|
39 | $include_properties[] = $v->GetTag(); /** $include_properties is referenced in DAVResource where allprop is expanded */ |
---|
40 | if ( $v->GetTag() == 'urn:ietf:params:xml:ns:caldav:calendar-data' ) check_for_expansion($v); |
---|
41 | } |
---|
42 | } |
---|
43 | break; |
---|
44 | |
---|
45 | default: |
---|
46 | $propertyname = preg_replace( '/^.*:/', '', $proptype ); |
---|
47 | $properties[$propertyname] = 1; |
---|
48 | } |
---|
49 | |
---|
50 | /** |
---|
51 | * There can only be *one* FILTER element, and it must contain *one* COMP-FILTER |
---|
52 | * element. In every case I can see this contained COMP-FILTER element will be a |
---|
53 | * VCALENDAR, but perhaps there are others. In our case we strip it if that is |
---|
54 | * the case and leave it alone otherwise. |
---|
55 | */ |
---|
56 | $qry_filters = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-query/urn:ietf:params:xml:ns:caldav:filter/*'); |
---|
57 | if ( count($qry_filters) == 1 ) { |
---|
58 | $qry_filters = $qry_filters[0]; // There can only be one FILTER element |
---|
59 | if ( $qry_filters->GetTag() == "urn:ietf:params:xml:ns:caldav:comp-filter" && $qry_filters->GetAttribute("name") == "VCALENDAR" ) |
---|
60 | $qry_filters = $qry_filters->GetContent(); // Everything is inside a VCALENDAR AFAICS |
---|
61 | else { |
---|
62 | dbg_error_log("calquery", "Got bizarre CALDAV:FILTER[%s=%s]] which does not contain comp-filter = VCALENDAR!!", $qry_filters->GetTag(), $qry_filters->GetAttribute("name") ); |
---|
63 | $qry_filters = false; |
---|
64 | } |
---|
65 | } |
---|
66 | else { |
---|
67 | $qry_filters = false; |
---|
68 | } |
---|
69 | |
---|
70 | |
---|
71 | /** |
---|
72 | * While we can construct our SQL to apply some filters in the query, other filters |
---|
73 | * need to be checked against the retrieved record. This is for handling those ones. |
---|
74 | * |
---|
75 | * @param array $filter An array of XMLElement which is the filter definition |
---|
76 | * @param string $item The database row retrieved for this calendar item |
---|
77 | * |
---|
78 | * @return boolean True if the check succeeded, false otherwise. |
---|
79 | */ |
---|
80 | function apply_filter( $filters, $item ) { |
---|
81 | global $session, $c, $request; |
---|
82 | |
---|
83 | if ( count($filters) == 0 ) return true; |
---|
84 | |
---|
85 | dbg_error_log("calquery","Applying filter for item '%s'", $item->dav_name ); |
---|
86 | $ical = new iCalendar( array( "icalendar" => $item->caldav_data) ); |
---|
87 | return $ical->TestFilter($filters); |
---|
88 | } |
---|
89 | |
---|
90 | |
---|
91 | /** |
---|
92 | * Process a filter fragment returning an SQL fragment |
---|
93 | */ |
---|
94 | $need_post_filter = false; |
---|
95 | function SqlFilterFragment( $filter, $components, $property = null, $parameter = null ) { |
---|
96 | global $need_post_filter, $target_collection; |
---|
97 | $sql = ""; |
---|
98 | $params = array(); |
---|
99 | if ( !is_array($filter) ) { |
---|
100 | dbg_error_log( "calquery", "Filter is of type '%s', but should be an array of XML Tags.", gettype($filter) ); |
---|
101 | } |
---|
102 | |
---|
103 | foreach( $filter AS $k => $v ) { |
---|
104 | $tag = $v->GetTag(); |
---|
105 | dbg_error_log("calquery", "Processing $tag into SQL - %d, '%s', %d\n", count($components), $property, isset($parameter) ); |
---|
106 | |
---|
107 | $not_defined = ""; |
---|
108 | switch( $tag ) { |
---|
109 | case 'urn:ietf:params:xml:ns:caldav:is-not-defined': |
---|
110 | $not_defined = "not-"; // then fall through to IS-DEFINED case |
---|
111 | case 'urn:ietf:params:xml:ns:caldav:is-defined': |
---|
112 | if ( isset( $parameter ) ) { |
---|
113 | $need_post_filter = true; |
---|
114 | dbg_error_log("calquery", "Could not handle 'is-%sdefined' on property %s, parameter %s in SQL", $not_defined, $property, $parameter ); |
---|
115 | return false; // Not handled in SQL |
---|
116 | } |
---|
117 | if ( isset( $property ) ) { |
---|
118 | switch( $property ) { |
---|
119 | case 'created': |
---|
120 | case 'completed': /** @todo when it can be handled in the SQL - see around line 200 below */ |
---|
121 | case 'dtend': |
---|
122 | case 'dtstamp': |
---|
123 | case 'dtstart': |
---|
124 | if ( ! $target_collection->IsSchedulingCollection() ) { |
---|
125 | $property_defined_match = "IS NOT NULL"; |
---|
126 | } |
---|
127 | break; |
---|
128 | |
---|
129 | case 'priority': |
---|
130 | $property_defined_match = "IS NOT NULL"; |
---|
131 | break; |
---|
132 | |
---|
133 | default: |
---|
134 | $property_defined_match = "LIKE '_%'"; // i.e. contains a single character or more |
---|
135 | } |
---|
136 | $sql .= sprintf( "AND %s %s%s ", $property, $not_defined, $property_defined_match ); |
---|
137 | } |
---|
138 | break; |
---|
139 | |
---|
140 | case 'urn:ietf:params:xml:ns:caldav:time-range': |
---|
141 | /** |
---|
142 | * @todo We should probably allow time range queries against other properties, since eventually some client may want to do this. |
---|
143 | */ |
---|
144 | $start_column = ($components[sizeof($components)-1] == 'VTODO' ? "due" : 'dtend'); // The column we compare against the START attribute |
---|
145 | $finish_column = 'dtstart'; // The column we compare against the END attribute |
---|
146 | $start = $v->GetAttribute("start"); |
---|
147 | $finish = $v->GetAttribute("end"); |
---|
148 | if ( isset($start) || isset($finish) ) { |
---|
149 | $sql .= ' AND (rrule_event_overlaps( dtstart, dtend, rrule, :time_range_start, :time_range_end ) OR event_has_exceptions(caldav_data.caldav_data) ) '; |
---|
150 | $params[':time_range_start'] = $start; |
---|
151 | $params[':time_range_end'] = $finish; |
---|
152 | } |
---|
153 | break; |
---|
154 | |
---|
155 | case 'urn:ietf:params:xml:ns:caldav:text-match': |
---|
156 | $search = $v->GetContent(); |
---|
157 | $negate = $v->GetAttribute("negate-condition"); |
---|
158 | $collation = $v->GetAttribute("collation"); |
---|
159 | switch( strtolower($collation) ) { |
---|
160 | case 'i;octet': |
---|
161 | $comparison = 'LIKE'; |
---|
162 | break; |
---|
163 | case 'i;ascii-casemap': |
---|
164 | default: |
---|
165 | $comparison = 'ILIKE'; |
---|
166 | break; |
---|
167 | } |
---|
168 | $params[':text_match'] = '%'.$search.'%'; |
---|
169 | dbg_error_log("calquery", " text-match: (%s IS NULL OR %s%s %s '%s') ", $property, (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""), |
---|
170 | $property, $comparison, $params[':text_match'] ); |
---|
171 | $sql .= sprintf( "AND (%s IS NULL OR %s%s %s :text_match) ", $property, (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""), |
---|
172 | $property, $comparison ); |
---|
173 | break; |
---|
174 | |
---|
175 | case 'urn:ietf:params:xml:ns:caldav:comp-filter': |
---|
176 | $comp_filter_name = $v->GetAttribute("name"); |
---|
177 | if ( count($components) == 0 ) { |
---|
178 | $sql .= "AND caldav_data.caldav_type = :component_name_filter "; |
---|
179 | $params[':component_name_filter'] = $comp_filter_name; |
---|
180 | } |
---|
181 | $components[] = $comp_filter_name; |
---|
182 | $subfilter = $v->GetContent(); |
---|
183 | if ( is_array( $subfilter ) ) { |
---|
184 | $success = SqlFilterFragment( $subfilter, $components, $property, $parameter ); |
---|
185 | if ( $success === false ) continue; else { |
---|
186 | $sql .= $success['sql']; |
---|
187 | $params = array_merge( $params, $success['params'] ); |
---|
188 | } |
---|
189 | } |
---|
190 | break; |
---|
191 | |
---|
192 | case 'urn:ietf:params:xml:ns:caldav:prop-filter': |
---|
193 | $propertyname = $v->GetAttribute("name"); |
---|
194 | switch( $propertyname ) { |
---|
195 | case 'PERCENT-COMPLETE': |
---|
196 | $property = 'percent_complete'; |
---|
197 | break; |
---|
198 | |
---|
199 | case 'UID': |
---|
200 | case 'SUMMARY': |
---|
201 | case 'LOCATION': |
---|
202 | case 'DESCRIPTION': |
---|
203 | case 'CLASS': |
---|
204 | case 'TRANSP': |
---|
205 | case 'RRULE': // Likely that this is not much use |
---|
206 | case 'URL': |
---|
207 | case 'STATUS': |
---|
208 | case 'CREATED': |
---|
209 | case 'DTSTAMP': |
---|
210 | case 'DTSTART': |
---|
211 | case 'DTEND': |
---|
212 | case 'DUE': |
---|
213 | case 'PRIORITY': |
---|
214 | $property = strtolower($propertyname); |
---|
215 | break; |
---|
216 | |
---|
217 | case 'COMPLETED': /** @todo this should be moved into the properties supported in SQL. */ |
---|
218 | default: |
---|
219 | $need_post_filter = true; |
---|
220 | dbg_error_log("calquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname ); |
---|
221 | continue; |
---|
222 | } |
---|
223 | $subfilter = $v->GetContent(); |
---|
224 | $success = SqlFilterFragment( $subfilter, $components, $property, $parameter ); |
---|
225 | if ( $success === false ) continue; else { |
---|
226 | $sql .= $success['sql']; |
---|
227 | $params = array_merge( $params, $success['params'] ); |
---|
228 | } |
---|
229 | break; |
---|
230 | |
---|
231 | case 'urn:ietf:params:xml:ns:caldav:param-filter': |
---|
232 | $need_post_filter = true; |
---|
233 | return false; // Can't handle PARAM-FILTER conditions in the SQL |
---|
234 | $parameter = $v->GetAttribute("name"); |
---|
235 | $subfilter = $v->GetContent(); |
---|
236 | $success = SqlFilterFragment( $subfilter, $components, $property, $parameter ); |
---|
237 | if ( $success === false ) continue; else { |
---|
238 | $sql .= $success['sql']; |
---|
239 | $params = array_merge( $params, $success['params'] ); |
---|
240 | } |
---|
241 | break; |
---|
242 | |
---|
243 | default: |
---|
244 | dbg_error_log("calquery", "Could not handle unknown tag '%s' in calendar query report", $tag ); |
---|
245 | break; |
---|
246 | } |
---|
247 | } |
---|
248 | dbg_error_log("calquery", "Generated SQL was '%s'", $sql ); |
---|
249 | return array( 'sql' => $sql, 'params' => $params ); |
---|
250 | } |
---|
251 | |
---|
252 | /** |
---|
253 | * Build an SQL 'WHERE' clause which implements (parts of) the filter. The |
---|
254 | * elements of the filter which are implemented in the SQL will be removed. |
---|
255 | * |
---|
256 | * @param arrayref &$filter A reference to an array of XMLElement defining the filter |
---|
257 | * |
---|
258 | * @return string A string suitable for use as an SQL 'WHERE' clause selecting the desired records. |
---|
259 | */ |
---|
260 | function BuildSqlFilter( $filter ) { |
---|
261 | $components = array(); |
---|
262 | return SqlFilterFragment( $filter, $components ); |
---|
263 | } |
---|
264 | |
---|
265 | |
---|
266 | /** |
---|
267 | * Something that we can handle, at least roughly correctly. |
---|
268 | */ |
---|
269 | |
---|
270 | $responses = array(); |
---|
271 | $target_collection = new DAVResource($request->path); |
---|
272 | $bound_from = $target_collection->bound_from(); |
---|
273 | if ( !$target_collection->Exists() ) { |
---|
274 | $request->DoResponse( 404 ); |
---|
275 | } |
---|
276 | if ( ! ($target_collection->IsCalendar() || $target_collection->IsSchedulingCollection()) ) { |
---|
277 | $request->DoResponse( 403, translate('The calendar-query report must be run against a calendar or a scheduling collection') ); |
---|
278 | } |
---|
279 | |
---|
280 | /** |
---|
281 | * @todo Once we are past DB version 1.2.1 we can change this query more radically. The best performance to |
---|
282 | * date seems to be: |
---|
283 | * SELECT caldav_data.*,calendar_item.* FROM collection JOIN calendar_item USING (collection_id,user_no) |
---|
284 | * JOIN caldav_data USING (dav_id) WHERE collection.dav_name = '/user1/home/' |
---|
285 | * AND caldav_data.caldav_type = 'VEVENT' ORDER BY caldav_data.user_no, caldav_data.dav_name; |
---|
286 | */ |
---|
287 | |
---|
288 | $params = array(); |
---|
289 | //$where = ' WHERE caldav_data.collection_id = ' . $target_collection->resource_id(); |
---|
290 | //if ( is_array($qry_filters) ) { |
---|
291 | // dbg_log_array( "calquery", "qry_filters", $qry_filters, true ); |
---|
292 | // $components = array(); |
---|
293 | // $filter_fragment = SqlFilterFragment( $qry_filters, $components ); |
---|
294 | // if ( $filter_fragment !== false ) { |
---|
295 | // $where .= ' '.$filter_fragment['sql']; |
---|
296 | // $params = $filter_fragment['params']; |
---|
297 | // } |
---|
298 | //} |
---|
299 | //if ( $target_collection->Privileges() != privilege_to_bits('DAV::all') ) { |
---|
300 | // $where .= " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; |
---|
301 | //} |
---|
302 | |
---|
303 | //if ( isset($c->hide_TODO) && $c->hide_TODO && ! $target_collection->HavePrivilegeTo('DAV::write-content') ) { |
---|
304 | // $where .= " AND caldav_data.caldav_type NOT IN ('VTODO') "; |
---|
305 | //} |
---|
306 | |
---|
307 | //if ( isset($c->hide_older_than) && intval($c->hide_older_than > 0) ) { |
---|
308 | // $where .= " AND calendar_item.dtstart > (now() - interval '".intval($c->hide_older_than)." days') "; |
---|
309 | //} |
---|
310 | |
---|
311 | //$sql = 'SELECT * FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name)'. $where; |
---|
312 | //if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id"; |
---|
313 | //$qry = new AwlQuery( $sql, $params ); |
---|
314 | //if ( $qry->Exec("calquery",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
315 | // while( $calendar_object = $qry->Fetch() ) { |
---|
316 | // if ( !$need_post_filter || apply_filter( $qry_filters, $calendar_object ) ) { |
---|
317 | // if ( $bound_from != $target_collection->dav_name() ) { |
---|
318 | // $calendar_object->dav_name = str_replace( $bound_from, $target_collection->dav_name(), $calendar_object->dav_name); |
---|
319 | // } |
---|
320 | // if ( $need_expansion ) { |
---|
321 | // $vResource = new vComponent($calendar_object->caldav_data); |
---|
322 | // $expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end); |
---|
323 | // $calendar_object->caldav_data = $expanded->Render(); |
---|
324 | // } |
---|
325 | // $responses[] = calendar_to_xml( $properties, $calendar_object ); |
---|
326 | // } |
---|
327 | // } |
---|
328 | //} |
---|
329 | $nome = $target_collection->GetProperty('user_no'); |
---|
330 | if ( $target_collection->Privileges() != privilege_to_bits('DAV::all') ) { |
---|
331 | $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; |
---|
332 | } |
---|
333 | |
---|
334 | if ( isset($c->hide_TODO) && $c->hide_TODO && ! $target_collection->Privileges() == privilege_to_bits('all') ) { |
---|
335 | $where .= "AND caldav_data.caldav_type NOT IN ('VTODO') "; |
---|
336 | } |
---|
337 | $sqlp = "SELECT cal_id FROM phpgw_cal_user WHERE cal_type = 'u' AND cal_status = 'A' AND cal_login = :nome"; |
---|
338 | $qryp = new AwlQuery( $sqlp, array( ':nome' => $nome) ); |
---|
339 | if ( $qryp->Exec("REPORT-MULTIGET",__LINE__,__FILE__) && $qryp->rows() > 0 ) { |
---|
340 | while( $part = $qryp->Fetch() ) { |
---|
341 | $sql = "SELECT * FROM phpgw_cal WHERE cal_id = $part->cal_id AND cal_type = 'E'"; |
---|
342 | $qry = new AwlQuery( $sql); |
---|
343 | if ( $qry->Exec("REPORT-MULTIGET",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
344 | while( $calendar_object = $qry->Fetch() ) { |
---|
345 | if ( $bound_from != $target_collection->dav_name() ) { |
---|
346 | $calendar_object->dav_name = str_replace( $bound_from, $target_collection->dav_name(), $calendar_object->dav_name); |
---|
347 | } |
---|
348 | if ( $need_expansion ) { |
---|
349 | $vResource = new vComponent($calendar_object->caldav_data); |
---|
350 | $expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end); |
---|
351 | $calendar_object->caldav_data = $expanded->Render(); |
---|
352 | } |
---|
353 | $responses[] = calendar_to_xml( $properties, $calendar_object ); |
---|
354 | } |
---|
355 | } |
---|
356 | $sql = "SELECT * FROM phpgw_cal INNER JOIN phpgw_cal_repeats USING(cal_id) WHERE cal_id = $part->cal_id"; |
---|
357 | $qry = new AwlQuery( $sql); |
---|
358 | if ( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows() > 0 ) { |
---|
359 | while( $calendar_object = $qry->Fetch() ) { |
---|
360 | if ( $bound_from != $target_collection->dav_name() ) { |
---|
361 | $calendar_object->dav_name = str_replace( $bound_from, $target_collection->dav_name(), $calendar_object->dav_name); |
---|
362 | } |
---|
363 | if ( $need_expansion ) { |
---|
364 | $vResource = new vComponent($calendar_object->caldav_data); |
---|
365 | $expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end); |
---|
366 | $calendar_object->caldav_data = $expanded->Render(); |
---|
367 | } |
---|
368 | $responses[] = calendar_to_xml( $properties, $calendar_object ); |
---|
369 | } |
---|
370 | } |
---|
371 | } |
---|
372 | } |
---|
373 | |
---|
374 | |
---|
375 | |
---|
376 | |
---|
377 | $multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() ); |
---|
378 | |
---|
379 | $request->XMLResponse( 207, $multistatus ); |
---|