Skip to content

Commit 0cdd23f

Browse files
author
Zane
committed
Set image hash on TLI1 import
1 parent af9064c commit 0cdd23f

File tree

5 files changed

+257
-2
lines changed

5 files changed

+257
-2
lines changed

‎src/Command/TLI1ImporterCommand.php

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use App\Repository\PhpBB\TopicRepository;
2323
use App\Repository\PhpBB\UserRepository;
2424
use App\Service\Cms\Image;
25+
use App\Service\Cms\ImageEditor;
2526
use App\Service\Cms\Tag;
2627
use App\Service\Factory;
2728
use App\Service\HtmlProcessorForStorage;
@@ -31,6 +32,7 @@
3132
use Doctrine\ORM\Mapping\ClassMetadata;
3233
use PDO;
3334
use Symfony\Component\Console\Attribute\AsCommand;
35+
use Symfony\Component\Console\Helper\Table;
3436
use Symfony\Component\Console\Input\InputInterface;
3537
use Symfony\Component\Console\Input\InputOption;
3638
use Symfony\Component\Console\Output\OutputInterface;
@@ -62,6 +64,12 @@ class TLI1ImporterCommand extends AbstractBaseCommand
6264
protected array $arrNewFiles = [];
6365
protected array $arrNewBadges = [];
6466

67+
protected array $arrImagesReHashReport = [
68+
self::SUCCESS => 0,
69+
self::FAILURE => 0,
70+
'list' => []
71+
];
72+
6573

6674
public function __construct(
6775
array $arrConfig, protected ProjectDir $projectDir, protected Factory $factory,
@@ -149,6 +157,10 @@ protected function execute(InputInterface $input, OutputInterface $output) : int
149157
$this->em->flush();
150158
}
151159

160+
$this
161+
->fxTitle("Set hash on imported images...")
162+
->hashImages();
163+
152164
return $this->endWithSuccess();
153165
}
154166

@@ -493,7 +505,7 @@ protected function importImages() : static
493505
$this->fxOK( count($arrTli2Images) . " item(s) loaded");
494506
unset($arrTli2Images);
495507

496-
$this->io->text("Processing every TLI2 image...");
508+
$this->io->text("Processing every TLI1 image...");
497509
$this->processItems($arrTli1Images, [$this, 'processTli1Image'], null, [$this, 'buildItemTitle']);
498510

499511
$this
@@ -606,6 +618,84 @@ protected function assignSpotlight(int $articleId, ArticleEntity $article) : sta
606618
}
607619

608620

