|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZWebDAVContentBackend class. 00004 * 00005 * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved. 00006 * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 00007 * @version //autogentag// 00008 * @package kernel 00009 */ 00010 00011 /*! 00012 \class eZWebDAVContentBackend ezwebdavcontentbackend.php 00013 \ingroup eZWebDAV 00014 \brief Provides access to eZ Publish kernel using WebDAV. 00015 Based on the eZ Components Webdav component. 00016 00017 @todo Replace direct path manipulation with path factory from ezcWebdav 00018 @todo Fix appendLogEntry to write in only one log file 00019 @todo Fix using [0] for content object attributes (could be another index in some classes) 00020 @todo Add lock/unlock calls in setProperty and removeProperty 00021 @todo Use PathPrefix, PathPrefixExclude (site.ini) and StartNode (webdav.ini) in all functions where necessary 00022 @todo Remove all todos. 00023 00024 */ 00025 00026 /** 00027 * WebDAV backend for eZ Publish, based on eZ Components Webdav component. 00028 */ 00029 class eZWebDAVContentBackend extends ezcWebdavSimpleBackend implements ezcWebdavLockBackend 00030 { 00031 /** 00032 * The name of the content folder in eZ Publish. 00033 */ 00034 const VIRTUAL_CONTENT_FOLDER_NAME = 'Content'; 00035 00036 /** 00037 * The name of the media folder in eZ Publish. 00038 */ 00039 const VIRTUAL_MEDIA_FOLDER_NAME = 'Media'; 00040 00041 /** 00042 * The ini file which holds settings for WebDAV. 00043 */ 00044 const WEBDAV_INI_FILE = "webdav.ini"; 00045 00046 /** 00047 * Mimetype for directories. 00048 */ 00049 const DIRECTORY_MIMETYPE = 'httpd/unix-directory'; 00050 00051 /** 00052 * Mimetype for eZ Publish objects which don't have a mimetype. 00053 */ 00054 const DEFAULT_MIMETYPE = "application/octet-stream"; 00055 00056 /** 00057 * Default size in bytes for eZ Publish objects which don't have a size. 00058 */ 00059 const DEFAULT_SIZE = 0; 00060 00061 /** 00062 * Names of live properties from the DAV: namespace which will be handled 00063 * live, and should not be stored like dead properties. 00064 * 00065 * @var array(string) 00066 */ 00067 protected $handledLiveProperties = array( 00068 'getcontentlength', 00069 'getlastmodified', 00070 'creationdate', 00071 'displayname', 00072 'getetag', 00073 'getcontenttype', 00074 'resourcetype', 00075 'supportedlock', 00076 'lockdiscovery', 00077 ); 00078 00079 /** 00080 * Contains an array with classes that are considered folder. 00081 * 00082 * @var array(string) 00083 */ 00084 protected $FolderClasses = null; 00085 00086 /** 00087 * The list of available sites. 00088 * 00089 * @var array(string) 00090 */ 00091 protected $availableSites = array(); 00092 00093 /** 00094 * Holds the retrieved nodes to allow for faster retrieval on subsequent requests. 00095 * 00096 * @var array(string=>array()) 00097 */ 00098 protected $cachedNodes = array(); 00099 00100 /** 00101 * Holds the retrieved properties to allow for faster retrieval on subsequent requests. 00102 * 00103 * @var array(string=>array()) 00104 */ 00105 protected $cachedProperties = array(); 00106 00107 /** 00108 * Specifies weather to log with appendLogEntry(). 00109 * 00110 * Value defined in webdav.ini, read in appendLogEntry(). 00111 * 00112 * @var bool 00113 */ 00114 protected static $useLogging; 00115 00116 /** 00117 * Creates a new backend instance. 00118 */ 00119 public function __construct() 00120 { 00121 // @as @todo check how to make Article class to be handled as a resource (document) instead of a collection 00122 $webdavINI = eZINI::instance( self::WEBDAV_INI_FILE ); 00123 00124 $folderClasses = array(); 00125 if ( $webdavINI->hasGroup( 'GeneralSettings' ) and 00126 $webdavINI->hasVariable( 'GeneralSettings', 'FolderClasses' ) ) 00127 { 00128 $folderClasses = $webdavINI->variable( 'GeneralSettings', 'FolderClasses' ); 00129 } 00130 $this->FolderClasses = $folderClasses; 00131 00132 $ini = eZINI::instance(); 00133 $this->availableSites = $ini->variable( 'SiteSettings', 'SiteList' ); 00134 } 00135 00136 /** 00137 * Locks the backend. 00138 * 00139 * Tries to lock the backend. If the lock is already owned by this process, 00140 * locking is successful. If $timeout is reached before a lock could be 00141 * acquired, an {@link ezcWebdavLockTimeoutException} is thrown. Waits 00142 * $waitTime microseconds between attempts to lock the backend. 00143 * 00144 * @param int $waitTime 00145 * @param int $timeout 00146 * @return void 00147 */ 00148 public function lock( $waitTime, $timeout ) 00149 { 00150 // @as @todo implement locking with eZ Publish functionality (object states) 00151 } 00152 00153 /** 00154 * Removes the lock. 00155 * 00156 * @return void 00157 */ 00158 public function unlock() 00159 { 00160 // @as @todo implement locking with eZ Publish functionality (object states) 00161 } 00162 00163 /** 00164 * Wait and get lock for complete directory tree. 00165 * 00166 * Acquire lock for the complete tree for read or write operations. This 00167 * does not implement any priorities for operations, or check if several 00168 * read operation may run in parallel. The plain locking should / could be 00169 * extended by something more sophisticated. 00170 * 00171 * If the tree already has been locked, the method waits until the lock can 00172 * be acquired. 00173 * 00174 * The optional second parameter $readOnly indicates wheather a read only 00175 * lock should be acquired. This may be used by extended implementations, 00176 * but it is not used in this implementation. 00177 * 00178 * @param bool $readOnly 00179 */ 00180 protected function acquireLock( $readOnly = false ) 00181 { 00182 // @as @todo implement locking with eZ Publish functionality (object states) 00183 } 00184 00185 /** 00186 * Free lock. 00187 * 00188 * Frees the lock after the operation has been finished. 00189 */ 00190 protected function freeLock() 00191 { 00192 // @as @todo implement locking with eZ Publish functionality (object states) 00193 } 00194 00195 /** 00196 * Returns all child nodes. 00197 * 00198 * Get all nodes from the resource identified by $source up to the given 00199 * depth. Reuses the method {@link getCollectionMembers()}, but you may 00200 * want to overwrite this implementation by somethings which fits better 00201 * with your backend. 00202 * 00203 * @param string $source 00204 * @param int $depth 00205 * @return array(ezcWebdavResource|ezcWebdavCollection) 00206 */ 00207 protected function getNodes( $requestUri, $depth ) 00208 { 00209 $source = $requestUri; 00210 $nodeInfo = $this->getNodeInfo( $requestUri ); 00211 00212 if ( !$nodeInfo['nodeExists'] ) 00213 { 00214 return array(); 00215 } 00216 00217 // No special handling for plain resources 00218 if ( !$nodeInfo['isCollection'] ) 00219 { 00220 return array( new ezcWebdavResource( $source, $this->getAllProperties( $source ) ) ); 00221 } 00222 00223 // For zero depth just return the collection 00224 if ( $depth === ezcWebdavRequest::DEPTH_ZERO ) 00225 { 00226 return array( new ezcWebdavCollection( $source, $this->getAllProperties( $source ) ) ); 00227 } 00228 00229 $nodes = array( new ezcWebdavCollection( $source, $this->getAllProperties( $source ) ) ); 00230 $recurseCollections = array( $source ); 00231 00232 // Collect children for all collections listed in $recurseCollections. 00233 for ( $i = 0; $i < count( $recurseCollections ); ++$i ) 00234 { 00235 $source = $recurseCollections[$i]; 00236 00237 // add the slash at the end of the path if it is missing 00238 if ( $source{strlen( $source ) - 1} !== '/' ) 00239 { 00240 $source .= '/'; 00241 } 00242 $children = $this->getCollectionMembers( $source, $depth ); 00243 00244 foreach ( $children as $child ) 00245 { 00246 $nodes[] = $child; 00247 00248 // Check if we should recurse deeper, and add collections to 00249 // processing list in this case. 00250 if ( $child instanceof ezcWebdavCollection 00251 && $depth === ezcWebdavRequest::DEPTH_INFINITY 00252 && $child->path !== $source ) // @as added for recursive DEPTH_INFINITY 00253 { 00254 $recurseCollections[] = $child->path; 00255 } 00256 } 00257 } 00258 00259 return $nodes; 00260 } 00261 00262 /** 00263 * Returns the contents of a resource. 00264 * 00265 * This method returns the content of the resource identified by $path as a 00266 * string. 00267 * 00268 * @param string $target 00269 * @return string 00270 */ 00271 protected function getResourceContents( $target ) 00272 { 00273 $result = array( 'data' => false, 'file' => false ); 00274 $fullPath = $target; 00275 $target = $this->splitFirstPathElement( $fullPath, $currentSite ); 00276 00277 $data = $this->getVirtualFolderData( $result, $currentSite, $target, $fullPath ); 00278 if ( $data['file'] ) 00279 { 00280 $file = eZClusterFileHandler::instance( $data['file'] ); 00281 //$this->cachedProperties[ $data['file'] ]['size'] = $file->size(); 00282 return $file->fetchContents(); 00283 } 00284 00285 return false; 00286 } 00287 00288 /** 00289 * Returns members of collection. 00290 * 00291 * Returns an array with the members of the collection identified by $path. 00292 * The returned array can contain {@link ezcWebdavCollection}, and {@link 00293 * ezcWebdavResource} instances and might also be empty, if the collection 00294 * has no members. 00295 * 00296 * Added $depth. 00297 * 00298 * @param string $path 00299 * @param int $depth Added by @as 00300 * @return array(ezcWebdavResource|ezcWebdavCollection) 00301 */ 00302 protected function getCollectionMembers( $path, $depth = ezcWebdavRequest::DEPTH_INFINITY ) 00303 { 00304 $properties = $this->handledLiveProperties; 00305 $fullPath = $path; 00306 $collection = $this->splitFirstPathElement( $fullPath, $currentSite ); 00307 00308 if ( !$currentSite ) 00309 { 00310 // Display the root which contains a list of sites 00311 $entries = $this->fetchSiteListContent( $fullPath, $depth, $properties ); 00312 } 00313 else 00314 { 00315 $entries = $this->getVirtualFolderCollection( $currentSite, $collection, $fullPath, $depth, $properties ); 00316 } 00317 00318 $contents = array(); 00319 00320 foreach ( $entries as $entry ) 00321 { 00322 // prevent infinite recursion 00323 if ( $path === $entry['href'] ) 00324 { 00325 continue; 00326 } 00327 00328 if ( $entry['mimetype'] === self::DIRECTORY_MIMETYPE ) 00329 { 00330 // Add collection without any children 00331 $contents[] = new ezcWebdavCollection( $entry['href'], $this->getAllProperties( $path ) ); 00332 } 00333 else 00334 { 00335 // If this is not a collection, don't leave a trailing '/' 00336 // on the href. If you do, Goliath gets confused. 00337 $entry['href'] = rtrim( $entry['href'], '/' ); 00338 00339 // Add files without content 00340 $contents[] = new ezcWebdavResource( $entry['href'], $this->getAllProperties( $path ) ); 00341 } 00342 } 00343 00344 return $contents; 00345 } 00346 00347 /** 00348 * Returns an array with information about the node with path $path. 00349 * 00350 * The returned array is of this form: 00351 * <code> 00352 * array( 'nodeExists' => boolean, 'isCollection' => boolean ) 00353 * </code> 00354 * 00355 * @param string $path 00356 * @return array(string=>boolean) 00357 */ 00358 protected function getNodeInfo( $requestUri, $source = null ) 00359 { 00360 $path = ( $source === null ) ? $requestUri : $source; 00361 00362 $fullPath = $path; 00363 $target = $this->splitFirstPathElement( $path, $currentSite ); 00364 00365 if ( !$currentSite ) 00366 { 00367 $data = $this->fetchSiteListContent( $fullPath, 0, array() ); 00368 $data = $data[0]; 00369 $data['nodeExists'] = true; 00370 $data['isCollection'] = true; 00371 } 00372 else 00373 { 00374 if ( !in_array( $currentSite, $this->availableSites ) ) 00375 { 00376 $data = array(); 00377 $data['nodeExists'] = false; 00378 $data['isCollection'] = false; 00379 } 00380 else 00381 { 00382 if ( $target === "" ) 00383 { 00384 $data = $this->fetchVirtualSiteContent( $fullPath, $currentSite, 0, array() ); 00385 } 00386 else if ( in_array( $target, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 00387 { 00388 $data = $this->fetchContainerNodeInfo( $fullPath, $currentSite, $target ); 00389 } 00390 else 00391 { 00392 $data = $this->getCollectionContent( $fullPath, 0, array() ); 00393 } 00394 00395 if ( is_array( $data ) ) 00396 { 00397 $data = $data[0]; 00398 $data['nodeExists'] = true; 00399 $data['isCollection'] = ( $data['mimetype'] === self::DIRECTORY_MIMETYPE ); 00400 $data['href'] = $fullPath; // @as @todo move this hack to correct function 00401 } 00402 else 00403 { 00404 $data = array(); 00405 $data['nodeExists'] = false; 00406 $data['isCollection'] = false; 00407 } 00408 } 00409 } 00410 return $data; 00411 } 00412 00413 /** 00414 * Returns a property of a resource. 00415 * 00416 * Returns the property with the given $propertyName, from the resource 00417 * identified by $path. You may optionally define a $namespace to receive 00418 * the property from. 00419 * 00420 * @param string $path 00421 * @param string $propertyName 00422 * @param string $namespace 00423 * @return ezcWebdavProperty 00424 */ 00425 public function getProperty( $path, $propertyName, $namespace = 'DAV:' ) 00426 { 00427 $storage = $this->getPropertyStorage( $path ); 00428 00429 // Handle dead propreties 00430 if ( $namespace !== 'DAV:' ) 00431 { 00432 $properties = $storage->getAllProperties(); 00433 return $properties[$namespace][$name]; 00434 } 00435 00436 if ( !isset( $this->cachedProperties[$path] ) ) 00437 { 00438 $this->cachedProperties[$path] = $this->getNodeInfo( $path ); 00439 } 00440 00441 $item = $this->cachedProperties[$path]; 00442 00443 // Handle live properties 00444 switch ( $propertyName ) 00445 { 00446 case 'getcontentlength': 00447 $property = new ezcWebdavGetContentLengthProperty(); 00448 $mimetype = isset( $item['mimetype'] ) ? $item['mimetype'] : self::DEFAULT_MIMETYPE; 00449 $size = isset( $item['size'] ) ? $item['size'] : self::DEFAULT_SIZE; 00450 $property->length = ( $mimetype === self::DIRECTORY_MIMETYPE ) ? 00451 ezcWebdavGetContentLengthProperty::COLLECTION : 00452 (string) $size; 00453 break; 00454 00455 case 'getlastmodified': 00456 $property = new ezcWebdavGetLastModifiedProperty(); 00457 $timestamp = isset( $item['mtime'] ) ? $item['mtime'] : time(); 00458 $property->date = new ezcWebdavDateTime( '@' . $timestamp ); 00459 break; 00460 00461 case 'creationdate': 00462 $property = new ezcWebdavCreationDateProperty(); 00463 $timestamp = isset( $item['ctime'] ) ? $item['ctime'] : time(); 00464 $property->date = new ezcWebdavDateTime( '@' . $timestamp ); 00465 break; 00466 00467 case 'displayname': 00468 $property = new ezcWebdavDisplayNameProperty(); 00469 $property->displayName = isset( $item['name'] ) ? $item['name'] : 'Unknown displayname'; 00470 break; 00471 00472 case 'getcontenttype': 00473 $property = new ezcWebdavGetContentTypeProperty(); 00474 $property->mime = isset( $item['mimetype'] ) ? $item['mimetype'] : self::DEFAULT_MIMETYPE; 00475 break; 00476 00477 case 'getetag': 00478 $property = new ezcWebdavGetEtagProperty(); 00479 $mimetype = isset( $item['mimetype'] ) ? $item['mimetype'] : self::DEFAULT_MIMETYPE; 00480 $size = isset( $item['size'] ) ? $item['size'] : self::DEFAULT_SIZE; 00481 $size = ( $mimetype === self::DIRECTORY_MIMETYPE ) ? 00482 ezcWebdavGetContentLengthProperty::COLLECTION : 00483 (string) $size; 00484 $timestamp = isset( $item['mtime'] ) ? $item['mtime'] : time(); 00485 $property->etag = md5( $path . $size . date( 'c', $timestamp ) ); 00486 break; 00487 00488 case 'resourcetype': 00489 $property = new ezcWebdavResourceTypeProperty(); 00490 $mimetype = isset( $item['mimetype'] ) ? $item['mimetype'] : self::DEFAULT_MIMETYPE; 00491 $property->type = ( $mimetype === self::DIRECTORY_MIMETYPE ) ? 00492 ezcWebdavResourceTypeProperty::TYPE_COLLECTION : 00493 ezcWebdavResourceTypeProperty::TYPE_RESOURCE; 00494 break; 00495 00496 case 'supportedlock': 00497 $property = new ezcWebdavSupportedLockProperty(); 00498 break; 00499 00500 case 'lockdiscovery': 00501 $property = new ezcWebdavLockDiscoveryProperty(); 00502 break; 00503 00504 default: 00505 // Handle all other live properties like dead properties 00506 $properties = $storage->getAllProperties(); 00507 $property = $properties['DAV:'][$name]; // @as (need to figure $namespace) 00508 break; 00509 } 00510 00511 return $property; 00512 } 00513 00514 /** 00515 * Returns all properties for a resource. 00516 * 00517 * Returns all properties for the resource identified by $path as a {@link 00518 * ezcWebdavBasicPropertyStorage}. 00519 * 00520 * @param string $path 00521 * @return ezcWebdavPropertyStorage 00522 */ 00523 public function getAllProperties( $path ) 00524 { 00525 $storage = $this->getPropertyStorage( $path ); 00526 00527 // Add all live properties to stored properties 00528 foreach ( $this->handledLiveProperties as $property ) 00529 { 00530 $storage->attach( 00531 $this->getProperty( $path, $property ) 00532 ); 00533 } 00534 00535 return $storage; 00536 } 00537 00538 /** 00539 * Returns the property storage for a resource. 00540 * 00541 * Returns the {@link ezcWebdavPropertyStorage} instance containing the 00542 * properties for the resource identified by $path. 00543 * 00544 * @param string $path 00545 * @return ezcWebdavBasicPropertyStorage 00546 */ 00547 protected function getPropertyStorage( $path ) 00548 { 00549 $storage = new ezcWebdavBasicPropertyStorage(); 00550 00551 // @todo implement property storage 00552 return $storage; 00553 } 00554 00555 /** 00556 * Returns if a resource exists. 00557 * 00558 * Returns if a the resource identified by $path exists. 00559 * 00560 * @param string $path 00561 * @return bool 00562 */ 00563 protected function nodeExists( $path ) 00564 { 00565 if ( !isset( $this->cachedNodes[$path] ) ) 00566 { 00567 $this->cachedNodes[$path] = $this->getNodeInfo( $path ); 00568 } 00569 00570 return $this->cachedNodes[$path]['nodeExists']; 00571 } 00572 00573 /** 00574 * Returns if resource is a collection. 00575 * 00576 * Returns if the resource identified by $path is a collection resource 00577 * (true) or a non-collection one (false). 00578 * 00579 * @param string $path 00580 * @return bool 00581 */ 00582 protected function isCollection( $path ) 00583 { 00584 if ( !isset( $this->cachedNodes[$path] ) ) 00585 { 00586 $this->cachedNodes[$path] = $this->getNodeInfo( $path ); 00587 } 00588 00589 return $this->cachedNodes[$path]['isCollection']; 00590 } 00591 00592 /** 00593 * Serves GET requests. 00594 * 00595 * The method receives a {@link ezcWebdavGetRequest} object containing all 00596 * relevant information obout the clients request and will return an {@link 00597 * ezcWebdavErrorResponse} instance on error or an instance of {@link 00598 * ezcWebdavGetResourceResponse} or {@link ezcWebdavGetCollectionResponse} 00599 * on success, depending on the type of resource that is referenced by the 00600 * request. 00601 * 00602 * This method acquires the internal lock of the backend, dispatches to 00603 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00604 * lock afterwards. 00605 * 00606 * @param ezcWebdavGetRequest $request 00607 * @return ezcWebdavResponse 00608 */ 00609 public function get( ezcWebdavGetRequest $request ) 00610 { 00611 $this->acquireLock( true ); 00612 $return = parent::get( $request ); 00613 $this->freeLock(); 00614 00615 return $return; 00616 } 00617 00618 /** 00619 * Serves HEAD requests. 00620 * 00621 * The method receives a {@link ezcWebdavHeadRequest} object containing all 00622 * relevant information obout the clients request and will return an {@link 00623 * ezcWebdavErrorResponse} instance on error or an instance of {@link 00624 * ezcWebdavHeadResponse} on success. 00625 * 00626 * This method acquires the internal lock of the backend, dispatches to 00627 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00628 * lock afterwards. 00629 * 00630 * @param ezcWebdavHeadRequest $request 00631 * @return ezcWebdavResponse 00632 */ 00633 public function head( ezcWebdavHeadRequest $request ) 00634 { 00635 $this->acquireLock( true ); 00636 $return = parent::head( $request ); 00637 $this->freeLock(); 00638 00639 return $return; 00640 } 00641 00642 /** 00643 * Serves PROPFIND requests. 00644 * 00645 * The method receives a {@link ezcWebdavPropFindRequest} object containing 00646 * all relevant information obout the clients request and will either 00647 * return an instance of {@link ezcWebdavErrorResponse} to indicate an error 00648 * or a {@link ezcWebdavPropFindResponse} on success. If the referenced 00649 * resource is a collection or if some properties produced errors, an 00650 * instance of {@link ezcWebdavMultistatusResponse} may be returned. 00651 * 00652 * The {@link ezcWebdavPropFindRequest} object contains a definition to 00653 * find one or more properties of a given collection or non-collection 00654 * resource. 00655 * 00656 * This method acquires the internal lock of the backend, dispatches to 00657 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00658 * lock afterwards. 00659 * 00660 * This method is an overwrite of the propFind method from 00661 * ezcWebdavSimpleBackend, a hack necessary to permit correct 00662 * output of eZ Publish nodes. The array of ezcWebdavPropFindResponse 00663 * objects returned by ezcWebdavSimpleBackend::propFind is iterated and 00664 * the paths of the nodes in the ezcWebdavPropFindResponse objects 00665 * are encoded properly, in order to be displayed correctly in WebDAV 00666 * clients. The encoding is from the ini setting Charset in 00667 * [CharacterSettings] in i18n.ini. 00668 * 00669 * The code for coding is taken from eZWebDAVServer::outputCollectionContent(). 00670 * 00671 * @param ezcWebdavPropFindRequest $request 00672 * @return ezcWebdavResponse 00673 */ 00674 public function propFind( ezcWebdavPropFindRequest $request ) 00675 { 00676 $ini = eZINI::instance( 'i18n.ini' ); 00677 $dataCharset = $ini->variable( 'CharacterSettings', 'Charset' ); 00678 $xmlCharset = 'utf-8'; 00679 00680 $this->acquireLock( true ); 00681 $return = parent::propFind( $request ); 00682 if ( isset( $return->responses ) && is_array( $return->responses ) ) 00683 { 00684 foreach ( $return->responses as $response ) 00685 { 00686 $href = $response->node->path; 00687 $pathArray = explode( '/', self::recode( $href, $dataCharset, $xmlCharset ) ); 00688 $encodedPath = '/'; 00689 00690 foreach ( $pathArray as $pathElement ) 00691 { 00692 if ( $pathElement != '' ) 00693 { 00694 $encodedPath .= rawurlencode( $pathElement ); 00695 $encodedPath .= '/'; 00696 } 00697 } 00698 $encodedPath = rtrim( $encodedPath, '/' ); 00699 $response->node->path = $encodedPath; 00700 } 00701 } 00702 $this->freeLock(); 00703 00704 return $return; 00705 } 00706 00707 /** 00708 * Serves PROPPATCH requests. 00709 * 00710 * The method receives a {@link ezcWebdavPropPatchRequest} object 00711 * containing all relevant information obout the clients request and will 00712 * return an instance of {@link ezcWebdavErrorResponse} on error or a 00713 * {@link ezcWebdavPropPatchResponse} response on success. If the 00714 * referenced resource is a collection or if only some properties produced 00715 * errors, an instance of {@link ezcWebdavMultistatusResponse} may be 00716 * returned. 00717 * 00718 * This method acquires the internal lock of the backend, dispatches to 00719 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00720 * lock afterwards. 00721 * 00722 * @param ezcWebdavPropPatchRequest $request 00723 * @return ezcWebdavResponse 00724 */ 00725 public function propPatch( ezcWebdavPropPatchRequest $request ) 00726 { 00727 $this->acquireLock(); 00728 $return = parent::propPatch( $request ); 00729 $this->freeLock(); 00730 00731 return $return; 00732 } 00733 00734 /** 00735 * Stores properties for a resource. 00736 * 00737 * Creates a new property storage file and stores the properties given for 00738 * the resource identified by $path. This depends on the affected resource 00739 * and the actual properties in the property storage. 00740 * 00741 * @param string $path 00742 * @param ezcWebdavBasicPropertyStorage $storage 00743 */ 00744 protected function storeProperties( $path, ezcWebdavBasicPropertyStorage $storage ) 00745 { 00746 // @as @todo implement storing properties 00747 return true; 00748 } 00749 00750 /** 00751 * Manually sets a property on a resource. 00752 * 00753 * Sets the given $propertyBackup for the resource identified by $path. 00754 * 00755 * @param string $path 00756 * @param ezcWebdavProperty $property 00757 * @return bool 00758 */ 00759 public function setProperty( $path, ezcWebdavProperty $property ) 00760 { 00761 if ( !in_array( $property->name, $this->handledLiveProperties, true ) ) 00762 { 00763 return false; 00764 } 00765 00766 // @as @todo implement setting properties 00767 // @todo implement locking and unlocking based on the code 00768 // lock: 00769 // replace 30607 with your object ID 00770 // $object = eZContentObject::fetch( 30607 ); 00771 // $stateGroup = eZContentObjectStateGroup::fetchByIdentifier( 'ez_lock' ); 00772 // $state = eZContentObjectState::fetchByIdentifier( 'locked', $stateGroup->attribute( 'id' ) ); 00773 // $object->assignState( $state ); 00774 00775 // unlock: 00776 // $state = eZContentObjectState::fetchByIdentifier( 'not_locked', $stateGroup->attribute( 'id' ) ); 00777 // $object->assignState( $state ); 00778 00779 // Get namespace property storage 00780 $storage = new ezcWebdavBasicPropertyStorage(); 00781 00782 // Attach property to store 00783 $storage->attach( $property ); 00784 00785 // Store document back 00786 $this->storeProperties( $path, $storage ); 00787 00788 return true; 00789 } 00790 00791 /** 00792 * Manually removes a property from a resource. 00793 * 00794 * Removes the given $property form the resource identified by $path. 00795 * 00796 * @param string $path 00797 * @param ezcWebdavProperty $property 00798 * @return bool 00799 */ 00800 public function removeProperty( $path, ezcWebdavProperty $property ) 00801 { 00802 // @as @todo implement removing properties 00803 return true; 00804 } 00805 00806 /** 00807 * Resets the property storage for a resource. 00808 * 00809 * Discards the current {@link ezcWebdavPropertyStorage} of the resource 00810 * identified by $path and replaces it with the given $properties. 00811 * 00812 * @param string $path 00813 * @param ezcWebdavPropertyStorage $storage 00814 * @return bool 00815 */ 00816 public function resetProperties( $path, ezcWebdavPropertyStorage $storage ) 00817 { 00818 $this->storeProperties( $path, $storage ); 00819 } 00820 00821 /** 00822 * Serves PUT requests. 00823 * 00824 * The method receives a {@link ezcWebdavPutRequest} objects containing all 00825 * relevant information obout the clients request and will return an 00826 * instance of {@link ezcWebdavErrorResponse} on error or {@link 00827 * ezcWebdavPutResponse} on success. 00828 * 00829 * This method acquires the internal lock of the backend, dispatches to 00830 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00831 * lock afterwards. 00832 * 00833 * @param ezcWebdavPutRequest $request 00834 * @return ezcWebdavResponse 00835 */ 00836 public function put( ezcWebdavPutRequest $request ) 00837 { 00838 $this->acquireLock(); 00839 $return = parent::put( $request ); 00840 $this->freeLock(); 00841 00842 return $return; 00843 } 00844 00845 /** 00846 * Creates a new resource. 00847 * 00848 * Creates a new resource at the given $path, optionally with the given 00849 * content. If $content is empty, an empty resource will be created. 00850 * 00851 * @param string $path 00852 * @param string $content 00853 */ 00854 protected function createResource( $path, $content = null ) 00855 { 00856 // the creation of the resource is done in setResourceContents() 00857 } 00858 00859 /** 00860 * Sets the contents of a resource. 00861 * 00862 * This method replaces the content of the resource identified by $path 00863 * with the submitted $content. 00864 * 00865 * @param string $path 00866 * @param string $content 00867 */ 00868 protected function setResourceContents( $path, $content ) 00869 { 00870 // Attempt to get file/resource sent from client/browser. 00871 $tempFile = $this->storeUploadedFile( $path, $content ); 00872 eZWebDAVContentBackend::appendLogEntry( 'SetResourceContents:' . $path . ';' . $tempFile ); 00873 00874 // If there was an actual file: 00875 if ( !$tempFile ) 00876 { 00877 return false; // @as self::FAILED_FORBIDDEN; 00878 } 00879 00880 // Attempt to do something with it (copy/whatever). 00881 $fullPath = $path; 00882 $target = $this->splitFirstPathElement( $path, $currentSite ); 00883 00884 if ( !$currentSite ) 00885 { 00886 return false; // @as self::FAILED_FORBIDDEN; 00887 } 00888 00889 $result = $this->putVirtualFolderData( $currentSite, $target, $tempFile ); 00890 00891 unlink( $tempFile ); 00892 eZDir::cleanupEmptyDirectories( dirname( $tempFile ) ); 00893 00894 return $result; 00895 } 00896 00897 /** 00898 * Serves DELETE requests. 00899 * 00900 * The method receives a {@link ezcWebdavDeleteRequest} objects containing 00901 * all relevant information obout the clients request and will return an 00902 * instance of {@link ezcWebdavErrorResponse} on error or {@link 00903 * ezcWebdavDeleteResponse} on success. 00904 * 00905 * This method acquires the internal lock of the backend, dispatches to 00906 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 00907 * lock afterwards. 00908 * 00909 * @param ezcWebdavDeleteRequest $request 00910 * @return ezcWebdavResponse 00911 */ 00912 public function delete( ezcWebdavDeleteRequest $request ) 00913 { 00914 $this->acquireLock(); 00915 $return = parent::delete( $request ); 00916 $this->freeLock(); 00917 00918 return $return; 00919 } 00920 00921 /** 00922 * Returns if everything below a path can be deleted recursively. 00923 * 00924 * Checks files and directories recursively and returns if everything can 00925 * be deleted. Returns an empty array if no errors occured, and an array 00926 * with the files which caused errors otherwise. 00927 * 00928 * @param string $source 00929 * @return array 00930 */ 00931 public function checkDeleteRecursive( $target ) 00932 { 00933 $errors = array(); 00934 00935 $fullPath = $target; 00936 $target = $this->splitFirstPathElement( $target, $currentSite ); 00937 00938 if ( !$currentSite ) 00939 { 00940 // Cannot delete root folder 00941 return array( 00942 new ezcWebdavErrorResponse( 00943 ezcWebdavResponse::STATUS_403, 00944 $fullPath 00945 ), 00946 ); 00947 } 00948 00949 if ( $target === "" ) 00950 { 00951 // Cannot delete entries in site list 00952 return array( 00953 new ezcWebdavErrorResponse( 00954 ezcWebdavResponse::STATUS_403, 00955 $fullPath 00956 ), 00957 ); 00958 } 00959 00960 return $errors; 00961 } 00962 00963 /** 00964 * Deletes everything below a path. 00965 * 00966 * Deletes the resource identified by $path recursively. Returns an 00967 * instance of {@link ezcWebdavErrorResponse} if the deletion failed, and 00968 * null on success. 00969 * 00970 * In case performDelete() was called from a MOVE operation, it does not 00971 * delete anything, because the move() function in ezcWebdavSimpleBackend 00972 * first calls performDelete(), which deletes the destination if the source 00973 * and destination are the same in URL alias terms. 00974 * 00975 * @param string $path 00976 * @return ezcWebdavErrorResponse 00977 */ 00978 protected function performDelete( $target ) 00979 { 00980 switch ( $_SERVER['REQUEST_METHOD'] ) 00981 { 00982 case 'MOVE': 00983 // in case performDelete() was called from a MOVE operation, 00984 // do not delete anything, because the move() function in 00985 // ezcWebdavSimpleBackend first calls performDelete(), which 00986 // deletes the destination if the source and destination 00987 // are the same in URL alias terms 00988 return null; 00989 00990 default: 00991 $errors = $this->checkDeleteRecursive( $target ); 00992 break; 00993 } 00994 00995 // If an error will occur return the proper status. We return 00996 // multistatus in any case. 00997 if ( count( $errors ) > 0 ) 00998 { 00999 return new ezcWebdavMultistatusResponse( 01000 $errors 01001 ); 01002 } 01003 01004 $fullPath = $target; 01005 $target = $this->splitFirstPathElement( $target, $currentSite ); 01006 01007 $status = $this->deleteVirtualFolder( $currentSite, $target ); 01008 // @as @todo return based on $status 01009 01010 // Return success 01011 return null; 01012 } 01013 01014 /** 01015 * Serves COPY requests. 01016 * 01017 * The method receives a {@link ezcWebdavCopyRequest} objects containing 01018 * all relevant information obout the clients request and will return an 01019 * instance of {@link ezcWebdavErrorResponse} on error or {@link 01020 * ezcWebdavCopyResponse} on success. If only some operations failed, this 01021 * method may return an instance of {@link ezcWebdavMultistatusResponse}. 01022 * 01023 * This method acquires the internal lock of the backend, dispatches to 01024 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 01025 * lock afterwards. 01026 * 01027 * @param ezcWebdavCopyRequest $request 01028 * @return ezcWebdavResponse 01029 */ 01030 public function copy( ezcWebdavCopyRequest $request ) 01031 { 01032 $this->acquireLock(); 01033 $return = parent::copy( $request ); 01034 $this->freeLock(); 01035 01036 return $return; 01037 } 01038 01039 /** 01040 * Copies resources recursively from one path to another. 01041 * 01042 * Copies the resourced identified by $fromPath recursively to $toPath with 01043 * the given $depth, where $depth is one of {@link 01044 * ezcWebdavRequest::DEPTH_ZERO}, {@link ezcWebdavRequest::DEPTH_ONE}, 01045 * {@link ezcWebdavRequest::DEPTH_INFINITY}. 01046 * 01047 * Returns an array with {@link ezcWebdavErrorResponse}s for all subtrees, 01048 * where the copy operation failed. Errors for subsequent resources in a 01049 * subtree should be ommitted. 01050 * 01051 * If an empty array is return, the operation has been completed 01052 * successfully. 01053 * 01054 * In case performCopy() was called from a MOVE operation, do a real move 01055 * operation, because the move() function from ezcWebdavSimpleBackend 01056 * calls performCopy() and performDelete(). 01057 * 01058 * @param string $fromPath 01059 * @param string $toPath 01060 * @param int $depth 01061 * @return array(ezcWebdavErrorResponse) 01062 */ 01063 protected function performCopy( $source, $destination, $depth = ezcWebdavRequest::DEPTH_INFINITY ) 01064 { 01065 switch ( $_SERVER['REQUEST_METHOD'] ) 01066 { 01067 case 'MOVE': 01068 // in case performCopy() was called from a MOVE operation, 01069 // do a real move operation, because the move() function 01070 // from ezcWebdavSimpleBackend calls performCopy() and 01071 // performDelete() 01072 $errors = $this->moveRecursive( $source, $destination, $depth ); 01073 break; 01074 01075 case 'COPY': 01076 $errors = $this->copyRecursive( $source, $destination, $depth ); 01077 break; 01078 01079 default: 01080 $errors = $this->moveRecursive( $source, $destination, $depth ); 01081 break; 01082 } 01083 01084 // Transform errors 01085 foreach ( $errors as $nr => $error ) 01086 { 01087 $errors[$nr] = new ezcWebdavErrorResponse( 01088 ezcWebdavResponse::STATUS_423, 01089 $error 01090 ); 01091 } 01092 01093 // Copy dead properties 01094 $storage = $this->getPropertyStorage( $source ); 01095 $this->storeProperties( $destination, $storage ); 01096 01097 // Updateable live properties are updated automagically, because they 01098 // are regenerated on request on base of the file they affect. So there 01099 // is no reason to keep them "alive". 01100 01101 return $errors; 01102 } 01103 01104 /** 01105 * Recursively copy a file or directory. 01106 * 01107 * Recursively copy a file or directory in $source to the given 01108 * $destination. If a $depth is given, the operation will stop as soon as 01109 * the given recursion depth is reached. A depth of -1 means no limit, 01110 * while a depth of 0 means, that only the current file or directory will 01111 * be copied, without any recursion. 01112 * 01113 * Returns an empty array if no errors occured, and an array with the files 01114 * which caused errors otherwise. 01115 * 01116 * @param string $source 01117 * @param string $destination 01118 * @param int $depth 01119 * @return array 01120 */ 01121 public function copyRecursive( $source, $destination, $depth = ezcWebdavRequest::DEPTH_INFINITY ) 01122 { 01123 $errors = array(); 01124 $fullSource = $source; 01125 $fullDestination = $destination; 01126 $source = $this->splitFirstPathElement( $source, $sourceSite ); 01127 $destination = $this->splitFirstPathElement( $destination, $destinationSite ); 01128 01129 if ( $sourceSite !== $destinationSite ) 01130 { 01131 // We do not support copying from one site to another yet 01132 // TODO: Check if the sites are using the same db, 01133 // if so allow the copy as a simple object copy 01134 // If not we will have to do an object export from 01135 // $sourceSite and import it in $destinationSite 01136 return array(); // @as self::FAILED_FORBIDDEN; 01137 } 01138 01139 if ( !$sourceSite or 01140 !$destinationSite ) 01141 { 01142 // Cannot copy entries in site list 01143 return array( $fullSource ); // @as self::FAILED_FORBIDDEN; 01144 } 01145 01146 $this->copyVirtualFolder( $sourceSite, $destinationSite, 01147 $source, $destination ); 01148 01149 if ( $depth === ezcWebdavRequest::DEPTH_ZERO || !$this->isCollection( $fullSource ) ) 01150 { 01151 // Do not recurse (any more) 01152 return array(); 01153 } 01154 01155 // Recurse 01156 $nodes = $this->getCollectionMembers( $fullSource ); 01157 foreach ( $nodes as $node ) 01158 { 01159 if ( $node->path === $fullSource . '/' ) 01160 { 01161 // skip the current node which was copied with copyVirtualFolder() above 01162 continue; 01163 } 01164 01165 $newDepth = ( $depth !== ezcWebdavRequest::DEPTH_ONE ) ? $depth : ezcWebdavRequest::DEPTH_ZERO; 01166 $errors = array_merge( 01167 $errors, 01168 $this->copyRecursive( $node->path, 01169 $fullDestination . '/' . $this->getProperty( $node->path, 'displayname' )->displayName, 01170 $newDepth ) ); 01171 } 01172 01173 return $errors; 01174 } 01175 01176 /** 01177 * Recursively move a file or directory. 01178 * 01179 * Recursively move a file or directory in $source to the given 01180 * $destination. If a $depth is given, the operation will stop as soon as 01181 * the given recursion depth is reached. A depth of -1 means no limit, 01182 * while a depth of 0 means, that only the current file or directory will 01183 * be copied, without any recursion. 01184 * 01185 * Returns an empty array if no errors occured, and an array with the files 01186 * which caused errors otherwise. 01187 * 01188 * @param string $source 01189 * @param string $destination 01190 * @param int $depth 01191 * @return array 01192 */ 01193 public function moveRecursive( $source, $destination, $depth = ezcWebdavRequest::DEPTH_INFINITY ) 01194 { 01195 $errors = array(); 01196 $fullSource = $source; 01197 $fullDestination = $destination; 01198 $source = $this->splitFirstPathElement( $source, $sourceSite ); 01199 $destination = $this->splitFirstPathElement( $destination, $destinationSite ); 01200 01201 if ( $sourceSite !== $destinationSite ) 01202 { 01203 // We do not support copying from one site to another yet 01204 // TODO: Check if the sites are using the same db, 01205 // if so allow the copy as a simple object copy 01206 // If not we will have to do an object export from 01207 // $sourceSite and import it in $destinationSite 01208 return array(); // @as self::FAILED_FORBIDDEN; 01209 } 01210 01211 if ( !$sourceSite or 01212 !$destinationSite ) 01213 { 01214 // Cannot copy entries in site list 01215 return array( $fullSource ); // @as self::FAILED_FORBIDDEN; 01216 } 01217 01218 $this->moveVirtualFolder( $sourceSite, $destinationSite, 01219 $source, $destination, 01220 $fullSource, $fullDestination ); 01221 01222 if ( $depth === ezcWebdavRequest::DEPTH_ZERO || !$this->isCollection( $fullSource ) ) 01223 { 01224 // Do not recurse (any more) 01225 return array(); 01226 } 01227 01228 // Recurse 01229 $nodes = $this->getCollectionMembers( $fullSource ); 01230 foreach ( $nodes as $node ) 01231 { 01232 if ( $node->path === $fullSource . '/' ) 01233 { 01234 // skip the current node which was copied with copyVirtualFolder() above 01235 continue; 01236 } 01237 01238 $newDepth = ( $depth !== ezcWebdavRequest::DEPTH_ONE ) ? $depth : ezcWebdavRequest::DEPTH_ZERO; 01239 $errors = array_merge( 01240 $errors, 01241 $this->moveRecursive( $node->path, 01242 $fullDestination . '/' . $this->getProperty( $node->path, 'displayname' )->displayName, 01243 $newDepth ) ); 01244 } 01245 01246 return $errors; 01247 } 01248 01249 /** 01250 * Serves MOVE requests. 01251 * 01252 * The method receives a {@link ezcWebdavMoveRequest} objects containing 01253 * all relevant information obout the clients request and will return an 01254 * instance of {@link ezcWebdavErrorResponse} on error or {@link 01255 * ezcWebdavMoveResponse} on success. If only some operations failed, this 01256 * method may return an instance of {@link ezcWebdavMultistatusResponse}. 01257 * 01258 * This method acquires the internal lock of the backend, dispatches to 01259 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 01260 * lock afterwards. 01261 * 01262 * @param ezcWebdavMoveRequest $request 01263 * @return ezcWebdavResponse 01264 */ 01265 public function move( ezcWebdavMoveRequest $request ) 01266 { 01267 $this->acquireLock(); 01268 $return = parent::move( $request ); 01269 $this->freeLock(); 01270 01271 return $return; 01272 } 01273 01274 /** 01275 * Serves MKCOL (make collection) requests. 01276 * 01277 * The method receives a {@link ezcWebdavMakeCollectionRequest} objects 01278 * containing all relevant information obout the clients request and will 01279 * return an instance of {@link ezcWebdavErrorResponse} on error or {@link 01280 * ezcWebdavMakeCollectionResponse} on success. 01281 * 01282 * This method acquires the internal lock of the backend, dispatches to 01283 * {@link ezcWebdavSimpleBackend} to perform the operation and releases the 01284 * lock afterwards. 01285 * 01286 * @param ezcWebdavMakeCollectionRequest $request 01287 * @return ezcWebdavResponse 01288 */ 01289 public function makeCollection( ezcWebdavMakeCollectionRequest $request ) 01290 { 01291 $this->acquireLock(); 01292 $return = parent::makeCollection( $request ); 01293 $this->freeLock(); 01294 01295 return $return; 01296 } 01297 01298 01299 /** 01300 * Required method to serve OPTIONS requests. 01301 * 01302 * The method receives a {@link ezcWebdavOptionsRequest} object containing all 01303 * relevant information obout the clients request and should either return 01304 * an error by returning an {@link ezcWebdavErrorResponse} object, or any 01305 * other {@link ezcWebdavResponse} objects. 01306 * 01307 * @param ezcWebdavOptionsRequest $request 01308 * @return ezcWebdavResponse 01309 */ 01310 public function options( ezcWebdavOptionsRequest $request ) 01311 { 01312 $response = new ezcWebdavOptionsResponse( '1' ); 01313 01314 // Always allowed 01315 $allowed = 'GET, HEAD, PROPFIND, PROPPATCH, OPTIONS, '; 01316 01317 // Check if modifications are allowed 01318 if ( $this instanceof ezcWebdavBackendChange ) 01319 { 01320 $allowed .= 'DELETE, COPY, MOVE, '; 01321 } 01322 01323 // Check if MKCOL is allowed 01324 if ( $this instanceof ezcWebdavBackendMakeCollection ) 01325 { 01326 $allowed .= 'MKCOL, '; 01327 } 01328 01329 // Check if PUT is allowed 01330 if ( $this instanceof ezcWebdavBackendPut ) 01331 { 01332 $allowed .= 'PUT, '; 01333 } 01334 01335 // Check if LOCK and UNLOCK are allowed 01336 if ( $this instanceof ezcWebdavLockBackend ) 01337 { 01338 $allowed .= 'LOCK, UNLOCK, '; 01339 } 01340 01341 $response->setHeader( 'Allow', substr( $allowed, 0, -2 ) ); 01342 01343 return $response; 01344 } 01345 01346 01347 // eZ Publish functionality ----------------------------------------- 01348 01349 01350 /** 01351 * Sets the current site. 01352 * 01353 * From eZ Publish. 01354 * 01355 * @param string $site Eg. 'plain_site_user' 01356 * @todo remove or move in another class? 01357 */ 01358 public function setCurrentSite( $site ) 01359 { 01360 eZWebDAVContentBackend::appendLogEntry( __FUNCTION__ . '1:' . $site ); 01361 $access = array( 'name' => $site, 01362 'type' => eZSiteAccess::TYPE_STATIC ); 01363 01364 $access = eZSiteAccess::change( $access ); 01365 eZWebDAVContentBackend::appendLogEntry( __FUNCTION__ . '2:' . $site ); 01366 01367 eZDebugSetting::writeDebug( 'kernel-siteaccess', $access, 'current siteaccess' ); 01368 01369 // Clear/flush global database instance. 01370 $nullVar = null; 01371 eZDB::setInstance( $nullVar ); 01372 } 01373 01374 /** 01375 * Detects a possible/valid site-name in start of a path. 01376 * 01377 * From eZ Publish. 01378 * 01379 * @param string $path Eg. '/plain_site_user/Content/Folder1/file1.txt' 01380 * @return string The name of the site that was detected (eg. 'plain_site_user') 01381 * or false if not site could be detected 01382 * @todo remove or move in another class? 01383 */ 01384 public function currentSiteFromPath( $path ) 01385 { 01386 $indexDir = eZSys::indexDir(); 01387 01388 // Remove indexDir if used in non-virtualhost mode. 01389 if ( preg_match( "#^" . preg_quote( $indexDir ) . "(.+)$#", $path, $matches ) ) 01390 { 01391 $path = $matches[1]; 01392 } 01393 01394 foreach ( $this->availableSites as $site ) 01395 { 01396 // Check if given path starts with this site-name, if so: return it. 01397 if ( preg_match( "#^/" . preg_quote( $site ) . "(.*)$#", $path, $matches ) ) 01398 { 01399 return $site; 01400 } 01401 } 01402 01403 return false; 01404 } 01405 01406 /** 01407 * Gathers information about a given node specified as parameter. 01408 * 01409 * The format of the returned array is: 01410 * <code> 01411 * array( 'name' => node name (eg. 'Group picture'), 01412 * 'size' => storage size of the_node in bytes (eg. 57123), 01413 * 'mimetype' => mime type of the node (eg. 'image/jpeg'), 01414 * 'ctime' => creation time as timestamp, 01415 * 'mtime' => latest modification time as timestamp, 01416 * 'href' => the path to the node (eg. '/plain_site_user/Content/Folder1/file1.jpg') 01417 * </code> 01418 * 01419 * @param string $target Eg. '/plain_site_user/Content/Folder1/file1.jpg 01420 * @param eZContentObject &$node The node corresponding to $target 01421 * @return array(string=>mixed) 01422 * @todo remove/replace .ini calls, eZContentUpload, eZMimeType, eZSys RequestURI 01423 * @todo handle articles as files 01424 */ 01425 protected function fetchNodeInfo( $target, &$node ) 01426 { 01427 // When finished, we'll return an array of attributes/properties. 01428 $entry = array(); 01429 01430 $classIdentifier = $node->attribute( 'class_identifier' ); 01431 01432 $object = $node->attribute( 'object' ); 01433 $urlAlias = $node->urlAlias(); 01434 01435 // By default, everything is displayed as a folder: 01436 // Trim the name of the node, it is in some cases whitespace in eZ Publish 01437 $name = trim( $node->attribute( 'name' ) ); 01438 01439 // @as 2009-03-09: return node_id as displayname in case name is missing 01440 // displayname is not actually used by WebDAV clients 01441 $entry["name"] = ( $name !== '' && $name !== NULL ) ? $name : $node->attribute( 'node_id' ); 01442 $entry["size"] = 0; 01443 $entry["mimetype"] = self::DIRECTORY_MIMETYPE; 01444 eZWebDAVContentBackend::appendLogEntry( 'FetchNodeInfo:' . $node->attribute( 'name' ) . '/' . $urlAlias ); 01445 01446 // @todo handle articles as files 01447 // if ( $classIdentifier === 'article' ) 01448 // { 01449 // $entry["mimetype"] = 'application/ms-word'; 01450 // } 01451 01452 $entry["ctime"] = $object->attribute( 'published' ); 01453 $entry["mtime"] = $object->attribute( 'modified' ); 01454 01455 $upload = new eZContentUpload(); 01456 $info = $upload->objectFileInfo( $object ); 01457 01458 $suffix = ''; 01459 $class = $object->contentClass(); 01460 $isObjectFolder = $this->isObjectFolder( $object, $class ); 01461 01462 if ( $isObjectFolder ) 01463 { 01464 // We do nothing, the default is to see it as a folder 01465 } 01466 else if ( $info ) 01467 { 01468 $filePath = $info['filepath']; 01469 $entry['filepath'] = $filePath; 01470 $entry["mimetype"] = false; 01471 $entry["size"] = false; 01472 if ( isset( $info['filesize'] ) ) 01473 { 01474 $entry['size'] = $info['filesize']; 01475 } 01476 if ( isset( $info['mime_type'] ) ) 01477 { 01478 $entry['mimetype'] = $info['mime_type']; 01479 } 01480 01481 // Fill in information from the actual file if they are missing. 01482 $file = eZClusterFileHandler::instance( $filePath ); 01483 if ( !$entry['size'] and $file->exists() ) 01484 { 01485 $entry["size"] = $file->size(); 01486 } 01487 if ( !$entry['mimetype'] ) 01488 { 01489 $mimeInfo = eZMimeType::findByURL( $filePath ); 01490 $entry["mimetype"] = $mimeInfo['name']; 01491 01492 $suffix = $mimeInfo['suffix']; 01493 01494 if ( strlen( $suffix ) > 0 ) 01495 { 01496 $entry["name"] .= '.' . $suffix; 01497 } 01498 } 01499 else 01500 { 01501 // eZMimeType returns first suffix in its list 01502 // this could be another one than the original file extension 01503 // so let's try to get the suffix from the file path first 01504 $suffix = eZFile::suffix( $filePath ); 01505 if ( !$suffix ) 01506 { 01507 $mimeInfo = eZMimeType::findByName( $entry['mimetype'] ); 01508 $suffix = $mimeInfo['suffix']; 01509 } 01510 01511 if ( strlen( $suffix ) > 0 ) 01512 { 01513 $entry["name"] .= '.' . $suffix; 01514 } 01515 } 01516 01517 if ( $file->exists() ) 01518 { 01519 $entry["ctime"] = $file->mtime(); 01520 $entry["mtime"] = $file->mtime(); 01521 } 01522 } 01523 else 01524 { 01525 // Here we only show items as folders if they have 01526 // is_container set to true, otherwise it's an unknown binary file 01527 if ( !$class->attribute( 'is_container' ) ) 01528 { 01529 $entry['mimetype'] = 'application/octet-stream'; 01530 } 01531 } 01532 01533 $scriptURL = $target; 01534 if ( strlen( $scriptURL ) > 0 and $scriptURL[ strlen( $scriptURL ) - 1 ] !== "/" ) 01535 { 01536 $scriptURL .= "/"; 01537 } 01538 01539 $trimmedScriptURL = trim( $scriptURL, '/' ); 01540 $scriptURLParts = explode( '/', $trimmedScriptURL ); 01541 01542 $urlPartCount = count( $scriptURLParts ); 01543 01544 if ( $urlPartCount >= 2 ) 01545 { 01546 // one of the virtual folders 01547 // or inside one of the virtual folders 01548 $siteAccess = $scriptURLParts[0]; 01549 $virtualFolder = $scriptURLParts[1]; 01550 01551 // only when the virtual folder is Content we need to add its path to the start URL 01552 // the paths of other top level folders (like Media) are included in URL aliases of their descending nodes 01553 if ( $virtualFolder === self::virtualContentFolderName() ) 01554 { 01555 $startURL = '/' . $siteAccess . '/' . $virtualFolder . '/'; 01556 } 01557 // Media folder is special since the virtual folder name is translated via ezpI18n (@see self::virtualMediaFolderName()) 01558 // but the URL Alias of this top node can vary (depends if content objects are available in the current siteaccess's language). 01559 // So we need to replace the media folder URL Alias part by the one translated via ezpI18n, which never varies. 01560 // Otherwise unexpected errors can occurred, depending on WebDAV client, especially when URL Alias from media child nodes 01561 // doesn't match EXACTLY with the virtual media folder name. 01562 // e.g. : having "Média" as virtual media folder name and a child node with Media/Images (without accent) as URL alias will break. 01563 // See http://issues.ez.no/15035 01564 else if ( $virtualFolder === self::virtualMediaFolderName() ) 01565 { 01566 $startURL = '/' . $siteAccess . '/' . $virtualFolder . '/'; 01567 $urlAlias = substr( $urlAlias, strpos( $urlAlias, '/' ) + 1 ); 01568 } 01569 else 01570 { 01571 $startURL = '/' . $siteAccess . '/'; 01572 } 01573 } 01574 else 01575 { 01576 // site access level 01577 $startURL = $scriptURL; 01578 } 01579 01580 // Set the href attribute (note that it doesn't just equal the name). 01581 if ( !isset( $entry['href'] ) ) 01582 { 01583 if ( strlen( $suffix ) > 0 ) 01584 { 01585 $suffix = '.' . $suffix; 01586 } 01587 01588 $entry["href"] = $startURL . $urlAlias . $suffix; 01589 } 01590 01591 // Return array of attributes/properties (name, size, mime, times, etc.). 01592 return $entry; 01593 } 01594 01595 /** 01596 * Handles data retrival on the virtual folder level. 01597 * 01598 * The format of the returned array is the same as $result: 01599 * <code> 01600 * array( 'isFile' => bool, 01601 * 'isCollection' => bool, 01602 * 'file' => path to the storage file which contain the contents ); 01603 * </code> 01604 * 01605 * @param array(string=>mixed) $result 01606 * @param string $currentSite Eg. 'plain_site_user' 01607 * @param string $target Eg. 'Content/Folder1/file1.txt' 01608 * @param string $fullPath Eg. '/plain_site_user/Content/Folder1/file1.txt' 01609 * @return array(string=>mixed) Or false if an error appeared 01610 */ 01611 protected function getVirtualFolderData( $result, $currentSite, $target, $fullPath ) 01612 { 01613 $target = $this->splitFirstPathElement( $target, $virtualFolder ); 01614 if ( !$target ) 01615 { 01616 // The rest in the virtual folder does not have any data 01617 return false; // self::FAILED_NOT_FOUND; 01618 } 01619 01620 if ( !in_array( $virtualFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 01621 { 01622 return false; // self::FAILED_NOT_FOUND; 01623 } 01624 01625 if ( $virtualFolder === self::virtualContentFolderName() or 01626 $virtualFolder === self::virtualMediaFolderName() ) 01627 { 01628 return $this->getContentNodeData( $result, $currentSite, $virtualFolder, $target, $fullPath ); 01629 } 01630 return false; // self::FAILED_NOT_FOUND; 01631 } 01632 01633 /** 01634 * Handles data retrival on the content tree level. 01635 * 01636 * The format of the returned array is the same as $result: 01637 * <code> 01638 * array( 'isFile' => bool, 01639 * 'isCollection' => bool, 01640 * 'file' => path to the storage file which contain the contents ); 01641 * </code> 01642 * 01643 * @param array(string=>mixed) $result 01644 * @param string $currentSite Eg. 'plain_site_user' 01645 * @param string $virtualFolder Eg. 'Content' 01646 * @param string $target Eg. 'Folder1/file1.txt' 01647 * @param string $fullPath Eg. '/plain_site_user/Content/Folder1/file1.txt' 01648 * @return array(string=>mixed) Or false if an error appeared 01649 * @todo remove/replace eZContentUpload 01650 */ 01651 protected function getContentNodeData( $result, $currentSite, $virtualFolder, $target, $fullPath ) 01652 { 01653 // Attempt to fetch the node the client wants to get. 01654 $nodePath = $this->internalNodePath( $virtualFolder, $target ); 01655 eZWebDAVContentBackend::appendLogEntry( __FUNCTION__ . " Path:" . $nodePath ); 01656 $node = $this->fetchNodeByTranslation( $nodePath ); 01657 $info = $this->fetchNodeInfo( $fullPath, $node ); 01658 01659 // Proceed only if the node is valid: 01660 if ( $node === null ) 01661 { 01662 return $result; 01663 } 01664 01665 // Can we fetch the contents of the node 01666 if ( !$node->canRead() ) 01667 { 01668 return false; // @as self::FAILED_FORBIDDEN; 01669 } 01670 01671 $object = $node->attribute( 'object' ); 01672 01673 foreach ( $info as $key => $value ) 01674 { 01675 $result[$key] = $value; 01676 } 01677 01678 $upload = new eZContentUpload(); 01679 $info = $upload->objectFileInfo( $object ); 01680 01681 if ( $info ) 01682 { 01683 $result['file'] = $info['filepath']; 01684 } 01685 else 01686 { 01687 $class = $object->contentClass(); 01688 if ( $this->isObjectFolder( $object, $class ) ) 01689 { 01690 $result['isCollection'] = true; 01691 } 01692 01693 } 01694 return $result; 01695 } 01696 01697 /** 01698 * Produces the collection content. 01699 * 01700 * Builds either the virtual start folder with the virtual content folder 01701 * in it (and additional files). OR: if we're browsing within the content 01702 * folder: it gets the content of the target/given folder. 01703 * 01704 * @param string $collection Eg. '/plain_site_user/Content/Folder1' 01705 * @param int $depth One of -1 (infinite), 0, 1 01706 * @param array(string) $properties Currently not used 01707 * @return array(string=>array()) 01708 */ 01709 protected function getCollectionContent( $collection, $depth = false, $properties = false ) 01710 { 01711 $fullPath = $collection; 01712 $collection = $this->splitFirstPathElement( $collection, $currentSite ); 01713 01714 if ( !$currentSite ) 01715 { 01716 // Display the root which contains a list of sites 01717 $entries = $this->fetchSiteListContent( $fullPath, $depth, $properties ); 01718 return $entries; 01719 } 01720 01721 return $this->getVirtualFolderCollection( $currentSite, $collection, $fullPath, $depth, $properties ); 01722 } 01723 01724 /** 01725 * Same as fetchVirtualSiteContent(), but only one entry is returned 01726 * (Content or Media). 01727 * 01728 * An entry in the the returned array is of this form: 01729 * <code> 01730 * array( 'name' => node name (eg. 'Group picture'), 01731 * 'size' => storage size of the_node in bytes (eg. 57123), 01732 * 'mimetype' => mime type of the node (eg. 'image/jpeg'), 01733 * 'ctime' => creation time as timestamp, 01734 * 'mtime' => latest modification time as timestamp, 01735 * 'href' => the path to the node (eg. '/plain_site_user/Content/Folder1/file1.jpg') 01736 * </code> 01737 * 01738 * @param string $fullPath Eg. '/plain_site_user/Content/Folder1' 01739 * @param string $site Eg. 'plain_site_user' 01740 * @param string $nodeName Eg. 'Folder1' 01741 * @return array(array(string=>mixed)) 01742 */ 01743 protected function fetchContainerNodeInfo( $fullPath, $site, $nodeName ) 01744 { 01745 $contentEntry = array(); 01746 $contentEntry["name"] = $nodeName; 01747 $contentEntry["size"] = 0; 01748 $contentEntry["mimetype"] = self::DIRECTORY_MIMETYPE; 01749 $contentEntry["ctime"] = filectime( 'settings/siteaccess/' . $site ); 01750 $contentEntry["mtime"] = filemtime( 'settings/siteaccess/' . $site ); 01751 $contentEntry["href"] = $fullPath; 01752 01753 $entries[] = $contentEntry; 01754 01755 return $entries; 01756 } 01757 01758 /** 01759 * Builds and returns the content of the virtual start folder for a site. 01760 * 01761 * The virtual startfolder is an intermediate step between the site-list 01762 * and actual content. This directory contains the "content" folder which 01763 * leads to the site's actual content. 01764 * 01765 * An entry in the the returned array is of this form: 01766 * <code> 01767 * array( 'name' => node name (eg. 'Group picture'), 01768 * 'size' => storage size of the_node in bytes (eg. 57123), 01769 * 'mimetype' => mime type of the node (eg. 'image/jpeg'), 01770 * 'ctime' => creation time as timestamp, 01771 * 'mtime' => latest modification time as timestamp, 01772 * 'href' => the path to the node (eg. '/plain_site_user/Content/Folder1/file1.jpg') 01773 * </code> 01774 * 01775 * @param string $target Eg. '/plain_site_user/Content/Folder1' 01776 * @param string $site Eg. 'plain_site_user 01777 * @param string $depth One of -1 (infinite), 0, 1 01778 * @param array(string) $properties Currently not used 01779 * @return array(array(string=>mixed)) 01780 */ 01781 protected function fetchVirtualSiteContent( $target, $site, $depth, $properties ) 01782 { 01783 $requestUri = $target; 01784 01785 // Always add the current collection 01786 $contentEntry = array(); 01787 $scriptURL = $requestUri; 01788 if ( $scriptURL{strlen( $scriptURL ) - 1} !== '/' ) 01789 { 01790 $scriptURL .= "/"; 01791 } 01792 $contentEntry["name"] = $scriptURL; 01793 $contentEntry["size"] = 0; 01794 $contentEntry["mimetype"] = self::DIRECTORY_MIMETYPE; 01795 $contentEntry["ctime"] = filectime( 'settings/siteaccess/' . $site ); 01796 $contentEntry["mtime"] = filemtime( 'settings/siteaccess/' . $site ); 01797 $contentEntry["href"] = $requestUri; 01798 $entries[] = $contentEntry; 01799 01800 $defctime = $contentEntry['ctime']; 01801 $defmtime = $contentEntry['mtime']; 01802 01803 if ( $depth > 0 ) 01804 { 01805 $scriptURL = $requestUri; 01806 if ( $scriptURL{strlen( $scriptURL ) - 1} !== '/' ) 01807 { 01808 $scriptURL .= "/"; 01809 } 01810 01811 // Set up attributes for the virtual content folder: 01812 foreach ( array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) as $name ) 01813 { 01814 $entry = array(); 01815 $entry["name"] = $name; 01816 $entry["size"] = 0; 01817 $entry["mimetype"] = self::DIRECTORY_MIMETYPE; 01818 $entry["ctime"] = $defctime; 01819 $entry["mtime"] = $defmtime; 01820 $entry["href"] = $scriptURL . $name; 01821 01822 $entries[] = $entry; 01823 } 01824 } 01825 01826 return $entries; 01827 } 01828 01829 /** 01830 * Handles collections on the virtual folder level, if no virtual folder 01831 * elements are accessed it lists the virtual folders. 01832 * 01833 * An entry in the the returned array is of this form: 01834 * <code> 01835 * array( 'name' => node name (eg. 'Group picture'), 01836 * 'size' => storage size of the_node in bytes (eg. 57123), 01837 * 'mimetype' => mime type of the node (eg. 'image/jpeg'), 01838 * 'ctime' => creation time as timestamp, 01839 * 'mtime' => latest modification time as timestamp, 01840 * 'href' => the path to the node (eg. '/plain_site_user/Content/Folder1/file1.jpg') 01841 * </code> 01842 * 01843 * @param string $site Eg. 'plain_site_user 01844 * @param string $collection Eg. 'Folder1' 01845 * @param string $fullPath Eg. '/plain_site_user/Content/Folder1' 01846 * @param string $depth One of -1 (infinite), 0, 1 01847 * @param array(string) $properties Currently not used 01848 * @return array(array(string=>mixed)) 01849 */ 01850 protected function getVirtualFolderCollection( $currentSite, $collection, $fullPath, $depth, $properties ) 01851 { 01852 if ( !$collection ) 01853 { 01854 // We are inside a site so we display the virtual folder for the site 01855 $entries = $this->fetchVirtualSiteContent( $fullPath, $currentSite, $depth, $properties ); 01856 return $entries; 01857 } 01858 01859 $collection = $this->splitFirstPathElement( $collection, $virtualFolder ); 01860 01861 if ( !in_array( $virtualFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 01862 { 01863 return false; // self::FAILED_NOT_FOUND; 01864 } 01865 01866 // added by @ds 2008-12-07 to fix problems with IE6 SP2 01867 $ini = eZINI::instance(); 01868 $prefixAdded = false; 01869 $prefix = $ini->hasVariable( 'SiteAccessSettings', 'PathPrefix' ) && 01870 $ini->variable( 'SiteAccessSettings', 'PathPrefix' ) != '' ? eZURLAliasML::cleanURL( $ini->variable( 'SiteAccessSettings', 'PathPrefix' ) ) : false; 01871 01872 if ( $prefix ) 01873 { 01874 $escapedPrefix = preg_quote( $prefix, '#' ); 01875 // Only prepend the path prefix if it's not already the first element of the url. 01876 if ( !preg_match( "#^$escapedPrefix(/.*)?$#i", $collection ) ) 01877 { 01878 $exclude = $ini->hasVariable( 'SiteAccessSettings', 'PathPrefixExclude' ) 01879 ? $ini->variable( 'SiteAccessSettings', 'PathPrefixExclude' ) 01880 : false; 01881 $breakInternalURI = false; 01882 foreach ( $exclude as $item ) 01883 { 01884 $escapedItem = preg_quote( $item, '#' ); 01885 if ( preg_match( "#^$escapedItem(/.*)?$#i", $collection ) ) 01886 { 01887 $breakInternalURI = true; 01888 break; 01889 } 01890 } 01891 01892 if ( !$breakInternalURI ) 01893 { 01894 $collection = $prefix . '/' . $collection; 01895 $prefixAdded = true; 01896 } 01897 } 01898 } 01899 01900 return $this->getContentTreeCollection( $currentSite, $virtualFolder, $collection, $fullPath, $depth, $properties ); 01901 } 01902 01903 /** 01904 * Handles collections on the content tree level. 01905 * 01906 * Depending on the virtual folder we will generate a node path url and fetch 01907 * the nodes for that path. 01908 * 01909 * An entry in the the returned array is of this form: 01910 * <code> 01911 * array( 'name' => node name (eg. 'Folder1'), 01912 * 'size' => storage size of the_node in bytes (eg. 4096 for collections), 01913 * 'mimetype' => mime type of the node (eg. 'httpd/unix-directory'), 01914 * 'ctime' => creation time as timestamp, 01915 * 'mtime' => latest modification time as timestamp, 01916 * 'href' => the path to the node (eg. '/plain_site_user/Content/Folder1/') 01917 * </code> 01918 * 01919 * @param string $site Eg. 'plain_site_user 01920 * @param string $virtualFolder Eg. 'Content' 01921 * @param string $collection Eg. 'Folder1' 01922 * @param string $fullPath Eg. '/plain_site_user/Content/Folder1/' 01923 * @param string $depth One of -1 (infinite), 0, 1 01924 * @param array(string) $properties Currently not used 01925 * @return array(array(string=>mixed)) 01926 */ 01927 protected function getContentTreeCollection( $currentSite, $virtualFolder, $collection, $fullPath, $depth, $properties ) 01928 { 01929 $nodePath = $this->internalNodePath( $virtualFolder, $collection ); 01930 $node = $this->fetchNodeByTranslation( $nodePath ); 01931 01932 if ( !$node ) 01933 { 01934 return false; // self::FAILED_NOT_FOUND; 01935 } 01936 01937 // Can we list the children of the node? 01938 if ( !$node->canRead() ) 01939 { 01940 return false; // self::FAILED_FORBIDDEN; 01941 } 01942 01943 $entries = $this->fetchContentList( $fullPath, $node, $nodePath, $depth, $properties ); 01944 return $entries; 01945 } 01946 01947 /** 01948 * Gets and returns the content of an actual node. 01949 * 01950 * List of other nodes belonging to the target node (one level below it) 01951 * will be returned as well. 01952 * 01953 * An entry in the the returned array is of this form: 01954 * <code> 01955 * array( 'name' => node name (eg. 'Content'), 01956 * 'size' => storage size of the_node in bytes (eg. 4096 for collections), 01957 * 'mimetype' => mime type of the node (eg. 'httpd/unix-directory'), 01958 * 'ctime' => creation time as timestamp, 01959 * 'mtime' => latest modification time as timestamp, 01960 * 'href' => the path to the node (eg. '/plain_site_user/Content/') 01961 * </code> 01962 * 01963 * @param string $fullPath Eg. '/plain_site_user/Content/' 01964 * @param eZContentObject &$node The note corresponding to $fullPath 01965 * @param string $target Eg. 'Content' 01966 * @param string $depth One of -1 (infinite), 0, 1 01967 * @param array(string) $properties Currently not used 01968 * @return array(array(string=>mixed)) 01969 */ 01970 protected function fetchContentList( $fullPath, &$node, $target, $depth, $properties ) 01971 { 01972 // We'll return an array of entries (which is an array of attributes). 01973 $entries = array(); 01974 01975 if ( $depth === ezcWebdavRequest::DEPTH_ONE || $depth === ezcWebdavRequest::DEPTH_INFINITY ) 01976 // @as added || $depth === ezcWebdavRequest::DEPTH_INFINITY 01977 { 01978 // Get all the children of the target node. 01979 $subTree = $node->subTree( array ( 'Depth' => 1 ) ); 01980 01981 // Build the entries array by going through all the 01982 // nodes in the subtree and getting their attributes: 01983 foreach ( $subTree as $someNode ) 01984 { 01985 $entries[] = $this->fetchNodeInfo( $fullPath, $someNode ); 01986 } 01987 } 01988 01989 // Always include the information about the current level node 01990 $thisNodeInfo = $this->fetchNodeInfo( $fullPath, $node ); 01991 01992 $scriptURL = $fullPath; 01993 if ( $scriptURL{strlen( $scriptURL ) - 1} !== '/' ) 01994 { 01995 $scriptURL .= "/"; 01996 } 01997 01998 $thisNodeInfo["href"] = $scriptURL; 01999 $entries[] = $thisNodeInfo; 02000 02001 // Return the content of the target. 02002 return $entries; 02003 } 02004 02005 /** 02006 * Builds a content-list of available sites and returns it. 02007 * 02008 * An entry in the the returned array is of this form: 02009 * <code> 02010 * array( 'name' => node name (eg. 'plain_site_user'), 02011 * 'size' => storage size of the_node in bytes (eg. 4096 for collections), 02012 * 'mimetype' => mime type of the node (eg. 'httpd/unix-directory'), 02013 * 'ctime' => creation time as timestamp, 02014 * 'mtime' => latest modification time as timestamp, 02015 * 'href' => the path to the node (eg. '/plain_site_user/') 02016 * </code> 02017 * 02018 * @param string $target Eg. '/' 02019 * @param string $depth One of -1 (infinite), 0, 1 02020 * @param array(string) $properties Currently not used 02021 * @return array(array(string=>mixed)) 02022 */ 02023 protected function fetchSiteListContent( $target, $depth, $properties ) 02024 { 02025 // At the end: we'll return an array of entry-arrays. 02026 $entries = array(); 02027 02028 // An entry consists of several attributes (name, size, etc). 02029 $contentEntry = array(); 02030 $entries = array(); 02031 02032 // add a slash at the end of the path, if it is missing 02033 $scriptURL = $target; 02034 if ( $scriptURL{strlen( $scriptURL ) - 1} !== '/' ) 02035 { 02036 $scriptURL .= "/"; 02037 } 02038 02039 $thisNodeInfo["href"] = $scriptURL; 02040 02041 // Set up attributes for the virtual site-list folder: 02042 $contentEntry["name"] = '/'; 02043 $contentEntry["href"] = $scriptURL; 02044 $contentEntry["size"] = 0; 02045 $contentEntry["mimetype"] = self::DIRECTORY_MIMETYPE; 02046 $contentEntry["ctime"] = filectime( 'var' ); 02047 $contentEntry["mtime"] = filemtime( 'var' ); 02048 02049 $entries[] = $contentEntry; 02050 02051 if ( $depth > 0 ) 02052 { 02053 // For all available sites: 02054 foreach ( $this->availableSites as $site ) 02055 { 02056 // Set up attributes for the virtual site-list folder: 02057 $contentEntry["name"] = $scriptURL . $site . '/'; // @as added '/' 02058 $contentEntry["size"] = 0; 02059 $contentEntry["mimetype"] = self::DIRECTORY_MIMETYPE; 02060 $contentEntry["ctime"] = filectime( 'settings/siteaccess/' . $site ); 02061 $contentEntry["mtime"] = filemtime( 'settings/siteaccess/' . $site ); 02062 02063 if ( $target === '/' ) 02064 { 02065 $contentEntry["href"] = $contentEntry["name"]; 02066 } 02067 else 02068 { 02069 $contentEntry["href"] = $scriptURL . $contentEntry["name"]; 02070 } 02071 02072 $entries[] = $contentEntry; 02073 } 02074 } 02075 02076 return $entries; 02077 } 02078 02079 /** 02080 * Attempts to fetch a possible node by translating the provided 02081 * string/path to a node-number. 02082 * 02083 * @param string $nodePathString Eg. 'Folder1/file1.txt' 02084 * @return eZContentObject Eg. the node of 'Folder1/file1.txt' 02085 */ 02086 protected function fetchNodeByTranslation( $nodePathString ) 02087 { 02088 // Get rid of possible extensions, remove .jpeg .txt .html etc.. 02089 $nodePathString = $this->fileBasename( $nodePathString ); 02090 02091 // Strip away last slash 02092 if ( strlen( $nodePathString ) > 0 and 02093 $nodePathString[strlen( $nodePathString ) - 1] === '/' ) 02094 { 02095 $nodePathString = substr( $nodePathString, 0, strlen( $nodePathString ) - 1 ); 02096 } 02097 02098 if ( strlen( $nodePathString ) > 0 ) 02099 { 02100 $nodePathString = eZURLAliasML::convertPathToAlias( $nodePathString ); 02101 } 02102 02103 $nodeID = eZURLAliasML::fetchNodeIDByPath( $nodePathString ); 02104 if ( $nodeID == 2 ) 02105 { 02106 // added by @ds 2008-12-07 to fix problems with IE6 SP2 02107 $ini = eZINI::instance( 'webdav.ini' ); 02108 if ( $ini->hasVariable( 'GeneralSettings', 'StartNode' ) ) 02109 { 02110 $nodeID = $ini->variable( 'GeneralSettings', 'StartNode' ); 02111 } 02112 } 02113 elseif ( $nodeID ) 02114 { 02115 } 02116 else 02117 { 02118 return false; 02119 } 02120 02121 // Attempt to fetch the node. 02122 $node = eZContentObjectTreeNode::fetch( $nodeID ); 02123 02124 // Return the node. 02125 return $node; 02126 } 02127 02128 /** 02129 * Attempts to fetch a possible node by translating the provided 02130 * string/path to a node-number. 02131 * 02132 * The last section of the path is removed before the actual 02133 * translation: hence, the PARENT node is returned. 02134 * 02135 * @param string $nodePathString Eg. 'Folder1/file1.txt' 02136 * @return eZContentObject Eg. the node of 'Folder1' 02137 */ 02138 protected function fetchParentNodeByTranslation( $nodePathString ) 02139 { 02140 // Strip extensions. E.g. .jpg 02141 $nodePathString = $this->fileBasename( $nodePathString ); 02142 02143 // Strip away last slash 02144 if ( strlen( $nodePathString ) > 0 and 02145 $nodePathString[strlen( $nodePathString ) - 1] === '/' ) 02146 { 02147 $nodePathString = substr( $nodePathString, 0, strlen( $nodePathString ) - 1 ); 02148 } 02149 02150 $nodePathString = $this->splitLastPathElement( $nodePathString, $element ); 02151 02152 if ( strlen( $nodePathString ) === 0 ) 02153 { 02154 $nodePathString = '/'; 02155 } 02156 02157 $nodePathString = eZURLAliasML::convertPathToAlias( $nodePathString ); 02158 02159 // Attempt to translate the URL to something like "/content/view/full/84". 02160 $translateResult = eZURLAliasML::translate( $nodePathString ); 02161 02162 // handle redirects 02163 while ( $nodePathString === 'error/301' ) 02164 { 02165 $nodePathString = $translateResult; 02166 02167 $translateResult = eZURLAliasML::translate( $nodePathString ); 02168 } 02169 02170 // Get the ID of the node (which is the last part of the translated path). 02171 if ( preg_match( "#^content/view/full/([0-9]+)$#", $nodePathString, $matches ) ) 02172 { 02173 $nodeID = $matches[1]; 02174 } 02175 else 02176 { 02177 $ini = eZINI::instance( 'webdav.ini' ); 02178 if ( $ini->hasVariable( 'GeneralSettings', 'StartNode' ) ) 02179 { 02180 $nodeID = $ini->variable( 'GeneralSettings', 'StartNode' ); 02181 } 02182 } 02183 02184 // Attempt to fetch the node. 02185 $node = eZContentObjectTreeNode::fetch( $nodeID ); 02186 02187 // Return the node. 02188 return $node; 02189 } 02190 02191 /** 02192 * Creates a new collection (folder) at the given path $target. 02193 * 02194 * @param string $target Eg. '/plain_site_user/Content/Folder1' 02195 * @return bool 02196 */ 02197 protected function createCollection( $target ) 02198 { 02199 $target = $this->splitFirstPathElement( $target, $currentSite ); 02200 02201 if ( !$currentSite ) 02202 { 02203 // Site list cannot get new entries 02204 return false; // @as self::FAILED_FORBIDDEN; 02205 } 02206 02207 return $this->mkcolVirtualFolder( $currentSite, $target ); 02208 } 02209 02210 /** 02211 * Handles collection creation on the virtual folder level. 02212 * 02213 * It will check if the target is below a content folder in which it 02214 * calls mkcolContent(). 02215 * 02216 * @param string $currentSite Eg. 'plain_site_user' 02217 * @param string $target Eg. 'Content/Folder1' 02218 * @return bool 02219 */ 02220 protected function mkcolVirtualFolder( $currentSite, $target ) 02221 { 02222 $target = $this->splitFirstPathElement( $target, $virtualFolder ); 02223 02224 if ( !in_array( $virtualFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 02225 { 02226 return false; // @as self::FAILED_NOT_FOUND; 02227 } 02228 02229 if ( !$target ) 02230 { 02231 // We have reached the end of the path 02232 // We do not allow 'mkcol' operations for the virtual folder. 02233 return false; // @as self::FAILED_FORBIDDEN; 02234 } 02235 02236 if ( $virtualFolder === self::virtualContentFolderName() or 02237 $virtualFolder === self::virtualMediaFolderName() ) 02238 { 02239 return $this->mkcolContent( $currentSite, $virtualFolder, $target ); 02240 } 02241 02242 return false; // @as self::FAILED_FORBIDDEN; 02243 } 02244 02245 /** 02246 * Handles collection creation on the content tree level. 02247 * 02248 * It will try to find the parent node of the wanted placement and 02249 * create a new collection (folder etc.) as a child. 02250 * 02251 * @param string $currentSite Eg. 'plain_site_user' 02252 * @param string $virtualFolder Eg. 'Content' 02253 * @param string $target Eg. 'Folder1' 02254 * @return bool 02255 */ 02256 protected function mkcolContent( $currentSite, $virtualFolder, $target ) 02257 { 02258 $nodePath = $this->internalNodePath( $virtualFolder, $target ); 02259 $node = $this->fetchNodeByTranslation( $nodePath ); 02260 if ( $node ) 02261 { 02262 return false; // @as self::FAILED_EXISTS; 02263 } 02264 02265 $parentNode = $this->fetchParentNodeByTranslation( $nodePath ); 02266 02267 if ( !$parentNode ) 02268 { 02269 return false; // @as self::FAILED_NOT_FOUND; 02270 } 02271 02272 // Can we create a collection in the parent node 02273 if ( !$parentNode->canRead() ) 02274 { 02275 return false; // @as self::FAILED_FORBIDDEN; 02276 } 02277 02278 return $this->createFolder( $parentNode, $nodePath ); 02279 } 02280 02281 /** 02282 * Creates a new folder under the given $target path. 02283 * 02284 * @param eZContentObject $parentNode 02285 * @param string $target Eg. 'Folder1' 02286 * @return bool 02287 */ 02288 protected function createFolder( $parentNode, $target ) 02289 { 02290 // Grab settings from the ini file: 02291 $webdavINI = eZINI::instance( self::WEBDAV_INI_FILE ); 02292 $folderClassID = $webdavINI->variable( 'FolderSettings', 'FolderClass' ); 02293 $languageCode = eZContentObject::defaultLanguage(); 02294 02295 $contentObject = eZContentObject::createWithNodeAssignment( $parentNode, $folderClassID, $languageCode ); 02296 if ( $contentObject ) 02297 { 02298 $db = eZDB::instance(); 02299 $db->begin(); 02300 $version = $contentObject->version( 1 ); 02301 $version->setAttribute( 'status', eZContentObjectVersion::STATUS_DRAFT ); 02302 $version->store(); 02303 02304 $contentObjectID = $contentObject->attribute( 'id' ); 02305 $contentObjectAttributes = $version->contentObjectAttributes(); 02306 02307 // @todo @as avoid using [0] (could be another index in some classes) 02308 $contentObjectAttributes[0]->setAttribute( 'data_text', basename( $target ) ); 02309 $contentObjectAttributes[0]->store(); 02310 $db->commit(); 02311 02312 $operationResult = eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $contentObjectID, 02313 'version' => 1 ) ); 02314 return true; // @as self::OK_CREATED; 02315 } 02316 else 02317 { 02318 return false; // @as self::FAILED_FORBIDDEN; 02319 } 02320 } 02321 02322 /** 02323 * Handles deletion on the virtual folder level. 02324 * 02325 * It will check if the target is below a content folder in which it calls 02326 * deleteContent(). 02327 * 02328 * @param string $currentSite Eg. 'plain_site_user' 02329 * @param string $target Eg. 'Content/Folder1/file1.txt' 02330 * @return bool 02331 */ 02332 protected function deleteVirtualFolder( $currentSite, $target ) 02333 { 02334 $target = $this->splitFirstPathElement( $target, $virtualFolder ); 02335 02336 if ( !in_array( $virtualFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 02337 { 02338 return false; // @as self::FAILED_NOT_FOUND; 02339 } 02340 02341 if ( !$target ) 02342 { 02343 // We have reached the end of the path 02344 // We do not allow 'delete' operations for the virtual folder. 02345 return false; // @as self::FAILED_FORBIDDEN; 02346 } 02347 02348 if ( $virtualFolder === self::virtualContentFolderName() or 02349 $virtualFolder === self::virtualMediaFolderName() ) 02350 { 02351 return $this->deleteContent( $currentSite, $virtualFolder, $target ); 02352 } 02353 02354 return false; // @as self::FAILED_FORBIDDEN; 02355 } 02356 02357 /** 02358 * Handles deletion on the content tree level. 02359 * 02360 * It will try to find the node with the path $target and then try to 02361 * remove it (ie. move to trash) if the user is allowed. 02362 * 02363 * @param string $currentSite Eg. 'plain_site_user' 02364 * @param string $virtualFolder Eg. 'Content' 02365 * @param string $target Eg. 'Folder1/file1.txt' 02366 * @return bool 02367 */ 02368 protected function deleteContent( $currentSite, $virtualFolder, $target ) 02369 { 02370 $nodePath = $this->internalNodePath( $virtualFolder, $target ); 02371 $node = $this->fetchNodeByTranslation( $nodePath ); 02372 02373 if ( $node === null ) 02374 { 02375 return false; // @as self::FAILED_NOT_FOUND; 02376 } 02377 02378 // Can we delete the node? 02379 if ( !$node->canRead() or 02380 !$node->canRemove() ) 02381 { 02382 return false; // @as self::FAILED_FORBIDDEN; 02383 } 02384 02385 // added by @as: use the content.ini setting for handling delete: 'delete' or 'trash' 02386 // default is 'trash' 02387 $contentINI = eZINI::instance( 'content.ini' ); 02388 $removeAction = $contentINI->hasVariable( 'RemoveSettings', 'DefaultRemoveAction' ) ? 02389 $contentINI->variable( 'RemoveSettings', 'DefaultRemoveAction' ) : 'trash'; 02390 02391 if ( $removeAction !== 'trash' && $removeAction !== 'delete' ) 02392 { 02393 // default remove action is to trash 02394 $removeAction = 'trash'; 02395 } 02396 02397 $moveToTrash = ( $removeAction === 'trash' ) ? true : false; 02398 02399 $node->removeNodeFromTree( $moveToTrash ); 02400 return true; // @as self::OK; 02401 } 02402 02403 /** 02404 * Handles data storage on the content tree level. 02405 * 02406 * It will check if the target is below a content folder in which it calls 02407 * putContentData(). 02408 * 02409 * @param string $currentSite Eg. 'plain_site_user' 02410 * @param string $target Eg. 'Content/Folder1/file1.txt' 02411 * @param string $tempFile The temporary file holding the contents 02412 * @return bool 02413 */ 02414 protected function putVirtualFolderData( $currentSite, $target, $tempFile ) 02415 { 02416 $target = $this->splitFirstPathElement( $target, $virtualFolder ); 02417 02418 if ( !$target ) 02419 { 02420 // We have reached the end of the path 02421 // We do not allow 'put' operations for the virtual folder. 02422 return false; // @as self::FAILED_FORBIDDEN; 02423 } 02424 02425 if ( !in_array( $virtualFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 02426 { 02427 return false; // @as self::FAILED_CONFLICT; 02428 } 02429 02430 if ( $virtualFolder === self::virtualContentFolderName() or 02431 $virtualFolder === self::virtualMediaFolderName() ) 02432 { 02433 $result = $this->putContentData( $currentSite, $virtualFolder, $target, $tempFile ); 02434 return $result; 02435 } 02436 02437 return false; // @as self::FAILED_FORBIDDEN; 02438 } 02439 02440 /** 02441 * Handles data storage on the content tree level. 02442 * 02443 * It will try to find the parent node of the wanted placement and 02444 * create a new object with data from $tempFile. 02445 * 02446 * @param string $currentSite Eg. 'plain_site_user' 02447 * @param string $virtualFolder Eg. 'Content' 02448 * @param string $target Eg. 'Folder1/file1.txt' 02449 * @param string $tempFile The temporary file holding the contents 02450 * @return bool 02451 * @todo remove/replace eZContentUpload 02452 */ 02453 protected function putContentData( $currentSite, $virtualFolder, $target, $tempFile ) 02454 { 02455 $nodePath = $this->internalNodePath( $virtualFolder, $target ); 02456 02457 $parentNode = $this->fetchParentNodeByTranslation( $nodePath ); 02458 if ( $parentNode === null ) 02459 { 02460 // The node does not exist, so we cannot put the file 02461 return false; // @as self::FAILED_CONFLICT; 02462 } 02463 02464 // Can we put content in the parent node 02465 if ( !$parentNode->canRead() ) 02466 { 02467 return false; // @as self::FAILED_FORBIDDEN; 02468 } 02469 02470 $parentNodeID = $parentNode->attribute( 'node_id' ); 02471 02472 $existingNode = $this->fetchNodeByTranslation( $nodePath ); 02473 02474 $upload = new eZContentUpload(); 02475 02476 if ( !$upload->handleLocalFile( $result, $tempFile, $parentNodeID, $existingNode ) ) 02477 { 02478 if ( $result['status'] === eZContentUpload::STATUS_PERMISSION_DENIED ) 02479 { 02480 return false; // @as self::FAILED_FORBIDDEN; 02481 } 02482 else 02483 { 02484 return false; // @as self::FAILED_UNSUPPORTED; 02485 } 02486 } 02487 02488 return true; // @as self::OK_CREATED; 02489 } 02490 02491 /** 02492 * Handles copying on the virtual folder level. 02493 * 02494 * It will check if the target is below a content folder in which it 02495 * calls copyContent(). 02496 * 02497 * @param string $sourceSite Eg. 'plain_site_user' 02498 * @param string $destinationSite Eg. 'plain_site_user' 02499 * @param string $source Eg. 'Content/Folder1/file1.txt' 02500 * @param string $destination Eg. 'Content/Folder2/file1.txt' 02501 * @return bool 02502 */ 02503 protected function copyVirtualFolder( $sourceSite, $destinationSite, 02504 $source, $destination ) 02505 { 02506 $source = $this->splitFirstPathElement( $source, $sourceVFolder ); 02507 $destination = $this->splitFirstPathElement( $destination, $destinationVFolder ); 02508 02509 if ( !in_array( $sourceVFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 02510 { 02511 return false; // @as self::FAILED_NOT_FOUND; 02512 } 02513 02514 if ( !in_array( $destinationVFolder, array( self::virtualContentFolderName(), self::virtualMediaFolderName() ) ) ) 02515 { 02516 return false; // @as self::FAILED_NOT_FOUND; 02517 } 02518 02519 if ( !$source or 02520 !$destination ) 02521 { 02522 // We have reached the end of the path for source or destination 02523 // We do not allow 'copy' operations for the virtual folder (from or to) 02524 return false; // @as self::FAILED_FORBIDDEN; 02525 } 02526 02527 if ( ( $sourceVFolder === self::virtualContentFolderName() or 02528 $sourceVFolder === self::virtualMediaFolderName() ) and 02529 ( $destinationVFolder === self::virtualContentFolderName() or 02530 $destinationVFolder === self::virtualMediaFolderName() ) ) 02531 { 02532 return $this->copyContent( $sourceSite, $destinationSite, 02533 $sourceVFolder, $destinationVFolder, 02534 $source, $destination ); 02535 } 02536 02537 return false; // @as self::FAILED_FORBIDDEN; 02538 } 02539 02540 /** 02541 * Copies the node specified by the path $source to $destination. 02542 * 02543 * @param string $sourceSite Eg. 'plain_site_user' 02544 * @param string $destinationSite Eg. 'plain_site_user' 02545 * @param string $sourceVFolder Eg. 'Content' 02546 * @param string $destinationVFolder Eg. 'Content' 02547 * @param string $source Eg. 'Folder1/file1.txt' 02548 * @param string $destination Eg. 'Folder2/file1.txt' 02549 * @return bool 02550 */ 02551 protected function copyContent( $sourceSite, $destinationSite, 02552 $sourceVFolder, $destinationVFolder, 02553 $source, $destination ) 02554 { 02555 $nodePath = $this->internalNodePath( $sourceVFolder, $source ); 02556 $destinationNodePath = $this->internalNodePath( $destinationVFolder, $destination ); 02557 02558 // Get rid of possible extensions, remove .jpeg .txt .html etc.. 02559 $source = $this->fileBasename( $source ); 02560 02561 $sourceNode = $this->fetchNodeByTranslation( $nodePath ); 02562 02563 if ( !$sourceNode ) 02564 { 02565 return false; // @as self::FAILED_NOT_FOUND; 02566 } 02567 02568 $object = $sourceNode->attribute( 'object' ); 02569 $classID = $object->attribute( 'contentclass_id' ); 02570 02571 // Get rid of possible extensions, remove .jpeg .txt .html etc.. 02572 $destination = $this->fileBasename( $destination ); 02573 02574 $destinationNode = $this->fetchNodeByTranslation( $destinationNodePath ); 02575 02576 // @as @todo check if this is needed 02577 // if ( $destinationNode ) 02578 // { 02579 // return false; // @as self::FAILED_EXISTS; 02580 // } 02581 02582 $destinationNode = $this->fetchParentNodeByTranslation( $destinationNodePath ); 02583 02584 if ( !$destinationNode ) 02585 { 02586 return false; // @as self::FAILED_NOT_FOUND; 02587 } 02588 02589 // Can we move the node to $destinationNode 02590 if ( !$destinationNode->canMoveTo( $classID ) ) 02591 { 02592 return false; // @as self::FAILED_FORBIDDEN; 02593 } 02594 02595 $srcParentPath = $this->splitLastPathElement( $nodePath, $srcNodeName ); 02596 $dstParentPath = $this->splitLastPathElement( $destinationNodePath, $dstNodeName ); 02597 if ( $srcParentPath === $dstParentPath ) 02598 { 02599 // @todo check if copy in the same folder is correct 02600 return $this->copyObjectSameDirectory( $sourceNode, $destinationNode, $this->fileBasename( $dstNodeName ) ); 02601 } 02602 else 02603 { 02604 // @todo check if copy in different folders is correct 02605 return $this->copyObject( $sourceNode, $destinationNode ); 02606 } 02607 02608 /* 02609 // Todo: add lookup of the name setting for the current object 02610 $contentObjectID = $object->attribute( 'id' ); 02611 $contentObjectAttributes =& $object->contentObjectAttributes(); 02612 // @todo @as avoid using [0] (could be another index in some classes) 02613 $contentObjectAttributes[0]->setAttribute( 'data_text', basename( $destination ) ); 02614 $contentObjectAttributes[0]->store(); 02615 02616 $operationResult = eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $contentObjectID, 'version' => 1 ) ); 02617 $object->store(); 02618 */ 02619 02620 // @todo Unreachable code? 02621 return true; // @as self::OK_CREATED; 02622 } 02623 02624 /** 02625 * Copies the specified object as a child to the node $newParentNode. 02626 * 02627 * @param eZContentObject $object 02628 * @param eZContentObject $newParentNode 02629 * @return bool 02630 */ 02631 protected function copyObject( $object, $newParentNode ) 02632 { 02633 $object = $object->ContentObject; 02634 $newParentNodeID = $newParentNode->attribute( 'node_id' ); 02635 $classID = $object->attribute( 'contentclass_id' ); 02636 02637 if ( !$newParentNode->checkAccess( 'create', $classID ) ) 02638 { 02639 $objectID = $object->attribute( 'id' ); 02640 return false; 02641 } 02642 02643 $db = eZDB::instance(); 02644 $db->begin(); 02645 $newObject = $object->copy( true ); 02646 02647 // We should reset section that will be updated in updateSectionID(). 02648 // If sectionID is 0 than the object has been newly created 02649 $newObject->setAttribute( 'section_id', 0 ); 02650 $newObject->store(); 02651 02652 $curVersion = $newObject->attribute( 'current_version' ); 02653 $curVersionObject = $newObject->attribute( 'current' ); 02654 $newObjAssignments = $curVersionObject->attribute( 'node_assignments' ); 02655 unset( $curVersionObject ); 02656 02657 // remove old node assignments 02658 foreach ( $newObjAssignments as $assignment ) 02659 { 02660 $assignment->purge(); 02661 } 02662 02663 // and create a new one 02664 $nodeAssignment = eZNodeAssignment::create( array( 02665 'contentobject_id' => $newObject->attribute( 'id' ), 02666 'contentobject_version' => $curVersion, 02667 'parent_node' => $newParentNodeID, 02668 'is_main' => 1 02669 ) ); 02670 $nodeAssignment->store(); 02671 02672 // publish the newly created object 02673 eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $newObject->attribute( 'id' ), 02674 'version' => $curVersion ) ); 02675 // Update "is_invisible" attribute for the newly created node. 02676 $newNode = $newObject->attribute( 'main_node' ); 02677 eZContentObjectTreeNode::updateNodeVisibility( $newNode, $newParentNode ); 02678 02679 $db->commit(); 02680 return true; 02681 } 02682 02683 /** 02684 * Copies the specified object to the same folder, with optional $destinationName. 02685 * 02686 * @param eZContentObject $object 02687 * @param eZContentObject $newParentNode 02688 * @param string $destinationName 02689 * @return bool 02690 */ 02691 protected function copyObjectSameDirectory( $object, $newParentNode, $destinationName = null ) 02692 { 02693 $object = $object->ContentObject; 02694 $newParentNodeID = $newParentNode->attribute( 'node_id' ); 02695 $classID = $object->attribute( 'contentclass_id' ); 02696 02697 if ( !$newParentNode->checkAccess( 'create', $classID ) ) 02698 { 02699 $objectID = $object->attribute( 'id' ); 02700 return false; 02701 } 02702 02703 $db = eZDB::instance(); 02704 $db->begin(); 02705 $newObject = $object->copy( true ); 02706 02707 // We should reset section that will be updated in updateSectionID(). 02708 // If sectionID is 0 than the object has been newly created 02709 $newObject->setAttribute( 'section_id', 0 ); 02710 02711 // @as 2009-01-08 - Added check for destination name the same as the source name 02712 // (this was causing problems with nodes disappearing after renaming) 02713 $newName = $destinationName; 02714 if ( $destinationName === null 02715 || strtolower( $destinationName ) === strtolower( $object->attribute( 'name' ) ) ) 02716 { 02717 $newName = 'Copy of ' . $object->attribute( 'name' ); 02718 } 02719 02720 // @todo @as avoid using [0] (could be another index in some classes) 02721 $contentObjectAttributes = $newObject->contentObjectAttributes(); 02722 $contentObjectAttributes[0]->setAttribute( 'data_text', $newName ); 02723 $contentObjectAttributes[0]->store(); 02724 02725 $newObject->store(); 02726 02727 $curVersion = $newObject->attribute( 'current_version' ); 02728 $curVersionObject = $newObject->attribute( 'current' ); 02729 $newObjAssignments = $curVersionObject->attribute( 'node_assignments' ); 02730 unset( $curVersionObject ); 02731 02732 // remove old node assignments 02733 foreach ( $newObjAssignments as $assignment ) 02734 { 02735 $assignment->purge(); 02736 } 02737 02738 // and create a new one 02739 $nodeAssignment = eZNodeAssignment::create( array( 02740 'contentobject_id' => $newObject->attribute( 'id' ), 02741 'contentobject_version' => $curVersion, 02742 'parent_node' => $newParentNodeID, 02743 'is_main' => 1 02744 ) ); 02745 $nodeAssignment->store(); 02746 02747 // publish the newly created object 02748 eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $newObject->attribute( 'id' ), 02749 'version' => $curVersion ) ); 02750 // Update "is_invisible" attribute for the newly created node. 02751 $newNode = $newObject->attribute( 'main_node' ); 02752 eZContentObjectTreeNode::updateNodeVisibility( $newNode, $newParentNode ); 02753 02754 $db->commit(); 02755 return true; 02756 } 02757 02758 /** 02759 * Handles moving on the virtual folder level. 02760 * 02761 * It will check if the target is below a content folder in which it 02762 * calls moveContent(). 02763 * 02764 * @param string $sourceSite Eg. 'plain_site_user' 02765 * @param string $destinationSite Eg. 'plain_site_user' 02766 * @param string $source Eg. 'Content/Folder1/file1.txt' 02767 * @param string $destination Eg. 'Content/Folder2/file1.txt' 02768 * @return bool 02769 */ 02770 protected function moveVirtualFolder( $sourceSite, $destinationSite, 02771 $source, $destination, 02772 $fullSource, $fullDestination ) 02773 { 02774 $this->setCurrentSite( $sourceSite ); 02775 02776 $source = $this->splitFirstPathElement( $source, $sourceVFolder ); 02777 $destination = $this->splitFirstPathElement( $destination, $destinationVFolder ); 02778 02779 if ( !$source or 02780 !$destination ) 02781 { 02782 // We have reached the end of the path for source or destination 02783 // We do not allow 'move' operations for the virtual folder (from or to) 02784 return eZWebDAVServer::FAILED_FORBIDDEN; 02785 } 02786 02787 if ( ( $sourceVFolder === self::virtualContentFolderName() or 02788 $sourceVFolder === self::virtualMediaFolderName() ) and 02789 ( $destinationVFolder === self::virtualContentFolderName() or 02790 $destinationVFolder === self::virtualMediaFolderName() ) ) 02791 { 02792 return $this->moveContent( $sourceSite, $destinationSite, 02793 $sourceVFolder, $destinationVFolder, 02794 $source, $destination, 02795 $fullSource, $fullDestination ); 02796 } 02797 02798 return eZWebDAVServer::FAILED_FORBIDDEN; 02799 } 02800 02801 /** 02802 * Moves the node specified by the path $source to $destination. 02803 * 02804 * @param string $sourceSite Eg. 'plain_site_user' 02805 * @param string $destinationSite Eg. 'plain_site_user' 02806 * @param string $sourceVFolder Eg. 'Content' 02807 * @param string $destinationVFolder Eg. 'Content' 02808 * @param string $source Eg. 'Folder1/file1.txt' 02809 * @param string $destination Eg. 'Folder2/file1.txt' 02810 * @return bool 02811 */ 02812 protected function moveContent( $sourceSite, $destinationSite, 02813 $sourceVFolder, $destinationVFolder, 02814 $source, $destination, 02815 $fullSource, $fullDestination ) 02816 { 02817 $nodePath = $this->internalNodePath( $sourceVFolder, $source ); 02818 $destinationNodePath = $this->internalNodePath( $destinationVFolder, $destination ); 02819 02820 // Get rid of possible extensions, remove .jpeg .txt .html etc.. 02821 $source = $this->fileBasename( $source ); 02822 02823 $sourceNode = $this->fetchNodeByTranslation( $nodePath ); 02824 02825 if ( !$sourceNode ) 02826 { 02827 return false; // @as self::FAILED_NOT_FOUND; 02828 } 02829 02830 // Can we move the node from $sourceNode 02831 if ( !$sourceNode->canMoveFrom() ) 02832 { 02833 $this->appendLogEntry( "No access to move the node '$sourceSite':'$nodePath'", 'CS:moveContent' ); 02834 return false; // @as self::FAILED_FORBIDDEN; 02835 } 02836 02837 $object = $sourceNode->attribute( 'object' ); 02838 $classID = $object->attribute( 'contentclass_id' ); 02839 02840 // Get rid of possible extensions, remove .jpeg .txt .html etc.. 02841 $destination = $this->fileBasename( $destination ); 02842 02843 $destinationNode = $this->fetchNodeByTranslation( $destinationNodePath ); 02844 $this->appendLogEntry( "Destination: $destinationNodePath", 'CS:moveContent' ); 02845 02846 if ( $destinationNode ) 02847 { 02848 return false; // @as self::FAILED_EXISTS; 02849 } 02850 02851 $destinationNode = $this->fetchParentNodeByTranslation( $destinationNodePath ); 02852 02853 // Can we move the node to $destinationNode 02854 if ( !$destinationNode->canMoveTo( $classID ) ) 02855 { 02856 $this->appendLogEntry( "No access to move the node '$sourceSite':'$nodePath' to '$destinationSite':'$destinationNodePath'", 'CS:moveContent' ); 02857 return false; // @as self::FAILED_FORBIDDEN; 02858 } 02859 02860 $srcParentPath = $this->splitLastPathElement( $nodePath, $srcNodeName ); 02861 $dstParentPath = $this->splitLastPathElement( $destinationNodePath, $dstNodeName ); 02862 if ( $srcParentPath == $dstParentPath ) 02863 { 02864 // @as 2009-03-02 - removed urldecode of name before renaming 02865 $dstNodeName = $this->fileBasename( $dstNodeName ); 02866 if( !$object->rename( $dstNodeName ) ) 02867 { 02868 $this->appendLogEntry( "Unable to rename the node '$sourceSite':'$nodePath' to '$destinationSite':'$destinationNodePath'", 'CS:moveContent' ); 02869 return false; // @as self::FAILED_FORBIDDEN; 02870 } 02871 } 02872 else 02873 { 02874 if( !eZContentObjectTreeNodeOperations::move( $sourceNode->attribute( 'node_id' ), $destinationNode->attribute( 'node_id' ) ) ) 02875 { 02876 $this->appendLogEntry( "Unable to move the node '$sourceSite':'$nodePath' to '$destinationSite':'$destinationNodePath'", 'CS:moveContent' ); 02877 return false; // @as self::FAILED_FORBIDDEN; 02878 } 02879 } 02880 02881 /* 02882 02883 // Todo: add lookup of the name setting for the current object 02884 $contentObjectID = $object->attribute( 'id' ); 02885 $contentObjectAttributes =& $object->contentObjectAttributes(); 02886 $contentObjectAttributes[0]->setAttribute( 'data_text', basename( $destination ) ); 02887 $contentObjectAttributes[0]->store(); 02888 02889 $operationResult = eZOperationHandler::execute( 'content', 'publish', array( 'object_id' => $contentObjectID, 'version' => 1 ) ); 02890 $object->store(); 02891 */ 02892 02893 return true; // @as self::OK_CREATED; 02894 } 02895 02896 /** 02897 * Returns whether $class is an folder class. 02898 * 02899 * @param resource $object 02900 * @param resource $class 02901 */ 02902 protected function isObjectFolder( $object, $class ) 02903 { 02904 $classIdentifier = $class->attribute( 'identifier' ); 02905 02906 return in_array( $classIdentifier, $this->FolderClasses ); 02907 } 02908 02909 /** 02910 * Stores $contents to a temporary location under the file name $target. 02911 * 02912 * @param string $target 02913 * @param string $contents 02914 * @return string The name of the temp file or false if it failed. 02915 * @todo remove or replace with eZ Components functionality 02916 */ 02917 protected function storeUploadedFile( $target, $contents ) 02918 { 02919 $dir = self::tempDirectory() . '/' . md5( microtime() . '-' . $target ); 02920 $filePath = $dir . '/' . basename( $target ); 02921 02922 if ( !file_exists( $dir ) ) 02923 { 02924 eZDir::mkdir( $dir, false, true ); 02925 } 02926 02927 $result = file_put_contents( $filePath, $contents ); 02928 if ( !$result ) 02929 { 02930 $result = file_exists( $filePath ); 02931 } 02932 02933 if ( $result ) 02934 { 02935 return $filePath; 02936 } 02937 return false; 02938 } 02939 02940 /** 02941 * Returns the path to the WebDAV temporary directory. 02942 * 02943 * If the directory does not exist yet, it will be created first. 02944 * 02945 * @return string 02946 * @todo remove or replace with eZ Components functionality 02947 */ 02948 protected static function tempDirectory() 02949 { 02950 $tempDir = eZSys::varDirectory() . '/webdav/tmp'; 02951 if ( !file_exists( $tempDir ) ) 02952 { 02953 eZDir::mkdir( $tempDir, eZDir::directoryPermission(), true ); 02954 } 02955 return $tempDir; 02956 } 02957 02958 /** 02959 * Returns $name without the final suffix (.jpg, .gif etc.). 02960 * 02961 * @param string $name 02962 * @return string 02963 * @todo remove or replace 02964 */ 02965 protected function fileBasename( $name ) 02966 { 02967 $pos = strrpos( $name, '.' ); 02968 if ( $pos !== false ) 02969 { 02970 $name = substr( $name, 0, $pos ); 02971 } 02972 return $name; 02973 } 02974 02975 /** 02976 * Returns a path that corresponds to the internal path of nodes. 02977 * 02978 * @param string $virtualFolder 02979 * @param string $collection 02980 * @return string 02981 * @todo remove or replace 02982 */ 02983 protected function internalNodePath( $virtualFolder, $collection ) 02984 { 02985 // All root nodes needs to prepend their name to get the correct path 02986 // except for the content root which uses the path directly. 02987 if ( $virtualFolder === self::virtualMediaFolderName() ) 02988 { 02989 $nodePath = 'media'; 02990 if ( strlen( $collection ) > 0 ) 02991 { 02992 $nodePath .= '/' . $collection; 02993 } 02994 } 02995 else 02996 { 02997 $nodePath = $collection; 02998 } 02999 return $nodePath; 03000 } 03001 03002 /** 03003 * Takes the first path element from \a $path and removes it from 03004 * the path, the extracted part will be placed in \a $name. 03005 * 03006 * <code> 03007 * $path = '/path/to/item/'; 03008 * $newPath = self::splitFirstPathElement( $path, $root ); 03009 * print( $root ); // prints 'path', $newPath is now 'to/item/' 03010 * $newPath = self::splitFirstPathElement( $newPath, $second ); 03011 * print( $second ); // prints 'to', $newPath is now 'item/' 03012 * $newPath = self::splitFirstPathElement( $newPath, $third ); 03013 * print( $third ); // prints 'item', $newPath is now '' 03014 * </code> 03015 * @param string $path A path of elements delimited by a slash, if the path ends with a slash it will be removed 03016 * @param string &$element The name of the first path element without any slashes 03017 * @return string The rest of the path without the ending slash 03018 * @todo remove or replace 03019 */ 03020 protected function splitFirstPathElement( $path, &$element ) 03021 { 03022 if ( $path[0] === '/' ) 03023 { 03024 $path = substr( $path, 1 ); 03025 } 03026 $pos = strpos( $path, '/' ); 03027 if ( $pos === false ) 03028 { 03029 $element = $path; 03030 $path = ''; 03031 } 03032 else 03033 { 03034 $element = substr( $path, 0, $pos ); 03035 $path = substr( $path, $pos + 1 ); 03036 } 03037 return $path; 03038 } 03039 03040 /** 03041 * Takes the last path element from \a $path and removes it from 03042 * the path, the extracted part will be placed in \a $name. 03043 * 03044 * <code> 03045 * $path = '/path/to/item/'; 03046 * $newPath = self::splitLastPathElement( $path, $root ); 03047 * print( $root ); // prints 'item', $newPath is now '/path/to' 03048 * $newPath = self::splitLastPathElement( $newPath, $second ); 03049 * print( $second ); // prints 'to', $newPath is now '/path' 03050 * $newPath = self::splitLastPathElement( $newPath, $third ); 03051 * print( $third ); // prints 'path', $newPath is now '' 03052 * </code> 03053 * @param string $path A path of elements delimited by a slash, if the path ends with a slash it will be removed 03054 * @param string &$element The name of the first path element without any slashes 03055 * @return string The rest of the path without the ending slash 03056 * @todo remove or replace 03057 */ 03058 protected function splitLastPathElement( $path, &$element ) 03059 { 03060 $len = strlen( $path ); 03061 if ( $len > 0 and $path[$len - 1] === '/' ) 03062 { 03063 $path = substr( $path, 0, $len - 1 ); 03064 } 03065 $pos = strrpos( $path, '/' ); 03066 if ( $pos === false ) 03067 { 03068 $element = $path; 03069 $path = ''; 03070 } 03071 else 03072 { 03073 $element = substr( $path, $pos + 1 ); 03074 $path = substr( $path, 0, $pos ); 03075 } 03076 return $path; 03077 } 03078 03079 /** 03080 * Logs to var/log/webdav.log AND var/<site_name>/log/webdav.log. 03081 * 03082 * From eZ Publish. 03083 * 03084 * @param string $logString String to record 03085 * @param string $label Label to put in front of $logString 03086 */ 03087 public static function appendLogEntry( $logString, $label = false ) 03088 { 03089 if ( !isset( self::$useLogging ) ) 03090 { 03091 $webdavINI = eZINI::instance( self::WEBDAV_INI_FILE ); 03092 self::$useLogging = $webdavINI->variable( 'GeneralSettings', 'Logging' ) === 'enabled'; 03093 } 03094 03095 if ( self::$useLogging ) 03096 { 03097 if ( PHP_SAPI === 'cli' ) 03098 { 03099 // var_dump( $logString ); 03100 } 03101 else 03102 { 03103 $varDir = realpath( eZSys::varDirectory() ); 03104 $logDir = 'log'; 03105 $logName = 'webdav.log'; 03106 $fileName = $varDir . DIRECTORY_SEPARATOR . $logDir . DIRECTORY_SEPARATOR . $logName; 03107 if ( !file_exists( $varDir . DIRECTORY_SEPARATOR . $logDir ) ) 03108 { 03109 eZDir::mkdir( $varDir . DIRECTORY_SEPARATOR . $logDir, 0775, true ); 03110 } 03111 03112 $logFile = fopen( $fileName, 'a' ); 03113 $nowTime = date( "Y-m-d H:i:s : " ); 03114 $text = $nowTime . $logString; 03115 if ( $label ) 03116 { 03117 $text .= ' [' . $label . ']'; 03118 } 03119 fwrite( $logFile, $text . "\n" ); 03120 fclose( $logFile ); 03121 } 03122 } 03123 } 03124 03125 /** 03126 * Recodes $string from charset $fromCharset to charset $toCharset. 03127 * 03128 * Method from eZWebDAVServer. 03129 * 03130 * @param string $string 03131 * @param string $fromCharset 03132 * @param string $toCharset 03133 * @param bool $stop 03134 * @return string 03135 */ 03136 protected static function recode( $string, $fromCharset, $toCharset, $stop = false ) 03137 { 03138 $codec = eZTextCodec::instance( $fromCharset, $toCharset, false ); 03139 if ( $codec ) 03140 { 03141 $string = $codec->convertString( $string ); 03142 } 03143 03144 return $string; 03145 } 03146 03147 /** 03148 * Encodes the path stored in $response in order to be displayed properly 03149 * in WebDAV clients. 03150 * 03151 * Code from eZWebDAVServer::outputCollectionContent. 03152 * 03153 * @param ezcWebdavResponse $response 03154 * @return ezcWebdavResponse 03155 */ 03156 protected function encodeResponse( ezcWebdavResponse $response ) 03157 { 03158 03159 return $response; 03160 } 03161 03162 /** 03163 * The name of the content folder in eZ Publish, translated. 03164 * 03165 * @return string 03166 */ 03167 public static function virtualContentFolderName() 03168 { 03169 return ezpI18n::tr( 'kernel/content', 'Content' ); 03170 } 03171 03172 /** 03173 * The name of the media folder in eZ Publish, translated. 03174 * 03175 * @return string 03176 */ 03177 public static function virtualMediaFolderName() 03178 { 03179 return ezpI18n::tr( 'kernel/content', 'Media' ); 03180 } 03181 } 03182 ?>