Skip to content

Commit 2f4870d

Browse files
committed
Merge branch '4.6'
2 parents be75e34 + 9275d10 commit 2f4870d

File tree

5 files changed

+141
-22
lines changed

5 files changed

+141
-22
lines changed

src/lib/FieldType/Image/ImageStorage.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ public function storeFieldData(VersionInfo $versionInfo, Field $field): bool
9191
),
9292
$field->value->externalData['fileName']
9393
);
94-
$targetPath = $this->filePathNormalizer->normalizePath($targetPath);
94+
$targetPath = $this->filePathNormalizer->normalizePath(
95+
$targetPath,
96+
true,
97+
$field->value->externalData['inputUri'] ?? null,
98+
);
9599

96100
if (isset($field->value->externalData['inputUri'])) {
97101
$localFilePath = $field->value->externalData['inputUri'];

src/lib/IO/FilePathNormalizer/Flysystem.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Ibexa\Core\IO\FilePathNormalizer;
1010

11+
use const DIRECTORY_SEPARATOR;
1112
use Ibexa\Core\IO\FilePathNormalizerInterface;
1213
use Ibexa\Core\Persistence\Legacy\Content\UrlAlias\SlugConverter;
1314
use League\Flysystem\PathNormalizer;
@@ -26,20 +27,27 @@ public function __construct(SlugConverter $slugConverter, PathNormalizer $pathNo
2627
$this->pathNormalizer = $pathNormalizer;
2728
}
2829

29-
public function normalizePath(string $filePath, bool $doHash = true): string
30+
public function normalizePath(string $filePath, bool $doHash = true, ?string $realFilePath = null): string
3031
{
3132
$fileName = pathinfo($filePath, PATHINFO_BASENAME);
3233
$directory = pathinfo($filePath, PATHINFO_DIRNAME);
3334

3435
$fileName = $this->slugConverter->convert($fileName, '_1', 'urlalias');
3536

36-
$hash = $doHash
37-
? (preg_match(self::HASH_PATTERN, $fileName) ? '' : bin2hex(random_bytes(6)) . '-')
37+
$hash = $doHash && !preg_match(self::HASH_PATTERN, $fileName)
38+
? $this->generateFilePathHash($realFilePath)
3839
: '';
3940

40-
$filePath = $directory . \DIRECTORY_SEPARATOR . $hash;
41+
$filePath = $directory . DIRECTORY_SEPARATOR . $hash;
4142
$normalizedFileName = $this->pathNormalizer->normalizePath($fileName);
4243

4344
return $filePath . $normalizedFileName;
4445
}
46+
47+
private function generateFilePathHash(?string $realFilePath = null): string
48+
{
49+
$hash = $realFilePath !== null ? md5_file($realFilePath) : false;
50+
51+
return ($hash !== false ? substr($hash, 0, 12) : bin2hex(random_bytes(6))) . '-';
52+
}
4553
}

src/lib/IO/FilePathNormalizerInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
*/
1414
interface FilePathNormalizerInterface
1515
{
16-
public function normalizePath(string $filePath, bool $doHash = true): string;
16+
public function normalizePath(string $filePath, bool $doHash = true, ?string $realFilePath = null): string;
1717
}

tests/integration/Core/Image/ImageStorage/ImageStorageTest.php

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,25 @@
2323
use Ibexa\Core\IO\Values\BinaryFile;
2424
use Ibexa\Core\IO\Values\BinaryFileCreateStruct;
2525
use Ibexa\Tests\Integration\Core\BaseCoreFieldTypeIntegrationTestCase;
26+
use PHPUnit\Framework\MockObject\MockObject;
2627

