Sindbad~EG File Manager

Current Path : /var/www/web3/modules/webdav/classes/
Upload File :
Current File : //var/www/web3/modules/webdav/classes/WebDavHelper.class

<?php
/*
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2007 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */

GalleryCoreApi::requireOnce('modules/webdav/lib/HTTP/WebDAV/Server.php');

/* WebDAV status codes */
define('WEBDAV_STATUS_NO_XML_PARSER', 0x00000002);
define('WEBDAV_STATUS_METHOD_NOT_HANDLED', 0x00000004);
define('WEBDAV_STATUS_HTTPAUTH_MODULE_DISABLED', 0x00000008);
define('WEBDAV_STATUS_REWRITE_MODULE_DISABLED', 0x00000010);
define('WEBDAV_STATUS_CONNECT_RULE_DISABLED', 0x00000020);
define('WEBDAV_STATUS_MISSING_DAV_HEADERS', 0x00000040);
define('WEBDAV_STATUS_ALTERNATIVE_URL_HEADERS', 0x00000080);
define('WEBDAV_STATUS_BAD_REWRITE_PARSER', 0x00000100);
define('WEBDAV_STATUS_OPTIONS_RULE_DISABLED', 0x00000200);
define('WEBDAV_STATUS_HTTPAUTH_AUTH_PLUGINS_DISABLED', 0x00000400);
define('WEBDAV_STATUS_ERROR_UNKNOWN', 0x80000000);

/* Gallery property namespace - RFC2518 18 */
define('WEBDAV_GALLERY_NAMESPACE', 'http://gallery2.org/dav/props/');

/**
 * WebDAV helper class.
 * @package WebDav
 * @subpackage Classes
 * @author Jack Bates <ms419@freezone.co.uk>
 * @version $Revision: 16508 $
 * @static
 */
class WebDavHelper {

    /**
     * Check this module's configuration.
     * @return array object GalleryStatus a status code
     *               int WebDAV status code
     */
    function checkConfiguration() {
	global $gallery;
	$phpVm = $gallery->getPhpVm();
	$urlGenerator =& $gallery->getUrlGenerator();

	$code = 0x00000000;

	list ($ret, $moduleStatus) = GalleryCoreApi::fetchPluginList('module');
	if ($ret) {
	    return array($ret, null);
	}

	/*
	 * URL rewrite module must be enabled.  Check it regardless of missing DAV headers because
	 * it also implies the connect rule is disabled.  Check it before checking for missing DAV
	 * headers causes because it is a missing DAV headers cause.
	 */
	if (empty($moduleStatus['rewrite']['active'])) {
	    $code |= WEBDAV_STATUS_REWRITE_MODULE_DISABLED;
	} else {
	    list ($ret, $rewriteApi) = GalleryCoreApi::newFactoryInstance('RewriteApi');
	    if ($ret) {
		return array($ret, null);
	    }
	    if (!isset($rewriteApi)) {
		return array(GalleryCoreApi::error(ERROR_CONFIGURATION_REQUIRED), null);
	    }

	    list ($ret, $isCompatible) = $rewriteApi->isCompatibleWithApi(array(1, 1));
	    if ($ret) {
		return array($ret, null);
	    }
	    if (!$isCompatible) {
		return array(GalleryCoreApi::error(ERROR_CONFIGURATION_REQUIRED), null);
	    }

	    list ($ret, $activeRules) = $rewriteApi->fetchActiveRulesForModule('webdav');
	    if ($ret) {
		return array($ret, null);
	    }
	}

	/*
	 * Check for missing DAV headers causes first so we can show the error unknown warning if no
	 * causes are found.
	 */
	if (!WebDavHelper::checkDavHeaders($urlGenerator->generateUrl(
		array('controller' => 'webdav.WebDav'),
		array('forceFullUrl' => true,
		      'htmlEntities' => false)))) {

	    /* Already checked one cause: URL rewrite module disabled.  Check other causes. */
	    if (!WebDavHelper::checkDavHeaders($urlGenerator->generateUrl(
		    array('href' => 'modules/webdav/data/options/'),
		    array('forceFullUrl' => true,
			  'htmlEntities' => false)))) {
		$code |= WEBDAV_STATUS_ALTERNATIVE_URL_HEADERS;
	    }

	    if (!empty($moduleStatus['rewrite']['active'])) {
		if ($rewriteApi->getParserType() != 'preGallery') {
		    $code |= WEBDAV_STATUS_BAD_REWRITE_PARSER;
		} else {
		    if (!in_array('options', $activeRules)) {
			$code |= WEBDAV_STATUS_OPTIONS_RULE_DISABLED;
		    }
		}
	    }

	    /* No causes found for missing DAV headers! */
	    if (!$code) {
		$code |= WEBDAV_STATUS_ERROR_UNKNOWN;
	    }

	    $code |= WEBDAV_STATUS_MISSING_DAV_HEADERS;
	}

	/*
	 * Must use short URL because most WebDAV clients don't support query strings.  Check it
	 * after checking for missing DAV headers causes so we can show the error unknown warning if
	 * no causes are found.
	 */
	if (!empty($moduleStatus['rewrite']['active'])) {
	    if (!in_array('connect', $activeRules)) {
		$code |= WEBDAV_STATUS_CONNECT_RULE_DISABLED;
	    }
	}

	/*
	 * HTTP auth module must be enabled to authenticate with WebDAV.  Check it after checking
	 * for missing DAV headers causes so we can show the error unknown warning if no causes are
	 * found.
	 */
	if (empty($moduleStatus['httpauth']['active'])) {
	    $code |= WEBDAV_STATUS_HTTPAUTH_MODULE_DISABLED;
	} else {
	    /* Ensure HTTP auth is enabled */
	    list ($ret, $httpAuthInterface) =
	    	GalleryCoreApi::newFactoryInstance('HttpAuthInterface_1_0');
	    if ($ret) {
		return array($ret, null);
	    }
	    if (isset($httpAuthInterface)) {
		list ($ret, $httpAuthPluginEnabled, $serverAuthPluginEnabled) =
			$httpAuthInterface->getConfiguration();
		if ($ret) {
		    return array($ret, null);
		}
		if (!$httpAuthPluginEnabled && !$serverAuthPluginEnabled) {
		    $code |= WEBDAV_STATUS_HTTPAUTH_AUTH_PLUGINS_DISABLED;
		}
	    }
	}

	/*
	 * Check that Gallery handles WebDAV request methods.  Check it after checking for missing
	 * DAV headers causes so we can show the error unknown warning if no causes are found.
	 */
	foreach (array('PROPFIND', 'PROPPATCH', 'MKCOL', 'DELETE', 'PUT', 'MOVE', 'LOCK', 'UNLOCK')
		as $requestMethod) {
	    if (!WebDavHelper::checkRequestMethod($requestMethod)) {
		if ($gallery->getDebug()) {
		    $gallery->debug('Error in WebDavHelper::checkConfiguration:'
			. ' this server doesn\'t pass ' . $requestMethod . ' requests to Gallery.');
		}
		$code |= WEBDAV_STATUS_METHOD_NOT_HANDLED;
	    }
	}

	/*
	 * The WebDAV library requires a PHP XML parser.  Check it after checking for missing DAV
	 * headers causes so we can show the error unknown warning if no causes are found.
	 */
	if (!$phpVm->extension_loaded('xml')) {
	    $code |= WEBDAV_STATUS_NO_XML_PARSER;
	}

	return array(null, $code);
    }

