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
65 changes: 18 additions & 47 deletions src/controllers/AssetsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@
use craft\fields\Assets as AssetsField;
use craft\helpers\Assets;
use craft\helpers\Db;
use craft\helpers\FileHelper;
use craft\helpers\Image;
use craft\models\Volume;
use craft\web\Controller;
use DateTime;
use Throwable;
use yii\base\Event;
use yii\base\Exception;
use yii\base\Model;
Expand Down Expand Up @@ -97,7 +94,6 @@ public function actionCreateAsset(): Response
$filename = $this->request->getRequiredBodyParam('filename');
$originalFilename = $this->request->getRequiredBodyParam('originalFilename');
$targetFilename = $this->request->getRequiredBodyParam('targetFilename');
$size = $this->request->getBodyParam('size');
$elementsService = Craft::$app->getElements();
$lastModifiedMs = (int) $this->request->getBodyParam('lastModified');
$dateModified = $lastModifiedMs
Expand Down Expand Up @@ -160,7 +156,6 @@ public function actionCreateAsset(): Response
$asset->uploaderId = Craft::$app->getUser()->getId();
$asset->avoidFilenameConflicts = true;
$asset->dateModified = $dateModified;
$asset->size = $size;

// Setting newFolderId, so that extension validation on newLocation occurs
$asset->newFolderId = $folder->id;
Expand All @@ -171,7 +166,11 @@ public function actionCreateAsset(): Response
// Handle special characters that have been encoded from the presigned URL
$asset->folderPath = is_string($folder->path) ? Fs::urlEncodePathSegments($folder->path) : $asset->folderPath;

[$asset->width, $asset->height] = $this->uploadedImageDimensions($asset, $filename);
$asset->size = $this->uploadedAssetSize($asset, $filename);
$fs = $asset->getVolume()->getFs();
[$asset->width, $asset->height] = $fs instanceof Fs
? $fs->getImageDimensions($asset->getPath()) ?? [null, null]
: [null, null];

