depth > 0 ) { $request->DoResponse( 400, 'The principal-property-search REPORT is only defined for Depth "0".' ); } /** * Build the array of properties to include in the report output */ $searches = $xmltree->GetPath('/DAV::principal-property-search/DAV::property-search'); dbg_log_array( "principal", "SEARCH", $searches, true ); $clause_joiner = " AND "; $CS_search_test = $xmltree->GetAttribute('test'); if ( isset($CS_search_test) && $CS_search_test == 'anyof' ) { $clause_joiner = " OR "; } # Calendar on macOS searches for the principal type as an attribute. $principal_type = $xmltree->GetAttribute('type'); $params = array(); $typewhere = ""; $where = ""; $join = ""; dbg_error_log("SEARCH", "type: '%s'", $principal_type ); // This is ugly, but macOS specifies the calendar type as an attribute if (isset($principal_type)) { $typewhere = 'principal_type.label = :principal_type'; $params[':principal_type'] = $principal_type; $join = 'LEFT JOIN principal_type ON (type_id = principal_type_id)'; } foreach( $searches AS $k => $search ) { $qry_props = $search->GetPath('/DAV::property-search/DAV::prop/*'); // There may be many $match = $search->GetPath('/DAV::property-search/DAV::match'); // There may only be one if ( empty($match) or !is_object($match[0]) ) { dbg_error_log("ERROR", "Unable to extract a match clause from '%s'", $search->RenderContent() ); continue; } dbg_log_array( "principal", "MATCH", $match, true ); $match = $match[0]->GetContent(); $subwhere = ""; foreach( $qry_props AS $k1 => $v1 ) { if ( $subwhere != "" ) $subwhere .= " OR "; switch( $v1->GetNSTag() ) { case 'DAV::displayname': $subwhere .= ' displayname ILIKE :displayname_match '; $params[':displayname_match'] = '%'.$match.'%'; break; case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set': $match = preg_replace('{^.*/caldav.php/([^/]+)(/.*)?$}', '\\1', $match); $match = preg_replace('{^mailto:}', '', $match); $subwhere .= ' (email ILIKE :user_address_match OR username ILIKE :user_address_match) '; $params[':user_address_match'] = '%'.$match.'%'; break; case 'urn:ietf:params:xml:ns:caldav:calendar-home-set': $match = preg_replace('{^.*/caldav.php}', '', $match); $subwhere .= ' dav_name LIKE :calendar_home_match '; $params[':calendar_home_match'] = $match.'%'; break; default: /** * @todo We should handle a lot more properties here. principal-URL seems a likely one to be used. * @todo We should catch the unsupported properties in the query and fire back an error indicating so. */ dbg_error_log("principal", "Unhandled tag '%s' to match '%s'\n", $v1->GetNSTag(), $match ); } } if ( $subwhere != "" ) { $where .= sprintf( "%s(%s)", ($where == "" ? "" : $clause_joiner), $subwhere ); } } if ( $where != "" && $typewhere != "" ) { $where = "WHERE $typewhere AND $where"; } elseif ( $where != "" ) { $where = "WHERE $where"; } elseif ( $typewhere != "" ) { $where = "WHERE $typewhere"; } $sql = "SELECT * FROM dav_principal $join $where ORDER BY principal_id LIMIT 100"; $qry = new AwlQuery($sql, $params); $get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*'); $properties = array(); foreach( $get_props AS $k1 => $v1 ) { $properties[] = $v1->GetNSTag(); } $responses = array(); if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows() > 0 ) { while( $row = $qry->Fetch() ) { $principal = new DAVResource($row); $responses[] = $principal->RenderAsXML( $properties, $reply ); } } $multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() ); $request->XMLResponse( 207, $multistatus );