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

Revision 3733, 8.3 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
3require_once('vcard.php');
4
5$address_data_properties = array();
6function get_address_properties( $address_data_xml ) {
7  global $address_data_properties;
8  $expansion = $address_data_xml->GetElements();
9  foreach( $expansion AS $k => $v ) {
10    $address_data_properties[strtoupper($v->GetAttribute('name'))] = true;
11  }
12}
13
14
15/**
16 * Build the array of properties to include in the report output
17 */
18$qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:carddav:addressbook-query');
19$proptype = $qry_content[0]->GetTag();
20$properties = array();
21switch( $proptype ) {
22  case 'DAV::prop':
23    $qry_props = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/'.$proptype.'/*');
24    foreach( $qry_content[0]->GetElements() AS $k => $v ) {
25      $propertyname = preg_replace( '/^.*:/', '', $v->GetTag() );
26      $properties[$propertyname] = 1;
27      if ( $v->GetTag() == 'urn:ietf:params:xml:ns:carddav:address-data' ) get_address_properties($v);
28    }
29    break;
30
31  case 'DAV::allprop':
32    $properties['allprop'] = 1;
33    if ( $qry_content[1]->GetTag() == 'DAV::include' ) {
34      foreach( $qry_content[1]->GetElements() AS $k => $v ) {
35        $include_properties[] = $v->GetTag(); /** $include_properties is referenced in DAVResource where allprop is expanded */
36        if ( $v->GetTag() == 'urn:ietf:params:xml:ns:carddav:address-data' ) get_address_properties($v);
37      }
38    }
39    break;
40
41  default:
42    $propertyname = preg_replace( '/^.*:/', '', $proptype );
43    $properties[$propertyname] = 1;
44}
45
46/**
47 * There can only be *one* FILTER element.
48 */
49$qry_filters = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/urn:ietf:params:xml:ns:carddav:filter/*');
50if ( count($qry_filters) != 1 ) {
51/*  $qry_filters = $qry_filters[0];  // There can only be one FILTER element
52}
53else { */
54  $qry_filters = false;
55}
56
57
58/**
59* While we can construct our SQL to apply some filters in the query, other filters
60* need to be checked against the retrieved record.  This is for handling those ones.
61*
62* @param array $filter An array of XMLElement which is the filter definition
63* @param string $item The database row retrieved for this calendar item
64*
65* @return boolean True if the check succeeded, false otherwise.
66*/
67function apply_filter( $filters, $item ) {
68  global $session, $c, $request;
69
70  if ( count($filters) == 0 ) return true;
71
72  dbg_error_log("cardquery","Applying filter for item '%s'", $item->dav_name );
73  $vcard = new vComponent( $item->caldav_data );
74  return $vcard->TestFilter($filters);
75}
76
77
78/**
79 * Process a filter fragment returning an SQL fragment
80 */
81$need_post_filter = false;
82$matchnum = 0;
83function SqlFilterCardDAV( $filter, $components, $property = null, $parameter = null ) {
84  global $need_post_filter, $target_collection, $matchnum;
85  $sql = "";
86  $params = array();
87  if ( !is_array($filter) ) {
88    dbg_error_log( "cardquery", "Filter is of type '%s', but should be an array of XML Tags.", gettype($filter) );
89  }
90
91  foreach( $filter AS $k => $v ) {
92    $tag = $v->GetTag();
93    dbg_error_log("cardquery", "Processing $tag into SQL - %d, '%s', %d\n", count($components), $property, isset($parameter) );
94
95    $not_defined = "";
96    switch( $tag ) {
97      case 'urn:ietf:params:xml:ns:carddav:text-match':
98        $search = $v->GetContent();
99        $negate = $v->GetAttribute("negate-condition");
100        $collation = $v->GetAttribute("collation");
101        switch( strtolower($collation) ) {
102          case 'i;octet':
103            $comparison = 'LIKE';
104            break;
105          case 'i;ascii-casemap':
106          case 'i;unicode-casemap':
107          default:
108            $comparison = 'ILIKE';
109            break;
110        }
111        $pname = ':text_match_'.$matchnum++;
112        $params[$pname] = '%'.$search.'%';
113        dbg_error_log("cardquery", " text-match: (%s%s %s '%s') ", (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""),
114                                          $property, $comparison, $params[$pname] );
115        $sql .= sprintf( "AND (%s%s %s $pname) ", (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""),
116                                          $property, $comparison );
117        break;
118
119      case 'urn:ietf:params:xml:ns:carddav:prop-filter':
120        $propertyname = $v->GetAttribute("name");
121        switch( $propertyname ) {
122          case 'VERSION':
123          case 'UID':
124          case 'NICKNAME':
125          case 'FN':
126          case 'NOTE':
127          case 'ORG':
128          case 'URL':
129          case 'FBURL':
130          case 'CALADRURI':
131          case 'CALURI':
132            $property = strtolower($propertyname);
133            break;
134
135          case 'N':
136            $property = 'name';
137            break;
138
139          default:
140            $need_post_filter = true;
141            dbg_error_log("cardquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname );
142            continue;
143        }
144        $subfilter = $v->GetContent();
145        $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter );
146        if ( $success === false ) continue; else {
147          $sql .= $success['sql'];
148          $params = array_merge( $params, $success['params'] );
149        }
150        break;
151
152      case 'urn:ietf:params:xml:ns:carddav:param-filter':
153        $need_post_filter = true;
154        return false; /** Figure out how to handle PARAM-FILTER conditions in the SQL */
155        /*
156        $parameter = $v->GetAttribute("name");
157        $subfilter = $v->GetContent();
158        $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter );
159        if ( $success === false ) continue; else {
160          $sql .= $success['sql'];
161          $params = array_merge( $params, $success['params'] );
162        }
163        break;
164        */
165
166      default:
167        dbg_error_log("cardquery", "Could not handle unknown tag '%s' in calendar query report", $tag );
168        break;
169    }
170  }
171  dbg_error_log("cardquery", "Generated SQL was '%s'", $sql );
172  return array( 'sql' => $sql, 'params' => $params );
173}
174
175
176/**
177* Something that we can handle, at least roughly correctly.
178*/
179
180$responses = array();
181$target_collection = new DAVResource($request->path);
182$bound_from = $target_collection->bound_from();
183if ( !$target_collection->Exists() ) {
184  $request->DoResponse( 404 );
185}
186if ( ! ($target_collection->IsAddressbook() || $target_collection->IsSchedulingCollection()) ) {
187  $request->DoResponse( 403, translate('The addressbook-query report must be run against an addressbook collection') );
188}
189
190/**
191* @todo Once we are past DB version 1.2.1 we can change this query more radically.  The best performance to
192* date seems to be:
193*   SELECT caldav_data.*,address_item.* FROM collection JOIN address_item USING (collection_id,user_no)
194*         JOIN caldav_data USING (dav_id) WHERE collection.dav_name = '/user1/home/'
195*              AND caldav_data.caldav_type = 'VEVENT' ORDER BY caldav_data.user_no, caldav_data.dav_name;
196*/
197
198$params = array();
199$where = ' WHERE caldav_data.collection_id = ' . $target_collection->resource_id();
200if ( is_array($qry_filters) ) {
201  dbg_log_array( 'cardquery', 'qry_filters', $qry_filters, true );
202  $components = array();
203  $filter_fragment =  SqlFilterCardDAV( $qry_filters, $components );
204  if ( $filter_fragment !== false ) {
205    $where .= ' '.$filter_fragment['sql'];
206    $params = $filter_fragment['params'];
207  }
208}
209else {
210  dbg_error_log( 'cardquery', 'No query filters' );
211}
212
213$sql = 'SELECT * FROM caldav_data INNER JOIN addressbook_resource USING(dav_id)'. $where;
214if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id";
215$qry = new AwlQuery( $sql, $params );
216if ( $qry->Exec("cardquery",__LINE__,__FILE__) && $qry->rows() > 0 ) {
217  while( $address_object = $qry->Fetch() ) {
218    if ( !$need_post_filter || apply_filter( $qry_filters, $address_object ) ) {
219      if ( $bound_from != $target_collection->dav_name() ) {
220        $address_object->dav_name = str_replace( $bound_from, $target_collection->dav_name(), $address_object->dav_name);
221      }
222      if ( count($address_data_properties) > 0 ) {
223        $vcard = new VCard($address_object->caldav_data);
224        $vcard->MaskProperties($address_data_properties);
225        $address_object->caldav_data = $vcard->Render();
226      }
227      $responses[] = component_to_xml( $properties, $address_object );
228    }
229  }
230}
231$multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
232
233$request->XMLResponse( 207, $multistatus );
Note: See TracBrowser for help on using the repository browser.