if (!$selectionCondition) {
$asset->newFilename = $targetFilename;
Expand Down Expand Up @@ -240,7 +239,6 @@ public function actionReplaceFile(): Response
$sourceAssetId = $this->request->getBodyParam('sourceAssetId');
$filename = $this->request->getBodyParam('filename');
$targetFilename = $this->request->getBodyParam('targetFilename');
$size = $this->request->getBodyParam('size');
$lastModifiedMs = (int) $this->request->getBodyParam('lastModified');
$dateModified = $lastModifiedMs
? DateTime::createFromFormat('U', (string) floor($lastModifiedMs / 1000))
Expand Down Expand Up @@ -271,7 +269,7 @@ public function actionReplaceFile(): Response

// Handle the Element Action
if ($assetToReplace !== null && $filename) {
$assetToReplace->size = $size;
$assetToReplace->size = $this->uploadedAssetSize($assetToReplace, $filename);
$assetToReplace->dateModified = $dateModified;
if (!$this->replaceAssetFile($assetToReplace, $filename, $targetFilename)) {
throw new Exception('Unable to replace asset.');
Expand Down Expand Up @@ -354,7 +352,10 @@ public function replaceAssetFile(Asset $asset, string $filename, string $targetF
$asset->avoidFilenameConflicts = true;
$asset->setScenario(Asset::SCENARIO_REPLACE);
$asset->setFilename($filename);
[$asset->width, $asset->height] = $this->uploadedImageDimensions($asset, $filename);
$fs = $asset->getVolume()->getFs();
[$asset->width, $asset->height] = $fs instanceof Fs
? $fs->getImageDimensions($asset->getPath()) ?? [null, null]
: [null, null];
$asset->newFilename = $targetFilename;

$saved = $this->saveAsset($asset);
Expand Down Expand Up @@ -395,46 +396,16 @@ private function volumeSubpath(Volume $volume): string
return method_exists($volume, 'getSubpath') ? $volume->getSubpath() : '';
}

protected function uploadedImageDimensions(Asset $asset, string $filename): array
protected function uploadedAssetSize(Asset $asset, string $filename): int
{
if (Assets::getFileKindByExtension($filename) !== Asset::KIND_IMAGE) {
return [null, null];
}

return $this->readUploadedImageDimensions($asset) ?? [null, null];
}

protected function readUploadedImageDimensions(Asset $asset): ?array
{
$stream = null;
$tempPath = null;

try {
$stream = $asset->getVolume()->getFs()->getFileStream($asset->getPath());
$imageSize = Image::imageSizeByStream($stream);
$size = $asset->getVolume()->getFileSize($asset->getPath($filename));

if ($imageSize === false || !isset($imageSize[0], $imageSize[1])) {
fclose($stream);
$stream = null;

$tempPath = $asset->getCopyOfFile();
$imageSize = Image::imageSize($tempPath);
}

return [
(int)$imageSize[0] ?: null,
(int)$imageSize[1] ?: null,
];
} catch (Throwable) {
return null;
} finally {
if (is_resource($stream)) {
fclose($stream);
}

if ($tempPath !== null) {
FileHelper::unlink($tempPath);
}
if ($size > Assets::getMaxUploadSize()) {
throw new BadRequestHttpException(Craft::t('app', '“{filename}” is too large.', [
'filename' => $filename,
]));
}

return $size;
}
}
99 changes: 99 additions & 0 deletions src/fs/Fs.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
use craft\cloud\Module;
use craft\cloud\StaticCache;
use craft\cloud\StaticCacheTag;
use craft\elements\Asset;
use craft\errors\FsException;
use craft\flysystem\base\FlysystemFs;
use craft\fs\Local;
use craft\helpers\App;
use craft\helpers\Assets;
use craft\helpers\DateTimeHelper;
use craft\helpers\Image;
use DateTime;
use DateTimeInterface;
use Generator;
Expand All @@ -37,6 +39,8 @@
*/
abstract class Fs extends FlysystemFs
{
private const IMAGE_DIMENSION_HEADER_BYTES = 1048576;

protected static bool $showUrlSetting = false;
protected ?string $expires = null;
protected ?Local $localFs = null;
Expand Down Expand Up @@ -519,6 +523,101 @@ public function getFileStream(string $uriPath)
return parent::getFileStream($uriPath);
}

public function getImageDimensions(string $uriPath): ?array
{
if (Assets::getFileKindByExtension($uriPath) !== Asset::KIND_IMAGE) {
return null;
}

$stream = $this->getFileStreamRange($uriPath, 0, self::IMAGE_DIMENSION_HEADER_BYTES - 1);

if ($stream === null) {
return null;
}

try {
$imageSize = Image::imageSizeByStream($stream);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support all image formats when reading dimensions

When a direct upload is an allowed image type other than JPEG/GIF/PNG (for example WebP, SVG, AVIF, TIFF, or BMP), this now records null dimensions because Craft’s Image::imageSizeByStream() stream parser only recognizes JPEG, GIF, and PNG signatures. Those formats were previously able to supply dimensions from the client, so uploads can now show no dimensions and can fail asset selection rules that depend on width/height even though Craft allows the file kind.

Useful? React with 👍 / 👎.


if ($imageSize === false || !isset($imageSize[0], $imageSize[1])) {
return null;
}

return [
(int)$imageSize[0] ?: null,
(int)$imageSize[1] ?: null,
];
} catch (Throwable) {
return null;
} finally {
if (is_resource($stream)) {
fclose($stream);
}
}
}

/**
* @return resource|null
*/
public function getFileStreamRange(string $uriPath, int $start, int $end)
{
if ($start < 0 || $end < $start) {
return null;
}

$sourceStream = null;

try {
if (!$this->useLocalFs) {
$bucket = $this->getBucketName();

if ($bucket === null) {
return null;
}

$object = $this->getClient()->getObject([
'Bucket' => $bucket,
'Key' => $this->createBucketPath($uriPath)->toString(),
'Range' => "bytes=$start-$end",
]);

return $this->stringStream((string)$object->get('Body'));
}

$sourceStream = $this->getFileStream($uriPath);

if (fseek($sourceStream, $start) === -1) {
return null;
}

$data = stream_get_contents($sourceStream, $end - $start + 1);

return is_string($data) ? $this->stringStream($data) : null;
} catch (Throwable) {
return null;
} finally {
if (is_resource($sourceStream)) {
fclose($sourceStream);
}
}
}

/**
* @return resource|null
*/
private function stringStream(string $contents)
{
$stream = fopen('php://temp', 'r+');

if ($stream === false) {
return null;
}

fwrite($stream, $contents);
rewind($stream);

return $stream;
}

/**
* @inheritDoc
*/
Expand Down
Loading
Loading