    /**
     * Check that Gallery handles WebDAV request methods.
     * @param string $requestMethod
     * @return boolean true if Gallery handles the request method
     */
    function checkRequestMethod($requestMethod) {
	global $gallery;
	$urlGenerator =& $gallery->getUrlGenerator();

	list ($status, $headers, $body) = GalleryCoreApi::requestWebPage($urlGenerator->generateUrl(
		array('view' => 'webdav.WebDavWorks'),
		array('forceFullUrl' => true,
		      'htmlEntities' => false)), $requestMethod, array('Content-length' => 0));

	if (!preg_match('/^HTTP\/[0-9]\.[0-9] 200/', $status)) {
	    return false;
	}

	if (trim($body) != 'PASS_WEBDAV') {
	    return false;
	}

	return true;
    }

    /**
     * Check that OPTIONS responses includes the DAV headers.
     * @param string $url
     * @return boolean true if OPTIONS responses include the DAV headers
     */
    function checkDavHeaders($url) {
	list ($status, $headers, $body) = GalleryCoreApi::requestWebPage($url, 'OPTIONS');

	if (!preg_match('/^HTTP\/[0-9]\.[0-9] 200/', $status)) {
	    return false;
	}

	if (empty($headers['Allow']) || $headers['Allow'] != 'OPTIONS,PROPFIND,PROPPATCH,MKCOL,GET'
		. ',HEAD,DELETE,PUT,MOVE,LOCK,UNLOCK') {
	    return false;
	}

	if (empty($headers['DAV']) || $headers['DAV'] != '1,2') {
	    return false;
	}

	if (empty($headers['MS-Author-Via']) || $headers['MS-Author-Via'] != 'DAV') {
	    return false;
	}

	return true;
    }

    /**
     * Returns a browser-specifc mount link for the given item.
     * @param int $itemId
     * @return array('href' => string the davmount URL,
     *               'script' (optional) => string JavaScript to be used as onclick,
     *               'attrs' => array() string additional link tag attributes)
     */
    function getMountLink($itemId) {
	global $gallery;
	$urlGenerator =& $gallery->getUrlGenerator();

	$userAgent = GalleryUtilities::getServerVar('HTTP_USER_AGENT');

	$url = $urlGenerator->generateUrl(array('controller' => 'webdav.WebDav',
						'itemId' => $itemId),
					  array('forceFullUrl' => true,
						'forceSessionId' => false,
						'useAuthToken' => false));
	$link['attrs'] = 'style="behavior: url(#default#anchorClick)" folder="'. $url . '"';

	if (strpos($userAgent, 'MSIE') !== false) {
	    /*
	     * Mount with JavaScript only if using MSIE.  By default, dropdowns link to davmount
	     * resources.
	     */
	    $url = $urlGenerator->generateUrl(array('controller' => 'webdav.WebDav',
						    'itemId' => $itemId),
					      array('forceFullUrl' => true,
						    'htmlEntities' => false,
						    'forceSessionId' => false,
						    'useAuthToken' => false));
	    $link['script'] =
		    "this.style.behavior = 'url(#default#httpFolder)'; this.navigate('$url')";
	}
	if (strpos($userAgent, 'Konqueror') !== false) {
	    /* Konqueror supports webdav:// URLs */
	    $urlParams = array('controller' => 'webdav.WebDav', 'itemId' => $itemId);
	    $urlOptions = array('protocol' => 'webdav', 'forceSessionId' => false,
				'useAuthToken' => false);
	} else {
	    $urlParams = array('view' => 'webdav.DownloadDavMount', 'itemId' => $itemId);
	    $urlOptions = array();
	}

	$link['href'] = $urlGenerator->generateUrl($urlParams, $urlOptions);

	return $link;
    }

    /**
     * Returns the id of item that corresponds to the parent of the given path.
     * The item at the given path doesn't have to exist, but its parent is expected to exist.
     *
     * @param string $path, e.g. /foo/bar
     * @return array object GalleryStatus a status code,
     *               int the id of the parent item
     */
    function getParentItemIdByPath($path) {
	$parentPath = dirname($path);

	/* dirname('foo') is '.' and \ for dirname ('/foo') on Windows */
	if (in_array($parentPath, array('.', '\\'))) {
	    list ($ret, $parentId) =
		    GalleryCoreApi::getPluginParameter('module', 'core', 'id.rootAlbum');
	    if ($ret) {
		return array($ret, null);
	    }
	} else {
	    list ($ret, $parentId) = GalleryCoreApi::fetchItemIdByPath($parentPath);
	    if ($ret) {
		return array($ret, null);
	    }
	}

	return array(null, (int)$parentId);
    }

    /**
     * Take two entities of possibly different classes and make the second entity as close a copy of
     * the first entity as possible.  Copy the id but not the entity type because the entity type
     * must always match the class name.
     * @param object GalleryEntity $sourceEntity entity to copy from
     * @param object GalleryEntity $mirrorEntity entity to copy to
     * @return array object GalleryStatus a status code
     *               object GalleryEntity the mirror entity
     */
    function mirrorEntity($sourceEntity, $mirrorEntity) {
	$className = $mirrorClassName = $mirrorEntity->getClassName();
	list ($ret, $entityInfo) = GalleryCoreApi::describeEntity($className);
	if ($ret) {
	    return array($ret, null);
	}

	list ($ret, $memberAccessInfo) =
	    GalleryCoreApi::getExternalAccessMemberList($className);
	if ($ret) {
	    return array($ret, null);
	}
	/* We need to override id and pathComponent */
	$override = array('id', 'pathComponent');

	/*
	 * Walk down the mirror entity's class hierarchy copying class members from the source
	 * entity if they are defined
	 */
	while (!empty($className)) {
	    foreach ($entityInfo[$className]['members'] as $memberName => $memberInfo) {
		if (isset($sourceEntity->$memberName)
			&& (!empty($memberAccessInfo[$memberName]['write'])
			    || in_array($memberName, $override))) {
		    $mirrorEntity->$memberName = $sourceEntity->$memberName;
		}
	    }

	    $className = $entityInfo[$className]['parent'];
	}

	/*
	 * Reset the entity type to the mirror entity's class name because the entity type must
	 * always match the class name
	 */
	$mirrorEntity->entityType = $mirrorClassName;

	return array(null, $mirrorEntity);
    }

    /**
     * Get singleton WebDAV server library instance.
     *
     * If it didn't need path and baseUrl, we could eliminate and call library methods staticly.
     *
     * @return object WebDavServer instance
     */
    function &getWebDavServer() {
	static $webDavServer;
	if (!isset($webDavServer)) {
	    global $gallery;
	    $urlGenerator =& $gallery->getUrlGenerator();

	    $webDavServer = new WebDavServer();

	    /*
	     * Needed by HTTP_WebDAV_Server::copymove_request_helper and
	     * HTTP_WebDAV_Server::_check_if_header_conditions
	     */
	    $path = GalleryUtilities::getRequestVariables('path');
	    $path = trim($path, '/');

	    $webDavServer->path = $path;
	    $webDavServer->baseUrl = parse_url($urlGenerator->generateUrl(
		array('controller' => 'webdav.WebDav'),
		array('forceFullUrl' => true,
		      'htmlEntities' => false,
		      'forceSessionId' => false,
		      'useAuthToken' => false)));
	}

	return $webDavServer;
    }

