1996 lines
58 KiB
Plaintext
1996 lines
58 KiB
Plaintext
<?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);
|
|
}
|
|
}
|
|
?>
|