diff --git a/application/Espo/Controllers/Attachment.php b/application/Espo/Controllers/Attachment.php index 5e862df488..ff2c488779 100644 --- a/application/Espo/Controllers/Attachment.php +++ b/application/Espo/Controllers/Attachment.php @@ -87,7 +87,7 @@ class Attachment extends Record $response ->setHeader('Content-Type', $fileData->type) ->setHeader('Content-Disposition', 'attachment; filename="' . $fileData->name . '"') - ->setHeader('Content-Length', $fileData->stream->getSize()) + ->setHeader('Content-Length', (string) $fileData->size) ->setBody($fileData->stream); } } diff --git a/application/Espo/Core/FileStorage/Manager.php b/application/Espo/Core/FileStorage/Manager.php index 576b9918d4..f0a46e93fc 100644 --- a/application/Espo/Core/FileStorage/Manager.php +++ b/application/Espo/Core/FileStorage/Manager.php @@ -61,6 +61,16 @@ class Manager return $implementation->exists(self::wrapAttachmentEntity($attachment)); } + /** + * Get a file size. + */ + public function getSize(AttachmentEntity $attachment) : int + { + $implementation = $this->getImplementation($attachment); + + return $implementation->getSize(self::wrapAttachmentEntity($attachment)); + } + /** * Get file contents. */ diff --git a/application/Espo/Core/FileStorage/Storage.php b/application/Espo/Core/FileStorage/Storage.php index 96c4222941..a1b54f9c09 100644 --- a/application/Espo/Core/FileStorage/Storage.php +++ b/application/Espo/Core/FileStorage/Storage.php @@ -55,4 +55,9 @@ interface Storage * Delete a file. */ public function unlink(Attachment $attachment) : void; + + /** + * Get a file size. + */ + public function getSize(Attachment $attachment) : int; } diff --git a/application/Espo/Core/FileStorage/Storages/AwsS3.php b/application/Espo/Core/FileStorage/Storages/AwsS3.php new file mode 100644 index 0000000000..79fecff49f --- /dev/null +++ b/application/Espo/Core/FileStorage/Storages/AwsS3.php @@ -0,0 +1,120 @@ +get('awsS3Storage.bucketName') ?? null; + $path = $config->get('awsS3Storage.path') ?? null; + + $region = $config->get('awsS3Storage.region') ?? null; + $version = $config->get('awsS3Storage.version') ?? null; + $credentials = $config->get('awsS3Storage.credentials') ?? null; + + if (!$bucketName) { + throw new RuntimeException("AWS S3 bucket name is not specified in config."); + } + + $clientOptions = [ + 'region' => $region, + 'version' => $version, + ]; + + if ($credentials) { + $clientOptions['credentials'] = $credentials; + } + + $client = new S3Client($clientOptions); + + $adapter = new AwsS3V3Adapter($client, $bucketName, $path); + + $this->filesystem = new Filesystem($adapter); + } + + public function unlink(Attachment $attachment) : void + { + $this->filesystem->delete($attachment->getSourceId()); + } + + public function exists(Attachment $attachment) : bool + { + return $this->filesystem->fileExists($attachment->getSourceId()); + } + + public function getSize(Attachment $attachment) : int + { + return $this->filesystem->fileSize($attachment->getSourceId()); + } + + public function getStream(Attachment $attachment) : StreamInterface + { + $resource = $this->filesystem->readStream($attachment->getSourceId()); + + return new Stream($resource); + } + + public function putStream(Attachment $attachment, StreamInterface $stream) : void + { + // League\Flysystem does not support StreamInterface. + // Need to pass a resource. + + $resource = fopen('php://memory', 'r+'); + + fwrite($resource, $stream->getContents()); + rewind($resource); + + $this->filesystem->writeStream($attachment->getSourceId(), $resource); + + fclose($resource); + } +} diff --git a/application/Espo/Core/FileStorage/Storages/EspoUploadDir.php b/application/Espo/Core/FileStorage/Storages/EspoUploadDir.php index 4ed11eabe5..b4e2aa48e6 100644 --- a/application/Espo/Core/FileStorage/Storages/EspoUploadDir.php +++ b/application/Espo/Core/FileStorage/Storages/EspoUploadDir.php @@ -64,12 +64,23 @@ class EspoUploadDir implements Storage, Local return $this->fileManager->isFile($filePath); } + public function getSize(Attachment $attachment) : int + { + $filePath = $this->getFilePath($attachment); + + if (!$this->exists($attachment)) { + throw new Error("Could not get size for non-existing file '{$filePath}'."); + } + + return filesize($filePath); + } + public function getStream(Attachment $attachment) : StreamInterface { $filePath = $this->getFilePath($attachment); if (!$this->exists($attachment)) { - throw new Error("Could not get stream for the file '{$filePath}'."); + throw new Error("Could not get stream for non-existing '{$filePath}'."); } $resouce = fopen($filePath, 'r'); diff --git a/application/Espo/EntryPoints/Attachment.php b/application/Espo/EntryPoints/Attachment.php index e96e95cf67..4fbab4748d 100644 --- a/application/Espo/EntryPoints/Attachment.php +++ b/application/Espo/EntryPoints/Attachment.php @@ -97,9 +97,11 @@ class Attachment implements EntryPoint $stream = $this->fileStorageManager->getStream($attachment); + $size = $stream->getSize() ?? $this->fileStorageManager->getSize($attachment); + $response ->setHeader('Pragma', 'public') - ->setHeader('Content-Length', (string) $stream->getSize()) + ->setHeader('Content-Length', (string) $size) ->setBody($stream); } } diff --git a/application/Espo/EntryPoints/Download.php b/application/Espo/EntryPoints/Download.php index 30b4122ab0..c1470940b7 100644 --- a/application/Espo/EntryPoints/Download.php +++ b/application/Espo/EntryPoints/Download.php @@ -107,12 +107,14 @@ class Download implements EntryPoint $response->setHeader('Content-Type', $type); } + $size = $stream->getSize() ?? $this->fileStorageManager->getSize($attachment); + $response ->setHeader('Content-Disposition', $disposition . ";filename=\"" . $outputFileName . "\"") ->setHeader('Expires', '0') ->setHeader('Cache-Control', 'must-revalidate') ->setHeader('Pragma', 'public') - ->setHeader('Content-Length', $stream->getSize()) + ->setHeader('Content-Length', (string) $size) ->setBody($stream); } } diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 6263ebc939..b9adc68818 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -199,7 +199,7 @@ class Image implements EntryPoint, $stream = $this->fileStorageManager->getStream($attachment); - $fileSize = $stream->getSize(); + $fileSize = $stream->getSize() ?? $this->fileStorageManager->getSize($attachment); $response->setBody($stream); } @@ -212,7 +212,7 @@ class Image implements EntryPoint, ->setHeader('Content-Disposition', 'inline;filename="' . $fileName . '"') ->setHeader('Pragma', 'public') ->setHeader('Cache-Control', 'max-age=360000, must-revalidate') - ->setHeader('Content-Length', $fileSize); + ->setHeader('Content-Length', (string) $fileSize); } protected function getThumbImage($filePath, $fileType, $size) diff --git a/application/Espo/Repositories/Attachment.php b/application/Espo/Repositories/Attachment.php index 63cd74aabe..b9e400a4f7 100644 --- a/application/Espo/Repositories/Attachment.php +++ b/application/Espo/Repositories/Attachment.php @@ -66,47 +66,34 @@ class Attachment extends \Espo\Core\Repositories\Database implements 'xx-large', ]; - public function beforeSave(Entity $entity, array $options = []) + protected function beforeSave(Entity $entity, array $options = []) { parent::beforeSave($entity, $options); - $storage = $entity->get('storage'); - - if (!$storage) { - $entity->set('storage', $this->config->get('defaultFileStorage', null)); - } - if ($entity->isNew()) { - if (!$entity->has('size') && $entity->has('contents')) { - $contents = $entity->get('contents'); - - $entity->set( - 'size', - mb_strlen($contents, '8bit') - ); - } + $this->processBeforeSaveNew($entity); } } - public function save(Entity $entity, array $options = []) + protected function processBeforeSaveNew(Entity $entity) : void { - $isNew = $entity->isNew(); + if (!$entity->get('storage')) { + $defaultStorage = $this->config->get('defaultFileStorage'); - if ($isNew) { - $entity->id = Util::generateId(); - - if ($entity->has('contents')) { - $contents = $entity->get('contents'); - - if (is_string($contents)) { - $this->fileStorageManager->putContents($entity, $contents); - } - } + $entity->set('storage', $defaultStorage); } - $result = parent::save($entity, $options); + $entity->id = Util::generateId(); - return $result; + $contents = $entity->get('contents'); + + if (is_null($contents)) { + return; + } + + $entity->set('size', strlen($contents)); + + $this->fileStorageManager->putContents($entity, $contents); } protected function afterRemove(Entity $entity, array $options = []) @@ -175,6 +162,11 @@ class Attachment extends \Espo\Core\Repositories\Database implements return $this->fileStorageManager->getStream($entity); } + public function getSize(AttachmentEntity $entity) : int + { + return $this->fileStorageManager->getSize($entity); + } + public function getFilePath(AttachmentEntity $entity) : string { return $this->fileStorageManager->getLocalFilePath($entity); diff --git a/application/Espo/Resources/defaults/config.php b/application/Espo/Resources/defaults/config.php index 12601e155d..76a4eaedf1 100644 --- a/application/Espo/Resources/defaults/config.php +++ b/application/Espo/Resources/defaults/config.php @@ -157,5 +157,6 @@ return [ 'personNameFormat' => 'firstLast', 'newNotificationCountInTitle' => false, 'pdfEngine' => 'Tcpdf', + 'defaultFileStorage' => 'EspoUploadDir', 'isInstalled' => false, ]; diff --git a/application/Espo/Resources/metadata/app/fileStorage.json b/application/Espo/Resources/metadata/app/fileStorage.json index 48b1b1bd90..6d13f2c201 100644 --- a/application/Espo/Resources/metadata/app/fileStorage.json +++ b/application/Espo/Resources/metadata/app/fileStorage.json @@ -1,5 +1,6 @@ { "implementationClassNameMap": { - "EspoUploadDir": "Espo\\Core\\FileStorage\\Storages\\EspoUploadDir" + "EspoUploadDir": "Espo\\Core\\FileStorage\\Storages\\EspoUploadDir", + "AwsS3": "Espo\\Core\\FileStorage\\Storages\\AwsS3" } } \ No newline at end of file diff --git a/application/Espo/Resources/metadata/entityDefs/Settings.json b/application/Espo/Resources/metadata/entityDefs/Settings.json index 5c88790b43..178d7d1a66 100644 --- a/application/Espo/Resources/metadata/entityDefs/Settings.json +++ b/application/Espo/Resources/metadata/entityDefs/Settings.json @@ -635,6 +635,10 @@ "useWebSocket": { "type": "bool", "tooltip": true + }, + "awsS3Storage": { + "type": "jsonObject", + "onlySystem": true } } } diff --git a/application/Espo/Services/Attachment.php b/application/Espo/Services/Attachment.php index 03e8b62ebe..43330497b3 100644 --- a/application/Espo/Services/Attachment.php +++ b/application/Espo/Services/Attachment.php @@ -448,7 +448,7 @@ class Attachment extends Record 'name' => $attachment->get('name'), 'type' => $attachment->get('type'), 'stream' => $this->getRepository()->getStream($attachment), - 'size' => $attachment->get('size'), + 'size' => $this->getRepository()->getSize($attachment), ]; return $data; diff --git a/composer.json b/composer.json index 264f61b86c..cf34dd5615 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,9 @@ "nesbot/carbon": "^2.26", "zbateson/mail-mime-parser": "1.3.*", "phpoffice/phpspreadsheet": "^1.16", - "doctrine/dbal": "^3.0" + "doctrine/dbal": "^3.0", + "league/flysystem": "^2.0", + "league/flysystem-aws-s3-v3": "^2.0" }, "require-dev": { "phpunit/phpunit": "^9" diff --git a/composer.lock b/composer.lock index 269945607f..7cccf77a15 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,101 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc02d5c59ae017fcfae1bb229746e0c2", + "content-hash": "2be325991598c4526c7d1350a463c397", "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.173.19", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "63c6feca49bf4083f33bf250401c0c286759e101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/63c6feca49bf4083f33bf250401c0c286759e101", + "reference": "63c6feca49bf4083f33bf250401c0c286759e101", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.173.19" + }, + "time": "2021-03-01T19:15:59+00:00" + }, { "name": "cboden/ratchet", "version": "v0.4.1", @@ -652,6 +742,163 @@ ], "time": "2020-02-05T20:36:27+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.1.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7427d6f99df41cc01f33cd59832f721c150ffdf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7427d6f99df41cc01f33cd59832f721c150ffdf3", + "reference": "7427d6f99df41cc01f33cd59832f721c150ffdf3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": "^7.2.5", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/client-integration-tests": "dev-phpunit8", + "phpunit/phpunit": "^8.5.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2020-09-30T08:51:17+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "60d379c243457e073cff02bc323a2a86cb355631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", + "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.0" + }, + "time": "2020-09-30T07:37:28+00:00" + }, { "name": "guzzlehttp/psr7", "version": "1.6.1", @@ -1198,6 +1445,201 @@ ], "time": "2020-09-14T14:23:00+00:00" }, + { + "name": "league/flysystem", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "7cbbb7222e8d8a34e71273d243dfcf383ed6779f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7cbbb7222e8d8a34e71273d243dfcf383ed6779f", + "reference": "7cbbb7222e8d8a34e71273d243dfcf383ed6779f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "league/mime-type-detection": "^1.0.0", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "guzzlehttp/ringphp": "<1.1.1" + }, + "require-dev": { + "async-aws/s3": "^1.5", + "async-aws/simple-s3": "^1.0", + "aws/aws-sdk-php": "^3.132.4", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "friendsofphp/php-cs-fixer": "^2.16", + "google/cloud-storage": "^1.23", + "phpseclib/phpseclib": "^2.0", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^8.5 || ^9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/2.0.4" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-02-12T19:37:50+00:00" + }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "c89931e9c4b294493234798564cc814ef478fbc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c89931e9c4b294493234798564cc814ef478fbc6", + "reference": "c89931e9c4b294493234798564cc814ef478fbc6", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.132.4", + "league/flysystem": "^2.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/2.0.4" + }, + "time": "2021-02-09T21:10:56+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-01-18T20:58:21+00:00" + }, { "name": "maennchen/zipstream-php", "version": "2.1.0", @@ -1551,6 +1993,67 @@ ], "time": "2020-07-23T08:41:23+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.0" + }, + "time": "2020-07-31T21:01:56+00:00" + }, { "name": "myclabs/php-enum", "version": "1.7.7", @@ -5784,5 +6287,6 @@ "ext-curl": "*", "ext-exif": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.0.0" }