    /**
     * Get active WebDAV locks at specified path.
     * @param string $path
     * @param boolean $getDescendentsLocks (optional) also get locks at any descendant path
     * @return array object GalleryStatus a status code
     *               array active WebDAV locks (scope, type, depth, owner, expires, token, path)
     */
    function getLocks($path, $getDescendentsLocks=false) {
	global $gallery;

	/* We haven't done any database calls yet, so GallerySqlFragment isn't defined */
	GalleryCoreApi::requireOnce('modules/core/classes/GalleryStorage.class');

	/* Remove stale locks */
	$ret = GalleryCoreApi::removeMapEntry('WebDavLockMap',
	    array('expires' => new GallerySqlFragment('< ?', time())));
	if ($ret) {
	    return array($ret, null);
	}

	$data = array();
	$query = '
	    SELECT
	      [WebDavLockMap::depth],
	      [WebDavLockMap::owner],
	      [WebDavLockMap::expires],
	      [WebDavLockMap::token],
	      [WebDavLockMap::path]
	    FROM
	      [WebDavLockMap]
	    WHERE';

	/*
	 * Hacks to get ancestors' and descendants' locks will disappear with MPTT -
	 * http://codex.gallery2.org/index.php/Gallery2:Modified_Preorder_Tree_Traversal
	 */
	if ($getDescendentsLocks) {
	    $data[] = "$path%";
	    $query .= '
		  [WebDavLockMap::path] LIKE ?';
	} else {
	    $data[] = $path;
	    $query .= '
		  [WebDavLockMap::path] = ?';
	}

	$pathComponents = explode('/', $path);
	$count = 0;

	/* Get ancestors' locks */
	while (array_pop($pathComponents) !== null) {
	    $data[] = implode('/', $pathComponents);
	    $count++;
	}

	if ($count) {
	    $query .= '
		OR
		  ([WebDavLockMap::path] IN (' . GalleryUtilities::makeMarkers($count)
		  . ') AND [WebDavLockMap::depth] = \'infinity\')';
	}

	list ($ret, $results) = $gallery->search($query, $data);
	if ($ret) {
	    return array($ret, null);
	}

	$locks = array();
	while (($result = $results->nextResult()) !== false) {
	    $locks[] = array('scope' => 'exclusive',
		'type' => 'write',
		'depth' => $result[0],
		'owner' => $result[1],
		'expires' => (int)$result[2],
		'token' => $result[3],
		'path' => $result[4]);
	}

	return array(null, $locks);
    }

    /**
     * Get active locks at specified path or any descendant path.
     * @see WebDavHelper::getLocks
     */
    function getDescendentsLocks($path) {
	return WebDavHelper::getLocks($path, true);
    }

    /**
     * Check if there are no active locks at the specified path, or the request matches the token of
     * the active lock.
     * @param string $path
     * @return boolean no active locks or the request matches the active lock
     */
    function checkLocks($path) {
	$webDavServer =& WebDavHelper::getWebDavServer();

        list ($ret, $locks) = WebDavHelper::getLocks($path);
	if ($ret) {
	    return $ret;
	}

	if (!empty($locks) && !$webDavServer->check_locks_helper($locks, $path)) {
	    WebDavServer::setResponseStatus('423 Locked');
	    return GalleryCoreApi::error(ERROR_LOCK_IN_USE);
	}
    }

    /**
     * OPTIONS handler.
     * @see HTTP_WebDAV_Server::options
     */
    function options() {
	/* TODO: COPY not implemented */
	GalleryUtilities::setResponseHeader(
	    'Allow: OPTIONS,PROPFIND,PROPPATCH,MKCOL,GET,HEAD,DELETE,PUT,MOVE,LOCK,UNLOCK');
	GalleryUtilities::setResponseHeader('DAV: 1,2');
	GalleryUtilities::setResponseHeader('Content-Length: 0');
	GalleryUtilities::setResponseHeader('MS-Author-Via: DAV');
    }

    /**
     * PROPFIND request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::propfind_request_helper which prepares data-structures
     * from PROPFIND requests.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               int maximum depth of descendant paths
     * @see HTTP_WebDAV_Server::propfind_request_helper
     */
    function propfindRequestHelper() {
	$webDavServer =& WebDavHelper::getWebDavServer();

	if (!$webDavServer->propfind_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
	}

	return array(null, $webDavOptions, $webDavOptions['depth']);
    }

    /**
     * PROPFIND response helper.
     *
     * Wrapper around HTTP_WebDAV_Server::propfind_response_helper which formats PROPFIND responses.
     *
     * @param array $webDavOptions WebDAV library options
     * @param array $files files for WebDAV response (path, props)
     * @param array $namespaces namespaces for WebDAV response (URI => prefix)
     * @see HTTP_WebDAV_Server::propfind_response_helper
     */
    function propfindResponseHelper($webDavOptions, $files, $namespaces) {
	$webDavServer =& WebDavHelper::getWebDavServer();
	$webDavOptions['namespaces'] = $namespaces;
	$webDavServer->propfind_response_helper($webDavOptions, $files);
    }

    /**
     * PROPFIND handler.
     * @return object GalleryStatus status code
     */
    function propfind() {
	/* Prepare data-structure from PROPFIND request */
	list ($ret, $webDavOptions, $depth) = WebDavHelper::propfindRequestHelper();
	if ($ret) {
	    return $ret;
	}

	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	if (empty($path)) {
	    list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
	    if ($ret) {
		return $ret;
	    }
	} else {
	    list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	    if ($ret) {
		return $ret;
	    }
	}

	list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
	if ($ret) {
	    return $ret;
	}

	$files = array();
	$ret = WebDavHelper::_propfindFiles($item, $path, $depth, $files);
	if ($ret) {
	    return $ret;
	}

	$namespaces = array(WEBDAV_GALLERY_NAMESPACE => 'G');

	/* Format PROPFIND response */
	$ret = WebDavHelper::propfindResponseHelper($webDavOptions, $files, $namespaces);
	if ($ret) {
	    return $ret;
	}
    }

    /**
     * PROPFIND recursive function.
     *
     * Builds file arrays (path, props) from items until depth is exhausted.
     *
     * Could be done iteratively, but waiting for MPTT for the ultimate solution -
     * http://codex.gallery2.org/index.php/Gallery2:Modified_Preorder_Tree_Traversal
     *
     * @param object GalleryItem $item
     * @param string $path
     * @param int $depth maximum depth of descendant paths
     * @param array $files files for WebDAV response (path, props)
     * @return object GalleryStatus a status code
     * @access private
     */
    function _propfindFiles($item, $path, $depth, &$files) {
	/* Verify that the provided object implements the required methods */
	foreach (array('creationTimestamp', 'title', 'modificationTimestamp',
		'pathComponent') as $memberName) {
	    if (!method_exists($item, 'get' . $memberName)) {
		return GalleryCoreApi::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
			"Item object does not implement a getter for '$memberName'");
	    }
	}

	$file = array('path' => $path, 'props' => array());

	/* Build standard DAV: properties */
	$file['props'][] = WebDavServer::mkprop('creationdate', $item->getCreationTimestamp());
	$displayName = $item->getTitle();
	if (empty($displayName)) {
	    $displayName = $item->getPathComponent();
	}
	$file['props'][] = WebDavServer::mkprop('displayname', $displayName);
	$file['props'][] =
		WebDavServer::mkprop('getlastmodified', $item->getModificationTimestamp());

	/*
	 * Support exclusive write locks.
	 *
	 * Any DAV compliant resource that supports the LOCK method MUST support the supportedlock
	 * property.
	 */
	$file['props'][] = WebDavServer::mkprop(
	    'supportedlock',
	    array(array('scope' => 'exclusive', 'type' => 'write')));

	/*
	 * WebDavHelper::getLocks is potentially expensive.  Could optimize this if we knew
	 * $webDavOptions['props'] didn't contain 'lockdiscovery' or 'allprop'.
	 */
	list ($ret, $locks) = WebDavHelper::getLocks($path);
	if ($ret) {
	    return $ret;
	}

	$file['props'][] = WebDavServer::mkprop('lockdiscovery', $locks);

	if (GalleryUtilities::isA($item, 'GalleryAlbumItem')) {
	    if (!empty($path)) {
		$file['path'] = "$path/";
	    }

	    $file['props'][] = WebDavServer::mkprop('getcontentlength', 0);
	    $file['props'][] = WebDavServer::mkprop('getcontenttype', 'httpd/unix-directory');
	    $file['props'][] = WebDavServer::mkprop('resourcetype', 'collection');
	} else {
	    $size = 0;
	    if (method_exists($item, 'getSize')) {
		$size = $item->getSize();
	    }
	    $mimeType = 'application/unknown';
	    if (method_exists($item, 'getMimeType')) {
		$mimeType = $item->getMimeType();
	    }
	    $file['props'][] = WebDavServer::mkprop('getcontentlength', $size);
	    $file['props'][] = WebDavServer::mkprop('getcontenttype', $mimeType);
	    $file['props'][] = WebDavServer::mkprop('resourcetype', null);
	}

	/* Build Gallery properties */
	if (method_exists($item, 'getClassName')) {
	    list ($ret, $memberInfo) =
		GalleryCoreApi::getExternalAccessMemberList($item->getClassName());
	    if ($ret) {
		return $ret;
	    }

	    /* Keep track of the properties that we add to prevent repetition */
	    $defaultMembers = array('pathComponent', 'creationTimestamp', 'title',
				    'modificationTimestamp', 'mimeType', 'size');
	    foreach ($memberInfo as $memberName => $accessInfo) {
		$getter = 'get' . $memberName;
		/* Only show properties that are not intended for internal use only */
		if ($accessInfo['read'] && !in_array($memberName, $defaultMembers)
			&& method_exists($item, $getter)) {
		    $value = $item->$getter();
		    /* Ignore array valued properties */
		    if (!is_array($value) && !is_object($value)) {
			$file['props'][] = WebDavServer::mkprop(WEBDAV_GALLERY_NAMESPACE,
								$memberName, $value);
		    }
		}
	    }
	}

	$files[] = $file;

	if ($depth <= 0) {
	    return null;
	}

	list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds($item);
	if ($ret) {
	    return $ret;
	}

	if (empty($childIds)) {
	    return null;
	}

	list ($ret, $childItems) = GalleryCoreApi::loadEntitiesById($childIds);
	if ($ret) {
	    return $ret;
	}

	foreach ($childItems as $childItem) {
	    /* Could we simply use something like $childItem->fetchLogicalPath? */
	    $childPath = $childItem->getPathComponent();
	    if (!empty($path)) {
		$childPath = "$path/" . $childPath;
	    }

	    $ret = WebDavHelper::_propfindFiles($childItem, $childPath, $depth - 1, $files);
	    if ($ret) {
		return $ret;
	    }
	}

	return null;
    }

    /**
     * PROPPATCH request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::proppatch_request_helper which prepares data-structures
     * from PROPPATCH requests.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               array properties to set (ns => namespace, name => name, val => value)
     * @see HTTP_WebDAV_Server::proppatch_request_helper
     */
    function proppatchRequestHelper() {
	$webDavServer =& WebDavHelper::getWebDavServer();

	if (!$webDavServer->proppatch_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
	}

	return array(null, $webDavOptions, $webDavOptions['props']);
    }

    /**
     * PROPPATCH response helper.
     *
     * Wrapper around HTTP_WebDAV_Server::proppatch_response_helper which formats PROPPATCH
     * responses.
     *
     * @param array $webDavOptions WebDAV library options
     * @param string $path
     * @param array $props properties set (ns => namespace,
     					   name => name,
					   val => value,
					   status => status)
     * @param array $namespace namespaces for WebDAV response (URI => prefix)
     * @see HTTP_WebDAV_Server::proppatch_response_helper
     */
    function proppatchResponseHelper($webDavOptions, $path, $props, $namespaces) {
	$webDavServer =& WebDavHelper::getWebDavServer();
	$webDavOptions['path'] = $path;
	$webDavOptions['props'] = $props;
	$webDavOptions['namespaces'] = $namespaces;
	$webDavServer->proppatch_response_helper($webDavOptions);
    }

    /**
     * PROPPATCH handler.
     * @return object GalleryStatus a status code
     */
    function proppatch() {
	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	/* Check resource is not locked */
	$ret = WebDavHelper::checkLocks($path);
	if ($ret) {
	    return $ret;
	}

	if (empty($path)) {
	    list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
	    if ($ret) {
		return $ret;
	    }
	} else {
	    list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	    if ($ret) {
		return $ret;
	    }
	}

	list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
	if ($ret) {
	    return $ret;
	}

	/* Prepare data-structure from PROPPATCH request */
	list ($ret, $webDavOptions, $props) = WebDavHelper::proppatchRequestHelper();
	if ($ret) {
	    return $ret;
	}

	list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
	if ($ret) {
	    return $ret;
	}

	$ret = WebDavHelper::_setItemProps($item, $props);
	if ($ret) {
	    return $ret;
	}

	if ($item->isModified()) {
	    $ret = $item->save();
	    if ($ret) {
		GalleryCoreApi::releaseLocks($lockId);
		return $ret;
	    }
	}

	$ret = GalleryCoreApi::releaseLocks($lockId);
	if ($ret) {
	    return $ret;
	}

	$namespaces = array(WEBDAV_GALLERY_NAMESPACE => 'G');

	/* Format PROPPATCH response */
	$ret = WebDavHelper::proppatchResponseHelper($webDavOptions, $path, $props, $namespaces);
	if ($ret) {
	    return $ret;
	}

	return null;
    }

    /**
     * Set item properties
     * @param object GalleryItem reference $item
     * @param array reference $propos DAV file properties
     * @return object GalleryStatus a status code
     */
    function _setItemProps(&$item, &$props) {
	if (!method_exists($item, 'getClassName')) {
	    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	}

	list ($ret, $memberInfo) =
		GalleryCoreApi::getExternalAccessMemberList($item->getClassName());
	if ($ret) {
	    return $ret;
	}

	foreach ($props as $key => $prop) {
	    $name = $prop['name'];
	    if ($prop['ns'] == 'DAV:') {
		if ($prop['name'] == 'displayname') {
		    $name = 'title';
		    /* Want to support any other DAV: properties? */
		} else {
		    $props[$key]['status'] = '403 Forbidden';
		    continue;
		}
	    } else if ($prop['ns'] != WEBDAV_GALLERY_NAMESPACE) {
		$props[$key]['status'] = '403 Forbidden';
		continue;
	    }

	    $setter = 'set' . $name;
	    if (!isset($memberInfo[$name])|| !$memberInfo[$name]['write']
		    || !method_exists($item, $setter)) {
		$props[$key]['status'] = '403 Forbidden';
		continue;
	    }

	    $item->$setter($prop['value']);
	}
	return null;
    }

    /**
     * Validate MKCOL requests.
     *
     * Copied from ItemAddAlbumController::handleRequest for consistancy.  Maybe eventually should
     * go in ItemAddAlbumController::validateRequest or a GalleryCoreApi method.
     *
     * @param int $parentId id of parent album
     * @param string $pathComponent path component of new album
     * @return array object GalleryStatus a status code
     *               array error strings
     * @see ItemAddAlbumController::handleRequest
     */
    function mkcolValidateHelper($parentId, $pathComponent) {
	global $gallery;
	$platform =& $gallery->getPlatform();

	$error = array();

	/* Make sure we have permission do edit this item */
	$ret = GalleryCoreApi::assertHasItemPermission($parentId, 'core.addAlbumItem');
	if ($ret) {
	    return array($ret, null);
	}

	if (empty($pathComponent)) {
	    $error[] = 'form[error][pathComponent][missing]';
	} else if (!$platform->isLegalPathComponent($pathComponent)) {
	    $error[] = 'form[error][pathComponent][invalid]';
	}

	return array(null, $error);
    }

    /**
     * MKCOL helper.
     *
     * Acquire locks, create album and set permissions.
     *
     * Copied from ItemAddAlbumController::handleRequest for consistancy.  Maybe eventually should
     * go in ItemAddAlbumController::requestHelper or a GalleryCoreApi method.
     *
     * @param int $parentId id of parent album
     * @param string $pathComponent path component of new album
     * @param string $title title of new album
     * @param string $summary summary of new album
     * @param string $description description of new album
     * @param array $keywords keywords of new album
     * @return object GalleryStatus a status code
     * @see ItemAddAlbumController::handleRequest
     */
    function mkcolHelper($parentId, $pathComponent, $title, $summary, $description, $keywords) {
	list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLock($parentId);
	if ($ret) {
	    return $ret;
	}

	list ($ret, $albumItem) = GalleryCoreApi::createAlbum($parentId, $pathComponent,
	    $title, $summary, $description, $keywords);
	if ($ret) {
	    GalleryCoreApi::releaseLocks($lockIds);
	    return $ret;
	}

	if (!isset($albumItem)) {
	    GalleryCoreApi::releaseLocks($lockIds);
	    return GalleryCoreApi::error(ERROR_MISSING_OBJECT);
	}

	$ret = GalleryCoreApi::addUserPermission($albumItem->getId(), $albumItem->getOwnerId(),
	    'core.all', false);
	if ($ret) {
	    GalleryCoreApi::releaseLocks($lockIds);
	    return $ret;
	}

	$ret = GalleryCoreApi::releaseLocks($lockIds);
	if ($ret) {
	    return $ret;
	}
    }

    /**
     * MKCOL handler.
     * @return object GalleryStatus a status code
     */
    function mkcol() {
	/* Body parsing not yet supported */
	if (GalleryUtilities::getServerVar('CONTENT_LENGTH')) {
	    /*
	     * 415 (Unsupported Media Type) - The server does not support the request type of the
	     * body.
	     */
	    WebDavServer::setResponseStatus('415 Unsupported Media Type');
	    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	}

	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
	    return $ret;
	}
	if (!$ret) {
	    /*
	     * 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent
	     * resource.
	     */
	    WebDavServer::setResponseStatus('405 Method Not Allowed');
	    return GalleryCoreApi::error(ERROR_COLLISION);
	}

	$pathComponent = basename($path);

	list ($ret, $parentId) = WebDavHelper::getParentItemIdByPath($path);
	if ($ret) {
	    if ($ret->getErrorCode() & ERROR_MISSING_OBJECT) {
		/*
		 * 409 (Conflict) - A resource cannot be created at the destination until one or
		 * more intermediate collections have been created.
		 */
		WebDavServer::setResponseStatus('409 Conflict');
	    }
	    return $ret;
	}

	list ($ret, $error) = WebDavHelper::mkcolValidateHelper($parentId, $pathComponent);
	if ($ret) {
	    return $ret;
	}

	if (!empty($error)) {
	    foreach ($error as $error) {
		if (!strpos($error, 'permission')) {
		    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
		}
	    }

	    /* If all errors were permission denied return more specific error */
	    return GalleryCoreApi::error(ERROR_PERMISSION_DENIED);
	}

	$originalPath = GalleryUtilities::getRequestVariables('originalPath');
	$title = empty($originalPath) ? $pathComponent : basename($originalPath);
	$ret = WebDavHelper::mkcolHelper($parentId, $pathComponent, $title, '', '', '');
	if ($ret) {
	    if ($ret->getErrorCode() & ERROR_ILLEGAL_CHILD) {

		/*
		 * 403 (Forbidden) - This indicates at least one of two conditions: 1) the server
		 * does not allow the creation of collections at the given location in its
		 * namespace, or 2) the parent collection of the Request-URI exists but cannot
		 * accept members.
		 */
		WebDavServer::setResponseStatus('403 Forbidden');
	    }

	    return $ret;
	}

	/*
	 * 201 (Created) - The collection or structured resource was created in its entirety.
	 */
	WebDavServer::setResponseStatus('201 Created');
    }

