diff --git a/Classes/Driver/AmazonS3Driver.php b/Classes/Driver/AmazonS3Driver.php
index fd92f83f..4d5c7abf 100644
--- a/Classes/Driver/AmazonS3Driver.php
+++ b/Classes/Driver/AmazonS3Driver.php
@@ -80,6 +80,14 @@ class AmazonS3Driver extends AbstractHierarchicalFilesystemDriver implements Str
*/
protected $baseUrl = '';
+ /**
+ * Folder that is used as root folder.
+ * Must be empty or have a trailing slash.
+ *
+ * @var string
+ */
+ protected $baseFolder = '';
+
/**
* Stream wrapper protocol: Will be set in the constructor
*
@@ -219,8 +227,10 @@ public function processConfiguration()
public function initialize()
{
$this->initializeBaseUrl()
+ ->initializeBaseFolder()
->initializeSettings()
->initializeClient();
+ $this->resetRequestCache();
// Test connection if we are in the edit view of this storage
if (
$this->compatibilityService->isBackend()
@@ -238,7 +248,7 @@ public function getPublicUrl($identifier)
{
$uriParts = GeneralUtility::trimExplode('/', ltrim($identifier, '/'), true);
$uriParts = array_map('rawurlencode', $uriParts);
- return $this->baseUrl . '/' . implode('/', $uriParts);
+ return $this->baseUrl . '/' . $this->addBaseFolder(implode('/', $uriParts));
}
/**
@@ -394,14 +404,14 @@ public function addFile($localFilePath, $targetFolderIdentifier, $newFileName =
if (filesize($localFilePath) === 0) { // Multipart uploader would fail to upload empty files
$this->s3Client->upload(
$this->configuration['bucket'],
- $targetIdentifier,
+ $this->addBaseFolder($targetIdentifier),
''
);
} else {
$multipartUploadAdapter = GeneralUtility::makeInstance(MultipartUploaderAdapter::class, $this->s3Client);
$multipartUploadAdapter->upload(
$localFilePath,
- $targetIdentifier,
+ $this->addBaseFolder($targetIdentifier),
$this->configuration['bucket'],
$this->getCacheControl($targetIdentifier)
);
@@ -524,7 +534,7 @@ public function getFileForLocalProcessing($fileIdentifier, $writable = true)
$temporaryPath = $this->getTemporaryPathForFile($fileIdentifier);
$this->s3Client->getObject([
'Bucket' => $this->configuration['bucket'],
- 'Key' => $fileIdentifier,
+ 'Key' => $this->addBaseFolder($fileIdentifier),
'SaveAs' => $temporaryPath,
]);
if (!is_file($temporaryPath)) {
@@ -599,7 +609,7 @@ public function getFileContents($fileIdentifier)
{
$result = $this->s3Client->getObject([
'Bucket' => $this->configuration['bucket'],
- 'Key' => $fileIdentifier
+ 'Key' => $this->addBaseFolder($fileIdentifier)
]);
return (string)$result['Body'];
}
@@ -1083,6 +1093,19 @@ protected function initializeBaseUrl()
return $this;
}
+ /**
+ * Set the $baseFolder variable from configuration
+ */
+ protected function initializeBaseFolder(): self
+ {
+ $baseFolder = $this->configuration['baseFolder'] ?? '';
+ if ($baseFolder != '') {
+ $baseFolder = rtrim($baseFolder, '/') . '/';
+ }
+ $this->baseFolder = $baseFolder;
+ return $this;
+ }
+
/**
* initializeSettings
*
@@ -1183,6 +1206,30 @@ protected function testConnection()
}
}
+ /**
+ * Prefix the given file/path identifier with the base folder.
+ * Used by internal functions that directly speak with S3Client
+ */
+ protected function addBaseFolder(string $identifier): string
+ {
+ if ($this->baseFolder) {
+ $identifier = str_replace('//', '/', $this->baseFolder . $identifier);
+ }
+ return $identifier;
+ }
+
+ /**
+ * Remove base folder prefix from a given S3 path.
+ * Used when returning information from S3.
+ */
+ protected function removeBaseFolder(string $identifier): string
+ {
+ if ($this->baseFolder) {
+ $identifier = substr($identifier, strlen($this->baseFolder));
+ }
+ return $identifier;
+ }
+
/**
* @return \TYPO3\CMS\Core\Messaging\FlashMessageQueue
*/
@@ -1241,7 +1288,7 @@ protected function getMetaInfo($identifier): ?array
try {
$metadata = $this->s3Client->headObject([
'Bucket' => $this->configuration['bucket'],
- 'Key' => $identifier
+ 'Key' => $this->addBaseFolder($identifier)
])->toArray();
$metaInfoDownloadAdapter = GeneralUtility::makeInstance(MetaInfoDownloadAdapter::class);
$metaInfo = $metaInfoDownloadAdapter->getMetaInfoFromResponse($this, $identifier, $metadata);
@@ -1257,6 +1304,7 @@ protected function getMetaInfo($identifier): ?array
$this->metaInfoCache->remove($cacheIdentifier);
return null;
}
+ return $this->metaInfoCache->get($cacheIdentifier);
}
/**
@@ -1330,7 +1378,7 @@ protected function getObjectPermissions($identifier)
try {
$response = $this->s3Client->getObjectAcl([
'Bucket' => $this->configuration['bucket'],
- 'Key' => $identifier
+ 'Key' => $this->addBaseFolder($identifier)
])->toArray();
// Until the SDK provides any useful information about folder permissions, we take full access for granted as long as one user with full access exists.
@@ -1368,10 +1416,10 @@ protected function getObjectPermissions($identifier)
*/
protected function deleteObject(string $identifier): bool
{
- $this->s3Client->deleteObject(['Bucket' => $this->configuration['bucket'], 'Key' => $identifier]);
+ $this->s3Client->deleteObject(['Bucket' => $this->configuration['bucket'], 'Key' => $this->addBaseFolder($identifier)]);
$this->flushMetaInfoCache($identifier);
$this->resetRequestCache();
- return !$this->s3Client->doesObjectExist($this->configuration['bucket'], $identifier);
+ return !$this->s3Client->doesObjectExist($this->configuration['bucket'], $this->addBaseFolder($identifier));
}
/**
@@ -1399,7 +1447,7 @@ protected function createObject($identifier, $body = '', $overrideArgs = [])
$this->normalizeIdentifier($identifier);
$args = [
'Bucket' => $this->configuration['bucket'],
- 'Key' => $identifier,
+ 'Key' => $this->addBaseFolder($identifier),
'Body' => $body
];
$this->s3Client->putObject(array_merge_recursive($args, $overrideArgs));
@@ -1458,7 +1506,7 @@ protected function getStreamWrapperPath($file)
throw new \RuntimeException('Type "' . gettype($file) . '" is not supported.', 1325191178);
}
$this->normalizeIdentifier($identifier);
- return $basePath . $identifier;
+ return $basePath . $this->addBaseFolder($identifier);
}
/**
@@ -1522,7 +1570,7 @@ protected function getListObjects($identifier, $overrideArgs = [])
{
$args = [
'Bucket' => $this->configuration['bucket'] ?? '',
- 'Prefix' => $identifier,
+ 'Prefix' => $this->addBaseFolder($identifier),
];
$result = $this->getCachedResponse('listObjectsV2', array_merge_recursive($args, $overrideArgs));
// Cache the given meta info
@@ -1530,14 +1578,31 @@ protected function getListObjects($identifier, $overrideArgs = [])
// with many files we come to the recursion which lessens the home of a cache hit, so we do not create the cache here
if (isset($result['Contents']) && is_array($result['Contents'])) {
- foreach ($result['Contents'] as $content) {
- $fileIdentifier = $content['Key'];
+ $baseFolderSelfKey = null;
+ foreach ($result['Contents'] as $key => &$content) {
+ $content['Key'] = $this->removeBaseFolder($content['Key']);
+ if ($content['Key'] === '') {
+ $baseFolderSelfKey = $key;
+ continue;
+ }
+ $fileIdentifier = $identifier . $content['Key'];
$this->normalizeIdentifier($fileIdentifier);
$cacheIdentifier = md5($fileIdentifier);
if (!$this->metaInfoCache->has($cacheIdentifier) || !$this->metaInfoCache->get($cacheIdentifier)) {
$this->metaInfoCache->set($cacheIdentifier, $metaInfoDownloadAdapter->getMetaInfoFromResponse($this, $fileIdentifier, $content));
}
}
+ unset($content);
+ if ($baseFolderSelfKey !== null) {
+ unset($result['Contents'][$baseFolderSelfKey]);
+ }
+ }
+
+ if (isset($result['CommonPrefixes'])) {
+ foreach ($result['CommonPrefixes'] as &$prefix) {
+ $prefix['Prefix'] = $this->removeBaseFolder($prefix['Prefix']);
+ }
+ unset($prefix);
}
if (isset($overrideArgs['MaxKeys']) && $overrideArgs['MaxKeys'] <= 1000) {
@@ -1591,8 +1656,8 @@ protected function copyObject($identifier, $targetIdentifier)
{
$this->s3Client->copyObject([
'Bucket' => $this->configuration['bucket'],
- 'CopySource' => $this->configuration['bucket'] . '/' . $identifier,
- 'Key' => $targetIdentifier,
+ 'CopySource' => $this->configuration['bucket'] . '/' . $this->addBaseFolder($identifier),
+ 'Key' => $this->addBaseFolder($targetIdentifier),
'CacheControl' => $this->getCacheControl($targetIdentifier)
]);
$this->flushMetaInfoCache($targetIdentifier);
diff --git a/Configuration/FlexForm/AmazonS3DriverFlexForm.xml b/Configuration/FlexForm/AmazonS3DriverFlexForm.xml
index 4c2f0211..0846f54f 100644
--- a/Configuration/FlexForm/AmazonS3DriverFlexForm.xml
+++ b/Configuration/FlexForm/AmazonS3DriverFlexForm.xml
@@ -187,6 +187,16 @@
+
+
+
+ LLL:EXT:aus_driver_amazon_s3/Resources/Private/Language/locallang_flexform.xlf:driverConfiguration.baseFolder-description
+
+ input
+ 30
+
+
+
diff --git a/Resources/Private/Language/locallang_flexform.xlf b/Resources/Private/Language/locallang_flexform.xlf
index 9799c7a4..5c1dbd5b 100644
--- a/Resources/Private/Language/locallang_flexform.xlf
+++ b/Resources/Private/Language/locallang_flexform.xlf
@@ -33,6 +33,12 @@
+
+
+
+
+
+