1<?php
2
3namespace Sabre\DAV\Xml\Element;
4
5use Sabre\Xml\Element;
6use Sabre\Xml\Reader;
7use Sabre\Xml\Writer;
8
9/**
10 * WebDAV {DAV:}response parser
11 *
12 * This class parses the {DAV:}response element, as defined in:
13 *
14 * https://tools.ietf.org/html/rfc4918#section-14.24
15 *
16 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
17 * @author Evert Pot (http://www.rooftopsolutions.nl/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class Response implements Element {
21
22    /**
23     * Url for the response
24     *
25     * @var string
26     */
27    protected $href;
28
29    /**
30     * Propertylist, ordered by HTTP status code
31     *
32     * @var array
33     */
34    protected $responseProperties;
35
36    /**
37     * The HTTP status for an entire response.
38     *
39     * This is currently only used in WebDAV-Sync
40     *
41     * @var string
42     */
43    protected $httpStatus;
44
45    /**
46     * The href argument is a url relative to the root of the server. This
47     * class will calculate the full path.
48     *
49     * The responseProperties argument is a list of properties
50     * within an array with keys representing HTTP status codes
51     *
52     * Besides specific properties, the entire {DAV:}response element may also
53     * have a http status code.
54     * In most cases you don't need it.
55     *
56     * This is currently used by the Sync extension to indicate that a node is
57     * deleted.
58     *
59     * @param string $href
60     * @param array $responseProperties
61     * @param string $httpStatus
62     */
63    function __construct($href, array $responseProperties, $httpStatus = null) {
64
65        $this->href = $href;
66        $this->responseProperties = $responseProperties;
67        $this->httpStatus = $httpStatus;
68
69    }
70
71    /**
72     * Returns the url
73     *
74     * @return string
75     */
76    function getHref() {
77
78        return $this->href;
79
80    }
81
82    /**
83     * Returns the httpStatus value
84     *
85     * @return string
86     */
87    function getHttpStatus() {
88
89        return $this->httpStatus;
90
91    }
92
93    /**
94     * Returns the property list
95     *
96     * @return array
97     */
98    function getResponseProperties() {
99
100        return $this->responseProperties;
101
102    }
103
104
105    /**
106     * The serialize method is called during xml writing.
107     *
108     * It should use the $writer argument to encode this object into XML.
109     *
110     * Important note: it is not needed to create the parent element. The
111     * parent element is already created, and we only have to worry about
112     * attributes, child elements and text (if any).
113     *
114     * Important note 2: If you are writing any new elements, you are also
115     * responsible for closing them.
116     *
117     * @param Writer $writer
118     * @return void
119     */
120    function xmlSerialize(Writer $writer) {
121
122        if ($status = $this->getHTTPStatus()) {
123            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
124        }
125        $writer->writeElement('{DAV:}href', $writer->contextUri . $this->getHref());
126        foreach ($this->getResponseProperties() as $status => $properties) {
127
128            // Skipping empty lists
129            if (!$properties || (!ctype_digit($status) && !is_int($status))) {
130                continue;
131            }
132            $writer->startElement('{DAV:}propstat');
133            $writer->writeElement('{DAV:}prop', $properties);
134            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
135            $writer->endElement(); // {DAV:}propstat
136
137        }
138
139    }
140
141    /**
142     * The deserialize method is called during xml parsing.
143     *
144     * This method is called statictly, this is because in theory this method
145     * may be used as a type of constructor, or factory method.
146     *
147     * Often you want to return an instance of the current class, but you are
148     * free to return other data as well.
149     *
150     * You are responsible for advancing the reader to the next element. Not
151     * doing anything will result in a never-ending loop.
152     *
153     * If you just want to skip parsing for this element altogether, you can
154     * just call $reader->next();
155     *
156     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
157     * the next element.
158     *
159     * @param Reader $reader
160     * @return mixed
161     */
162    static function xmlDeserialize(Reader $reader) {
163
164        $reader->pushContext();
165
166        $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';
167
168        // We are overriding the parser for {DAV:}prop. This deserializer is
169        // almost identical to the one for Sabre\Xml\Element\KeyValue.
170        //
171        // The difference is that if there are any child-elements inside of
172        // {DAV:}prop, that have no value, normally any deserializers are
173        // called. But we don't want this, because a singlular element without
174        // child-elements implies 'no value' in {DAV:}prop, so we want to skip
175        // deserializers and just set null for those.
176        $reader->elementMap['{DAV:}prop'] = function(Reader $reader) {
177
178            if ($reader->isEmptyElement) {
179                $reader->next();
180                return [];
181            }
182            $values = [];
183            $reader->read();
184            do {
185                if ($reader->nodeType === Reader::ELEMENT) {
186                    $clark = $reader->getClark();
187
188                    if ($reader->isEmptyElement) {
189                        $values[$clark] = null;
190                        $reader->next();
191                    } else {
192                        $values[$clark] = $reader->parseCurrentElement()['value'];
193                    }
194                } else {
195                    $reader->read();
196                }
197            } while ($reader->nodeType !== Reader::END_ELEMENT);
198            $reader->read();
199            return $values;
200
201        };
202        $elems = $reader->parseInnerTree();
203        $reader->popContext();
204
205        $href = null;
206        $propertyLists = [];
207        $statusCode = null;
208
209        foreach ($elems as $elem) {
210
211            switch ($elem['name']) {
212
213                case '{DAV:}href' :
214                    $href = $elem['value'];
215                    break;
216                case '{DAV:}propstat' :
217                    $status = $elem['value']['{DAV:}status'];
218                    list(, $status, ) = explode(' ', $status, 3);
219                    $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
220                    $propertyLists[$status] = $properties;
221                    break;
222                case '{DAV:}status' :
223                    list(, $statusCode, ) = explode(' ', $elem['value'], 3);
224                    break;
225
226            }
227
228        }
229
230        return new self($href, $propertyLists, $statusCode);
231
232    }
233
234}
235