1: <?php
2:
3: require_once('vcard.php');
4:
5: $address_data_properties = array();
6: function get_address_properties( $address_data_xml ) {
7: global $address_data_properties;
8: $expansion = $address_data_xml->GetElements();
9: foreach( $expansion AS $k => $v ) {
10: if ( $v instanceof XMLElement )
11: $address_data_properties[strtoupper($v->GetAttribute('name'))] = true;
12: }
13: }
14:
15:
16: 17: 18:
19: $qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:carddav:addressbook-query');
20: $proptype = $qry_content[0]->GetNSTag();
21: $properties = array();
22: switch( $proptype ) {
23: case 'DAV::prop':
24: $qry_props = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/'.$proptype.'/*');
25: foreach( $qry_content[0]->GetElements() AS $k => $v ) {
26: $properties[$v->GetNSTag()] = 1;
27: if ( $v->GetNSTag() == 'urn:ietf:params:xml:ns:carddav:address-data' ) get_address_properties($v);
28: }
29: break;
30:
31: case 'DAV::allprop':
32: $properties['DAV::allprop'] = 1;
33: if ( $qry_content[1]->GetNSTag() == 'DAV::include' ) {
34: foreach( $qry_content[1]->GetElements() AS $k => $v ) {
35: $include_properties[] = $v->GetNSTag();
36: if ( $v->GetNSTag() == 'urn:ietf:params:xml:ns:carddav:address-data' ) get_address_properties($v);
37: }
38: }
39: break;
40:
41: default:
42: $properties[$proptype] = 1;
43: }
44: if ( empty($properties) ) $properties['DAV::allprop'] = 1;
45:
46: 47: 48:
49: $qry_filters = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/urn:ietf:params:xml:ns:carddav:filter/*');
50: if ( count($qry_filters) == 0 ) {
51: $qry_filters = false;
52: }
53:
54: $qry_limit = -1;
55: $qry_filters_combination='OR';
56: if ( is_array($qry_filters) ) {
57: $filters_parent = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/urn:ietf:params:xml:ns:carddav:filter');
58: $filters_parent = $filters_parent[0];
59:
60: if ( $filters_parent->GetAttribute("test") == 'allof' ) {
61: $qry_filters_combination='AND';
62: }
63:
64: $limits = $xmltree->GetPath('/urn:ietf:params:xml:ns:carddav:addressbook-query/urn:ietf:params:xml:ns:carddav:limit/urn:ietf:params:xml:ns:carddav:nresults');
65: if ( count($limits) == 1) {
66: $qry_limit = intval($limits[0]->GetContent());
67: }
68: }
69:
70: 71: 72: 73: 74: 75: 76: 77: 78: 79:
80: function cardquery_apply_filter( $filters, $item, $filter_type) {
81: global $session, $c, $request;
82:
83: if ( count($filters) == 0 ) return true;
84:
85: dbg_error_log("cardquery","Applying filter for item '%s'", $item->dav_name );
86: $vcard = new vComponent( $item->caldav_data );
87:
88: if ( $filter_type === 'AND' ) {
89: return $vcard->TestFilter($filters);
90: } else {
91: foreach($filters AS $filter) {
92: $filter_fragment[0] = $filter;
93: if ( $vcard->TestFilter($filter_fragment) ) {
94: return true;
95: }
96: }
97: return false;
98: }
99: }
100:
101:
102: 103: 104:
105: $post_filters = array();
106: $matchnum = 0;
107: function SqlFilterCardDAV( $filter, $components, $property = null, $parameter = null ) {
108: global $post_filters, $target_collection, $matchnum;
109: $sql = "";
110: $params = array();
111:
112: $tag = $filter->GetNSTag();
113: dbg_error_log("cardquery", "Processing $tag into SQL - %d, '%s', %d\n", count($components), $property, isset($parameter) );
114:
115: $not_defined = "";
116: switch( $tag ) {
117: case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
118: $sql .= $property . 'IS NULL';
119: break;
120:
121: case 'urn:ietf:params:xml:ns:carddav:text-match':
122: if ( empty($property) ) {
123: return false;
124: }
125:
126: $collation = $filter->GetAttribute("collation");
127: switch( strtolower($collation) ) {
128: case 'i;octet':
129: $comparison = 'LIKE';
130: break;
131: case 'i;ascii-casemap':
132: case 'i;unicode-casemap':
133: default:
134: $comparison = 'ILIKE';
135: break;
136: }
137:
138: $search = $filter->GetContent();
139: $match = $filter->GetAttribute("match-type");
140: switch( strtolower($match) ) {
141: case 'equals':
142: break;
143: case 'starts-with':
144: $search = $search.'%';
145: break;
146: case 'ends-with':
147: $search = $search.'%';
148: break;
149: case 'contains':
150: default:
151: $search = '%'.$search.'%';
152: break;
153: }
154:
155: $pname = ':text_match_'.$matchnum++;
156: $params[$pname] = $search;
157:
158: $negate = $filter->GetAttribute("negate-condition");
159: $negate = ( (isset($negate) && strtolower($negate) ) == "yes" ) ? "NOT " : "";
160: dbg_error_log("cardquery", " text-match: (%s%s %s '%s') ", $negate, $property, $comparison, $search );
161: $sql .= sprintf( "(%s%s %s $pname)", $negate, $property, $comparison );
162: break;
163:
164: case 'urn:ietf:params:xml:ns:carddav:prop-filter':
165: $propertyname = $filter->GetAttribute("name");
166: switch( $propertyname ) {
167: case 'VERSION':
168: case 'UID':
169: case 'NICKNAME':
170: case 'FN':
171: case 'NOTE':
172: case 'ORG':
173: case 'URL':
174: case 'FBURL':
175: case 'CALADRURI':
176: case 'CALURI':
177: $property = strtolower($propertyname);
178: break;
179:
180: case 'N':
181: $property = 'name';
182: break;
183:
184: default:
185: $post_filters[] = $filter;
186: dbg_error_log("cardquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname );
187: return false;
188: }
189:
190: $test_type = $filter->GetAttribute("test");
191: switch( $test_type ) {
192: case 'allOf':
193: $test_type = 'AND';
194: break;
195: case 'anyOf':
196: default:
197: $test_type = 'OR';
198: }
199:
200: $subfilters = $filter->GetContent();
201: if (count($subfilters) <= 1) {
202: $success = SqlFilterCardDAV( $subfilters[0], $components, $property, $parameter );
203: if ( $success !== false ) {
204: $sql .= $success['sql'];
205: $params = array_merge( $params, $success['params'] );
206: }
207: } else {
208: $subfilter_added_counter=0;
209: foreach ($subfilters as $subfilter) {
210: $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter );
211: if ( $success === false ) continue; else {
212: if ($subfilter_added_counter <= 0) {
213: $sql .= '(' . $success['sql'];
214: } else {
215: $sql .= $test_type . ' ' . $success['sql'];
216: }
217: $params = array_merge( $params, $success['params'] );
218: $subfilter_added_counter++;
219: }
220: }
221: if ($subfilter_added_counter > 0) {
222: $sql .= ')';
223: }
224: }
225: break;
226:
227: case 'urn:ietf:params:xml:ns:carddav:param-filter':
228: $post_filters[] = $filter;
229: return false;
230: 231: 232: 233: 234: 235: 236: 237: 238: 239:
240:
241: default:
242: dbg_error_log("cardquery", "Could not handle unknown tag '%s' in calendar query report", $tag );
243: break;
244: }
245: dbg_error_log("cardquery", "Generated SQL was '%s'", $sql );
246: return array( 'sql' => $sql, 'params' => $params );
247: }
248:
249:
250: 251: 252:
253:
254: $responses = array();
255: $target_collection = new DAVResource($request->path);
256: $bound_from = $target_collection->bound_from();
257: if ( !$target_collection->Exists() ) {
258: $request->DoResponse( 404 );
259: }
260: if ( ! $target_collection->IsAddressbook() ) {
261: $request->DoResponse( 403, translate('The addressbook-query report must be run against an addressbook collection') );
262: }
263:
264: 265: 266: 267: 268: 269: 270:
271:
272: $params = array();
273: $where = ' WHERE caldav_data.collection_id = ' . $target_collection->resource_id();
274: if ( is_array($qry_filters) ) {
275: dbg_log_array( 'cardquery', 'qry_filters', $qry_filters, true );
276:
277: $appended_where_counter=0;
278: foreach ($qry_filters as $qry_filter) {
279: $components = array();
280: $filter_fragment = SqlFilterCardDAV( $qry_filter, $components );
281: if ( $filter_fragment !== false ) {
282: $filter_fragment_sql = $filter_fragment['sql'];
283: if ( empty($filter_fragment_sql) ) {
284: continue;
285: }
286:
287: if ( $appended_where_counter == 0 ) {
288: $where .= ' AND (' . $filter_fragment_sql;
289: $params = $filter_fragment['params'];
290: } else {
291: $where .= ' ' . $qry_filters_combination . ' ' . $filter_fragment_sql;
292: $params = array_merge( $params, $filter_fragment['params'] );
293: }
294: $appended_where_counter++;
295: }
296: }
297: if ( $appended_where_counter > 0 ) {
298: $where .= ')';
299: }
300: }
301: else {
302: dbg_error_log( 'cardquery', 'No query filters' );
303: }
304:
305: $need_post_filter = !empty($post_filters);
306: if ( $need_post_filter && ( $qry_filters_combination == 'OR' )) {
307:
308:
309: $where = '';
310: $params = array();
311: $post_filters = $qry_filters;
312: }
313:
314: $sql = 'SELECT * FROM caldav_data INNER JOIN addressbook_resource USING(dav_id)'. $where;
315: if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id";
316: $qry = new AwlQuery( $sql, $params );
317: if ( $qry->Exec("cardquery",__LINE__,__FILE__) && $qry->rows() > 0 ) {
318: while( $address_object = $qry->Fetch() ) {
319: if ( !$need_post_filter || cardquery_apply_filter( $post_filters, $address_object, $qry_filters_combination ) ) {
320: if ( $bound_from != $target_collection->dav_name() ) {
321: $address_object->dav_name = str_replace( $bound_from, $target_collection->dav_name(), $address_object->dav_name);
322: }
323: if ( count($address_data_properties) > 0 ) {
324: $vcard = new VCard($address_object->caldav_data);
325: $vcard->MaskProperties($address_data_properties);
326: $address_object->caldav_data = $vcard->Render();
327: }
328: $responses[] = component_to_xml( $properties, $address_object );
329: if ( ($qry_limit > 0) && ( count($responses) >= $qry_limit ) ) {
330: break;
331: }
332: }
333: }
334: }
335: $multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
336:
337: $request->XMLResponse( 207, $multistatus );
338: