eZ Publish  [trunk]
ezwebdavcontentbackend.php
Go to the documentation of this file.
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 ?>