Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="container">
<div class="row">
<div class="col s12 l10 footer-text">
© 2018 - <?php echo date('Y') . ' ' . env('APP_NAME') ?>
© 2018 - <?php echo date('Y') . ' ' . env('APP_NAME') ?>
</div>
<div class="col s12 l2 footer-text">
<a href="https://quantumphp.io" target="_blank" class="white-text"><?php _t('common.learn_more') ?></a>
Expand Down
114 changes: 36 additions & 78 deletions src/Storage/UploadedFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@
use Quantum\Storage\Contracts\FilesystemAdapterInterface;
use Quantum\Storage\Exceptions\FileSystemException;
use Quantum\Storage\Exceptions\FileUploadException;
use Quantum\Loader\Exceptions\LoaderException;
use Quantum\Storage\Uploads\UploadConfigProvider;
use Quantum\Config\Exceptions\ConfigException;
use Quantum\Loader\Exceptions\LoaderException;
use Quantum\Lang\Exceptions\LangException;
use Quantum\Storage\Uploads\UploadStorage;
use Quantum\App\Exceptions\BaseException;
use Quantum\Storage\Uploads\UploadPolicy;
use Quantum\Di\Exceptions\DiException;
use Gumlet\ImageResizeException;
use Quantum\Loader\Loader;
use Quantum\Loader\Setup;
use ReflectionException;
use Gumlet\ImageResize;
use RuntimeException;
use Quantum\Di\Di;
use SplFileInfo;
use finfo;

Expand Down Expand Up @@ -123,6 +123,10 @@ class UploadedFile extends SplFileInfo
*/
protected bool $mimeTypesOverridden = false;

private ?UploadPolicy $uploadPolicy = null;

private ?UploadStorage $uploadStorage = null;

/**
* @param array<string, mixed> $meta
*/
Expand All @@ -140,7 +144,8 @@ public function __construct(array $meta)
*/
public function setAllowedMimeTypes(array $allowedMimeTypes, bool $merge = true): UploadedFile
{
$this->setAllowedMimeTypesMap($allowedMimeTypes, $merge);
$policy = $this->getUploadPolicy();
$merge ? $policy->merge($allowedMimeTypes) : $policy->replace($allowedMimeTypes);
$this->mimeTypesOverridden = true;
$this->mimeTypesLoaded = true;
return $this;
Expand Down Expand Up @@ -281,7 +286,7 @@ public function save(string $dest, bool $overwrite = false): bool
throw FileUploadException::fileNotFound($this->getPathname());
}

if (!$this->allowed($this->getExtension(), $this->getMimeType())) {
if (!$this->getUploadPolicy()->isAllowed($this->getExtension(), $this->getMimeType())) {
throw FileUploadException::fileTypeNotAllowed($this->getExtension());
}

Expand All @@ -301,7 +306,7 @@ public function save(string $dest, bool $overwrite = false): bool
}
}

if (!$this->moveUploadedFile($filePath)) {
if (!$this->getUploadStorage()->store($this, $filePath, $this->remoteFileSystem)) {
return false;
}

Expand Down Expand Up @@ -365,22 +370,6 @@ public function isImage(string $filePath): bool
return (bool) getimagesize($filePath);
}

/**
* Moves an uploaded file to a new location
*/
protected function moveUploadedFile(string $filePath): bool
{
$localFileSystem = $this->getLocalFileSystem();

if ($this->remoteFileSystem) {
return (bool) $this->remoteFileSystem->put($filePath, $localFileSystem->get($this->getPathname()));
} elseif ($this->isUploaded()) {
return move_uploaded_file($this->getPathname(), $filePath);
} else {
return $localFileSystem->copy($this->getPathname(), $filePath);
}
}

/**
* @throws ConfigException|DiException|BaseException|ReflectionException
*/
Expand Down Expand Up @@ -413,64 +402,12 @@ private function ensureAllowedMimeTypesLoaded(): void
return;
}

$this->loadAllowedMimeTypesFromConfig();
$this->getUploadPolicy()->merge(
(new UploadConfigProvider())->getAllowedMimeTypesMap()
);
$this->mimeTypesLoaded = true;
}

/**
* Validates upload against allowed mime types => extensions map
*/
protected function allowed(string $extension, string $mimeType): bool
{
$extension = strtolower($extension);
$mimeType = strtolower($mimeType);

return isset($this->allowedMimeTypes[$mimeType]) &&
in_array($extension, $this->allowedMimeTypes[$mimeType], true);
}

/**
* Loads allowed mime types from config (shared/config/uploads.php) if present.
* @throws FileUploadException|LoaderException|ConfigException|DiException|ReflectionException
*/
protected function loadAllowedMimeTypesFromConfig(): void
{
if (!config()->has('uploads')) {
if (!Di::isRegistered(Loader::class)) {
Di::register(Loader::class);
}

$loader = Di::get(Loader::class);
$setup = new Setup('config', 'uploads');
$loader->setup($setup);

if (!$loader->fileExists()) {
return;
}

config()->import($setup);
}

$allowedMimeTypesMap = config()->get('uploads.allowed_mime_types') ?? [];

if (!is_array($allowedMimeTypesMap)) {
throw FileUploadException::incorrectMimeTypesConfig('uploads');
}

if ($allowedMimeTypesMap !== []) {
$this->setAllowedMimeTypesMap($allowedMimeTypesMap);
}
}

