source: contrib/davical/inc/caldav-REPORT-calquery.php @ 3733

Revision 3733, 15.9 KB checked in by gabriel.malheiros, 13 years ago (diff)

Ticket #1541 - <Davical customizado para o Expresso.Utiliza Caldav e CardDav?>

Line 
1<?php
2
3$need_expansion = false;
4function 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();
25switch( $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/*');
57if ( 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}
66else {
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*/
80function 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;
95function 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 */
260function 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();
273if ( !$target_collection->Exists() ) {
274  $request->DoResponse( 404 );
275}
276if ( ! ($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 );
Note: See TracBrowser for help on using the repository browser.