    /**
     * DELETE helper.
     *
     * For an array of item ids, delete the items if not the root album and the user has permission.
     *
     * Copied from ItemDeleteController::handleRequest for consistancy.  Maybe eventually should go
     * in ItemDeleteController::requestHelper or a GalleryCoreApi method.
     *
     * @param array $itemIds ids of items to delete
     * @return array object GalleryStatus a status code
     *               int number of items deleted
     * @see ItemDeleteController::handleRequest
     */
    function deleteHelper($itemIds) {
        if (!is_array($itemIds)) {
	    $itemIds = array($itemIds);
	}

	/* Get the rootId, so we don't try to delete it */
	list ($ret, $rootId) = GalleryCoreApi::getDefaultAlbumId();
	if ($ret) {
	    return array($ret, null);
	}

	$ret = GalleryCoreApi::studyPermissions($itemIds);
	if ($ret) {
	    return array($ret, null);
	}

	foreach ($itemIds as $itemId) {
	    /* Make sure we have permission to delete this item */
	    list ($ret, $permissions) = GalleryCoreApi::getPermissions($itemId);
	    if ($ret) {
		return array($ret, null);
	    }

	    if (!isset($permissions['core.delete'])) {
		return array(GalleryCoreApi::error(ERROR_PERMISSION_DENIED, __FILE__, __LINE__,
		    "Don't have permission to delete this item"), null);
	    }

	    /* Make sure we're not deleting the root album */
	    if ($itemId == $rootId) {
		return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
		    "Can't delete the root album"), null);
	    }
	}

	/* If we're still here then all are deletable */
	$count = 0;
	foreach ($itemIds as $itemId) {
	    $ret = GalleryCoreApi::deleteEntityById($itemId);
	    if ($ret) {
		return array($ret, null);
	    }

	    $count++;
	}

	return array(null, $count);
    }

    /**
     * DELETE handler.
     * @return object GalleryStatus a status code
     */
    function delete() {
	/* RFC2518 9.2 last paragraph */
	if (GalleryUtilities::getServerVar('HTTP_DEPTH') != null
		&& GalleryUtilities::getServerVar('HTTP_DEPTH') != 'infinity') {
	    WebDavServer::setResponseStatus('400 Bad Request');
	    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	}

	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	/* Check resource is not locked */
	$ret = WebDavHelper::checkLocks($path);
	if ($ret) {
	    return $ret;
	}

	if (empty($path)) {
	    list ($ret, $itemId) = GalleryCoreApi::getPluginParameter('module', 'core',
		'id.rootAlbum');
	    if ($ret) {
		return $ret;
	    }
	} else {
	    list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	    if ($ret) {
		return $ret;
	    }
	}

	list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
	if ($ret) {
	    return $ret;
	}

	list ($ret, $count) = WebDavHelper::deleteHelper($itemId);
	if ($ret) {
	    return $ret;
	}

	/* What do we do if we weren't successful?  No thumbnail, I guess. */
	list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($item->getParentId());
	if ($ret) {
	    return $ret;
	}

	WebDavServer::setResponseStatus('204 No Content');
	return null;
    }

    /**
     * PUT request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::put_request_helper which prepares data-structures from PUT
     * requests.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               resource request body file handle
     *               string request content type
     * @see HTTP_WebDAV_Server::put_request_helper
     */
    function putRequestHelper() {
	$webDavServer =& WebDavHelper::getWebDavServer();

	if (!$webDavServer->put_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null, null);
	}

	return array(null, $webDavOptions,
		     $webDavOptions['stream'],
		     $webDavOptions['content_type']);
    }

    /**
     * PUT response helper.
     *
     * Wrapper around HTTP_WebDAV_Server::put_response_helper which formats PUT responses.
     *
     * @param array $webDavOptions WebDAV library options
     * @param resource $stream destination file handle
     * @see HTTP_WebDAV_Server::put_response_helper
     */
    function putResponseHelper($webDavOptions, $stream) {
	$webDavServer =& WebDavHelper::getWebDavServer();
	$webDavOptions['new'] = false;
	$webDavServer->put_response_helper($webDavOptions, $stream);
    }

    /**
     * COPY / MOVE request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::copymove_request_helper which prepates data-structures
     * from COPY / MOVE requests.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               int maximum depth of descendant paths
     *               boolean overwrite items at destination path
     *               string destination path
     * @see HTTP_WebDAV_Server::copymove_request_helper
     */
    function copyMoveRequestHelper() {
	global $gallery;
	$platform =& $gallery->getPlatform();
	$webDavServer =& WebDavHelper::getWebDavServer();

	/* Body parsing not yet supported */
	if (GalleryUtilities::getServerVar('CONTENT_LENGTH')) {
	    /*
	     * 415 (Unsupported Media Type) - The server does not support the request type of the
	     * body.
	     */
	    WebDavServer::setResponseStatus('415 Unsupported Media Type');
	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null, null, null, null);
	}

	if (!$webDavServer->copymove_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null, null, null);
	}

	/* Copying to remote servers not yet supported */
	if (isset($webDavOptions['dest_url'])) {
	    /*
	     * 502 (Bad Gateway) - This may occur when the destination is on another server and the
	     * destination server refuses to accept the resource.
	     */
	    WebDavServer::setResponseStatus('502 Bad Gateway');
	    return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null, null, null, null);
	}

	/* Check destination is legal */
	$pathComponent = basename($webDavOptions['dest']);
	if (!$platform->isLegalPathComponent($pathComponent)) {
	    WebDavServer::setResponseStatus('403 Forbidden');
	    return array(GalleryCoreApi::error(ERROR_BAD_PATH), null, null, null, null);
	}

	return array(null, $webDavOptions,
		     $webDavOptions['depth'],
		     $webDavOptions['overwrite'],
		     $webDavOptions['dest']);
    }

    /**
     * Validate MOVE requests.
     *
     * Copied from ItemMoveController::handleRequest for consistancy.  Maybe eventually should go in
     * ItemMoveController::validateRequest or a GalleryCoreApi method.
     *
     * @param array $items items to move
     * @param object GalleryAlbumItem $newParent
     * @return array object GalleryStatus a status code
     *               array error strings
     * @see ItemMoveController::handleRequest
     */
    function moveValidateHelper($items, $newParent) {
        if (!is_array($items)) {
	    $items = array($items);
	}

	$error = array();

	if (empty($newParent)) {
	    $error[] = 'form[error][destination][empty]';
	}

	if (!empty($newParent)) {
	    $newParentId = $newParent->getId();

	    list ($ret, $permissions) = GalleryCoreApi::getPermissions($newParentId);
	    if ($ret) {
		return array($ret, null);
	    }

	    $canAddItem = isset($permissions['core.addDataItem']);
	    $canAddAlbum = isset($permissions['core.addAlbumItem']);
	    if (!$canAddAlbum && !$canAddItem) {
		$error[] = 'form[error][destination][permission]';
	    }

	    if (!GalleryUtilities::isA($newParent, 'GalleryAlbumItem')) {
		/* The view should never let this happen */
		return array(GalleryCoreApi::error(ERROR_BAD_DATA_TYPE), null);
	    }

	    /* Load destination parent ids: We don't want recursive moves */
	    list ($ret, $newParentAncestorIds) = GalleryCoreApi::fetchParentSequence($newParentId);
	    if ($ret) {
		return array($ret, null);
	    }
	    $newParentAncestorIds[] = $newParentId;
	}

	foreach ($items as $item) {
	    $itemId = $item->getId();

	    if (!empty($newParent)) {

		/* Can't move into a tree that is included in the source */
		if (in_array($itemId, $newParentAncestorIds)) {
		    $error[] = 'form[error][source][' . $itemId . '][selfMove]';
		    continue;
		}
	    }

	    list ($ret, $permissions) = GalleryCoreApi::getPermissions($itemId);
	    if ($ret) {
		return array($ret, null);
	    }

	    /* Can we delete this item from here? */
	    if (!isset($permissions['core.delete'])) {
		$error[] = 'form[error][source][' . $itemId . '][permission][delete]';
	    }

	    if (!empty($newParent)) {

		/* Check if the destination allows this source to be added */
		if (GalleryUtilities::isA($item, 'GalleryDataItem')) {
		    if (!$canAddItem) {
			$error[] = 'form[error][source][' . $itemId . '][permission][addDataItem]';
		    }
		} else if (GalleryUtilities::isA($item, 'GalleryAlbumItem')) {
		    if (!$canAddAlbum) {
			$error[] = 'form[error][source][' . $itemId . '][permission][addAlbumItem]';
		    }
		} else {

		    /* The view should never let this happen */
		    return array(GalleryCoreApi::error(ERROR_BAD_DATA_TYPE), null);
		}
	    }
	}

	return array(null, $error);
    }

    /**
     * MOVE handler.
     *
     * Rename an item, change its parent, or both.
     *
     * @return object GalleryStatus a status code
     */
    function move() {
	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	/* Check source is not locked */
	$ret = WebDavHelper::checkLocks($path);
	if ($ret) {
	    return $ret;
	}

	/* Validate before deleting a conflicting item */
	list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	if ($ret) {
	    return $ret;
	}

	list ($ret, $rootId) = GalleryCoreApi::getPluginParameter('module', 'core', 'id.rootAlbum');
	if ($ret) {
	    return $ret;
	}
	if ($itemId == $rootId) {
	    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	}

	list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
	if ($ret) {
	    return $ret;
	}

	/* Prepare data-structure from MOVE request */
	list ($ret, $webDavOptions, $depth, $overwrite, $newPath) =
	    WebDavHelper::copyMoveRequestHelper();
	if ($ret) {
	    return $ret;
	}

	/* Check destination is not locked */
	$ret = WebDavHelper::checkLocks($newPath);
	if ($ret) {
	    return $ret;
	}

	if (GalleryUtilities::isA($item, 'GalleryAlbumItem') && $depth != 'infinity') {
	    /*
	     * The MOVE method on a collection MUST act as if a "Depth: infinity" header was used on
	     * it.  A client MUST NOT submit a Depth header on a MOVE on a collection with any value
	     * but "infinity".
	     */
	    WebDavServer::setResponseStatus('400 Bad Request');
	    return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	}

	list ($ret, $newParentId) = WebDavHelper::getParentItemIdByPath($newPath);
	if ($ret) {
	    if ($ret->getErrorCode() & ERROR_MISSING_OBJECT) {
		/*
		 * 409 (Conflict) - A resource cannot be created at the destination until
		 * one or more intermediate collections have been created.
		 */
		WebDavServer::setResponseStatus('409 Conflict');
	    }
	    return $ret;
	}

	list ($ret, $newParent) = GalleryCoreApi::loadEntitiesById($newParentId);
	if ($ret) {
	    return $ret;
	}

	$pathComponent = basename($path);
	$newPathComponent = basename($newPath);
	$oldParentId = $item->getParentId();

	if ($oldParentId != $newParentId) {
	    list ($ret, $error) = WebDavHelper::moveValidateHelper($item, $newParent);
	    if ($ret) {
		return $ret;
	    }
	    if (!empty($error)) {
		foreach ($error as $error) {
		    if (!strpos($error, 'permission')) {
			return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
		    }
		}

		/* If all errors were permission denied return more specific error */
		return GalleryCoreApi::error(ERROR_PERMISSION_DENIED);
	    }
	}

	list ($ret, $conflictingItemId) = GalleryCoreApi::fetchItemIdByPath($newPath);
	if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
	    return $ret;
	}
	if (!$ret) {
	    if (!$overwrite) {
		/*
		 * 412 (Precondition Failed) - The server was unable to maintain the liveness of the
		 * properties listed in the propertybehavior XML element or the Overwrite header is
		 * "F" and the state of the destination resource is non-null.
		 */
		WebDavServer::setResponseStatus('412 Precondition Failed');
		return GalleryCoreApi::error(ERROR_COLLISION);
	    }

	    list ($ret, $count) = WebDavHelper::deleteHelper($conflictingItemId);
	    if ($ret) {
		return $ret;
	    }
	}

	if ($oldParentId != $newParentId) {
	    /*
	     * Read lock both parent hierarchies
	     * TODO Optimize this
	     */
	    list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLockParents($newParentId);
	    if ($ret) {
		return $ret;
	    }

	    list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLockParents($oldParentId);
	    if ($ret) {
		GalleryCoreApi::releaseLocks($lockIds);
		return $ret;
	    }

	    list ($ret, $lockIds[]) =
		GalleryCoreApi::acquireReadLock(array($newParentId, $oldParentId));
	    if ($ret) {
		GalleryCoreApi::releaseLocks($lockIds);
		return $ret;
	    }
	}

	/* Write lock the item we're moving */
	list ($ret, $lockIds[]) = GalleryCoreApi::acquireWriteLock($itemId);
	if ($ret) {
	    GalleryCoreApi::releaseLocks($lockIds);
	    return $ret;
	}

	/* Refresh the item in case it changed before it was locked */
	list ($ret, $item) = $item->refresh();
	if ($ret) {
	    GalleryCoreApi::releaseLocks($lockIds);
	    return $ret;
	}

	/* Try renaming first - if it fails it's easier to undo */
	if ($newPathComponent != $pathComponent) {
	    $ret = $item->rename($newPathComponent);
	    if ($ret) {
		GalleryCoreApi::releaseLocks($lockIds);
		return $ret;
	    }
	}

	if ($newParentId != $oldParentId) {
	    /* Do the move */
	    $ret = $item->move($newParentId);
	    if ($ret) {
		if ($newPathComponent != $pathComponent) {
		    $item->rename($pathComponent);
		    /* Ignore cascading failures here */
		}

		GalleryCoreApi::releaseLocks($lockIds);
		return $ret;
	    }
	}

	$ret = $item->save();
	if ($ret) {
	    if ($newPathComponent != $pathComponent) {
		$ret = $item->rename($pathComponent);
		/* Ignore cascading failures here */
	    }

	    GalleryCoreApi::releaseLocks($lockIds);
	    return $ret;
	}

	if ($newParentId != $oldParentId) {
	    if (GalleryUtilities::isA($item, 'GalleryDataItem')) {
		/* Update for derivative preferences of new parent */
		$ret = GalleryCoreApi::addExistingItemToAlbum($item, $newParentId);
		if ($ret) {
		    GalleryCoreApi::releaseLocks($lockIds);
		    return $ret;
		}
	    }
	}

	/* Release all locks */
	$ret = GalleryCoreApi::releaseLocks($lockIds);
	if ($ret) {
	    return $ret;
	}

	/* Fix thumbnail integrity */
	if ($newParentId != $oldParentId) {
	    /* What do we do if we weren't successful?  No thumbnail, I guess. */
	    list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($oldParentId);
	    if ($ret) {
		return $ret;
	    }
	}

	/* If an item was overwritten fix thumbnail integrity */
	if (empty($count)) {
	    /*
	     * 201 (Created) - The source resource was successfully copied.  The copy operation
	     * resulted in the creation of a new resource.
	     */
	    WebDavServer::setResponseStatus('201 Created');
	    return null;
	}

	/* In case we only renamed the item */
	if (empty($newParentId)) {
	    $newParentId = $item->getParentId();
	}

	/* What do we do if we weren't successful?  No thumbnail, I guess. */
	list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($newParentId);
	if ($ret) {
	    return $ret;
	}

	/*
	 * 204 (No Content) - The source resource was successfully copied to a pre-existing
	 * destination resource.
	 */
	WebDavServer::setResponseStatus('204 No Content');
	return null;
    }

    /**
     * LOCK request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::lock_request_helper which prepares data-structures from
     * LOCK reqeusts.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               string token of WebDAV lock to refresh or null
     *               string scope of WebDAV lock (exclusive or shared)
     *               string type of WebDAV lock (read or write)
     *               int maximum depth of descendant paths
     *               string owner of WebDAV lock
     *               int timeout of WebDAV lock
     *               string token of WebDAV lock to create
     * @see HTTP_WebDAV_Server::lock_request_helper
     * @todo Simplify function signature
     */
    function lockRequestHelper() {
	$webDavServer =& WebDavHelper::getWebDavServer();

	if (!$webDavServer->lock_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN),
			 null, null, null, null, null, null, null, null);
	}

	if (isset($webDavOptions['update'])) {
	    return array(null, $webDavOptions,
	    		 $webDavOptions['update'], null, null, null, null, null, null);
	}

	return array(null, $webDavOptions,
		     null,
		     $webDavOptions['scope'],
		     $webDavOptions['type'],
		     $webDavOptions['depth'],
		     $webDavOptions['owner'],
		     $webDavOptions['timeout'],
		     $webDavOptions['token']);
    }

    /**
     * LOCK response helper.
     *
     * Wrapper around HTTP_WebDAV_Server::lock_response_helper which formates LOCK responses.
     *
     * @param array $webDavOptions WebDAV library options
     * @param array $locks for response (path)
     * @param mixed $status HTTP response status
     * @param string $scope of WebDAV lock (exclusive or shared)
     * @param string $type of WebDAV lock (read or write)
     * @param int $depth maximum depth of descendant paths
     * @param string $owner of WebDAV lock
     * @param int $timeout of WebDAV lock
     * @param int $expires timestamp of WebDAV lock expiration
     * @param string $token of WebDAV lock
     * @see HTTP_WebDAV_Server::lock_response_helper
     * @todo Simplify function signature
     */
    function lockResponseHelper(
	    $webDavOptions, $locks, $status, $scope, $type, $depth, $owner, $expires, $token) {
	$webDavServer =& WebDavHelper::getWebDavServer();
	$webDavOptions['locks'] = $locks;
	$webDavOptions['scope'] = $scope;
	$webDavOptions['type'] = $type;
	$webDavOptions['depth'] = $depth;
	$webDavOptions['owner'] = $owner;
	$webDavOptions['expires'] = $expires;
	$webDavOptions['token'] = $token;
	$webDavServer->lock_response_helper($webDavOptions, $status);
    }

    /**
     * LOCK handler.
     *
     * WebDAV locks persist between requests.
     *
     * @return object GalleryStatus a status code
     * @todo Make corresponding Gallery locks persist between requests
     */
    function lock() {
	global $gallery;

	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	/* Check resource is not locked */
	$ret = WebDavHelper::checkLocks($path);
	if ($ret) {
	    return $ret;
	}

	/* Prepare data-structure from LOCK request */
	list ($ret, $webDavOptions, $update, $scope, $type, $depth, $owner, $timeout, $token) =
	    WebDavHelper::lockRequestHelper();
	if ($ret) {
	    return $ret;
	}

	if (empty($path)) {
	    list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
	    if ($ret) {
		return $ret;
	    }
	} else {
	    list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
	    if ($ret) {
		return $ret;
	    }
	}

	/* Refresh lock */
	if (!empty($update)) {
	    /* Don't join with the Gallery lock table since we might be using flock system */
	    $query = '
		SELECT
		  [WebDavLockMap::depth],
		  [WebDavLockMap::owner],
		  [WebDavLockMap::galleryLockId]
		FROM
		  [WebDavLockMap]
		WHERE
		  [WebDavLockMap::path] = ?
		AND
		  [WebDavLockMap::token] = ?';
	    list ($ret, $results) = $gallery->search($query, array($path, $update));
	    if ($ret) {
		return $ret;
	    }

	    /* Tried to refresh a lock which no longer exists */
	    if (($result = $results->nextResult()) === false) {
		/*
		 * 412 (Precondition Failed) - The included lock token was not enforceable on this
		 * resource or the server could not satisfy the request in the lockinfo XML element.
		 */
		WebDavServer::setResponseStatus('412 Precondition Failed');
		return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	    }

	    /* Load WebDAV lock information */
	    $scope = 'exclusive';
	    $type = 'write';
	    $depth = $result[0];
	    $owner = $result[1];
	    $lockId = $result[2];

	    /* Check that the Gallery lock didn't disappear before the WebDAV lock */
	    if (GalleryCoreApi::isWriteLocked($itemId)) {
		/* TODO: Need an interface to update g_freshUntil for only $lockId */
	    } else {
		list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
		if ($ret) {
		    return $ret;
		}
	    }
	} else {
	    /* Support only exclusive write locks */
	    if ($scope != 'exclusive' || $type != 'write') {
		/*
		 * 412 (Precondition Failed) - The included lock token was not enforceable on this
		 * resource or the server could not satisfy the request in the lockinfo XML element.
		 */
		WebDavServer::setResponseStatus('412 Precondition Failed');
		return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
	    }

	    if ($depth == 'infinity') {
		list ($ret, $locks) = WebDavHelper::getDescendentsLocks($path);
		if ($ret) {
		    return $ret;
		}

		if (!empty($locks)) {
		    /* Format LOCK response */
		    return WebDavHelper::lockResponseHelper(
			$webDavOptions, $locks, null, null, null, null, null, null, null);
		}
	    }

	    list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
	    if ($ret) {
		return $ret;
	    }
	}

	/* Use Gallery lock freshUntil for WebDAV lock timeout */
	$query = '
	    SELECT
	      [Lock::freshUntil]
	    FROM
	      [Lock]
	    WHERE
	      [Lock::lockId] = ?';
	list ($ret, $results) = $gallery->search($query, array($lockId));
	if ($ret) {
	    return $ret;
	}

	if (($result = $results->nextResult()) !== false) {
	    $expires = $result[0];
	} else {
	    /*
	     * Might be using flock system
	     * TODO: Get expires from flock locks
	    return GalleryCoreApi::error(ERROR_MISSING_VALUE);
	     */
	    $expires = time() + 30;
	}

	/* Refresh lock */
	if (!empty($update)) {
	    $ret = GalleryCoreApi::updateMapEntry('WebDavLockMap',
		array('token' => $update, 'path' => $path),
		array('expires' => $expires, 'galleryLockId' => $lockId));
	    if ($ret) {
		return $ret;
	    }
	} else {
	    $ret = GalleryCoreApi::addMapEntry('WebDavLockMap', array('depth' => $depth,
		'owner' => $owner,
		'expires' => $expires,
		'token' => $token,
		'path' => $path,
		'galleryLockId' => $lockId));
	    if ($ret) {
		return $ret;
	    }
	}

	/* Format LOCK response */
	$ret = WebDavHelper::lockResponseHelper(
	    $webDavOptions, null, true, $scope, $type, $depth, $owner, $expires, $token);
	if ($ret) {
	    return $ret;
	}
    }

    /**
     * UNLOCK request helper.
     *
     * Wrapper around HTTP_WebDAV_Server::unlock_request_helper wich prepares data-structures from
     * UNLOCK requests.
     *
     * @return array object GalleryStatus a status code
     *               array WebDAV library options
     *               string token of WebDAV lock to clear
     * @see HTTP_WebDAV_Server::unlock_request_helper
     */
    function unlockRequestHelper() {
	$webDavServer =& WebDavHelper::getWebDavServer();

	if (!$webDavServer->unlock_request_helper($webDavOptions)) {
	    /* WebDAV library found error in the request */
	    return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
	}

	return array(null, $webDavOptions, $webDavOptions['token']);
    }

    /**
     * UNLOCK handler.
     * @return object GalleryStatus a status code
     */
    function unlock() {
	global $gallery;

	/* Prepare data-structure from UNLOCK request */
	list ($ret, $webDavOptions, $token) = WebDavHelper::unlockRequestHelper();
	if ($ret) {
	    return $ret;
	}

	$path = GalleryUtilities::getRequestVariables('path');
	$path = trim($path, '/');

	$query = '
	    SELECT
	      [WebDavLockMap::galleryLockId]
	    FROM
	      [WebDavLockMap]
	    WHERE
	      [WebDavLockMap::path] = ?
	    AND
	      [WebDavLockMap::token] = ?';
	list ($ret, $results) = $gallery->search($query, array($path, $token));
	if ($ret) {
	    return $ret;
	}

	if (($result = $results->nextResult()) === false) {
	    return GalleryCoreApi::error(ERROR_MISSING_VALUE);
	}
	$lockId = $result[0];

	$ret = GalleryCoreApi::releaseLocks($lockId);
	if ($ret) {
	    return $ret;
	}

	$ret = GalleryCoreApi::removeMapEntry(
	    'WebDavLockMap', array('token' => $token, 'path' => $path));
	if ($ret) {
	    return $ret;
	}

	/*
	 * The 204 (No Content) status code is used instead of 200 (OK) because there is no response
	 * entity body.
	 */
	WebDavServer::setResponseStatus('204 No Content');
	return null;
    }
}