/**
* Sets the allowed mime types => extensions map
* @param array<string, string> $allowedMimeTypes
*/
protected function setAllowedMimeTypesMap(array $allowedMimeTypes, bool $merge = true): void
{
$this->allowedMimeTypes = $merge ? array_merge_recursive($this->allowedMimeTypes, $allowedMimeTypes) : $allowedMimeTypes;
}

/**
* Applies modifications on image
* @param string $filePath
Expand All @@ -490,4 +427,25 @@ protected function applyModifications(string $filePath)

$image->save($filePath);
}

private function getUploadPolicy(): UploadPolicy
{
if (!$this->uploadPolicy) {
$this->uploadPolicy = new UploadPolicy($this->allowedMimeTypes);
}

return $this->uploadPolicy;
}

/**
* @throws ConfigException|DiException|BaseException|ReflectionException
*/
private function getUploadStorage(): UploadStorage
{
if (!$this->uploadStorage) {
$this->uploadStorage = new UploadStorage($this->getLocalFileSystem());
}

return $this->uploadStorage;
}
}
60 changes: 60 additions & 0 deletions src/Storage/Uploads/UploadConfigProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

/**
* Quantum PHP Framework
*
* An open source software development framework for PHP
*
* @package Quantum
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 3.0.0
*/

namespace Quantum\Storage\Uploads;

use Quantum\Storage\Exceptions\FileUploadException;
use Quantum\Loader\Exceptions\LoaderException;
use Quantum\Config\Exceptions\ConfigException;
use Quantum\Di\Exceptions\DiException;
use Quantum\Loader\Loader;
use Quantum\Loader\Setup;
use ReflectionException;
use Quantum\Di\Di;

class UploadConfigProvider
{
/**
* @return array<string, list<string>>
* @throws FileUploadException|LoaderException|ConfigException|DiException|ReflectionException
*/
public function getAllowedMimeTypesMap(): array
{
if (!config()->has('uploads')) {
if (!Di::isRegistered(Loader::class)) {
Di::register(Loader::class);
}

$loader = Di::get(Loader::class);
$setup = new Setup('config', 'uploads');
$loader->setup($setup);

if (!$loader->fileExists()) {
return [];
}

config()->import($setup);
}

$allowedMimeTypesMap = config()->get('uploads.allowed_mime_types') ?? [];

if (!is_array($allowedMimeTypesMap)) {
throw FileUploadException::incorrectMimeTypesConfig('uploads');
}

return $allowedMimeTypesMap;
}
}
77 changes: 77 additions & 0 deletions src/Storage/Uploads/UploadPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

/**
* Quantum PHP Framework
*
* An open source software development framework for PHP
*
* @package Quantum
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 3.0.0
*/

namespace Quantum\Storage\Uploads;

/**
* Holds upload MIME policy rules and validates extension/MIME pairs.
*/
class UploadPolicy
{
/**
* @var array<string, list<string>>
*/
private array $allowedMimeTypes;

/**
* @param array<string, list<string>|string> $allowedMimeTypes
*/
public function __construct(array $allowedMimeTypes)
{
$this->allowedMimeTypes = $this->normalize($allowedMimeTypes);
}

/**
* @param array<string, list<string>|string> $allowedMimeTypes
*/
public function merge(array $allowedMimeTypes): void
{
$this->allowedMimeTypes = array_merge_recursive($this->allowedMimeTypes, $this->normalize($allowedMimeTypes));
}

/**
* @param array<string, list<string>|string> $allowedMimeTypes
*/
public function replace(array $allowedMimeTypes): void
{
$this->allowedMimeTypes = $this->normalize($allowedMimeTypes);
}

public function isAllowed(string $extension, string $mimeType): bool
{
$extension = strtolower($extension);
$mimeType = strtolower($mimeType);

return isset($this->allowedMimeTypes[$mimeType])
&& in_array($extension, $this->allowedMimeTypes[$mimeType], true);
}

/**
* @param array<string, list<string>|string> $allowedMimeTypes
* @return array<string, list<string>>
*/
private function normalize(array $allowedMimeTypes): array
{
$normalized = [];

foreach ($allowedMimeTypes as $mimeType => $extensions) {
$values = is_array($extensions) ? $extensions : [$extensions];
$normalized[$mimeType] = array_values(array_map('strval', $values));
}

return $normalized;
}
}
44 changes: 44 additions & 0 deletions src/Storage/Uploads/UploadStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/**
* Quantum PHP Framework
*
* An open source software development framework for PHP
*
* @package Quantum
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 3.0.0
*/

namespace Quantum\Storage\Uploads;

use Quantum\Storage\Contracts\LocalFilesystemAdapterInterface;
use Quantum\Storage\Contracts\FilesystemAdapterInterface;
use Quantum\Storage\UploadedFile;

class UploadStorage
{
private LocalFilesystemAdapterInterface $localFileSystem;

public function __construct(LocalFilesystemAdapterInterface $localFileSystem)
{
$this->localFileSystem = $localFileSystem;
}

public function store(UploadedFile $file, string $targetPath, ?FilesystemAdapterInterface $remoteFileSystem = null): bool
{
if ($remoteFileSystem) {
return (bool) $remoteFileSystem->put($targetPath, $this->localFileSystem->get($file->getPathname()));
}

if ($file->isUploaded()) {
return move_uploaded_file($file->getPathname(), $targetPath);
}

return $this->localFileSystem->copy($file->getPathname(), $targetPath);
}
}
Loading
Loading