4 * Parses the addressbook-query report request body.
6 * Whoever designed this format, and the CalDAV equivalent even more so,
7 * has no feel for design.
11 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
12 * @author Evert Pot (http://www.rooftopsolutions.nl/)
13 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
15 class Sabre_CardDAV_AddressBookQueryParser {
17 const TEST_ANYOF = 'anyof';
18 const TEST_ALLOF = 'allof';
21 * List of requested properties the client wanted
25 public $requestedProperties;
28 * The number of results the client wants
30 * null means it wasn't specified, which in most cases means 'all results'.
37 * List of property filters.
44 * Either TEST_ANYOF or TEST_ALLOF
67 * @param DOMDocument $dom
69 public function __construct(DOMDocument $dom) {
73 $this->xpath = new DOMXPath($dom);
74 $this->xpath->registerNameSpace('card',Sabre_CardDAV_Plugin::NS_CARDDAV);
83 public function parse() {
87 $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)');
88 if (is_nan($limit)) $limit = null;
90 $filter = $this->xpath->query('/card:addressbook-query/card:filter');
92 // According to the CardDAV spec there needs to be exactly 1 filter
93 // element. However, KDE 4.8.2 contains a bug that will encode 0 filter
94 // elements, so this is a workaround for that.
96 // See: https://bugs.kde.org/show_bug.cgi?id=300047
97 if ($filter->length === 0) {
100 } elseif ($filter->length === 1) {
101 $filter = $filter->item(0);
102 $test = $this->xpath->evaluate('string(@test)', $filter);
104 throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
107 if (!$test) $test = self::TEST_ANYOF;
108 if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
109 throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"');
112 $propFilters = array();
114 $propFilterNodes = $this->xpath->query('card:prop-filter', $filter);
115 for($ii=0; $ii < $propFilterNodes->length; $ii++) {
117 $propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii));
122 $this->filters = $propFilters;
123 $this->limit = $limit;
124 $this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
130 * Parses the prop-filter xml element
132 * @param DOMElement $propFilterNode
135 protected function parsePropFilterNode(DOMElement $propFilterNode) {
137 $propFilter = array();
138 $propFilter['name'] = $propFilterNode->getAttribute('name');
139 $propFilter['test'] = $propFilterNode->getAttribute('test');
140 if (!$propFilter['test']) $propFilter['test'] = 'anyof';
142 $propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0;
144 $paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode);
146 $propFilter['param-filters'] = array();
149 for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
151 $propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii));
154 $propFilter['text-matches'] = array();
155 $textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode);
157 for($ii=0;$ii<$textMatchNodes->length;$ii++) {
159 $propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii));
168 * Parses the param-filter element
170 * @param DOMElement $paramFilterNode
173 public function parseParamFilterNode(DOMElement $paramFilterNode) {
175 $paramFilter = array();
176 $paramFilter['name'] = $paramFilterNode->getAttribute('name');
177 $paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0;
178 $paramFilter['text-match'] = null;
180 $textMatch = $this->xpath->query('card:text-match', $paramFilterNode);
181 if ($textMatch->length>0) {
182 $paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0));
192 * @param DOMElement $textMatchNode
195 public function parseTextMatchNode(DOMElement $textMatchNode) {
197 $matchType = $textMatchNode->getAttribute('match-type');
198 if (!$matchType) $matchType = 'contains';
200 if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) {
201 throw new Sabre_DAV_Exception_BadRequest('Unknown match-type: ' . $matchType);
204 $negateCondition = $textMatchNode->getAttribute('negate-condition');
205 $negateCondition = $negateCondition==='yes';
206 $collation = $textMatchNode->getAttribute('collation');
207 if (!$collation) $collation = 'i;unicode-casemap';
210 'negate-condition' => $negateCondition,
211 'collation' => $collation,
212 'match-type' => $matchType,
213 'value' => $textMatchNode->nodeValue