Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,342 @@
<?php
namespace Gaufrette\Adapter;
use AsyncAws\Core\Configuration;
use AsyncAws\SimpleS3\SimpleS3Client;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* Amazon S3 adapter using the AsyncAws.
*
* @author Michael Dowling <mtdowling@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class AsyncAwsS3 implements Adapter, MetadataSupporter, ListKeysAware, SizeCalculator, MimeTypeProvider
{
/** @var SimpleS3Client */
protected $service;
/** @var string */
protected $bucket;
/** @var array */
protected $options;
/** @var bool */
protected $bucketExists;
/** @var array */
protected $metadata = [];
/** @var bool */
protected $detectContentType;
/**
* @param SimpleS3Client $service
* @param string $bucket
* @param array $options
* @param bool $detectContentType
*/
public function __construct(SimpleS3Client $service, $bucket, array $options = [], $detectContentType = false)
{
if (!class_exists(SimpleS3Client::class)) {
throw new \LogicException('You need to install package "async-aws/simple-s3" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
[
'create' => false,
'directory' => '',
'acl' => 'private',
],
$options
);
$this->detectContentType = $detectContentType;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $content)
{
// BC with AmazonS3 adapter
if (isset($content['contentType'])) {
$content['ContentType'] = $content['contentType'];
unset($content['contentType']);
}
$this->metadata[$key] = $content;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
return $this->metadata[$key] ?? [];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
try {
// Get remote object
$object = $this->service->getObject($options);
// If there's no metadata array set up for this object, set it up
if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) {
$this->metadata[$key] = [];
}
// Make remote ContentType metadata available locally
$this->metadata[$key]['ContentType'] = $object->getContentType();
return $object->getBody()->getContentAsString();
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$options = $this->getOptions(
$targetKey,
['CopySource' => $this->bucket . '/' . $this->computePath($sourceKey)]
);
try {
$this->service->copyObject(array_merge($options, $this->getMetadata($targetKey)));
return $this->delete($sourceKey);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
* @param string|resource $content
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
unset($options['Bucket'], $options['Key']);
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as binary/octet-stream.
*/
if (!isset($options['ContentType']) && $this->detectContentType) {
$options['ContentType'] = $this->guessContentType($content);
}
try {
$this->service->upload($this->bucket, $this->computePath($key), $content, $options);
if (is_resource($content)) {
return (int) Util\Size::fromResource($content);
}
return Util\Size::fromContent($content);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return $this->service->has($this->bucket, $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return $result->getLastModified()->getTimestamp();
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
$result = $this->service->headObject($this->getOptions($key));
return (int) $result->getContentLength();
}
public function mimeType($key)
{
$result = $this->service->headObject($this->getOptions($key));
return $result->getContentType();
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = ['Bucket' => $this->bucket];
if ((string) $prefix != '') {
$options['Prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['Prefix'] = $this->options['directory'];
}
$keys = [];
$result = $this->service->listObjectsV2($options);
foreach ($result->getContents() as $file) {
$keys[] = $this->computeKey($file->getKey());
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
$this->service->deleteObject($this->getOptions($key));
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$result = $this->service->listObjectsV2([
'Bucket' => $this->bucket,
'Prefix' => rtrim($this->computePath($key), '/') . '/',
'MaxKeys' => 1,
]);
foreach ($result->getContents(true) as $file) {
return true;
}
return false;
}
/**
* Ensures the specified bucket exists. If the bucket does not exists
* and the create option is set to true, it will try to create the
* bucket. The bucket is created using the same region as the supplied
* client object.
*
* @throws \RuntimeException if the bucket does not exists or could not be
* created
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return true;
}
if ($this->bucketExists = $this->service->bucketExists(['Bucket' => $this->bucket])->isSuccess()) {
return true;
}
if (!$this->options['create']) {
throw new \RuntimeException(sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
));
}
$this->service->createBucket([
'Bucket' => $this->bucket,
'CreateBucketConfiguration' => [
'LocationConstraint' => $this->service->getConfiguration()->get(Configuration::OPTION_REGION),
],
]);
$this->bucketExists = true;
return true;
}
protected function getOptions($key, array $options = [])
{
$options['ACL'] = $this->options['acl'];
$options['Bucket'] = $this->bucket;
$options['Key'] = $this->computePath($key);
/*
* Merge global options for adapter, which are set in the constructor, with metadata.
* Metadata will override global options.
*/
$options = array_merge($this->options, $options, $this->getMetadata($key));
return $options;
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* Computes the key from the specified path.
*
* @param string $path
*
* return string
*/
protected function computeKey($path)
{
return ltrim(substr($path, strlen($this->options['directory'])), '/');
}
/**
* @param string|resource $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
}

View File

@@ -0,0 +1,346 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Aws\S3\S3Client;
use Gaufrette\Util;
/**
* Amazon S3 adapter using the AWS SDK for PHP v2.x.
*
* @author Michael Dowling <mtdowling@gmail.com>
*/
class AwsS3 implements Adapter, MetadataSupporter, ListKeysAware, SizeCalculator, MimeTypeProvider
{
/** @var S3Client */
protected $service;
/** @var string */
protected $bucket;
/** @var array */
protected $options;
/** @var bool */
protected $bucketExists;
/** @var array */
protected $metadata = [];
/** @var bool */
protected $detectContentType;
/**
* @param S3Client $service
* @param string $bucket
* @param array $options
* @param bool $detectContentType
*/
public function __construct(S3Client $service, $bucket, array $options = [], $detectContentType = false)
{
if (!class_exists(S3Client::class)) {
throw new \LogicException('You need to install package "aws/aws-sdk-php" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
[
'create' => false,
'directory' => '',
'acl' => 'private',
],
$options
);
$this->detectContentType = $detectContentType;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $metadata)
{
// BC with AmazonS3 adapter
if (isset($metadata['contentType'])) {
$metadata['ContentType'] = $metadata['contentType'];
unset($metadata['contentType']);
}
$this->metadata[$key] = $metadata;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
return $this->metadata[$key] ?? [];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
try {
// Get remote object
$object = $this->service->getObject($options);
// If there's no metadata array set up for this object, set it up
if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) {
$this->metadata[$key] = [];
}
// Make remote ContentType metadata available locally
$this->metadata[$key]['ContentType'] = $object->get('ContentType');
return (string) $object->get('Body');
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$options = $this->getOptions(
$targetKey,
['CopySource' => $this->bucket . '/' . $this->computePath($sourceKey)]
);
try {
$this->service->copyObject(array_merge($options, $this->getMetadata($targetKey)));
return $this->delete($sourceKey);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$options = $this->getOptions($key, ['Body' => $content]);
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as binary/octet-stream.
*/
if (!isset($options['ContentType']) && $this->detectContentType) {
$options['ContentType'] = $this->guessContentType($content);
}
try {
$this->service->putObject($options);
if (is_resource($content)) {
return Util\Size::fromResource($content);
}
return Util\Size::fromContent($content);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return $this->service->doesObjectExist($this->bucket, $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return strtotime($result['LastModified']);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return $result['ContentLength'];
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = ['Bucket' => $this->bucket];
if ((string) $prefix != '') {
$options['Prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['Prefix'] = $this->options['directory'];
}
$keys = [];
$iter = $this->service->getIterator('ListObjects', $options);
foreach ($iter as $file) {
$keys[] = $this->computeKey($file['Key']);
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
$this->service->deleteObject($this->getOptions($key));
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$result = $this->service->listObjects([
'Bucket' => $this->bucket,
'Prefix' => rtrim($this->computePath($key), '/') . '/',
'MaxKeys' => 1,
]);
if (isset($result['Contents'])) {
if (is_array($result['Contents']) || $result['Contents'] instanceof \Countable) {
return count($result['Contents']) > 0;
}
}
return false;
}
/**
* Ensures the specified bucket exists. If the bucket does not exists
* and the create option is set to true, it will try to create the
* bucket. The bucket is created using the same region as the supplied
* client object.
*
* @throws \RuntimeException if the bucket does not exists or could not be
* created
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return true;
}
if ($this->bucketExists = $this->service->doesBucketExist($this->bucket)) {
return true;
}
if (!$this->options['create']) {
throw new \RuntimeException(sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
));
}
$this->service->createBucket([
'Bucket' => $this->bucket,
'LocationConstraint' => $this->service->getRegion(),
]);
$this->bucketExists = true;
return true;
}
protected function getOptions($key, array $options = [])
{
$options['ACL'] = $this->options['acl'];
$options['Bucket'] = $this->bucket;
$options['Key'] = $this->computePath($key);
/*
* Merge global options for adapter, which are set in the constructor, with metadata.
* Metadata will override global options.
*/
$options = array_merge($this->options, $options, $this->getMetadata($key));
return $options;
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* Computes the key from the specified path.
*
* @param string $path
*
* return string
*/
protected function computeKey($path)
{
return ltrim(substr($path, strlen($this->options['directory'])), '/');
}
/**
* @param string $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
public function mimeType($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return ($result['ContentType']);
} catch (\Exception $e) {
return false;
}
}
}

View File

@@ -0,0 +1,598 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Util;
use Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface;
use MicrosoftAzure\Storage\Blob\Models\Blob;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use MicrosoftAzure\Storage\Blob\Models\Container;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
/**
* Microsoft Azure Blob Storage adapter.
*
* @author Luciano Mammino <lmammino@oryzone.com>
* @author Paweł Czyżewski <pawel.czyzewski@enginewerk.com>
*/
class AzureBlobStorage implements Adapter, MetadataSupporter, SizeCalculator, ChecksumCalculator, MimeTypeProvider
{
/**
* Error constants.
*/
const ERROR_CONTAINER_ALREADY_EXISTS = 'ContainerAlreadyExists';
const ERROR_CONTAINER_NOT_FOUND = 'ContainerNotFound';
/**
* @var AzureBlobStorage\BlobProxyFactoryInterface
*/
protected $blobProxyFactory;
/**
* @var string
*/
protected $containerName;
/**
* @var bool
*/
protected $detectContentType;
/**
* @var \MicrosoftAzure\Storage\Blob\Internal\IBlob
*/
protected $blobProxy;
/**
* @var bool
*/
protected $multiContainerMode = false;
/**
* @var CreateContainerOptions
*/
protected $createContainerOptions;
/**
* @param AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param string|null $containerName
* @param bool $create
* @param bool $detectContentType
*
* @throws \RuntimeException
*/
public function __construct(BlobProxyFactoryInterface $blobProxyFactory, $containerName = null, $create = false, $detectContentType = true)
{
$this->blobProxyFactory = $blobProxyFactory;
$this->containerName = $containerName;
$this->detectContentType = $detectContentType;
if (null === $containerName) {
$this->multiContainerMode = true;
} elseif ($create) {
$this->createContainer($containerName);
}
}
/**
* @return CreateContainerOptions
*/
public function getCreateContainerOptions()
{
return $this->createContainerOptions;
}
/**
* @param CreateContainerOptions $options
*/
public function setCreateContainerOptions(CreateContainerOptions $options)
{
$this->createContainerOptions = $options;
}
/**
* Creates a new container.
*
* @param string $containerName
* @param \MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions $options
*
* @throws \RuntimeException if cannot create the container
*/
public function createContainer($containerName, CreateContainerOptions $options = null)
{
$this->init();
if (null === $options) {
$options = $this->getCreateContainerOptions();
}
try {
$this->blobProxy->createContainer($containerName, $options);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($errorCode !== self::ERROR_CONTAINER_ALREADY_EXISTS) {
throw new \RuntimeException(sprintf(
'Failed to create the configured container "%s": %s (%s).',
$containerName,
$e->getErrorText(),
$errorCode
));
}
}
}
/**
* Deletes a container.
*
* @param string $containerName
* @param BlobServiceOptions $options
*
* @throws \RuntimeException if cannot delete the container
*/
public function deleteContainer($containerName, BlobServiceOptions $options = null)
{
$this->init();
try {
$this->blobProxy->deleteContainer($containerName, $options);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($errorCode !== self::ERROR_CONTAINER_NOT_FOUND) {
throw new \RuntimeException(sprintf(
'Failed to delete the configured container "%s": %s (%s).',
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function read($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$blob = $this->blobProxy->getBlob($containerName, $key);
return stream_get_contents($blob->getContentStream());
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function write($key, $content)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
if (class_exists(CreateBlockBlobOptions::class)) {
$options = new CreateBlockBlobOptions();
} else {
// for microsoft/azure-storage < 1.0
$options = new CreateBlobOptions();
}
if ($this->detectContentType) {
$contentType = $this->guessContentType($content);
$options->setContentType($contentType);
}
$size = is_resource($content)
? Util\Size::fromResource($content)
: Util\Size::fromContent($content)
;
try {
if ($this->multiContainerMode) {
$this->createContainer($containerName);
}
$this->blobProxy->createBlockBlob($containerName, $key, $content, $options);
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('write content for key "%s"', $key), $containerName);
return false;
}
return $size;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function exists($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
$listBlobsOptions = new ListBlobsOptions();
$listBlobsOptions->setPrefix($key);
try {
$blobsList = $this->blobProxy->listBlobs($containerName, $listBlobsOptions);
foreach ($blobsList->getBlobs() as $blob) {
if ($key === $blob->getName()) {
return true;
}
}
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($this->multiContainerMode && self::ERROR_CONTAINER_NOT_FOUND === $errorCode) {
return false;
}
$this->failIfContainerNotFound($e, 'check if key exists', $containerName);
throw new \RuntimeException(sprintf(
'Failed to check if key "%s" exists in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
return false;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
*/
public function keys()
{
$this->init();
try {
if ($this->multiContainerMode) {
$containersList = $this->blobProxy->listContainers();
return call_user_func_array('array_merge', array_map(
function (Container $container) {
$containerName = $container->getName();
return $this->fetchBlobs($containerName, $containerName);
},
$containersList->getContainers()
));
}
return $this->fetchBlobs($this->containerName);
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, 'retrieve keys', $this->containerName);
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to list keys for the container "%s": %s (%s).',
$this->containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function mtime($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getLastModified()->getTimestamp();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read mtime for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getContentLength();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content length for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function mimeType($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getContentType();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content mime type for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
$checksumBase64 = $properties->getProperties()->getContentMD5();
return \bin2hex(\base64_decode($checksumBase64, true));
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content MD5 for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function delete($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$this->blobProxy->deleteBlob($containerName, $key);
return true;
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('delete key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function rename($sourceKey, $targetKey)
{
$this->init();
list($sourceContainerName, $sourceKey) = $this->tokenizeKey($sourceKey);
list($targetContainerName, $targetKey) = $this->tokenizeKey($targetKey);
try {
if ($this->multiContainerMode) {
$this->createContainer($targetContainerName);
}
$this->blobProxy->copyBlob($targetContainerName, $targetKey, $sourceContainerName, $sourceKey);
$this->blobProxy->deleteBlob($sourceContainerName, $sourceKey);
return true;
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('rename key "%s"', $sourceKey), $sourceContainerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
// Windows Azure Blob Storage does not support directories
return false;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function setMetadata($key, $content)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$this->blobProxy->setBlobMetadata($containerName, $key, $content);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to set metadata for blob "%s" in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function getMetadata($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getMetadata();
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to get metadata for blob "%s" in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* Lazy initialization, automatically called when some method is called after construction.
*/
protected function init()
{
if ($this->blobProxy === null) {
$this->blobProxy = $this->blobProxyFactory->create();
}
}
/**
* Throws a runtime exception if a give ServiceException derived from a "container not found" error.
*
* @param ServiceException $exception
* @param string $action
* @param string $containerName
*
* @throws \RuntimeException
*/
protected function failIfContainerNotFound(ServiceException $exception, $action, $containerName)
{
$errorCode = $this->getErrorCodeFromServiceException($exception);
if ($errorCode === self::ERROR_CONTAINER_NOT_FOUND) {
throw new \RuntimeException(sprintf(
'Failed to %s: container "%s" not found.',
$action,
$containerName
), $exception->getCode());
}
}
/**
* Extracts the error code from a service exception.
*
* @param ServiceException $exception
*
* @return string
*/
protected function getErrorCodeFromServiceException(ServiceException $exception)
{
$xml = @simplexml_load_string($exception->getResponse()->getBody());
if ($xml && isset($xml->Code)) {
return (string) $xml->Code;
}
return $exception->getErrorText();
}
/**
* @param string|resource $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
/**
* @param string $key
*
* @return array
* @throws \InvalidArgumentException
*/
private function tokenizeKey($key)
{
$containerName = $this->containerName;
if (false === $this->multiContainerMode) {
return [$containerName, $key];
}
if (false === ($index = strpos($key, '/'))) {
throw new \InvalidArgumentException(sprintf(
'Failed to establish container name from key "%s", container name is required in multi-container mode',
$key
));
}
$containerName = substr($key, 0, $index);
$key = substr($key, $index + 1);
return [$containerName, $key];
}
/**
* @param string $containerName
* @param null $prefix
*
* @return array
*/
private function fetchBlobs($containerName, $prefix = null)
{
$blobList = $this->blobProxy->listBlobs($containerName);
return array_map(
function (Blob $blob) use ($prefix) {
$name = $blob->getName();
if (null !== $prefix) {
$name = $prefix . '/' . $name;
}
return $name;
},
$blobList->getBlobs()
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Gaufrette\Adapter\AzureBlobStorage;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Common\ServicesBuilder;
/**
* Basic implementation for a Blob proxy factory.
*
* @author Luciano Mammino <lmammino@oryzone.com>
*/
class BlobProxyFactory implements BlobProxyFactoryInterface
{
/**
* @var string
*/
protected $connectionString;
/**
* @param string $connectionString
*/
public function __construct($connectionString)
{
if (!class_exists(ServicesBuilder::class) && !class_exists(BlobRestProxy::class)) {
throw new \LogicException('You need to install package "microsoft/azure-storage-blob" to use this adapter');
}
$this->connectionString = $connectionString;
}
/**
* {@inheritdoc}
*/
public function create()
{
if (class_exists(ServicesBuilder::class)) {
// for microsoft/azure-storage < 1.0
return ServicesBuilder::getInstance()->createBlobService($this->connectionString);
}
return BlobRestProxy::createBlobService($this->connectionString);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gaufrette\Adapter\AzureBlobStorage;
/**
* Interface to define Blob proxy factories.
*
* @author Luciano Mammino <lmammino@oryzone.com>
*/
interface BlobProxyFactoryInterface
{
/**
* Creates a new instance of the Blob proxy.
*
* @return \MicrosoftAzure\Storage\Blob\Internal\IBlob
*/
public function create();
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add checksum calculation support to adapter.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface ChecksumCalculator
{
/**
* Returns the checksum of the specified key.
*
* @param string $key
*
* @return string
*/
public function checksum($key);
}

View File

@@ -0,0 +1,228 @@
<?php
namespace Gaufrette\Adapter;
use Doctrine\DBAL\Result;
use Gaufrette\Adapter;
use Gaufrette\Util;
use Doctrine\DBAL\Connection;
/**
* Doctrine DBAL adapter.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class DoctrineDbal implements Adapter, ChecksumCalculator, ListKeysAware
{
protected $connection;
protected $table;
protected $columns = [
'key' => 'key',
'content' => 'content',
'mtime' => 'mtime',
'checksum' => 'checksum',
];
/**
* @param Connection $connection The DBAL connection
* @param string $table The files table
* @param array $columns The column names
*/
public function __construct(Connection $connection, $table, array $columns = [])
{
if (!class_exists(Connection::class)) {
throw new \LogicException('You need to install package "doctrine/dbal" to use this adapter');
}
$this->connection = $connection;
$this->table = $table;
$this->columns = array_replace($this->columns, $columns);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
$stmt = $this->connection->executeQuery(sprintf(
'SELECT %s FROM %s',
$this->getQuotedColumn('key'),
$this->getQuotedTable()
));
if (class_exists(Result::class)) {
// dbal 3.x
return $stmt->fetchFirstColumn();
}
// BC layer for dbal 2.x
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
return (boolean) $this->connection->update(
$this->table,
[$this->getQuotedColumn('key') => $targetKey],
[$this->getQuotedColumn('key') => $sourceKey]
);
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->getColumnValue($key, 'mtime');
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
return $this->getColumnValue($key, 'checksum');
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, $method)) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
return (boolean) $this->connection->$method(
sprintf(
'SELECT COUNT(%s) FROM %s WHERE %s = :key',
$this->getQuotedColumn('key'),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['key' => $key]
);
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->getColumnValue($key, 'content');
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return (boolean) $this->connection->delete(
$this->table,
[$this->getQuotedColumn('key') => $key]
);
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$values = [
$this->getQuotedColumn('content') => $content,
$this->getQuotedColumn('mtime') => time(),
$this->getQuotedColumn('checksum') => Util\Checksum::fromContent($content),
];
if ($this->exists($key)) {
$this->connection->update(
$this->table,
$values,
[$this->getQuotedColumn('key') => $key]
);
} else {
$values[$this->getQuotedColumn('key')] = $key;
$this->connection->insert($this->table, $values);
}
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
private function getColumnValue($key, $column)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, $method)) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$value = $this->connection->$method(
sprintf(
'SELECT %s FROM %s WHERE %s = :key',
$this->getQuotedColumn($column),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['key' => $key]
);
return $value;
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$prefix = trim($prefix);
$method = 'fetchAllAssociative'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchAll'; // BC layer for dbal 2.x
}
$keys = $this->connection->$method(
sprintf(
'SELECT %s AS _key FROM %s WHERE %s LIKE :pattern',
$this->getQuotedColumn('key'),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['pattern' => sprintf('%s%%', $prefix)]
);
return [
'dirs' => [],
'keys' => array_map(
function ($value) {
return $value['_key'];
},
$keys
),
];
}
private function getQuotedTable()
{
return $this->connection->quoteIdentifier($this->table);
}
private function getQuotedColumn($column)
{
return $this->connection->quoteIdentifier($this->columns[$column]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\File;
use Gaufrette\Filesystem;
/**
* Interface for the file creation class.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface FileFactory
{
/**
* Creates a new File instance and returns it.
*
* @param string $key
* @param Filesystem $filesystem
*
* @return File
*/
public function createFile($key, Filesystem $filesystem);
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Exception\UnsupportedAdapterMethodException;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Util;
class Flysystem implements Adapter, ListKeysAware
{
/**
* @var AdapterInterface
*/
private $adapter;
/**
* @var Config
*/
private $config;
/**
* @param AdapterInterface $adapter
* @param \League\Flysystem\Config|array|null $config
*/
public function __construct(AdapterInterface $adapter, $config = null)
{
if (!interface_exists(AdapterInterface::class)) {
throw new \LogicException('You need to install package "league/flysystem" to use this adapter');
}
$this->adapter = $adapter;
$this->config = Util::ensureConfig($config);
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->adapter->read($key)['contents'];
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
return $this->adapter->write($key, $content, $this->config);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (bool) $this->adapter->has($key);
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_map(function ($content) {
return $content['path'];
}, $this->adapter->listContents());
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$dirs = [];
$keys = [];
foreach ($this->adapter->listContents() as $content) {
if (empty($prefix) || 0 === strpos($content['path'], $prefix)) {
if ('dir' === $content['type']) {
$dirs[] = $content['path'];
} else {
$keys[] = $content['path'];
}
}
}
return [
'keys' => $keys,
'dirs' => $dirs,
];
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->adapter->getTimestamp($key);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return $this->adapter->delete($key);
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
return $this->adapter->rename($sourceKey, $targetKey);
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
throw new UnsupportedAdapterMethodException('isDirectory is not supported by this adapter.');
}
}

View File

@@ -0,0 +1,596 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\File;
use Gaufrette\Filesystem;
/**
* Ftp adapter.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Ftp implements Adapter, FileFactory, ListKeysAware, SizeCalculator
{
/** @var null|resource|\FTP\Connection */
protected $connection = null;
protected $directory;
protected $host;
protected $port;
protected $username;
protected $password;
protected $passive;
protected $create;
protected $mode;
protected $ssl;
protected $timeout;
protected $fileData = [];
protected $utf8;
/**
* @param string $directory The directory to use in the ftp server
* @param string $host The host of the ftp server
* @param array $options The options like port, username, password, passive, create, mode
*/
public function __construct($directory, $host, $options = [])
{
if (!extension_loaded('ftp')) {
throw new \RuntimeException('Unable to use Gaufrette\Adapter\Ftp as the FTP extension is not available.');
}
$this->directory = (string) $directory;
$this->host = $host;
$this->port = $options['port'] ?? 21;
$this->username = $options['username'] ?? null;
$this->password = $options['password'] ?? null;
$this->passive = $options['passive'] ?? false;
$this->create = $options['create'] ?? false;
$this->mode = $options['mode'] ?? FTP_BINARY;
$this->ssl = $options['ssl'] ?? false;
$this->timeout = $options['timeout'] ?? 90;
$this->utf8 = $options['utf8'] ?? false;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$temp = fopen('php://temp', 'r+');
if (!ftp_fget($this->getConnection(), $temp, $this->computePath($key), $this->mode)) {
return false;
}
rewind($temp);
$contents = stream_get_contents($temp);
fclose($temp);
return $contents;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$path = $this->computePath($key);
$directory = \Gaufrette\Util\Path::dirname($path);
$this->ensureDirectoryExists($directory, true);
$temp = fopen('php://temp', 'r+');
$size = fwrite($temp, $content);
rewind($temp);
if (!ftp_fput($this->getConnection(), $path, $temp, $this->mode)) {
fclose($temp);
return false;
}
fclose($temp);
return $size;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return ftp_rename($this->getConnection(), $sourcePath, $targetPath);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$file = $this->computePath($key);
$lines = ftp_rawlist($this->getConnection(), '-al ' . \Gaufrette\Util\Path::dirname($file));
if (false === $lines) {
return false;
}
$pattern = '{(?<!->) ' . preg_quote(basename($file)) . '( -> |$)}m';
foreach ($lines as $line) {
if (preg_match($pattern, $line)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function keys()
{
$this->ensureDirectoryExists($this->directory, $this->create);
$keys = $this->fetchKeys();
return $keys['keys'];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureDirectoryExists($this->directory, $this->create);
preg_match('/(.*?)[^\/]*$/', $prefix, $match);
$directory = rtrim($match[1], '/');
$keys = $this->fetchKeys($directory, false);
if ($directory === $prefix) {
return $keys;
}
$filteredKeys = [];
foreach (['keys', 'dirs'] as $hash) {
$filteredKeys[$hash] = [];
foreach ($keys[$hash] as $key) {
if (0 === strpos($key, $prefix)) {
$filteredKeys[$hash][] = $key;
}
}
}
return $filteredKeys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$mtime = ftp_mdtm($this->getConnection(), $this->computePath($key));
// the server does not support this function
if (-1 === $mtime) {
throw new \RuntimeException('Server does not support ftp_mdtm function.');
}
return $mtime;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
if ($this->isDirectory($key)) {
return ftp_rmdir($this->getConnection(), $this->computePath($key));
}
return ftp_delete($this->getConnection(), $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
return $this->isDir($this->computePath($key));
}
/**
* Lists files from the specified directory. If a pattern is
* specified, it only returns files matching it.
*
* @param string $directory The path of the directory to list from
*
* @return array An array of keys and dirs
*/
public function listDirectory($directory = '')
{
$this->ensureDirectoryExists($this->directory, $this->create);
$directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory);
$items = $this->parseRawlist(
ftp_rawlist($this->getConnection(), '-al ' . $this->directory . $directory) ?: []
);
$fileData = $dirs = [];
foreach ($items as $itemData) {
if ('..' === $itemData['name'] || '.' === $itemData['name']) {
continue;
}
$item = [
'name' => $itemData['name'],
'path' => trim(($directory ? $directory . '/' : '') . $itemData['name'], '/'),
'time' => $itemData['time'],
'size' => $itemData['size'],
];
if ('-' === substr($itemData['perms'], 0, 1)) {
$fileData[$item['path']] = $item;
} elseif ('d' === substr($itemData['perms'], 0, 1)) {
$dirs[] = $item['path'];
}
}
$this->fileData = array_merge($fileData, $this->fileData);
return [
'keys' => array_keys($fileData),
'dirs' => $dirs,
];
}
/**
* {@inheritdoc}
*/
public function createFile($key, Filesystem $filesystem)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$file = new File($key, $filesystem);
if (!array_key_exists($key, $this->fileData)) {
$dirname = \Gaufrette\Util\Path::dirname($key);
$directory = $dirname == '.' ? '' : $dirname;
$this->listDirectory($directory);
}
if (isset($this->fileData[$key])) {
$fileData = $this->fileData[$key];
$file->setName($fileData['name']);
$file->setSize($fileData['size']);
}
return $file;
}
/**
* @param string $key
*
* @return int
*
* @throws \RuntimeException
*/
public function size($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
if (-1 === $size = ftp_size($this->connection, $key)) {
throw new \RuntimeException(sprintf('Unable to fetch the size of "%s".', $key));
}
return $size;
}
/**
* Ensures the specified directory exists. If it does not, and the create
* parameter is set to TRUE, it tries to create it.
*
* @param string $directory
* @param bool $create Whether to create the directory if it does not
* exist
*
* @throws RuntimeException if the directory does not exist and could not
* be created
*/
protected function ensureDirectoryExists($directory, $create = false)
{
if (!$this->isDir($directory)) {
if (!$create) {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $directory));
}
$this->createDirectory($directory);
}
}
/**
* Creates the specified directory and its parent directories.
*
* @param string $directory Directory to create
*
* @throws RuntimeException if the directory could not be created
*/
protected function createDirectory($directory)
{
// create parent directory if needed
$parent = \Gaufrette\Util\Path::dirname($directory);
if (!$this->isDir($parent)) {
$this->createDirectory($parent);
}
// create the specified directory
$created = ftp_mkdir($this->getConnection(), $directory);
if (false === $created) {
throw new \RuntimeException(sprintf('Could not create the \'%s\' directory.', $directory));
}
}
/**
* @param string $directory - full directory path
*
* @return bool
*/
private function isDir($directory)
{
if ('/' === $directory) {
return true;
}
if (!@ftp_chdir($this->getConnection(), $directory)) {
return false;
}
// change directory again to return in the base directory
ftp_chdir($this->getConnection(), $this->directory);
return true;
}
private function fetchKeys($directory = '', $onlyKeys = true)
{
$directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory);
$lines = ftp_rawlist($this->getConnection(), '-alR ' . $this->directory . $directory);
if (false === $lines) {
return ['keys' => [], 'dirs' => []];
}
$regexDir = '/' . preg_quote($this->directory . $directory, '/') . '\/?(.+):$/u';
$regexItem = '/^(?:([d\-\d])\S+)\s+\S+(?:(?:\s+\S+){5})?\s+(\S+)\s+(.+?)$/';
$prevLine = null;
$directories = [];
$keys = ['keys' => [], 'dirs' => []];
foreach ((array) $lines as $line) {
if ('' === $prevLine && preg_match($regexDir, $line, $match)) {
$directory = $match[1];
unset($directories[$directory]);
if ($onlyKeys) {
$keys = [
'keys' => array_merge($keys['keys'], $keys['dirs']),
'dirs' => [],
];
}
} elseif (preg_match($regexItem, $line, $tokens)) {
$name = $tokens[3];
if ('.' === $name || '..' === $name) {
continue;
}
$path = ltrim($directory . '/' . $name, '/');
if ('d' === $tokens[1] || '<dir>' === $tokens[2]) {
$keys['dirs'][] = $path;
$directories[$path] = true;
} else {
$keys['keys'][] = $path;
}
}
$prevLine = $line;
}
if ($onlyKeys) {
$keys = [
'keys' => array_merge($keys['keys'], $keys['dirs']),
'dirs' => [],
];
}
foreach (array_keys($directories) as $directory) {
$keys = array_merge_recursive($keys, $this->fetchKeys($directory, $onlyKeys));
}
return $keys;
}
/**
* Parses the given raw list.
*
* @param array $rawlist
*
* @return array
*/
private function parseRawlist(array $rawlist)
{
$parsed = [];
foreach ($rawlist as $line) {
$infos = preg_split("/[\s]+/", $line, 9);
if ($this->isLinuxListing($infos)) {
$infos[7] = (strrpos($infos[7], ':') != 2) ? ($infos[7] . ' 00:00') : (date('Y') . ' ' . $infos[7]);
if ('total' !== $infos[0]) {
$parsed[] = [
'perms' => $infos[0],
'num' => $infos[1],
'size' => $infos[4],
'time' => strtotime($infos[5] . ' ' . $infos[6] . '. ' . $infos[7]),
'name' => $infos[8],
];
}
} elseif (count($infos) >= 4) {
$isDir = (boolean) ('<dir>' === $infos[2]);
$parsed[] = [
'perms' => $isDir ? 'd' : '-',
'num' => '',
'size' => $isDir ? '' : $infos[2],
'time' => strtotime($infos[0] . ' ' . $infos[1]),
'name' => $infos[3],
];
}
}
return $parsed;
}
/**
* Computes the path for the given key.
*
* @param string $key
*/
private function computePath($key)
{
return rtrim($this->directory, '/') . '/' . $key;
}
/**
* Indicates whether the adapter has an open ftp connection.
*
* @return bool
*/
private function isConnected()
{
if (class_exists('\FTP\Connection')) {
return $this->connection instanceof \FTP\Connection;
}
return is_resource($this->connection);
}
/**
* Returns an opened ftp connection resource. If the connection is not
* already opened, it open it before.
*
* @return resource|\FTP\Connection The ftp connection
*/
private function getConnection()
{
if (!$this->isConnected()) {
$this->connect();
}
return $this->connection;
}
/**
* Opens the adapter's ftp connection.
*
* @throws RuntimeException if could not connect
*/
private function connect()
{
if ($this->ssl && !function_exists('ftp_ssl_connect')) {
throw new \RuntimeException('This Server Has No SSL-FTP Available.');
}
// open ftp connection
if (!$this->ssl) {
$this->connection = ftp_connect($this->host, $this->port, $this->timeout);
} else {
$this->connection = ftp_ssl_connect($this->host, $this->port, $this->timeout);
}
if (!$this->connection) {
throw new \RuntimeException(sprintf('Could not connect to \'%s\' (port: %s).', $this->host, $this->port));
}
if (defined('FTP_USEPASVADDRESS')) {
ftp_set_option($this->connection, FTP_USEPASVADDRESS, false);
}
$username = $this->username ?: 'anonymous';
$password = $this->password ?: '';
// login ftp user
if (!@ftp_login($this->connection, $username, $password)) {
$this->close();
throw new \RuntimeException(sprintf('Could not login as %s.', $username));
}
// switch to passive mode if needed
if ($this->passive && !ftp_pasv($this->connection, true)) {
$this->close();
throw new \RuntimeException('Could not turn passive mode on.');
}
// enable utf8 mode if configured
if ($this->utf8 == true) {
ftp_raw($this->connection, 'OPTS UTF8 ON');
}
// ensure the adapter's directory exists
if ('/' !== $this->directory) {
try {
$this->ensureDirectoryExists($this->directory, $this->create);
} catch (\RuntimeException $e) {
$this->close();
throw $e;
}
// change the current directory for the adapter's directory
if (!ftp_chdir($this->connection, $this->directory)) {
$this->close();
throw new \RuntimeException(sprintf('Could not change current directory for the \'%s\' directory.', $this->directory));
}
}
}
/**
* Closes the adapter's ftp connection.
*/
public function close()
{
if ($this->isConnected()) {
ftp_close($this->connection);
}
}
private function isLinuxListing($info)
{
return count($info) >= 9;
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Google\Service\Storage;
use Google\Service\Storage\Bucket;
use Google\Service\Storage\StorageObject;
use Google\Service\Exception as ServiceException;
use Google\Service\Storage\BucketIamConfiguration;
use Google\Service\Storage\BucketIamConfigurationUniformBucketLevelAccess;
use GuzzleHttp;
/**
* Google Cloud Storage adapter using the Google APIs Client Library for PHP.
*
* @author Patrik Karisch <patrik@karisch.guru>
*/
class GoogleCloudStorage implements Adapter, MetadataSupporter, ListKeysAware
{
public const OPTION_CREATE_BUCKET_IF_NOT_EXISTS = 'create';
public const OPTION_PROJECT_ID = 'project_id';
public const OPTION_LOCATION = 'bucket_location';
public const OPTION_STORAGE_CLASS = 'storage_class';
protected $service;
protected $bucket;
protected $options = [
self::OPTION_CREATE_BUCKET_IF_NOT_EXISTS => false,
self::OPTION_STORAGE_CLASS => 'STANDARD',
'directory' => '',
'acl' => 'private',
];
protected $bucketExists;
protected $metadata = [];
protected $detectContentType;
/**
* @param Storage $service The storage service class with authenticated
* client and full access scope
* @param string $bucket The bucket name
* @param array $options Options can be directory and acl
* @param bool $detectContentType Whether to detect the content type or not
*/
public function __construct(
Storage $service,
$bucket,
array $options = [],
$detectContentType = false
) {
if (!class_exists(Storage::class)) {
throw new \LogicException('You need to install package "google/apiclient" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
$this->options,
$options
);
$this->detectContentType = $detectContentType;
}
/**
* @return array The actual options
*/
public function getOptions()
{
return $this->options;
}
/**
* @param array $options The new options
*/
public function setOptions($options)
{
$this->options = array_replace($this->options, $options);
}
/**
* @return string The current bucket name
*/
public function getBucket()
{
return $this->bucket;
}
/**
* Sets a new bucket name.
*
* @param string $bucket The new bucket name
*/
public function setBucket($bucket)
{
$this->bucketExists = null;
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$object = $this->getObjectData($path);
if ($object === false) {
return false;
}
if (class_exists('Google_Http_Request')) {
$request = new \Google_Http_Request($object->getMediaLink());
$this->service->getClient()->getAuth()->sign($request);
$response = $this->service->getClient()->getIo()->executeRequest($request);
if ($response[2] == 200) {
$this->setMetadata($key, $object->getMetadata());
return $response[0];
}
} else {
$httpClient = new GuzzleHttp\Client();
$httpClient = $this->service->getClient()->authorize($httpClient);
$response = $httpClient->request('GET', $object->getMediaLink());
if ($response->getStatusCode() == 200) {
$this->setMetadata($key, $object->getMetadata());
return $response->getBody();
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$metadata = $this->getMetadata($key);
$options = [
'uploadType' => 'multipart',
'data' => $content,
];
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as application/octet-stream.
*/
if (!isset($metadata['ContentType']) && $this->detectContentType) {
$options['mimeType'] = $this->guessContentType($content);
unset($metadata['ContentType']);
} elseif (isset($metadata['ContentType'])) {
$options['mimeType'] = $metadata['ContentType'];
unset($metadata['ContentType']);
}
$object = new StorageObject();
$object->name = $path;
if (isset($metadata['ContentDisposition'])) {
$object->setContentDisposition($metadata['ContentDisposition']);
unset($metadata['ContentDisposition']);
}
if (isset($metadata['CacheControl'])) {
$object->setCacheControl($metadata['CacheControl']);
unset($metadata['CacheControl']);
}
if (isset($metadata['ContentLanguage'])) {
$object->setContentLanguage($metadata['ContentLanguage']);
unset($metadata['ContentLanguage']);
}
if (isset($metadata['ContentEncoding'])) {
$object->setContentEncoding($metadata['ContentEncoding']);
unset($metadata['ContentEncoding']);
}
$object->setMetadata($metadata);
try {
$object = $this->service->objects->insert($this->bucket, $object, $options);
if ($this->options['acl'] == 'public') {
$acl = new \Google_Service_Storage_ObjectAccessControl();
$acl->setEntity('allUsers');
$acl->setRole('READER');
$this->service->objectAccessControls->insert($this->bucket, $path, $acl);
}
return $object->getSize();
} catch (ServiceException $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
try {
$this->service->objects->get($this->bucket, $path);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$object = $this->getObjectData($path);
return $object ? strtotime($object->getUpdated()) : false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
try {
$this->service->objects->delete($this->bucket, $path);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$object = $this->getObjectData($sourcePath);
if ($object === false) {
return false;
}
try {
$this->service->objects->copy($this->bucket, $sourcePath, $this->bucket, $targetPath, $object);
$this->service->objects->delete($this->bucket, $sourcePath);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
if ($this->exists($key . '/')) {
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = [];
if ((string) $prefix != '') {
$options['prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['prefix'] = $this->options['directory'];
}
$list = $this->service->objects->listObjects($this->bucket, $options);
$keys = [];
// FIXME: Temporary workaround for google/google-api-php-client#375
$reflectionClass = new \ReflectionClass('Google_Service_Storage_Objects');
$reflectionProperty = $reflectionClass->getProperty('collection_key');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($list, 'items');
/** @var StorageObject $object */
foreach ($list as $object) {
$keys[] = $object->name;
}
sort($keys);
return $keys;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $content)
{
$path = $this->computePath($key);
$this->metadata[$path] = $content;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
$path = $this->computePath($key);
return $this->metadata[$path] ?? [];
}
/**
* Ensures the specified bucket exists.
*
* @throws \RuntimeException if the bucket does not exists
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return;
}
try {
$this->service->buckets->get($this->bucket);
$this->bucketExists = true;
return;
} catch (ServiceException $e) {
if ($this->options[self::OPTION_CREATE_BUCKET_IF_NOT_EXISTS]) {
if (!isset($this->options[self::OPTION_PROJECT_ID])) {
throw new \RuntimeException(
sprintf('Option "%s" missing, cannot create bucket', self::OPTION_PROJECT_ID)
);
}
if (!isset($this->options[self::OPTION_LOCATION])) {
throw new \RuntimeException(
sprintf('Option "%s" missing, cannot create bucket', self::OPTION_LOCATION)
);
}
$bucketIamConfigDetail = new BucketIamConfigurationUniformBucketLevelAccess();
$bucketIamConfigDetail->setEnabled(true);
$bucketIam = new BucketIamConfiguration();
$bucketIam->setUniformBucketLevelAccess($bucketIamConfigDetail);
$bucket = new Bucket();
$bucket->setName($this->bucket);
$bucket->setLocation($this->options[self::OPTION_LOCATION]);
$bucket->setStorageClass($this->options[self::OPTION_STORAGE_CLASS]);
$bucket->setIamConfiguration($bucketIam);
$this->service->buckets->insert(
$this->options[self::OPTION_PROJECT_ID],
$bucket
);
$this->bucketExists = true;
return;
}
$this->bucketExists = false;
throw new \RuntimeException(
sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
)
);
}
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* @param string $path
* @param array $options
*
* @return bool|StorageObject
*/
private function getObjectData($path, $options = [])
{
try {
return $this->service->objects->get($this->bucket, $path, $options);
} catch (ServiceException $e) {
return false;
}
}
/**
* @param string $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use MongoDB\BSON\Regex;
use MongoDB\GridFS\Bucket;
use MongoDB\GridFS\Exception\FileNotFoundException;
/**
* Adapter for the GridFS filesystem on MongoDB database.
*
* @author Tomi Saarinen <tomi.saarinen@rohea.com>
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class GridFS implements Adapter, ChecksumCalculator, MetadataSupporter, ListKeysAware, SizeCalculator
{
/** @var array */
private $metadata = [];
/** @var Bucket */
private $bucket;
/**
* @param Bucket $bucket
*/
public function __construct(Bucket $bucket)
{
if (!class_exists(Bucket::class)) {
throw new \LogicException('You need to install package "mongodb/mongodb" to use this adapter');
}
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
try {
$stream = $this->bucket->openDownloadStreamByName($key);
} catch (FileNotFoundException $e) {
return false;
}
try {
return stream_get_contents($stream);
} finally {
fclose($stream);
}
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$stream = $this->bucket->openUploadStream($key, ['metadata' => $this->getMetadata($key)]);
try {
return fwrite($stream, $content);
} finally {
fclose($stream);
}
return false;
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$metadata = $this->getMetadata($sourceKey);
$writable = $this->bucket->openUploadStream($targetKey, ['metadata' => $metadata]);
try {
$this->bucket->downloadToStreamByName($sourceKey, $writable);
$this->setMetadata($targetKey, $metadata);
$this->delete($sourceKey);
} catch (FileNotFoundException $e) {
return false;
} finally {
fclose($writable);
}
return true;
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (boolean) $this->bucket->findOne(['filename' => $key]);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
$cursor = $this->bucket->find([], ['projection' => ['filename' => 1]]);
foreach ($cursor as $file) {
$keys[] = $file['filename'];
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$file = $this->bucket->findOne(['filename' => $key], ['projection' => ['uploadDate' => 1]]);
return $file ? (int) $file['uploadDate']->toDateTime()->format('U') : false;
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
$file = $this->bucket->findOne(['filename' => $key], ['projection' => ['md5' => 1]]);
return $file ? $file['md5'] : false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
if (null === $file = $this->bucket->findOne(['filename' => $key], ['projection' => ['_id' => 1]])) {
return false;
}
$this->bucket->delete($file['_id']);
return true;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $metadata)
{
$this->metadata[$key] = $metadata;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
if (isset($this->metadata[$key])) {
return $this->metadata[$key];
}
$meta = $this->bucket->findOne(['filename' => $key], ['projection' => ['metadata' => 1,'_id' => 0]]);
if ($meta === null || !isset($meta['metadata'])) {
return [];
}
$this->metadata[$key] = iterator_to_array($meta['metadata']);
return $this->metadata[$key];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$prefix = trim($prefix);
if ($prefix === '') {
return [
'dirs' => [],
'keys' => $this->keys(),
];
}
$regex = new Regex(sprintf('^%s', $prefix), '');
$files = $this->bucket->find(['filename' => $regex], ['projection' => ['filename' => 1]]);
$result = [
'dirs' => [],
'keys' => [],
];
foreach ($files as $file) {
$result['keys'][] = $file['filename'];
}
return $result;
}
public function size($key)
{
if (!$this->exists($key)) {
return false;
}
$size = $this->bucket->findOne(['filename' => $key], ['projection' => ['length' => 1,'_id' => 0]]);
if (!isset($size['length'])) {
return false;
}
return $size['length'];
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* In memory adapter.
*
* Stores some files in memory for test purposes
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class InMemory implements Adapter, MimeTypeProvider
{
protected $files = [];
/**
* @param array $files An array of files
*/
public function __construct(array $files = [])
{
$this->setFiles($files);
}
/**
* Defines the files.
*
* @param array $files An array of files
*/
public function setFiles(array $files)
{
$this->files = [];
foreach ($files as $key => $file) {
if (!is_array($file)) {
$file = ['content' => $file];
}
$file = array_merge([
'content' => null,
'mtime' => null,
], $file);
$this->setFile($key, $file['content'], $file['mtime']);
}
}
/**
* Defines a file.
*
* @param string $key The key
* @param string $content The content
* @param int $mtime The last modified time (automatically set to now if NULL)
*/
public function setFile($key, $content = null, $mtime = null)
{
if (null === $mtime) {
$mtime = time();
}
$this->files[$key] = [
'content' => (string) $content,
'mtime' => (integer) $mtime,
];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->files[$key]['content'];
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$content = $this->read($sourceKey);
$this->delete($sourceKey);
return (boolean) $this->write($targetKey, $content);
}
/**
* {@inheritdoc}
*/
public function write($key, $content, array $metadata = null)
{
$this->files[$key]['content'] = $content;
$this->files[$key]['mtime'] = time();
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return array_key_exists($key, $this->files);
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_keys($this->files);
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->files[$key]['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
unset($this->files[$key]);
clearstatcache();
return true;
}
/**
* {@inheritdoc}
*/
public function isDirectory($path)
{
return false;
}
/**
* {@inheritdoc}
*/
public function mimeType($key)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
return $fileInfo->buffer($this->files[$key]['content']);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Gaufrette\Adapter;
/**
* interface that adds support of native listKeys to adapter.
*
* @author Andrew Tch <andrew.tchircoff@gmail.com>
*/
interface ListKeysAware
{
/**
* Lists keys beginning with pattern given
* (no wildcard / regex matching).
*
* @param string $prefix
*
* @return array
*/
public function listKeys($prefix = '');
}

View File

@@ -0,0 +1,353 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Util;
use Gaufrette\Adapter;
use Gaufrette\Stream;
/**
* Adapter for the local filesystem.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class Local implements Adapter, StreamFactory, ChecksumCalculator, SizeCalculator, MimeTypeProvider
{
protected $directory;
private $create;
private $mode;
/**
* @param string $directory Directory where the filesystem is located
* @param bool $create Whether to create the directory if it does not
* exist (default FALSE)
* @param int $mode Mode for mkdir
*
* @throws \RuntimeException if the specified directory does not exist and
* could not be created
*/
public function __construct($directory, $create = false, $mode = 0777)
{
$this->directory = Util\Path::normalize($directory);
if (is_link($this->directory)) {
$this->directory = realpath($this->directory);
}
$this->create = $create;
$this->mode = $mode;
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function read($key)
{
if ($this->isDirectory($key)) {
return false;
}
return file_get_contents($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function write($key, $content)
{
$path = $this->computePath($key);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($path), true);
return file_put_contents($path, $content);
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function rename($sourceKey, $targetKey)
{
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return rename($this->computePath($sourceKey), $targetPath);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return is_file($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function keys()
{
$this->ensureDirectoryExists($this->directory, $this->create);
try {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$this->directory,
\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
\RecursiveIteratorIterator::CHILD_FIRST
);
} catch (\Exception $e) {
$files = new \EmptyIterator();
}
$keys = [];
foreach ($files as $file) {
$keys[] = $this->computeKey($file);
}
sort($keys);
return $keys;
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function mtime($key)
{
return filemtime($this->computePath($key));
}
/**
* {@inheritdoc}
*
* Can also delete a directory recursively when the given $key matches a
* directory.
*/
public function delete($key)
{
if ($this->isDirectory($key)) {
return $this->deleteDirectory($this->computePath($key));
} elseif ($this->exists($key)) {
return unlink($this->computePath($key));
}
return false;
}
/**
* @param string $key
*
* @return bool
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function isDirectory($key)
{
return is_dir($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function createStream($key)
{
return new Stream\Local($this->computePath($key), $this->mode);
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function checksum($key)
{
return Util\Checksum::fromFile($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function size($key)
{
return Util\Size::fromFile($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function mimeType($key)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
return $fileInfo->file($this->computePath($key));
}
/**
* Computes the key from the specified path.
*
* @param $path
* @return string
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function computeKey($path)
{
$path = $this->normalizePath($path);
return ltrim(substr($path, strlen($this->directory)), '/');
}
/**
* Computes the path from the specified key.
*
* @param string $key The key which for to compute the path
*
* @return string A path
*
* @throws \InvalidArgumentException If the directory already exists
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \RuntimeException If directory does not exists and cannot be created
*/
protected function computePath($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
return $this->normalizePath($this->directory . '/' . $key);
}
/**
* Normalizes the given path.
*
* @param string $path
*
* @return string
* @throws \OutOfBoundsException If the computed path is out of the
* directory
*/
protected function normalizePath($path)
{
$path = Util\Path::normalize($path);
if (0 !== strpos($path, $this->directory)) {
throw new \OutOfBoundsException(sprintf('The path "%s" is out of the filesystem.', $path));
}
return $path;
}
/**
* Ensures the specified directory exists, creates it if it does not.
*
* @param string $directory Path of the directory to test
* @param bool $create Whether to create the directory if it does
* not exist
*
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory does not exists and could not
* be created
*/
protected function ensureDirectoryExists($directory, $create = false)
{
if (!is_dir($directory)) {
if (!$create) {
throw new \RuntimeException(sprintf('The directory "%s" does not exist.', $directory));
}
$this->createDirectory($directory);
}
}
/**
* Creates the specified directory and its parents.
*
* @param string $directory Path of the directory to create
*
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
protected function createDirectory($directory)
{
if (!@mkdir($directory, $this->mode, true) && !is_dir($directory)) {
throw new \RuntimeException(sprintf('The directory \'%s\' could not be created.', $directory));
}
}
/**
* @param string The directory's path to delete
*
* @throws \InvalidArgumentException When attempting to delete the root
* directory of this adapter.
*
* @return bool Wheter the operation succeeded or not
*/
private function deleteDirectory($directory)
{
if ($this->directory === $directory) {
throw new \InvalidArgumentException(
sprintf('Impossible to delete the root directory of this Local adapter ("%s").', $directory)
);
}
$status = true;
if (file_exists($directory)) {
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$directory,
\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
$status = $status && rmdir(strval($item));
} else {
$status = $status && unlink(strval($item));
}
}
$status = $status && rmdir($directory);
}
return $status;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add supports for metadata.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface MetadataSupporter
{
/**
* @param string $key
* @param array $content
*/
public function setMetadata($key, $content);
/**
* @param string $key
*
* @return array
*/
public function getMetadata($key);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add mime type provider support to adapter.
*
* @author Gildas Quemener <gildas.quemener@gmail.com>
*/
interface MimeTypeProvider
{
/**
* Returns the mime type of the specified key.
*
* @param string $key
*
* @return string
*/
public function mimeType($key);
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use phpseclib\Net\SFTP as SecLibSFTP;
use Gaufrette\Filesystem;
use Gaufrette\File;
class PhpseclibSftp implements Adapter, FileFactory, ListKeysAware
{
protected $sftp;
protected $directory;
protected $create;
protected $initialized = false;
/**
* @param SecLibSFTP $sftp An Sftp instance
* @param string $directory The distant directory
* @param bool $create Whether to create the remote directory if it
* does not exist
*/
public function __construct(SecLibSFTP $sftp, $directory = null, $create = false)
{
if (!class_exists(SecLibSFTP::class)) {
throw new \LogicException('You need to install package "phpseclib/phpseclib" to use this adapter');
}
$this->sftp = $sftp;
$this->directory = $directory;
$this->create = $create;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->sftp->get($this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->initialize();
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return $this->sftp->rename($sourcePath, $targetPath);
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->initialize();
$path = $this->computePath($key);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($path), true);
if ($this->sftp->put($path, $content)) {
return $this->sftp->size($path);
}
return false;
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->initialize();
return false !== $this->sftp->stat($this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$this->initialize();
$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($this->computePath($key))) {
$this->sftp->chdir($pwd);
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = $this->fetchKeys();
return $keys['keys'];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
preg_match('/(.*?)[^\/]*$/', $prefix, $match);
$directory = rtrim($match[1], '/');
$keys = $this->fetchKeys($directory, false);
if ($directory === $prefix) {
return $keys;
}
$filteredKeys = [];
foreach (['keys', 'dirs'] as $hash) {
$filteredKeys[$hash] = [];
foreach ($keys[$hash] as $key) {
if (0 === strpos($key, $prefix)) {
$filteredKeys[$hash][] = $key;
}
}
}
return $filteredKeys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->initialize();
$stat = $this->sftp->stat($this->computePath($key));
return $stat['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return $this->sftp->delete($this->computePath($key), false);
}
/**
* {@inheritdoc}
*/
public function createFile($key, Filesystem $filesystem)
{
$file = new File($key, $filesystem);
$stat = $this->sftp->stat($this->computePath($key));
if (isset($stat['size'])) {
$file->setSize($stat['size']);
}
return $file;
}
/**
* Performs the adapter's initialization.
*
* It will ensure the root directory exists
*/
protected function initialize()
{
if ($this->initialized) {
return;
}
$this->ensureDirectoryExists($this->directory, $this->create);
$this->initialized = true;
}
protected function ensureDirectoryExists($directory, $create)
{
$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($directory)) {
$this->sftp->chdir($pwd);
} elseif ($create) {
if (!$this->sftp->mkdir($directory, 0777, true)) {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist and could not be created (%s).', $this->directory, $this->sftp->getLastSFTPError()));
}
} else {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $this->directory));
}
}
protected function computePath($key)
{
return $this->directory . '/' . ltrim($key, '/');
}
protected function fetchKeys($directory = '', $onlyKeys = true)
{
$keys = ['keys' => [], 'dirs' => []];
$computedPath = $this->computePath($directory);
if (!$this->sftp->file_exists($computedPath)) {
return $keys;
}
$list = $this->sftp->rawlist($computedPath);
foreach ((array) $list as $filename => $stat) {
if ('.' === $filename || '..' === $filename) {
continue;
}
$path = ltrim($directory . '/' . $filename, '/');
if (isset($stat['type']) && $stat['type'] === NET_SFTP_TYPE_DIRECTORY) {
$keys['dirs'][] = $path;
} else {
$keys['keys'][] = $path;
}
}
$dirs = $keys['dirs'];
if ($onlyKeys && !empty($dirs)) {
$keys['keys'] = array_merge($keys['keys'], $dirs);
$keys['dirs'] = [];
}
foreach ($dirs as $dir) {
$keys = array_merge_recursive($keys, $this->fetchKeys($dir, $onlyKeys));
}
return $keys;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gaufrette\Adapter;
/**
* Safe local adapter that encodes key to avoid the use of the directories
* structure.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class SafeLocal extends Local
{
/**
* {@inheritdoc}
*/
public function computeKey($path)
{
return base64_decode(parent::computeKey($path));
}
/**
* {@inheritdoc}
*/
protected function computePath($key)
{
return parent::computePath(base64_encode($key));
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add size calculation support to adapter.
*
* @author Markus Poerschke <markus@eluceo.de>
*/
interface SizeCalculator
{
/**
* Returns the size of the specified key.
*
* @param string $key
*
* @return int
*/
public function size($key);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface for the stream creation class.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface StreamFactory
{
/**
* Creates a new stream instance of the specified file.
*
* @param string $key
*
* @return \Gaufrette\Stream
*/
public function createStream($key);
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Gaufrette\Adapter;
use ZipArchive;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* ZIP Archive adapter.
*
* @author Boris Guéry <guery.b@gmail.com>
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Zip implements Adapter
{
/**
* @var string The zip archive full path
*/
protected $zipFile;
/**
* @var ZipArchive
*/
protected $zipArchive;
public function __construct($zipFile)
{
if (!extension_loaded('zip')) {
throw new \RuntimeException(sprintf('Unable to use %s as the ZIP extension is not available.', __CLASS__));
}
$this->zipFile = $zipFile;
$this->reinitZipArchive();
}
/**
* {@inheritdoc}
*/
public function read($key)
{
if (false === ($content = $this->zipArchive->getFromName($key, 0))) {
return false;
}
return $content;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
if (!$this->zipArchive->addFromString($key, $content)) {
return false;
}
if (!$this->save()) {
return false;
}
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (boolean) $this->getStat($key);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
for ($i = 0; $i < $this->zipArchive->numFiles; ++$i) {
$keys[$i] = $this->zipArchive->getNameIndex($i);
}
return $keys;
}
/**
* @todo implement
*
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$stat = $this->getStat($key);
return $stat['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
if (!$this->zipArchive->deleteName($key)) {
return false;
}
return $this->save();
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
if (!$this->zipArchive->renameName($sourceKey, $targetKey)) {
return false;
}
return $this->save();
}
/**
* Returns the stat of a file in the zip archive
* (name, index, crc, mtime, compression size, compression method, filesize).
*
* @param $key
*
* @return array|bool
*/
public function getStat($key)
{
$stat = $this->zipArchive->statName($key);
if (false === $stat) {
return [];
}
return $stat;
}
public function __destruct()
{
if ($this->zipArchive) {
try {
$this->zipArchive->close();
} catch (\Exception $e) {
}
unset($this->zipArchive);
}
}
protected function reinitZipArchive()
{
$this->zipArchive = new ZipArchive();
if (true !== ($resultCode = $this->zipArchive->open($this->zipFile, ZipArchive::CREATE))) {
switch ($resultCode) {
case ZipArchive::ER_EXISTS:
$errMsg = 'File already exists.';
break;
case ZipArchive::ER_INCONS:
$errMsg = 'Zip archive inconsistent.';
break;
case ZipArchive::ER_INVAL:
$errMsg = 'Invalid argument.';
break;
case ZipArchive::ER_MEMORY:
$errMsg = 'Malloc failure.';
break;
case ZipArchive::ER_NOENT:
$errMsg = 'Invalid argument.';
break;
case ZipArchive::ER_NOZIP:
$errMsg = 'Not a zip archive.';
break;
case ZipArchive::ER_OPEN:
$errMsg = 'Can\'t open file.';
break;
case ZipArchive::ER_READ:
$errMsg = 'Read error.';
break;
case ZipArchive::ER_SEEK:
$errMsg = 'Seek error.';
break;
default:
$errMsg = 'Unknown error.';
break;
}
throw new \RuntimeException(sprintf('%s', $errMsg));
}
return $this;
}
/**
* Saves archive modifications and updates current ZipArchive instance.
*
* @throws \RuntimeException If file could not be saved
*/
protected function save()
{
// Close to save modification
if (!$this->zipArchive->close()) {
return false;
}
// Re-initialize to get updated version
$this->reinitZipArchive();
return true;
}
}