/**
 * Sub-class of HTTP_WebDAV_Server which overrides getHref, openRequestBody, setResponseHeader and
 * setResponseStatus.  getHref uses the URL generator.  openRequestBody uses the platform, for
 * testability.  setResponseHeader and setResponseStatus use GalleryUtilities::setResponseHeader to
 * avoid response headers being replaced elsewhere in Gallery and for testability, since
 * GalleryUtilities::setResponseHeader uses $phpVm->header.
 */
class WebDavServer extends HTTP_WebDAV_Server {

    /**
     * @see HTTP_WebDAV_Server::getHref
     */
    function getHref($path) {
	global $gallery;
	$urlGenerator =& $gallery->getUrlGenerator();

	return $urlGenerator->generateUrl(
	    array('controller' => 'webdav.WebDav', 'path' => $path),
	    array('forceServerRelativeUrl' => true,
		  'forceSessionId' => false,
		  'useAuthToken' => false));
    }

    /**
     * @see HTTP_WebDAV_Server::openRequestBody
     */
    function openRequestBody() {
	global $gallery;
	$platform =& $gallery->getPlatform();

	return $platform->fopen('php://input', 'rb');
    }

    /**
     * @see HTTP_WebDAV_Server::setResponseHeader
     */
    function setResponseHeader($header, $replace=true) {
	GalleryUtilities::setResponseHeader($header, $replace);
    }

    /**
     * @see HTTP_WebDAV_Server::setResponseStatus
     */
    function setResponseStatus($status, $replace=true) {
	GalleryUtilities::setResponseHeader("HTTP/1.0 $status", $replace);
    }
}
?>

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists