View source
<?php
namespace Drupal\s3fs;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\Exception\DirectoryNotReadyException;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\Exception\FileExistsException;
use Drupal\Core\File\Exception\FileNotExistsException;
use Drupal\Core\File\Exception\FileWriteException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Psr\Log\LoggerInterface;
class S3fsFileService implements FileSystemInterface {
protected $decorated;
protected $logger;
protected $streamWrapperManager;
protected $s3fs;
protected $configFactory;
protected $moduleHandler;
protected $mimeGuesser;
public function __construct(FileSystemInterface $decorated, StreamWrapperManagerInterface $stream_wrapper_manager, LoggerInterface $logger, S3fsServiceInterface $s3fs, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, $mimeGuesser) {
$this->decorated = $decorated;
$this->streamWrapperManager = $stream_wrapper_manager;
$this->logger = $logger;
$this->s3fs = $s3fs;
$this->moduleHandler = $moduleHandler;
$this->mimeGuesser = $mimeGuesser;
$this->configFactory = $configFactory;
}
public function moveUploadedFile($filename, $uri) {
$wrapper = $this->streamWrapperManager
->getViaUri($uri);
if (is_a($wrapper, 'Drupal\\s3fs\\StreamWrapper\\S3fsStream')) {
return $this
->putObject($filename, $uri);
}
else {
return $this->decorated
->moveUploadedFile($filename, $uri);
}
}
public function chmod($uri, $mode = NULL) {
return $this->decorated
->chmod($uri, $mode);
}
public function unlink($uri, $context = NULL) {
return $this->decorated
->unlink($uri, $context);
}
public function realpath($uri) {
return $this->decorated
->realpath($uri);
}
public function dirname($uri) {
return $this->decorated
->dirname($uri);
}
public function basename($uri, $suffix = NULL) {
return $this->decorated
->basename($uri, $suffix);
}
public function mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
return $this->decorated
->mkdir($uri, $mode, $recursive, $context);
}
public function rmdir($uri, $context = NULL) {
return $this->decorated
->rmdir($uri, $context);
}
public function tempnam($directory, $prefix) {
return $this->decorated
->tempnam($directory, $prefix);
}
public function uriScheme($uri) {
if (method_exists($this->decorated, 'uriScheme')) {
return $this->decorated
->uriScheme($uri);
}
else {
@trigger_error('S3FS: FileSystem::uriScheme() has been removed in core. Use \\Drupal\\Core\\StreamWrapper\\StreamWrapperManagerInterface::getScheme() instead. See https://www.drupal.org/node/3035273', E_USER_ERROR);
}
}
public function validScheme($scheme) {
if (method_exists($this->decorated, 'validScheme')) {
return $this->decorated
->validScheme($scheme);
}
else {
@trigger_error('S3FS: FileSystem::validScheme() Has been removed in core. Use \\Drupal\\Core\\StreamWrapper\\StreamWrapperManagerInterface::isValidScheme() instead. See https://www.drupal.org/node/3035273', E_USER_ERROR);
}
}
public function copy($source, $destination, $replace = self::EXISTS_RENAME) {
$wrapper = $this->streamWrapperManager
->getViaUri($destination);
if (is_a($wrapper, 'Drupal\\s3fs\\StreamWrapper\\S3fsStream')) {
$this
->prepareDestination($source, $destination, $replace);
$srcScheme = $this->streamWrapperManager
->getScheme($source);
$dstScheme = $this->streamWrapperManager
->getScheme($destination);
if ($srcScheme == $dstScheme) {
$result = $this
->copyObject($source, $destination);
}
else {
$result = $this
->putObject($source, $destination);
}
if (!$result) {
$this->logger
->error("The specified file '%source' could not be copied to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileWriteException("The specified file '{$source}' could not be copied to '{$destination}'.");
}
return $destination;
}
else {
return $this->decorated
->copy($source, $destination, $replace);
}
}
public function delete($path) {
return $this->decorated
->delete($path);
}
public function deleteRecursive($path, callable $callback = NULL) {
return $this->decorated
->deleteRecursive($path, $callback);
}
public function move($source, $destination, $replace = self::EXISTS_RENAME) {
$wrapper = $this->streamWrapperManager
->getViaUri($destination);
if (is_a($wrapper, 'Drupal\\s3fs\\StreamWrapper\\S3fsStream')) {
$this
->prepareDestination($source, $destination, $replace);
if (!$this->streamWrapperManager
->isValidUri($source) && substr(PHP_OS, 0, 3) == 'WIN') {
chmod($source, 0600);
}
$real_source = $this
->realpath($source) ?: $source;
$srcScheme = $this->streamWrapperManager
->getScheme($real_source);
$dstScheme = $this->streamWrapperManager
->getScheme($destination);
if ($srcScheme == $dstScheme) {
$result = $this
->copyObject($real_source, $destination);
}
else {
$result = $this
->putObject($real_source, $destination);
}
if (!$result) {
$this->logger
->error("The specified file '%source' could not be moved to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileWriteException("The specified file '{$source}' could not be moved to '{$destination}'.");
}
else {
if (!@unlink($real_source)) {
$this->logger
->error("The source file '%source' could not be unlinked after copying to '%destination'.", [
'%source' => $source,
'%destination' => $destination,
]);
throw new FileException("The source file '{$source}' could not be unlinked after copying to '{$destination}'.");
}
}
return $destination;
}
else {
return $this->decorated
->move($source, $destination, $replace);
}
}
protected function prepareDestination($source, &$destination, $replace) {
$original_source = $source;
if (!file_exists($source)) {
if (($realpath = $this
->realpath($original_source)) !== FALSE) {
$this->logger
->error("File '%original_source' ('%realpath') could not be copied because it does not exist.", [
'%original_source' => $original_source,
'%realpath' => $realpath,
]);
throw new FileNotExistsException("File '{$original_source}' ('{$realpath}') could not be copied because it does not exist.");
}
else {
$this->logger
->error("File '%original_source' could not be copied because it does not exist.", [
'%original_source' => $original_source,
]);
throw new FileNotExistsException("File '{$original_source}' could not be copied because it does not exist.");
}
}
if ($this
->prepareDirectory($destination)) {
$destination = $this->streamWrapperManager
->normalizeUri($destination . '/' . $this
->basename($source));
}
else {
$dirname = $this
->dirname($destination);
if (!$this
->prepareDirectory($dirname)) {
$this->logger
->error("The specified file '%original_source' could not be copied because the destination directory '%destination_directory' is not properly configured. This may be caused by a problem with file or directory permissions.", [
'%original_source' => $original_source,
'%destination_directory' => $dirname,
]);
throw new DirectoryNotReadyException("The specified file '{$original_source}' could not be copied because the destination directory '{$dirname}' is not properly configured. This may be caused by a problem with file or directory permissions.");
}
}
$destination = $this
->getDestinationFilename($destination, $replace);
if ($destination === FALSE) {
$this->logger
->error("File '%original_source' could not be copied because a file by that name already exists in the destination directory ('%destination').", [
'%original_source' => $original_source,
'%destination' => $destination,
]);
throw new FileExistsException("File '{$original_source}' could not be copied because a file by that name already exists in the destination directory ('{$destination}').");
}
$real_source = $this
->realpath($source);
$real_destination = $this
->realpath($destination);
if ($source == $destination || $real_source !== FALSE && $real_source == $real_destination) {
$this->logger
->error("File '%source' could not be copied because it would overwrite itself.", [
'%source' => $source,
]);
throw new FileException("File '{$source}' could not be copied because it would overwrite itself.");
}
}
public function saveData($data, $destination, $replace = self::EXISTS_RENAME) {
$temp_name = $this
->tempnam('temporary://', 'file');
if (file_put_contents($temp_name, $data) === FALSE) {
$this->logger
->error("Temporary file '%temp_name' could not be created.", [
'%temp_name' => $temp_name,
]);
throw new FileWriteException("Temporary file '{$temp_name}' could not be created.");
}
return $this
->move($temp_name, $destination, $replace);
}
public function prepareDirectory(&$directory, $options = self::MODIFY_PERMISSIONS) {
return $this->decorated
->prepareDirectory($directory, $options);
}
public function getDestinationFilename($destination, $replace) {
return $this->decorated
->getDestinationFilename($destination, $replace);
}
public function createFilename($basename, $directory) {
return $this->decorated
->createFilename($basename, $directory);
}
public function getTempDirectory() {
return $this->decorated
->getTempDirectory();
}
public function scanDirectory($dir, $mask, array $options = []) {
return $this->decorated
->scanDirectory($dir, $mask, $options);
}
protected function putObject($source, $destination) {
if (mb_strlen($destination) > S3fsServiceInterface::MAX_URI_LENGTH) {
$this->logger
->error("The specified file '%destination' exceeds max URI length limit.", [
'%destination' => $destination,
]);
return FALSE;
}
$config = $this->configFactory
->get('s3fs.settings')
->get();
$wrapper = $this->streamWrapperManager
->getViaUri($destination);
$scheme = $this->streamWrapperManager
->getScheme($destination);
$key_path = $this->streamWrapperManager
->getTarget($destination);
if ($scheme === 'public') {
$target_folder = !empty($config['public_folder']) ? $config['public_folder'] . '/' : 's3fs-public/';
$key_path = $target_folder . $key_path;
}
elseif ($scheme === 'private') {
$target_folder = !empty($config['private_folder']) ? $config['private_folder'] . '/' : 's3fs-private/';
$key_path = $target_folder . $key_path;
}
if (!empty($config['root_folder'])) {
$key_path = $config['root_folder'] . '/' . $key_path;
}
if (method_exists($this->mimeGuesser, 'guessMimeType')) {
$contentType = $this->mimeGuesser
->guessMimeType($key_path);
}
else {
$contentType = $this->mimeGuesser
->guess($key_path);
}
$uploadParams = [
'Bucket' => $config['bucket'],
'Key' => $key_path,
'SourceFile' => $source,
'ContentType' => $contentType,
];
if (!empty($config['encryption'])) {
$uploadParams['ServerSideEncryption'] = $config['encryption'];
}
if (!empty($config['cache_control_header'])) {
$uploadParams['CacheControl'] = $config['cache_control_header'];
}
$uploadAsPrivate = Settings::get('s3fs.upload_as_private');
if ($scheme !== 'private' && !$uploadAsPrivate) {
$uploadParams['ACL'] = 'public-read';
}
$this->moduleHandler
->alter('s3fs_upload_params', $uploadParams);
$s3 = $this->s3fs
->getAmazonS3Client($config);
try {
$s3
->putObject($uploadParams);
} catch (\Exception $e) {
return FALSE;
}
$wrapper
->writeUriToCache($destination);
return TRUE;
}
protected function copyObject($source, $destination) {
if (mb_strlen($destination) > S3fsServiceInterface::MAX_URI_LENGTH) {
$this->logger
->error("The specified file '%destination' exceeds max URI length limit.", [
'%destination' => $destination,
]);
return FALSE;
}
$config = $this->configFactory
->get('s3fs.settings')
->get();
$wrapper = $this->streamWrapperManager
->getViaUri($destination);
$scheme = $this->streamWrapperManager
->getScheme($destination);
$key_path = $this->streamWrapperManager
->getTarget($destination);
$src_key_path = $this->streamWrapperManager
->getTarget($source);
if ($scheme === 'public') {
$target_folder = !empty($config['public_folder']) ? $config['public_folder'] . '/' : 's3fs-public/';
$key_path = $target_folder . $key_path;
$src_key_path = $target_folder . $src_key_path;
}
elseif ($scheme === 'private') {
$target_folder = !empty($config['private_folder']) ? $config['private_folder'] . '/' : 's3fs-private/';
$key_path = $target_folder . $key_path;
$src_key_path = $target_folder . $src_key_path;
}
if (!empty($config['root_folder'])) {
$key_path = $config['root_folder'] . '/' . $key_path;
$src_key_path = $config['root_folder'] . '/' . $src_key_path;
}
if (method_exists($this->mimeGuesser, 'guessMimeType')) {
$contentType = $this->mimeGuesser
->guessMimeType($key_path);
}
else {
$contentType = $this->mimeGuesser
->guess($key_path);
}
$copyParams = [
'Bucket' => $config['bucket'],
'Key' => $key_path,
'CopySource' => $config['bucket'] . '/' . $src_key_path,
'ContentType' => $contentType,
'MetadataDirective' => 'REPLACE',
];
if (!empty($config['encryption'])) {
$copyParams['ServerSideEncryption'] = $config['encryption'];
}
if (!empty($config['cache_control_header'])) {
$copyParams['CacheControl'] = $config['cache_control_header'];
}
$uploadAsPrivate = Settings::get('s3fs.upload_as_private');
if ($scheme !== 'private' && !$uploadAsPrivate) {
$copyParams['ACL'] = 'public-read';
}
$this->moduleHandler
->alter('s3fs_copy_params_alter', $copyParams);
$s3 = $this->s3fs
->getAmazonS3Client($config);
try {
$s3
->copyObject($copyParams);
} catch (\Exception $e) {
return FALSE;
}
$wrapper
->writeUriToCache($destination);
return TRUE;
}
}