1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\DAV; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse Sabre\HTTP; 6*a1a3b679SAndreas Boehler 7*a1a3b679SAndreas Boehler/** 8*a1a3b679SAndreas Boehler * SabreDAV DAV client 9*a1a3b679SAndreas Boehler * 10*a1a3b679SAndreas Boehler * This client wraps around Curl to provide a convenient API to a WebDAV 11*a1a3b679SAndreas Boehler * server. 12*a1a3b679SAndreas Boehler * 13*a1a3b679SAndreas Boehler * NOTE: This class is experimental, it's api will likely change in the future. 14*a1a3b679SAndreas Boehler * 15*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). 16*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 17*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 18*a1a3b679SAndreas Boehler */ 19*a1a3b679SAndreas Boehlerclass Client extends HTTP\Client { 20*a1a3b679SAndreas Boehler 21*a1a3b679SAndreas Boehler /** 22*a1a3b679SAndreas Boehler * The xml service. 23*a1a3b679SAndreas Boehler * 24*a1a3b679SAndreas Boehler * Uset this service to configure the property and namespace maps. 25*a1a3b679SAndreas Boehler * 26*a1a3b679SAndreas Boehler * @var mixed 27*a1a3b679SAndreas Boehler */ 28*a1a3b679SAndreas Boehler public $xml; 29*a1a3b679SAndreas Boehler 30*a1a3b679SAndreas Boehler /** 31*a1a3b679SAndreas Boehler * The elementMap 32*a1a3b679SAndreas Boehler * 33*a1a3b679SAndreas Boehler * This property is linked via reference to $this->xml->elementMap. 34*a1a3b679SAndreas Boehler * It's deprecated as of version 3.0.0, and should no longer be used. 35*a1a3b679SAndreas Boehler * 36*a1a3b679SAndreas Boehler * @deprecated 37*a1a3b679SAndreas Boehler * @var array 38*a1a3b679SAndreas Boehler */ 39*a1a3b679SAndreas Boehler public $propertyMap = []; 40*a1a3b679SAndreas Boehler 41*a1a3b679SAndreas Boehler /** 42*a1a3b679SAndreas Boehler * Base URI 43*a1a3b679SAndreas Boehler * 44*a1a3b679SAndreas Boehler * This URI will be used to resolve relative urls. 45*a1a3b679SAndreas Boehler * 46*a1a3b679SAndreas Boehler * @var string 47*a1a3b679SAndreas Boehler */ 48*a1a3b679SAndreas Boehler protected $baseUri; 49*a1a3b679SAndreas Boehler 50*a1a3b679SAndreas Boehler /** 51*a1a3b679SAndreas Boehler * Basic authentication 52*a1a3b679SAndreas Boehler */ 53*a1a3b679SAndreas Boehler const AUTH_BASIC = 1; 54*a1a3b679SAndreas Boehler 55*a1a3b679SAndreas Boehler /** 56*a1a3b679SAndreas Boehler * Digest authentication 57*a1a3b679SAndreas Boehler */ 58*a1a3b679SAndreas Boehler const AUTH_DIGEST = 2; 59*a1a3b679SAndreas Boehler 60*a1a3b679SAndreas Boehler /** 61*a1a3b679SAndreas Boehler * NTLM authentication 62*a1a3b679SAndreas Boehler */ 63*a1a3b679SAndreas Boehler const AUTH_NTLM = 4; 64*a1a3b679SAndreas Boehler 65*a1a3b679SAndreas Boehler /** 66*a1a3b679SAndreas Boehler * Identity encoding, which basically does not nothing. 67*a1a3b679SAndreas Boehler */ 68*a1a3b679SAndreas Boehler const ENCODING_IDENTITY = 1; 69*a1a3b679SAndreas Boehler 70*a1a3b679SAndreas Boehler /** 71*a1a3b679SAndreas Boehler * Deflate encoding 72*a1a3b679SAndreas Boehler */ 73*a1a3b679SAndreas Boehler const ENCODING_DEFLATE = 2; 74*a1a3b679SAndreas Boehler 75*a1a3b679SAndreas Boehler /** 76*a1a3b679SAndreas Boehler * Gzip encoding 77*a1a3b679SAndreas Boehler */ 78*a1a3b679SAndreas Boehler const ENCODING_GZIP = 4; 79*a1a3b679SAndreas Boehler 80*a1a3b679SAndreas Boehler /** 81*a1a3b679SAndreas Boehler * Sends all encoding headers. 82*a1a3b679SAndreas Boehler */ 83*a1a3b679SAndreas Boehler const ENCODING_ALL = 7; 84*a1a3b679SAndreas Boehler 85*a1a3b679SAndreas Boehler /** 86*a1a3b679SAndreas Boehler * Content-encoding 87*a1a3b679SAndreas Boehler * 88*a1a3b679SAndreas Boehler * @var int 89*a1a3b679SAndreas Boehler */ 90*a1a3b679SAndreas Boehler protected $encoding = self::ENCODING_IDENTITY; 91*a1a3b679SAndreas Boehler 92*a1a3b679SAndreas Boehler /** 93*a1a3b679SAndreas Boehler * Constructor 94*a1a3b679SAndreas Boehler * 95*a1a3b679SAndreas Boehler * Settings are provided through the 'settings' argument. The following 96*a1a3b679SAndreas Boehler * settings are supported: 97*a1a3b679SAndreas Boehler * 98*a1a3b679SAndreas Boehler * * baseUri 99*a1a3b679SAndreas Boehler * * userName (optional) 100*a1a3b679SAndreas Boehler * * password (optional) 101*a1a3b679SAndreas Boehler * * proxy (optional) 102*a1a3b679SAndreas Boehler * * authType (optional) 103*a1a3b679SAndreas Boehler * * encoding (optional) 104*a1a3b679SAndreas Boehler * 105*a1a3b679SAndreas Boehler * authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST 106*a1a3b679SAndreas Boehler * and self::AUTH_NTLM. If you know which authentication method will be 107*a1a3b679SAndreas Boehler * used, it's recommended to set it, as it will save a great deal of 108*a1a3b679SAndreas Boehler * requests to 'discover' this information. 109*a1a3b679SAndreas Boehler * 110*a1a3b679SAndreas Boehler * Encoding is a bitmap with one of the ENCODING constants. 111*a1a3b679SAndreas Boehler * 112*a1a3b679SAndreas Boehler * @param array $settings 113*a1a3b679SAndreas Boehler */ 114*a1a3b679SAndreas Boehler function __construct(array $settings) { 115*a1a3b679SAndreas Boehler 116*a1a3b679SAndreas Boehler if (!isset($settings['baseUri'])) { 117*a1a3b679SAndreas Boehler throw new \InvalidArgumentException('A baseUri must be provided'); 118*a1a3b679SAndreas Boehler } 119*a1a3b679SAndreas Boehler 120*a1a3b679SAndreas Boehler parent::__construct(); 121*a1a3b679SAndreas Boehler 122*a1a3b679SAndreas Boehler $this->baseUri = $settings['baseUri']; 123*a1a3b679SAndreas Boehler 124*a1a3b679SAndreas Boehler if (isset($settings['proxy'])) { 125*a1a3b679SAndreas Boehler $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']); 126*a1a3b679SAndreas Boehler } 127*a1a3b679SAndreas Boehler 128*a1a3b679SAndreas Boehler if (isset($settings['userName'])) { 129*a1a3b679SAndreas Boehler $userName = $settings['userName']; 130*a1a3b679SAndreas Boehler $password = isset($settings['password']) ? $settings['password'] : ''; 131*a1a3b679SAndreas Boehler 132*a1a3b679SAndreas Boehler if (isset($settings['authType'])) { 133*a1a3b679SAndreas Boehler $curlType = 0; 134*a1a3b679SAndreas Boehler if ($settings['authType'] & self::AUTH_BASIC) { 135*a1a3b679SAndreas Boehler $curlType |= CURLAUTH_BASIC; 136*a1a3b679SAndreas Boehler } 137*a1a3b679SAndreas Boehler if ($settings['authType'] & self::AUTH_DIGEST) { 138*a1a3b679SAndreas Boehler $curlType |= CURLAUTH_DIGEST; 139*a1a3b679SAndreas Boehler } 140*a1a3b679SAndreas Boehler if ($settings['authType'] & self::AUTH_NTLM) { 141*a1a3b679SAndreas Boehler $curlType |= CURLAUTH_NTLM; 142*a1a3b679SAndreas Boehler } 143*a1a3b679SAndreas Boehler } else { 144*a1a3b679SAndreas Boehler $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST; 145*a1a3b679SAndreas Boehler } 146*a1a3b679SAndreas Boehler 147*a1a3b679SAndreas Boehler $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType); 148*a1a3b679SAndreas Boehler $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password); 149*a1a3b679SAndreas Boehler 150*a1a3b679SAndreas Boehler } 151*a1a3b679SAndreas Boehler 152*a1a3b679SAndreas Boehler if (isset($settings['encoding'])) { 153*a1a3b679SAndreas Boehler $encoding = $settings['encoding']; 154*a1a3b679SAndreas Boehler 155*a1a3b679SAndreas Boehler $encodings = []; 156*a1a3b679SAndreas Boehler if ($encoding & self::ENCODING_IDENTITY) { 157*a1a3b679SAndreas Boehler $encodings[] = 'identity'; 158*a1a3b679SAndreas Boehler } 159*a1a3b679SAndreas Boehler if ($encoding & self::ENCODING_DEFLATE) { 160*a1a3b679SAndreas Boehler $encodings[] = 'deflate'; 161*a1a3b679SAndreas Boehler } 162*a1a3b679SAndreas Boehler if ($encoding & self::ENCODING_GZIP) { 163*a1a3b679SAndreas Boehler $encodings[] = 'gzip'; 164*a1a3b679SAndreas Boehler } 165*a1a3b679SAndreas Boehler $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings)); 166*a1a3b679SAndreas Boehler } 167*a1a3b679SAndreas Boehler 168*a1a3b679SAndreas Boehler $this->xml = new Xml\Service(); 169*a1a3b679SAndreas Boehler // BC 170*a1a3b679SAndreas Boehler $this->propertyMap = & $this->xml->elementMap; 171*a1a3b679SAndreas Boehler 172*a1a3b679SAndreas Boehler } 173*a1a3b679SAndreas Boehler 174*a1a3b679SAndreas Boehler /** 175*a1a3b679SAndreas Boehler * Does a PROPFIND request 176*a1a3b679SAndreas Boehler * 177*a1a3b679SAndreas Boehler * The list of requested properties must be specified as an array, in clark 178*a1a3b679SAndreas Boehler * notation. 179*a1a3b679SAndreas Boehler * 180*a1a3b679SAndreas Boehler * The returned array will contain a list of filenames as keys, and 181*a1a3b679SAndreas Boehler * properties as values. 182*a1a3b679SAndreas Boehler * 183*a1a3b679SAndreas Boehler * The properties array will contain the list of properties. Only properties 184*a1a3b679SAndreas Boehler * that are actually returned from the server (without error) will be 185*a1a3b679SAndreas Boehler * returned, anything else is discarded. 186*a1a3b679SAndreas Boehler * 187*a1a3b679SAndreas Boehler * Depth should be either 0 or 1. A depth of 1 will cause a request to be 188*a1a3b679SAndreas Boehler * made to the server to also return all child resources. 189*a1a3b679SAndreas Boehler * 190*a1a3b679SAndreas Boehler * @param string $url 191*a1a3b679SAndreas Boehler * @param array $properties 192*a1a3b679SAndreas Boehler * @param int $depth 193*a1a3b679SAndreas Boehler * @return array 194*a1a3b679SAndreas Boehler */ 195*a1a3b679SAndreas Boehler function propFind($url, array $properties, $depth = 0) { 196*a1a3b679SAndreas Boehler 197*a1a3b679SAndreas Boehler $dom = new \DOMDocument('1.0', 'UTF-8'); 198*a1a3b679SAndreas Boehler $dom->formatOutput = true; 199*a1a3b679SAndreas Boehler $root = $dom->createElementNS('DAV:', 'd:propfind'); 200*a1a3b679SAndreas Boehler $prop = $dom->createElement('d:prop'); 201*a1a3b679SAndreas Boehler 202*a1a3b679SAndreas Boehler foreach ($properties as $property) { 203*a1a3b679SAndreas Boehler 204*a1a3b679SAndreas Boehler list( 205*a1a3b679SAndreas Boehler $namespace, 206*a1a3b679SAndreas Boehler $elementName 207*a1a3b679SAndreas Boehler ) = \Sabre\Xml\Service::parseClarkNotation($property); 208*a1a3b679SAndreas Boehler 209*a1a3b679SAndreas Boehler if ($namespace === 'DAV:') { 210*a1a3b679SAndreas Boehler $element = $dom->createElement('d:' . $elementName); 211*a1a3b679SAndreas Boehler } else { 212*a1a3b679SAndreas Boehler $element = $dom->createElementNS($namespace, 'x:' . $elementName); 213*a1a3b679SAndreas Boehler } 214*a1a3b679SAndreas Boehler 215*a1a3b679SAndreas Boehler $prop->appendChild($element); 216*a1a3b679SAndreas Boehler } 217*a1a3b679SAndreas Boehler 218*a1a3b679SAndreas Boehler $dom->appendChild($root)->appendChild($prop); 219*a1a3b679SAndreas Boehler $body = $dom->saveXML(); 220*a1a3b679SAndreas Boehler 221*a1a3b679SAndreas Boehler $url = $this->getAbsoluteUrl($url); 222*a1a3b679SAndreas Boehler 223*a1a3b679SAndreas Boehler $request = new HTTP\Request('PROPFIND', $url, [ 224*a1a3b679SAndreas Boehler 'Depth' => $depth, 225*a1a3b679SAndreas Boehler 'Content-Type' => 'application/xml' 226*a1a3b679SAndreas Boehler ], $body); 227*a1a3b679SAndreas Boehler 228*a1a3b679SAndreas Boehler $response = $this->send($request); 229*a1a3b679SAndreas Boehler 230*a1a3b679SAndreas Boehler if ((int)$response->getStatus() >= 400) { 231*a1a3b679SAndreas Boehler throw new Exception('HTTP error: ' . $response->getStatus()); 232*a1a3b679SAndreas Boehler } 233*a1a3b679SAndreas Boehler 234*a1a3b679SAndreas Boehler $result = $this->parseMultiStatus($response->getBodyAsString()); 235*a1a3b679SAndreas Boehler 236*a1a3b679SAndreas Boehler // If depth was 0, we only return the top item 237*a1a3b679SAndreas Boehler if ($depth === 0) { 238*a1a3b679SAndreas Boehler reset($result); 239*a1a3b679SAndreas Boehler $result = current($result); 240*a1a3b679SAndreas Boehler return isset($result[200]) ? $result[200] : []; 241*a1a3b679SAndreas Boehler } 242*a1a3b679SAndreas Boehler 243*a1a3b679SAndreas Boehler $newResult = []; 244*a1a3b679SAndreas Boehler foreach ($result as $href => $statusList) { 245*a1a3b679SAndreas Boehler 246*a1a3b679SAndreas Boehler $newResult[$href] = isset($statusList[200]) ? $statusList[200] : []; 247*a1a3b679SAndreas Boehler 248*a1a3b679SAndreas Boehler } 249*a1a3b679SAndreas Boehler 250*a1a3b679SAndreas Boehler return $newResult; 251*a1a3b679SAndreas Boehler 252*a1a3b679SAndreas Boehler } 253*a1a3b679SAndreas Boehler 254*a1a3b679SAndreas Boehler /** 255*a1a3b679SAndreas Boehler * Updates a list of properties on the server 256*a1a3b679SAndreas Boehler * 257*a1a3b679SAndreas Boehler * The list of properties must have clark-notation properties for the keys, 258*a1a3b679SAndreas Boehler * and the actual (string) value for the value. If the value is null, an 259*a1a3b679SAndreas Boehler * attempt is made to delete the property. 260*a1a3b679SAndreas Boehler * 261*a1a3b679SAndreas Boehler * @param string $url 262*a1a3b679SAndreas Boehler * @param array $properties 263*a1a3b679SAndreas Boehler * @return void 264*a1a3b679SAndreas Boehler */ 265*a1a3b679SAndreas Boehler function propPatch($url, array $properties) { 266*a1a3b679SAndreas Boehler 267*a1a3b679SAndreas Boehler $propPatch = new Xml\Request\PropPatch(); 268*a1a3b679SAndreas Boehler $propPatch->properties = $properties; 269*a1a3b679SAndreas Boehler $xml = $this->xml->write( 270*a1a3b679SAndreas Boehler '{DAV:}propertyupdate', 271*a1a3b679SAndreas Boehler $propPatch 272*a1a3b679SAndreas Boehler ); 273*a1a3b679SAndreas Boehler 274*a1a3b679SAndreas Boehler $url = $this->getAbsoluteUrl($url); 275*a1a3b679SAndreas Boehler $request = new HTTP\Request('PROPPATCH', $url, [ 276*a1a3b679SAndreas Boehler 'Content-Type' => 'application/xml', 277*a1a3b679SAndreas Boehler ], $xml); 278*a1a3b679SAndreas Boehler $this->send($request); 279*a1a3b679SAndreas Boehler } 280*a1a3b679SAndreas Boehler 281*a1a3b679SAndreas Boehler /** 282*a1a3b679SAndreas Boehler * Performs an HTTP options request 283*a1a3b679SAndreas Boehler * 284*a1a3b679SAndreas Boehler * This method returns all the features from the 'DAV:' header as an array. 285*a1a3b679SAndreas Boehler * If there was no DAV header, or no contents this method will return an 286*a1a3b679SAndreas Boehler * empty array. 287*a1a3b679SAndreas Boehler * 288*a1a3b679SAndreas Boehler * @return array 289*a1a3b679SAndreas Boehler */ 290*a1a3b679SAndreas Boehler function options() { 291*a1a3b679SAndreas Boehler 292*a1a3b679SAndreas Boehler $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl('')); 293*a1a3b679SAndreas Boehler $response = $this->send($request); 294*a1a3b679SAndreas Boehler 295*a1a3b679SAndreas Boehler $dav = $response->getHeader('Dav'); 296*a1a3b679SAndreas Boehler if (!$dav) { 297*a1a3b679SAndreas Boehler return []; 298*a1a3b679SAndreas Boehler } 299*a1a3b679SAndreas Boehler 300*a1a3b679SAndreas Boehler $features = explode(',', $dav); 301*a1a3b679SAndreas Boehler foreach ($features as &$v) { 302*a1a3b679SAndreas Boehler $v = trim($v); 303*a1a3b679SAndreas Boehler } 304*a1a3b679SAndreas Boehler return $features; 305*a1a3b679SAndreas Boehler 306*a1a3b679SAndreas Boehler } 307*a1a3b679SAndreas Boehler 308*a1a3b679SAndreas Boehler /** 309*a1a3b679SAndreas Boehler * Performs an actual HTTP request, and returns the result. 310*a1a3b679SAndreas Boehler * 311*a1a3b679SAndreas Boehler * If the specified url is relative, it will be expanded based on the base 312*a1a3b679SAndreas Boehler * url. 313*a1a3b679SAndreas Boehler * 314*a1a3b679SAndreas Boehler * The returned array contains 3 keys: 315*a1a3b679SAndreas Boehler * * body - the response body 316*a1a3b679SAndreas Boehler * * httpCode - a HTTP code (200, 404, etc) 317*a1a3b679SAndreas Boehler * * headers - a list of response http headers. The header names have 318*a1a3b679SAndreas Boehler * been lowercased. 319*a1a3b679SAndreas Boehler * 320*a1a3b679SAndreas Boehler * For large uploads, it's highly recommended to specify body as a stream 321*a1a3b679SAndreas Boehler * resource. You can easily do this by simply passing the result of 322*a1a3b679SAndreas Boehler * fopen(..., 'r'). 323*a1a3b679SAndreas Boehler * 324*a1a3b679SAndreas Boehler * This method will throw an exception if an HTTP error was received. Any 325*a1a3b679SAndreas Boehler * HTTP status code above 399 is considered an error. 326*a1a3b679SAndreas Boehler * 327*a1a3b679SAndreas Boehler * Note that it is no longer recommended to use this method, use the send() 328*a1a3b679SAndreas Boehler * method instead. 329*a1a3b679SAndreas Boehler * 330*a1a3b679SAndreas Boehler * @param string $method 331*a1a3b679SAndreas Boehler * @param string $url 332*a1a3b679SAndreas Boehler * @param string|resource|null $body 333*a1a3b679SAndreas Boehler * @param array $headers 334*a1a3b679SAndreas Boehler * @throws ClientException, in case a curl error occurred. 335*a1a3b679SAndreas Boehler * @return array 336*a1a3b679SAndreas Boehler */ 337*a1a3b679SAndreas Boehler function request($method, $url = '', $body = null, array $headers = []) { 338*a1a3b679SAndreas Boehler 339*a1a3b679SAndreas Boehler $url = $this->getAbsoluteUrl($url); 340*a1a3b679SAndreas Boehler 341*a1a3b679SAndreas Boehler $response = $this->send(new HTTP\Request($method, $url, $headers, $body)); 342*a1a3b679SAndreas Boehler return [ 343*a1a3b679SAndreas Boehler 'body' => $response->getBodyAsString(), 344*a1a3b679SAndreas Boehler 'statusCode' => (int)$response->getStatus(), 345*a1a3b679SAndreas Boehler 'headers' => array_change_key_case($response->getHeaders()), 346*a1a3b679SAndreas Boehler ]; 347*a1a3b679SAndreas Boehler 348*a1a3b679SAndreas Boehler } 349*a1a3b679SAndreas Boehler 350*a1a3b679SAndreas Boehler /** 351*a1a3b679SAndreas Boehler * Returns the full url based on the given url (which may be relative). All 352*a1a3b679SAndreas Boehler * urls are expanded based on the base url as given by the server. 353*a1a3b679SAndreas Boehler * 354*a1a3b679SAndreas Boehler * @param string $url 355*a1a3b679SAndreas Boehler * @return string 356*a1a3b679SAndreas Boehler */ 357*a1a3b679SAndreas Boehler function getAbsoluteUrl($url) { 358*a1a3b679SAndreas Boehler 359*a1a3b679SAndreas Boehler // If the url starts with http:// or https://, the url is already absolute. 360*a1a3b679SAndreas Boehler if (preg_match('/^http(s?):\/\//', $url)) { 361*a1a3b679SAndreas Boehler return $url; 362*a1a3b679SAndreas Boehler } 363*a1a3b679SAndreas Boehler 364*a1a3b679SAndreas Boehler // If the url starts with a slash, we must calculate the url based off 365*a1a3b679SAndreas Boehler // the root of the base url. 366*a1a3b679SAndreas Boehler if (strpos($url, '/') === 0) { 367*a1a3b679SAndreas Boehler $parts = parse_url($this->baseUri); 368*a1a3b679SAndreas Boehler return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url; 369*a1a3b679SAndreas Boehler } 370*a1a3b679SAndreas Boehler 371*a1a3b679SAndreas Boehler // Otherwise... 372*a1a3b679SAndreas Boehler return $this->baseUri . $url; 373*a1a3b679SAndreas Boehler 374*a1a3b679SAndreas Boehler } 375*a1a3b679SAndreas Boehler 376*a1a3b679SAndreas Boehler /** 377*a1a3b679SAndreas Boehler * Parses a WebDAV multistatus response body 378*a1a3b679SAndreas Boehler * 379*a1a3b679SAndreas Boehler * This method returns an array with the following structure 380*a1a3b679SAndreas Boehler * 381*a1a3b679SAndreas Boehler * [ 382*a1a3b679SAndreas Boehler * 'url/to/resource' => [ 383*a1a3b679SAndreas Boehler * '200' => [ 384*a1a3b679SAndreas Boehler * '{DAV:}property1' => 'value1', 385*a1a3b679SAndreas Boehler * '{DAV:}property2' => 'value2', 386*a1a3b679SAndreas Boehler * ], 387*a1a3b679SAndreas Boehler * '404' => [ 388*a1a3b679SAndreas Boehler * '{DAV:}property1' => null, 389*a1a3b679SAndreas Boehler * '{DAV:}property2' => null, 390*a1a3b679SAndreas Boehler * ], 391*a1a3b679SAndreas Boehler * ], 392*a1a3b679SAndreas Boehler * 'url/to/resource2' => [ 393*a1a3b679SAndreas Boehler * .. etc .. 394*a1a3b679SAndreas Boehler * ] 395*a1a3b679SAndreas Boehler * ] 396*a1a3b679SAndreas Boehler * 397*a1a3b679SAndreas Boehler * 398*a1a3b679SAndreas Boehler * @param string $body xml body 399*a1a3b679SAndreas Boehler * @return array 400*a1a3b679SAndreas Boehler */ 401*a1a3b679SAndreas Boehler function parseMultiStatus($body) { 402*a1a3b679SAndreas Boehler 403*a1a3b679SAndreas Boehler $multistatus = $this->xml->expect('{DAV:}multistatus', $body); 404*a1a3b679SAndreas Boehler 405*a1a3b679SAndreas Boehler $result = []; 406*a1a3b679SAndreas Boehler 407*a1a3b679SAndreas Boehler foreach ($multistatus->getResponses() as $response) { 408*a1a3b679SAndreas Boehler 409*a1a3b679SAndreas Boehler $result[$response->getHref()] = $response->getResponseProperties(); 410*a1a3b679SAndreas Boehler 411*a1a3b679SAndreas Boehler } 412*a1a3b679SAndreas Boehler 413*a1a3b679SAndreas Boehler return $result; 414*a1a3b679SAndreas Boehler 415*a1a3b679SAndreas Boehler } 416*a1a3b679SAndreas Boehler 417*a1a3b679SAndreas Boehler} 418