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) 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 . \Sabre\HTTP\encodePath($this->getHref())); 126 127 $empty = true; 128 129 foreach ($this->getResponseProperties() as $status => $properties) { 130 131 // Skipping empty lists 132 if (!$properties || (!ctype_digit($status) && !is_int($status))) { 133 continue; 134 } 135 $empty = false; 136 $writer->startElement('{DAV:}propstat'); 137 $writer->writeElement('{DAV:}prop', $properties); 138 $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); 139 $writer->endElement(); // {DAV:}propstat 140 141 } 142 if ($empty) { 143 /* 144 * The WebDAV spec _requires_ at least one DAV:propstat to appear for 145 * every DAV:response. In some circumstances however, there are no 146 * properties to encode. 147 * 148 * In those cases we MUST specify at least one DAV:propstat anyway, with 149 * no properties. 150 */ 151 $writer->writeElement('{DAV:}propstat', [ 152 '{DAV:}prop' => [], 153 '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418] 154 ]); 155 156 } 157 158 } 159 160 /** 161 * The deserialize method is called during xml parsing. 162 * 163 * This method is called statically, this is because in theory this method 164 * may be used as a type of constructor, or factory method. 165 * 166 * Often you want to return an instance of the current class, but you are 167 * free to return other data as well. 168 * 169 * You are responsible for advancing the reader to the next element. Not 170 * doing anything will result in a never-ending loop. 171 * 172 * If you just want to skip parsing for this element altogether, you can 173 * just call $reader->next(); 174 * 175 * $reader->parseInnerTree() will parse the entire sub-tree, and advance to 176 * the next element. 177 * 178 * @param Reader $reader 179 * @return mixed 180 */ 181 static function xmlDeserialize(Reader $reader) { 182 183 $reader->pushContext(); 184 185 $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue'; 186 187 // We are overriding the parser for {DAV:}prop. This deserializer is 188 // almost identical to the one for Sabre\Xml\Element\KeyValue. 189 // 190 // The difference is that if there are any child-elements inside of 191 // {DAV:}prop, that have no value, normally any deserializers are 192 // called. But we don't want this, because a singular element without 193 // child-elements implies 'no value' in {DAV:}prop, so we want to skip 194 // deserializers and just set null for those. 195 $reader->elementMap['{DAV:}prop'] = function(Reader $reader) { 196 197 if ($reader->isEmptyElement) { 198 $reader->next(); 199 return []; 200 } 201 $values = []; 202 $reader->read(); 203 do { 204 if ($reader->nodeType === Reader::ELEMENT) { 205 $clark = $reader->getClark(); 206 207 if ($reader->isEmptyElement) { 208 $values[$clark] = null; 209 $reader->next(); 210 } else { 211 $values[$clark] = $reader->parseCurrentElement()['value']; 212 } 213 } else { 214 $reader->read(); 215 } 216 } while ($reader->nodeType !== Reader::END_ELEMENT); 217 $reader->read(); 218 return $values; 219 220 }; 221 $elems = $reader->parseInnerTree(); 222 $reader->popContext(); 223 224 $href = null; 225 $propertyLists = []; 226 $statusCode = null; 227 228 foreach ($elems as $elem) { 229 230 switch ($elem['name']) { 231 232 case '{DAV:}href' : 233 $href = $elem['value']; 234 break; 235 case '{DAV:}propstat' : 236 $status = $elem['value']['{DAV:}status']; 237 list(, $status, ) = explode(' ', $status, 3); 238 $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : []; 239 if ($properties) $propertyLists[$status] = $properties; 240 break; 241 case '{DAV:}status' : 242 list(, $statusCode, ) = explode(' ', $elem['value'], 3); 243 break; 244 245 } 246 247 } 248 249 return new self($href, $propertyLists, $statusCode); 250 251 } 252 253} 254