1<?php
2
3namespace Sabre\CardDAV\Xml\Request;
4
5use Sabre\CardDAV\Plugin;
6use Sabre\DAV\Exception\BadRequest;
7use Sabre\Xml\Reader;
8use Sabre\Xml\XmlDeserializable;
9
10/**
11 * AddressBookQueryReport request parser.
12 *
13 * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-query
14 * REPORT, as defined in:
15 *
16 * http://tools.ietf.org/html/rfc6352#section-8.6
17 *
18 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
19 * @author Evert Pot (http://www.rooftopsolutions.nl/)
20 * @license http://sabre.io/license/ Modified BSD License
21 */
22class AddressBookQueryReport implements XmlDeserializable {
23
24    /**
25     * An array with requested properties.
26     *
27     * @var array
28     */
29    public $properties;
30
31    /**
32     * An array with requested vcard properties.
33     *
34     * @var array
35     */
36    public $addressDataProperties = [];
37
38    /**
39     * List of property/component filters.
40     *
41     * This is an array with filters. Every item is a property filter. Every
42     * property filter has the following keys:
43     *   * name - name of the component to filter on
44     *   * test - anyof or allof
45     *   * is-not-defined - Test for non-existence
46     *   * param-filters - A list of parameter filters on the property
47     *   * text-matches - A list of text values the filter needs to match
48     *
49     * Each param-filter has the following keys:
50     *   * name - name of the parameter
51     *   * is-not-defined - Test for non-existence
52     *   * text-match - Match the parameter value
53     *
54     * Each text-match in property filters, and the single text-match in
55     * param-filters have the following keys:
56     *
57     *   * value - value to match
58     *   * match-type - contains, starts-with, ends-with, equals
59     *   * negate-condition - Do the opposite match
60     *   * collation - Usually i;unicode-casemap
61     *
62     * @var array
63     */
64    public $filters;
65
66    /**
67     * The number of results the client wants
68     *
69     * null means it wasn't specified, which in most cases means 'all results'.
70     *
71     * @var int|null
72     */
73    public $limit;
74
75    /**
76     * Either 'anyof' or 'allof'
77     *
78     * @var string
79     */
80    public $test;
81
82    /**
83     * The mimetype of the content that should be returend. Usually
84     * text/vcard.
85     *
86     * @var string
87     */
88    public $contentType = null;
89
90    /**
91     * The version of vcard data that should be returned. Usually 3.0,
92     * referring to vCard 3.0.
93     *
94     * @var string
95     */
96    public $version = null;
97
98
99    /**
100     * The deserialize method is called during xml parsing.
101     *
102     * This method is called statically, this is because in theory this method
103     * may be used as a type of constructor, or factory method.
104     *
105     * Often you want to return an instance of the current class, but you are
106     * free to return other data as well.
107     *
108     * You are responsible for advancing the reader to the next element. Not
109     * doing anything will result in a never-ending loop.
110     *
111     * If you just want to skip parsing for this element altogether, you can
112     * just call $reader->next();
113     *
114     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
115     * the next element.
116     *
117     * @param Reader $reader
118     * @return mixed
119     */
120    static function xmlDeserialize(Reader $reader) {
121
122        $elems = (array)$reader->parseInnerTree([
123            '{urn:ietf:params:xml:ns:carddav}prop-filter'  => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
124            '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
125            '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
126            '{DAV:}prop'                                   => 'Sabre\\Xml\\Element\\KeyValue',
127        ]);
128
129        $newProps = [
130            'filters'    => null,
131            'properties' => [],
132            'test'       => 'anyof',
133            'limit'      => null,
134        ];
135
136        if (!is_array($elems)) $elems = [];
137
138        foreach ($elems as $elem) {
139
140            switch ($elem['name']) {
141
142                case '{DAV:}prop' :
143                    $newProps['properties'] = array_keys($elem['value']);
144                    if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
145                        $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
146                    }
147                    break;
148                case '{' . Plugin::NS_CARDDAV . '}filter' :
149
150                    if (!is_null($newProps['filters'])) {
151                        throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element');
152                    }
153                    if (isset($elem['attributes']['test'])) {
154                        $newProps['test'] = $elem['attributes']['test'];
155                        if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') {
156                            throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
157                        }
158                    }
159
160                    $newProps['filters'] = [];
161                    foreach ((array)$elem['value'] as $subElem) {
162                        if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') {
163                            $newProps['filters'][] = $subElem['value'];
164                        }
165                    }
166                    break;
167                case '{' . Plugin::NS_CARDDAV . '}limit' :
168                    foreach ($elem['value'] as $child) {
169                        if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') {
170                            $newProps['limit'] = (int)$child['value'];
171                        }
172                    }
173                    break;
174
175            }
176
177        }
178
179        if (is_null($newProps['filters'])) {
180            /*
181             * We are supposed to throw this error, but KDE sometimes does not
182             * include the filter element, and we need to treat it as if no
183             * filters are supplied
184             */
185            //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
186            $newProps['filters'] = [];
187
188        }
189
190        $obj = new self();
191        foreach ($newProps as $key => $value) {
192            $obj->$key = $value;
193        }
194
195        return $obj;
196
197    }
198
199}
200