2728
final class ImageStorageTest extends BaseCoreFieldTypeIntegrationTestCase
2829
{
29-
/** @var \Ibexa\Core\FieldType\Image\ImageStorage\Gateway */
30-
private $gateway;
30+
private DoctrineStorage $gateway;
3131

32-
/** @var \Ibexa\Core\IO\UrlRedecoratorInterface|\PHPUnit\Framework\MockObject\MockObject */
33-
private $redecorator;
32+
private UrlRedecoratorInterface & MockObject $redecorator;
3433

35-
/** @var \Ibexa\Core\FieldType\Image\PathGenerator|\PHPUnit\Framework\MockObject\MockObject */
36-
private $pathGenerator;
34+
private PathGenerator & MockObject $pathGenerator;
3735

38-
/** @var \Ibexa\Core\FieldType\Image\AliasCleanerInterface|\PHPUnit\Framework\MockObject\MockObject */
39-
private $aliasCleaner;
36+
private AliasCleanerInterface & MockObject $aliasCleaner;
4037

41-
/** @var \Ibexa\Core\IO\FilePathNormalizerInterface|\PHPUnit\Framework\MockObject\MockObject */
42-
private $filePathNormalizer;
38+
private FilePathNormalizerInterface & MockObject $filePathNormalizer;
4339

44-
/** @var \Ibexa\Core\IO\IOServiceInterface|\PHPUnit\Framework\MockObject\MockObject */
45-
private $ioService;
40+
private IOServiceInterface & MockObject $ioService;
4641

47-
/** @var \Ibexa\Core\FieldType\Image\ImageStorage */
48-
private $storage;
42+
private ImageStorage $storage;
4943

50-
/** @var \Ibexa\Core\FieldType\Validator\FileExtensionBlackListValidator&\PHPUnit\Framework\MockObject\MockObject */
51-
private $fileExtensionBlackListValidator;
44+
private FileExtensionBlackListValidator & MockObject $fileExtensionBlackListValidator;
5245

5346
protected function setUp(): void
5447
{
@@ -159,6 +152,71 @@ public function testStoreFieldDataDuringUpdateWithDifferentImage(VersionInfo $ve
159152
self::assertSame(1, $this->gateway->countImageReferences($binaryFile->uri));
160153
}
161154

155+
/**
156+
* @dataProvider providerOfFieldData
157+
*/
158+
public function testStoreFieldDataWithSameImageOnAutosave(VersionInfo $versionInfo, Field $field): void
159+
{
160+
$targetPath = '1/8/6/232-eng-GB/' . $field->value->externalData['fileName'];
161+
162+
$binaryFile = new BinaryFile([
163+
'id' => $targetPath,
164+
'uri' => $targetPath,
165+
]);
166+
167+
$this->filePathNormalizer
168+
->expects(self::exactly(2))
169+
->method('normalizePath')
170+
->willReturn($targetPath);
171+
172+
$this->ioService
173+
->expects(self::exactly(2))
174+
->method('newBinaryCreateStructFromLocalFile')
175+
->with($field->value->externalData['inputUri'])
176+
->willReturn(new BinaryFileCreateStruct());
177+
178+
$this->ioService
179+
->expects(self::exactly(2))
180+
->method('createBinaryFile')
181+
->willReturn($binaryFile);
182+
183+
$this->ioService
184+
->expects(self::exactly(2))
185+
->method('getMimeType')
186+
->with($binaryFile->id)
187+
->willReturn('image/jpeg');
188+
189+
$this->redecorator
190+
->method('redecorateFromSource')
191+
->with($binaryFile->uri)
192+
->willReturn($binaryFile->uri);
193+
194+
// First save
195+
$this->storage->storeFieldData($versionInfo, $field);
196+
197+
// Simulate autosave with same image — rebuild externalData
198+
$field->value = new FieldValue([
199+
'externalData' => [
200+
'id' => null,
201+
'path' => $field->value->data['path'] ?? __DIR__ . '/image.jpg',
202+
'inputUri' => __DIR__ . '/image.jpg',
203+
'fileName' => 'image.jpg',
204+
'fileSize' => '12345',
205+
'mimeType' => 'image/jpeg',
206+
'width' => null,
207+
'height' => null,
208+
'alternativeText' => null,
209+
'imageId' => null,
210+
'uri' => null,
211+
'additionalData' => [],
212+
],
213+
]);
214+
215+
$this->storage->storeFieldData($versionInfo, $field);
216+
217+
self::assertSame(1, $this->gateway->countImageReferences($binaryFile->uri));
218+
}
219+
162220
private function runCommonStoreFieldDataMocks(Field $field): BinaryFile
163221
{
164222
$this->filePathNormalizer

tests/lib/IO/FilePathNormalizer/FlysystemTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,53 @@ public function providerForTestNormalizePath(): array
112112
],
113113
];
114114
}
115+
116+
public function testNormalizePathWithRealFilePath(): void
117+
{
118+
$tempFile = tempnam(sys_get_temp_dir(), 'test_image_');
119+
file_put_contents($tempFile, 'test image content');
120+
121+
try {
122+
$md5File = md5_file($tempFile);
123+
self::assertNotFalse($md5File);
124+
$expectedHash = substr($md5File, 0, 12);
125+
126+
$this->slugConverter
127+
->expects(self::once())
128+
->method('convert')
129+
->with('image.jpg')
130+
->willReturn('image.jpg');
131+
132+
$normalizedPath = $this->filePathNormalizer->normalizePath(
133+
'4/3/2/234/1/image.jpg',
134+
true,
135+
$tempFile,
136+
);
137+
138+
self::assertSame('4/3/2/234/1/' . $expectedHash . '-image.jpg', $normalizedPath);
139+
} finally {
140+
unlink($tempFile);
141+
}
142+
}
143+
144+
public function testNormalizePathWithRealFilePathIsDeterministic(): void
145+
{
146+
$tempFile = tempnam(sys_get_temp_dir(), 'test_image_');
147+
file_put_contents($tempFile, 'test image content');
148+
149+
try {
150+
$this->slugConverter
151+
->expects(self::exactly(2))
152+
->method('convert')
153+
->with('image.jpg')
154+
->willReturn('image.jpg');
155+
156+
$first = $this->filePathNormalizer->normalizePath('4/3/2/234/1/image.jpg', true, $tempFile);
157+
$second = $this->filePathNormalizer->normalizePath('4/3/2/234/1/image.jpg', true, $tempFile);
158+
159+
self::assertSame($first, $second);
160+
} finally {
161+
unlink($tempFile);
162+
}
163+
}
115164
}

0 commit comments

Comments
 (0)