Overview

Packages

  • Jyxo_Beholder
  • Jyxo_Charset
  • Jyxo_Color
  • Jyxo_Css
  • Jyxo_ErrorHandling
  • Jyxo_FirePhp
  • Jyxo_Gettext
    • Parser
  • Jyxo_Html
  • Jyxo_Input
    • Chain
    • Filter
    • Validator
  • Jyxo_Mail
    • Email
    • Parser
    • Sender
  • Jyxo_Rpc
    • Json
    • Xml
  • Jyxo_Shell
  • Jyxo_SpamFilter
  • Jyxo_Spl
  • Jyxo_String
  • Jyxo_Svn
  • Jyxo_Time
  • Jyxo_Timer
  • Jyxo_Webdav
  • Jyxo_XmlReader
  • PHP

Classes

  • Jyxo_Webdav_Client

Exceptions

  • Jyxo_Webdav_Exception
  • Jyxo_Webdav_FileNotExistException
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * Jyxo PHP Library
  5:  *
  6:  * LICENSE
  7:  *
  8:  * This source file is subject to the new BSD license that is bundled
  9:  * with this package in the file license.txt.
 10:  * It is also available through the world-wide-web at this URL:
 11:  * https://github.com/jyxo/php/blob/master/license.txt
 12:  */
 13: 
 14: /**
 15:  * Client for work with WebDav. Uses the http PHP extension.
 16:  *
 17:  * @category Jyxo
 18:  * @package Jyxo_Webdav
 19:  * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
 20:  * @license https://github.com/jyxo/php/blob/master/license.txt
 21:  * @author Jaroslav HanslĂ­k
 22:  */
 23: class Jyxo_Webdav_Client
 24: {
 25:     /**
 26:      * Servers list.
 27:      *
 28:      * @var array
 29:      */
 30:     private $servers = array();
 31: 
 32:     /**
 33:      * Connection options.
 34:      *
 35:      * @var array
 36:      */
 37:     private $options = array(
 38:         'connecttimeout' => 1,
 39:         'timeout' => 30
 40:     );
 41: 
 42:     /**
 43:      * Request pool.
 44:      *
 45:      * @var HttpRequestPool
 46:      */
 47:     private $pool = null;
 48: 
 49:     /**
 50:      * Constructor.
 51:      *
 52:      * @param array $servers
 53:      */
 54:     public function __construct(array $servers)
 55:     {
 56:         $this->servers = $servers;
 57: 
 58:         $this->pool = new HttpRequestPool();
 59:     }
 60: 
 61:     /**
 62:      * Sets an option.
 63:      *
 64:      * @param string $name Option name
 65:      * @param mixed $value Option value
 66:      */
 67:     public function setOption($name, $value)
 68:     {
 69:         $this->options[(string) $name] = $value;
 70:     }
 71: 
 72:     /**
 73:      * Checks if a file exists.
 74:      *
 75:      * @param string $path File path
 76:      * @return boolean
 77:      * @throws Jyxo_Webdav_Exception On error
 78:      */
 79:     public function exists($path)
 80:     {
 81:         $response = $this->sendRequest($this->getFilePath($path), HttpRequest::METH_HEAD);
 82:         return (200 === $response->getResponseCode());
 83:     }
 84: 
 85:     /**
 86:      * Returns file contents.
 87:      *
 88:      * @param string $path File path
 89:      * @return string
 90:      * @throws Jyxo_Webdav_FileNotExistException If the file does not exist
 91:      * @throws Jyxo_Webdav_Exception On error
 92:      */
 93:     public function get($path)
 94:     {
 95:         // Asking random server
 96:         $path = $this->getFilePath($path);
 97:         $response = $this->sendRequest($path, HttpRequest::METH_GET);
 98: 
 99:         if (200 !== $response->getResponseCode()) {
100:             throw new Jyxo_Webdav_FileNotExistException(sprintf('File %s does not exist.', $path));
101:         }
102: 
103:         return $response->getBody();
104:     }
105: 
106:     /**
107:      * Returns a file property.
108:      * If no particular property is set, all properties are returned.
109:      *
110:      * @param string $path File path
111:      * @param string $property Property name
112:      * @return mixed
113:      * @throws Jyxo_Webdav_FileNotExistException If the file does not exist
114:      * @throws Jyxo_Webdav_Exception On error
115:      */
116:     public function getProperty($path, $property = '')
117:     {
118:         // Asking random server
119:         $path = $this->getFilePath($path);
120:         $response = $this->sendRequest($path, HttpRequest::METH_PROPFIND, array('Depth' => '0'));
121: 
122:         if (207 !== $response->getResponseCode()) {
123:             throw new Jyxo_Webdav_FileNotExistException(sprintf('File %s does not exist.', $path));
124:         }
125: 
126:         // Fetches file properties from the server
127:         $properties = $this->getProperties($response);
128: 
129:         // Returns the requested property value
130:         if (isset($properties[$property])) {
131:             return $properties[$property];
132:         }
133: 
134:         // Returns all properties
135:         return $properties;
136:     }
137: 
138:     /**
139:      * Saves data to a remote file.
140:      *
141:      * @param string $path File path
142:      * @param string $data Data
143:      * @return boolean
144:      * @throws Jyxo_Webdav_Exception On error
145:      */
146:     public function put($path, $data)
147:     {
148:         return $this->processPut($this->getFilePath($path), $data, false);
149:     }
150: 
151:     /**
152:      * Saves file contents to a remote file.
153:      *
154:      * @param string $path File path
155:      * @param string $file Local file path
156:      * @return boolean
157:      * @throws Jyxo_Webdav_Exception On error
158:      */
159:     public function putFile($path, $file)
160:     {
161:         return $this->processPut($this->getFilePath($path), $file, true);
162:     }
163: 
164:     /**
165:      * Copies a file.
166:      * Does not work on Lighttpd.
167:      *
168:      * @param string $pathFrom Source file path
169:      * @param string $pathTo Target file path
170:      * @return boolean
171:      * @throws Jyxo_Webdav_Exception On error
172:      */
173:     public function copy($pathFrom, $pathTo)
174:     {
175:         $requestList = $this->getRequestList($this->getFilePath($pathFrom), HttpRequest::METH_COPY);
176:         foreach ($requestList as $server => $request) {
177:             $request->addHeaders(array('Destination' => $server . $this->getFilePath($pathTo)));
178:         }
179: 
180:         foreach ($this->sendPool($requestList) as $request) {
181:             // 201 means copied
182:             if (201 !== $request->getResponseCode()) {
183:                 return false;
184:             }
185:         }
186: 
187:         return true;
188:     }
189: 
190:     /**
191:      * Renames a file.
192:      * Does not work on Lighttpd.
193:      *
194:      * @param string $pathFrom Original file name
195:      * @param string $pathTo New file name
196:      * @return boolean
197:      * @throws Jyxo_Webdav_Exception On error
198:      */
199:     public function rename($pathFrom, $pathTo)
200:     {
201:         $requestList = $this->getRequestList($this->getFilePath($pathFrom), HttpRequest::METH_MOVE);
202:         foreach ($requestList as $server => $request) {
203:             $request->addHeaders(array('Destination' => $server . $this->getFilePath($pathTo)));
204:         }
205: 
206:         foreach ($this->sendPool($requestList) as $request) {
207:             // 201 means renamed
208:             if (201 !== $request->getResponseCode()) {
209:                 return false;
210:             }
211:         }
212: 
213:         return true;
214:     }
215: 
216:     /**
217:      * Deletes a file.
218:      * Contains a check preventing from deleting directories.
219:      *
220:      * @param string $path Directory path
221:      * @return boolean
222:      * @throws Jyxo_Webdav_Exception On error
223:      */
224:     public function unlink($path)
225:     {
226:         // We do not delete directories
227:         if ($this->isDir($path)) {
228:             return false;
229:         }
230: 
231:         foreach ($this->send($this->getFilePath($path), HttpRequest::METH_DELETE) as $request) {
232:             // 204 means deleted
233:             if (204 !== $request->getResponseCode()) {
234:                 return false;
235:             }
236:         }
237: 
238:         return true;
239:     }
240: 
241:     /**
242:      * Checks if a directory exists.
243:      *
244:      * @param string $dir Directory path
245:      * @return boolean
246:      * @throws Jyxo_Webdav_Exception On error
247:      */
248:     public function isDir($dir)
249:     {
250:         // Asking random server
251:         $response = $this->sendRequest($this->getDirPath($dir), HttpRequest::METH_PROPFIND, array('Depth' => '0'));
252: 
253:         // The directory does not exist
254:         if (207 !== $response->getResponseCode()) {
255:             return false;
256:         }
257: 
258:         // Fetches properties from the server
259:         $properties = $this->getProperties($response);
260: 
261:         // Checks if it is a directory
262:         return isset($properties['getcontenttype']) && ('httpd/unix-directory' === $properties['getcontenttype']);
263:     }
264: 
265:     /**
266:      * Creates a directory.
267:      *
268:      * @param string $dir Directory path
269:      * @param boolean $recursive Create directories recursively?
270:      * @return boolean
271:      * @throws Jyxo_Webdav_Exception On error
272:      */
273:     public function mkdir($dir, $recursive = true)
274:     {
275:         // If creating directories recursively, create the parent directory first
276:         $dir = trim($dir, '/');
277:         if ($recursive) {
278:             $dirs = explode('/', $dir);
279:         } else {
280:             $dirs = array($dir);
281:         }
282: 
283:         $path = '';
284:         foreach ($dirs as $dir) {
285:             $path .= rtrim($dir);
286:             $path = $this->getDirPath($path);
287: 
288:             foreach ($this->send($path, HttpRequest::METH_MKCOL) as $request) {
289:                 switch ($request->getResponseCode()) {
290:                     // The directory was created
291:                     case 201:
292:                         break;
293:                     // The directory already exists
294:                     case 405:
295:                         break;
296:                     // The directory could not be created
297:                     default:
298:                         return false;
299:                 }
300:             }
301:         }
302: 
303:         // The directory was created
304:         return true;
305:     }
306: 
307:     /**
308:      * Deletes a directory.
309:      *
310:      * @param string $dir Directory path
311:      * @return boolean
312:      * @throws Jyxo_Webdav_Exception On error
313:      */
314:     public function rmdir($dir)
315:     {
316:         foreach ($this->send($this->getDirPath($dir), HttpRequest::METH_DELETE) as $request) {
317:             // 204 means deleted
318:             if (204 !== $request->getResponseCode()) {
319:                 return false;
320:             }
321:         }
322: 
323:         return true;
324:     }
325: 
326:     /**
327:      * Processes a PUT request.
328:      *
329:      * @param string $path File path
330:      * @param string $data Data
331:      * @param boolean $isFile Determines if $data is a file name or actual data
332:      * @return boolean
333:      * @throws Jyxo_Webdav_Exception On error
334:      */
335:     private function processPut($path, $data, $isFile)
336:     {
337:         $success = true;
338:         foreach ($this->sendPut($path, $data, $isFile) as $request) {
339:             switch ($request->getResponseCode()) {
340:                 // Saved
341:                 case 200:
342:                 case 201:
343:                     break;
344:                 // An existing file was modified
345:                 case 204:
346:                     break;
347:                 // The directory might not exist
348:                 case 403:
349:                 case 404:
350:                 case 409:
351:                     $success = false;
352:                     break;
353:                 // Could not save
354:                 default:
355:                     return false;
356:             }
357:         }
358: 
359:         // Saved
360:         if ($success) {
361:             return true;
362:         }
363: 
364:         // Not saved, try creating the directory first
365:         if (!$this->mkdir(dirname($path))) {
366:             return false;
367:         }
368: 
369:         // Try again
370:         foreach ($this->sendPut($path, $data, $isFile) as $request) {
371:             // 201 means saved
372:             if (201 !== $request->getResponseCode()) {
373:                 return false;
374:             }
375:         }
376: 
377:         return true;
378:     }
379: 
380:     /**
381:      * Sends a PUT request.
382:      *
383:      * @param string $path File path
384:      * @param string $data Data
385:      * @param boolean $isFile Determines if $data is a file name or actual data
386:      * @return HttpRequestPool
387:      * @throws Jyxo_Webdav_Exception On error
388:      */
389:     private function sendPut($path, $data, $isFile)
390:     {
391:         $requestList = $this->getRequestList($path, HttpRequest::METH_PUT);
392:         foreach ($requestList as $request) {
393:             if ($isFile) {
394:                 $request->setPutFile($data);
395:             } else {
396:                 $request->setPutData($data);
397:             }
398:         }
399: 
400:         return $this->sendPool($requestList);
401:     }
402: 
403:     /**
404:      * Creates a request pool and sends it.
405:      *
406:      * @param string $path Request path
407:      * @param integer $method Request method
408:      * @param array $headers Array of headers
409:      * @return HttpRequestPool
410:      */
411:     private function send($path, $method, array $headers = array())
412:     {
413:         return $this->sendPool($this->getRequestList($path, $method, $headers));
414:     }
415: 
416:     /**
417:      * Sends a request pool.
418:      *
419:      * @param ArrayObject $requestList Request list
420:      * @return HttpRequestPool
421:      * @throws Jyxo_Webdav_Exception On error
422:      */
423:     private function sendPool(ArrayObject $requestList)
424:     {
425:         try {
426:             // Clean the pool
427:             $this->pool->reset();
428: 
429:             // Attach requests
430:             foreach ($requestList as $request) {
431:                 $this->pool->attach($request);
432:             }
433: 
434:             // Send
435:             $this->pool->send();
436: 
437:             return $this->pool;
438:         } catch (HttpException $e) {
439:             // Find the innermost exception
440:             $inner = $e;
441:             while (null !== $inner->innerException) {
442:                 $inner = $inner->innerException;
443:             }
444:             throw new Jyxo_Webdav_Exception($inner->getMessage(), 0, $inner);
445:         }
446:     }
447: 
448:     /**
449:      * Sends a request.
450:      *
451:      * @param string $path Request path
452:      * @param integer $method Request method
453:      * @param array $headers Array of headers
454:      * @return HttpMessage
455:      * @throws Jyxo_Webdav_Exception On error
456:      */
457:     private function sendRequest($path, $method, array $headers = array())
458:     {
459:         try {
460:             // Send request to a random server
461:             $request = $this->getRequest($this->servers[array_rand($this->servers)] . $path, $method, $headers);
462:             return $request->send();
463:         } catch (HttpException $e) {
464:             throw new Jyxo_Webdav_Exception($e->getMessage(), 0, $e);
465:         }
466:     }
467: 
468:     /**
469:      * Returns a list of requests; one for each server.
470:      *
471:      * @param string $path Request path
472:      * @param integer $method Request method
473:      * @param array $headers Array of headers
474:      * @return ArrayObject
475:      */
476:     private function getRequestList($path, $method, array $headers = array())
477:     {
478:         $requestList = new ArrayObject();
479:         foreach ($this->servers as $server) {
480:             $requestList->offsetSet($server, $this->getRequest($server . $path, $method, $headers));
481:         }
482:         return $requestList;
483:     }
484: 
485:     /**
486:      * Creates a request.
487:      *
488:      * @param string $url Request URL
489:      * @param integer $method Request method
490:      * @param array $headers Array of headers
491:      * @return HttpRequest
492:      */
493:     private function getRequest($url, $method, array $headers = array())
494:     {
495:         $request = new HttpRequest($url, $method, $this->options);
496:         $request->setHeaders(array('Expect' => ''));
497:         $request->addHeaders($headers);
498:         return $request;
499:     }
500: 
501:     /**
502:      * Creates a file path without the trailing slash.
503:      *
504:      * @param string $path File path
505:      * @return string
506:      */
507:     private function getFilePath($path)
508:     {
509:         return '/' . trim($path, '/');
510:     }
511: 
512:     /**
513:      * Creates a directory path with the trailing slash.
514:      *
515:      * @param string $path Directory path
516:      * @return string
517:      */
518:     private function getDirPath($path)
519:     {
520:         return '/' . trim($path, '/') . '/';
521:     }
522: 
523:     /**
524:      * Fetches properties from the response.
525:      *
526:      * @param HttpMessage $response Response
527:      * @return array
528:      */
529:     private function getProperties(HttpMessage $response)
530:     {
531:         // Process the XML with properties
532:         $properties = array();
533:         $reader = new Jyxo_XmlReader();
534:         $reader->XML($response->getBody());
535: 
536:         // Ignore warnings
537:         while (@$reader->read()) {
538:             if ((XMLReader::ELEMENT === $reader->nodeType) && ('D:prop' === $reader->name)) {
539:                 while (@$reader->read()) {
540:                     // Element must not be empty and has to look something like <lp1:getcontentlength>13744</lp1:getcontentlength>
541:                     if ((XMLReader::ELEMENT === $reader->nodeType) && (!$reader->isEmptyElement)) {
542:                         if (preg_match('~^lp\d+:(.+)$~', $reader->name, $matches)) {
543:                             // Apache
544:                             $properties[$matches[1]] = $reader->getTextValue();
545:                         } elseif (preg_match('~^D:(.+)$~', $reader->name, $matches)) {
546:                             // Lighttpd
547:                             $properties[$matches[1]] = $reader->getTextValue();
548:                         }
549:                     } elseif ((XMLReader::END_ELEMENT === $reader->nodeType) && ('D:prop' === $reader->name)) {
550:                         break;
551:                     }
552:                 }
553:             }
554:         }
555: 
556:         return $properties;
557:     }
558: }
559: 
Jyxo PHP Library API documentation generated by ApiGen 2.3.0