621+
protected function hashImages() : static
622+
{
623+
if( $this->getCliOption(static::OPT_SKIP_IMAGES) ) {
624+
return $this->fxWarning('🦘 Skipped!');
625+
}
626+
627+
$sqlSelect = "SELECT id FROM `image` WHERE hash LIKE 'tli1%' ORDER BY id ASC";
628+
$arrImageIds = $this->em->getConnection()->fetchFirstColumn($sqlSelect);
629+
630+
if( empty($arrImageIds) ) {
631+
return $this->fxWarning("No TLI1 images to re-hash found!");
632+
}
633+
634+
$imagesToHash = $this->factory->createImageEditorCollection()->load($arrImageIds);
635+
$this->fxOK( $imagesToHash->count() . " image(s) to re-hash loaded");
636+
637+
$this->io->text("Re-hashing images...");
638+
$this->processItems($imagesToHash, [$this, 'rehashImage'], $imagesToHash->count(), [$this, 'buildItemTitle']);
639+
640+
(new Table($this->output))
641+
->setHeaders(['Total', '✅ OK', '❌ Dupes'])
642+
->setRows([
643+
[$imagesToHash->count(), $this->arrImagesReHashReport[static::SUCCESS], $this->arrImagesReHashReport[static::FAILURE]],
644+
])->render();
645+
646+
$rows = array_map(
647+
fn($key, $value) => [$key, $value],
648+
array_keys($this->arrImagesReHashReport['list']),
649+
$this->arrImagesReHashReport['list']
650+
);
651+
652+
(new Table($this->output))
653+
->setHeaders(['Image ID', 'Hash'])
654+
->setRows($rows)
655+
->render();
656+
657+
return $this;
658+
}
659+
660+
661+
protected function rehashImage($index, ImageEditor $image) : static
662+
{
663+
$imageId = $image->getId();
664+
$newHash = $image->rehash()->getEntity()->getHash();
665+
666+
$sqlUpdate = "
667+
UPDATE `image` SET hash = :hash WHERE id = :id AND
668+
NOT EXISTS (
669+
SELECT 1 FROM (
670+
SELECT id FROM `image` WHERE hash = :hash
671+
) AS temp
672+
)
673+
";
674+
675+
if( $this->isDryRun() ) {
676+
return $this;
677+
}
678+
679+
$affectedRows =
680+
$this->em->getConnection()->executeStatement($sqlUpdate, [
681+
'hash' => $newHash,
682+
'id' => $imageId,
683+
]);
684+
685+
if ($affectedRows === 0) {
686+
687+
$this->arrImagesReHashReport[static::FAILURE]++;
688+
$this->arrImagesReHashReport['list'][$imageId] = $newHash;
689+
690+
} else {
691+
692+
$this->arrImagesReHashReport[static::SUCCESS]++;
693+
}
694+
695+
return $this;
696+
}
697+
698+
609699
protected function importTags() : static
610700
{
611701
if( $this->getCliOption(static::OPT_SKIP_TAGS) ) {

‎src/Service/Cms/ImageEditor.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
namespace App\Service\Cms;
3+
4+
use App\Service\User;
5+
use Imagine\Exception\NotSupportedException;
6+
use Symfony\Component\HttpFoundation\File\UploadedFile;
7+
8+
9+
class ImageEditor extends Image
10+
{
11+
protected UploadedFile $file;
12+
13+
/*
14+
public function loadOrCreateFromUploadedFile(UploadedFile $uploadedFile) : ImageEditor
15+
{
16+
if( !str_starts_with($uploadedFile->getMimeType(), 'image') ) {
17+
throw new NotSupportedException('The MIME is not image/*');
18+
}
19+
20+
$fileHash =
21+
22+
$image = $this->loadByHash();
23+
}
24+
*/
25+
26+
public function createFromFilePath(UploadedFile $file, User $author, ?string $hash = null) : ImageEditor
27+
{
28+
if( !str_starts_with($file->getMimeType(), 'image') ) {
29+
throw new NotSupportedException('The MIME is not image/*');
30+
}
31+
32+
$hash = $hash ?: hash_file('md5', $file->getPathname() );
33+
34+
$this->entity
35+
->setTitle( $file->getClientOriginalName() )
36+
->setFormat( $file->guessExtension() )
37+
->setHash($hash)
38+
->addAuthor($author);
39+
40+
$this->save();
41+
42+
$destinationFullPath = $this->getOriginalFilePath();
43+
44+
$file->move( dirname($destinationFullPath), basename($destinationFullPath) );
45+
46+
return $this;
47+
}
48+
49+
50+
public function rehash() : static
51+
{
52+
$hash = hash_file('md5', $this->getOriginalFilePath() );
53+
$this->entity->setHash($hash);
54+
return $this;
55+
}
56+
57+
58+
59+
//<editor-fold defaultstate="collapsed" desc="*** 💾 Save ***">
60+
public function save(bool $persist = true) : static
61+
{
62+
if($persist) {
63+
64+
$this->factory->getEntityManager()->persist($this->entity);
65+
$this->factory->getEntityManager()->flush();
66+
}
67+
68+
return $this;
69+
}
70+
//</editor-fold>
71+
}

‎src/Service/Factory.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
use App\Service\Cms\File as FileService;
1313
use App\Service\Cms\FileUrlGenerator;
1414
use App\Service\Cms\Image as ImageService;
15+
use App\Service\Cms\ImageEditor;
1516
use App\Service\Cms\ImageUrlGenerator;
1617
use App\Service\Cms\Tag as TagService;
1718
use App\Service\Cms\TagEditor;
1819
use App\Service\Cms\TagUrlGenerator;
1920
use App\ServiceCollection\Cms\ArticleAuthorCollection;
21+
use App\ServiceCollection\Cms\ImageEditorCollection;
2022
use App\ServiceCollection\PhpBB\TopicCollection;
2123
use App\Service\PhpBB\ForumUrlGenerator;
2224
use App\Service\PhpBB\Topic as TopicService;
@@ -211,10 +213,23 @@ public function createImage(?ImageEntity $entity = null) : ImageService
211213
}
212214

213215

216+
public function createImageEditor(?ImageEntity $entity = null) : ImageEditor
217+
{
218+
$service = new ImageEditor($this);
219+
if( !empty($entity) ) {
220+
$service->setEntity($entity);
221+
}
222+
223+
return $service;
224+
}
225+
226+
214227
public function getImageUrlGenerator() : ImageUrlGenerator { return $this->imageUrlGenerator; }
215228

216229

217230
public function createImageCollection() : ImageCollection { return new ImageCollection($this); }
231+
232+
public function createImageEditorCollection() : ImageCollection { return new ImageEditorCollection($this); }
218233
//</editor-fold>
219234

220235
//<editor-fold defaultstate="collapsed" desc="*** File ***">

‎src/ServiceCollection/Cms/ImageCollection.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ImageCollection extends BaseServiceEntityCollection
1414
{
1515
const string ENTITY_CLASS = ImageService::ENTITY_CLASS;
1616

17+
1718
public function get404() : ImageService
1819
{
1920
$entity =
@@ -26,5 +27,7 @@ public function get404() : ImageService
2627

2728

2829
public function createService(?ImageEntity $entity = null) : ImageService
29-
{ return $this->factory->createImage($entity); }
30+
{
31+
return $this->factory->createImage($entity);
32+
}
3033
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
namespace App\ServiceCollection\Cms;
3+
4+
use App\Entity\Cms\Image as ImageEntity;
5+
use App\Service\Cms\ImageEditor;
6+
use App\Service\User;
7+
8+
9+
class ImageEditorCollection extends ImageCollection
10+
{
11+
public function setFromUpload(?array $arrUpload, User $author) : static
12+
{
13+
$this->clear();
14+
15+
if( empty($arrUpload) ) {
16+
return $this;
17+
}
18+
19+
$arrData = [];
20+
foreach($arrUpload as $file) {
21+
22+
if( !str_starts_with($file->getMimeType(), 'image') ) {
23+
continue;
24+
}
25+
26+
$fileHash = hash_file('md5', $file->getPathname() );
27+
28+
$arrData[$fileHash] = [
29+
'File' => $file,
30+
'Image' => null
31+
];
32+
}
33+
34+
/** @var ImageCollection $existingImages */
35+
$existingImages = $this->factory->createImageCollection()->loadByHash( array_keys($arrData) );
36+
37+
foreach($arrData as $hash => $item) {
38+
39+
$existingImage = $existingImages->lookupSearchExtract($hash, function(string $hashToCheck, string $image) {
40+
return $hashToCheck == $image->getHash();
41+
});
42+
43+
if( empty($existingImage) ) {
44+
45+
$newImage =
46+
$this->factory->createImageEditor()
47+
->createFromFilePath($item["File"], $hash)
48+
->save(false);
49+
50+
$item["Image"] = $newImage;
51+
52+
} else {
53+
54+
$item["Image"] = $existingImage;
55+
}
56+
57+
$item["Image"]->addAuthor($author);
58+
}
59+
60+
$this->factory->getEntityManager()->flush();
61+
62+
foreach($arrData as $item) {
63+
64+
$imageId = (string)$item["Image"]->getId();
65+
$this->arrData[$imageId] = $item["Image"];
66+
}
67+
68+
return $this;
69+
}
70+
71+
72+
public function createService(?ImageEntity $entity = null) : ImageEditor
73+
{
74+
return $this->factory->createImageEditor($entity);
75+
}
76+
}

0 commit comments

Comments
 (0)