Overview

Packages

  • awl
    • caldav-client-v2
    • RRule
  • davical
    • authentication
      • drivers
    • caldav
    • DAViCalSession
    • DAVTicket
    • external-bind
    • feed
    • HTTPAuthSession
    • iSchedule
    • iSchedule-POST
    • logging
    • metrics
    • Principal
    • propfind
    • PublicSession
    • Request
    • Resource
    • tzservice
  • None
  • PHP

Classes

  • CalDAVClient
  • CheckResult
  • setupFakeSession
  • VCard
  • VTimezone

Functions

  • access_ticket_browser
  • binding_row_editor
  • bindings_to_other_browser
  • bindings_to_us_browser
  • build_dependencies_table
  • build_privileges_html
  • build_site_statistics
  • BuildSqlFilter
  • calquery_apply_filter
  • cardquery_apply_filter
  • catch_setup_errors
  • check_awl_version
  • check_calendar
  • check_curl
  • check_database_connection
  • check_datetime
  • check_davical_version
  • check_gettext
  • check_iconv
  • check_ldap
  • check_magic_quotes_gpc
  • check_magic_quotes_runtime
  • check_pdo
  • check_pdo_pgsql
  • check_pgsql
  • check_real_php
  • check_schema_version
  • check_string
  • check_suhosin_server_strip
  • check_xml
  • collection_privilege_format_function
  • confirm_delete_bind_in
  • confirm_delete_binding
  • confirm_delete_collection
  • confirm_delete_principal
  • confirm_delete_ticket
  • do_error
  • edit_binding_row
  • edit_grant_row_collection
  • edit_grant_row_principal
  • edit_group_row
  • edit_ticket_row
  • errorResponse
  • expand_properties
  • fetch_external
  • get_address_properties
  • get_freebusy
  • get_href_containers
  • get_phpinfo
  • grant_row_editor
  • group_members_browser
  • group_memberships_browser
  • group_row_editor
  • handle_subaction
  • i18n
  • ischedule_get
  • log_setup_error
  • make_help_link
  • principal_collection_browser
  • principal_editor
  • principal_grants_browser
  • principal_privilege_format_function
  • send_page_header
  • SqlFilterCardDAV
  • SqlFilterFragment
  • ticket_row_editor
  • unicodeToUtf8
  • update_external
  • utf8ToUnicode
  • Overview
  • Package
  • Function
  • Tree
  • Deprecated
  • Todo
  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:  * Build the array of properties to include in the report output
 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(); /** $include_properties is referenced in DAVResource where allprop is expanded */
 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:  * 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/*');
 50: if ( count($qry_filters) == 0 ) {
 51:   $qry_filters = false;
 52: }
 53: 
 54: $qry_limit = -1; // everything
 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:   // only anyof (OR) or allof (AND) allowed, if missing anyof is default (RFC6352 10.5)
 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: * While we can construct our SQL to apply some filters in the query, other filters
 72: * need to be checked against the retrieved record.  This is for handling those ones.
 73: *
 74: * @param array $filter An array of XMLElement which is the filter definition
 75: * @param string $item The database row retrieved for this calendar item
 76: * @param string $filter_type possible values AND or OR (for OR only one filter fragment must match)
 77: *
 78: * @return boolean True if the check succeeded, false otherwise.
 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:  * Process a filter fragment returning an SQL fragment
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; /** Figure out how to handle PARAM-FILTER conditions in the SQL */
230:       /*
231:       $parameter = $filter->GetAttribute("name");
232:       $subfilter = $filter->GetContent();
233:       $success = SqlFilterCardDAV( $subfilter, $components, $property, $parameter );
234:       if ( $success === false ) continue; else {
235:         $sql .= $success['sql'];
236:         $params = array_merge( $params, $success['params'] );
237:       }
238:       break;
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: * Something that we can handle, at least roughly correctly.
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: * @todo Once we are past DB version 1.2.1 we can change this query more radically.  The best performance to
266: * date seems to be:
267: *   SELECT caldav_data.*,address_item.* FROM collection JOIN address_item USING (collection_id,user_no)
268: *         JOIN caldav_data USING (dav_id) WHERE collection.dav_name = '/user1/home/'
269: *              AND caldav_data.caldav_type = 'VEVENT' ORDER BY caldav_data.user_no, caldav_data.dav_name;
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:   // we need a post_filter step, and it should be sufficient, that only one
308:   // filter is enough to display the item => we can't prefilter values via SQL
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: 
DAViCal API documentation generated by ApiGen 2.8.0