Skip to content

Commit def4e2d

Browse files
committed
Add: template image provider
1 parent ec1fc51 commit def4e2d

7 files changed

Lines changed: 216 additions & 32 deletions

File tree

src/Domain/Messaging/Model/Template.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,14 @@ public function setContent(?string $content): self
118118

119119
public function setText(?string $text): self
120120
{
121-
$this->text = $text !== null ? fopen('data://text/plain,' . $text, 'r') : null;
121+
if ($text === null) {
122+
$text = '[CONTENT]';
123+
}
124+
$stream = fopen('data://text/plain,' . $text, 'r');
125+
rewind($stream);
126+
127+
$this->text = $stream;
128+
122129
return $this;
123130
}
124131

src/Domain/Messaging/Model/TemplateImage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public function setFilename(?string $filename): self
9898

9999
public function setData(?string $data): self
100100
{
101-
$this->data = $data !== null ? fopen('data://text/plain,' . $data, 'r') : null;
101+
$this->data = $data !== null ? fopen('data://text/plain,' . rawurlencode($data), 'r') : null;
102102
return $this;
103103
}
104104

src/Domain/Messaging/Repository/TemplateImageRepository.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
88
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
99
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
10+
use PhpList\Core\Domain\Messaging\Model\Template;
1011
use PhpList\Core\Domain\Messaging\Model\TemplateImage;
1112

1213
class TemplateImageRepository extends AbstractRepository implements PaginatableRepositoryInterface
@@ -43,4 +44,15 @@ public function findByTemplateIdAndFilename(int $templateId, string $filename):
4344
->getQuery()
4445
->getOneOrNullResult();
4546
}
47+
48+
public function poweredByImageExists(Template $template): bool
49+
{
50+
return $this->createQueryBuilder('ti')
51+
->andWhere('ti.template = :templateId')
52+
->setParameter('templateId', $template->getId())
53+
->andWhere('ti.filename = :filename')
54+
->setParameter('filename', 'powerphplist.png')
55+
->getQuery()
56+
->getOneOrNullResult() !== null;
57+
}
4658
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service\Manager;
6+
7+
class ImageProvider
8+
{
9+
private const NEW_POWERED_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAEsAAAAhCAYAAACRIVbWAAAABHNCSVQICAgIfAhkiAAAAAlwSFl'
10+
. 'zAAALEgAACxIB0t1+/AAAAB50RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNS4xqx9I6wAADmhJREFUaIHtmntw1FWWxz+/Xz/T6aQ'
11+
. 'TQgIkJgR5LGRIFDcPsSAEWEFkZSIisPIcRaTKEkEMwUGXRRaZWlSylKPrqBDFGCXyUAqiKLUg6ADBhCRAEGQhWU1IIpru9Lt/j/2jkx/ppEOw'
12+
. 'ZmoetX6rurrv45x77/fec+65t69AN4zOyMgDFgP5gK17+f8D2IG9QPGZmprDqqpqBULnj9EZGTFAMfDrv3Tv/obxEbC4trq6DTrI6iDqMHDbX'
13+
. '69ff7OoBvJqq6vbxI6MYn4hqjfcRpAfhA4f9d9/zd78nWCiHljRW+nwzPGk507T0g11VZwqLwPAZLEybtbD2PoPxP7DVY59uA2f20lK2hgSBg/j'
14+
. 'VHkZtviBZE6bzaF3tgIwfdlv2f9fL/TQC3BoR7DO5AXLtbyuem9WpvaLci6eOtpjLLb4gSH1ju3aTkv9xaD8wuVaO+m50/C6neF0rBCBvN7IssU'
15+
. 'HiTj1SRmnPiljRJcOT1/2WwBOfVIWkva6nKTn3gtcJ9tksZKSNgZb/MCwek99Uoa99Sq2+EGYI61aHsC4WQ/ftEztF+VkTXuQ4Znjw4wltF5nfw'
16+
. 'Fs/QeSPmGatgB8bmc4OvL09BEe2FubaDhXFfydOw1TpBVb/EBs8YPY/XKwwYZzVfxm03Zs8QNpqb+IuaNOyqgxAIzIHE9U3ACunKlEkiQURUFVF'
17+
. 'GRZBqD+bCUAsizjcbbzPzUVAPRPvpUBqSN+lozBbCF55G3UHQ/1LLIso6rBb0WWMVmsSJIEwJGdb/BgwX9gNEdy9fIFTZderw/hOyQVDuMeeJh'
18+
. 'xDzyskVJ7pJwBqcOxtzb1INUWPwh761UazlUxPHM8KWljOLZrGylpY4juP5CT+9/X6g/+1R3Y4geFDLwz/+nizzSdH/9+Q58y3fvxD1m5Ycts8Y'
19+
. 'PIvncOAD53O+ZIK16Xk+YrF7lQcYTs6XN4o2Bhr1z0SdaxXds0P9UJr8uJOdIaktfZMEBz/UWyps2mpf4iDedOkzVtNiaLlfpz1wdYfXg/J/d/0'
20+
. 'KO9+rOVHCwuYsG/vUr14f00X7nYp0xIPyxWvOHNiOYrFyjbXAhA7oNLyMibrun7puKLjsluCisLIIbNVRRt6Ssdv7t+Gi/VYbJYSRs3FUmSSB'
21+
. 's3FZPFSuOlOiRJ4nLtKUwWK+dPHqHxUh1eVzvNVy5qZPYFe2sTnxVvIffBJQxIHX5TMhCcsBHZE3pddV1hSxh003o70efK6g0f/34DMx5/jim'
22+
. 'LV/Qwl+YrF7G3NlF/thK9KFJ75ECwMTE4N6IgMG7WI+Q+uEST2V64mIDHhd/tRC+KXPr6GMc+fIs7p89l/2sbbyjT1XRPHdjJuS/KtbY60b1'
23+
. 'ew7kqznap17Xt3iCMzshQe+QqCpKi/CzyekM3J/l3jfAjEQSNYUEQkCQZj8eNXq8nOjqayMhIVFXF7XZjt9uRZRmLxYJOp6PrwfPPRfifikAg'
24+
. 'gCAIf/LE9SIdHLAgCnjcHvz+AP+YeQdT7p7CqFGjSEhIQFVVWltbqamp4dChQ5w+fRqj0YjZbA4hDIA+SFMUBa/XS4TZjHADM7gRZFnG5/N'
25+
. 'hiYgAQQjJH5SQgM/vp62tDZ1O16sOt9uN0WjEYDAEGeiiB3oxQ0FVAAG3243BYGDZsmXMnTuXAQMGhG2ksbGR0tJS3nzzTRRFwWQyoaoqkqIE'
26+
. 'Z/MGZCmKgsViITMzk4qKCrxeL0K3TvYFRVGIjY0lLS2NEydOIMsygiAQCARISUlh586dtLS0MG/ePFwuV1jCVFVl7NixXLlyhe+//x6dXt+DrL'
27+
. 'DTqKgqfr8fm83GunXrePLJJ0OI8ng8vPLKKzz11FPU1dWRmJjIqlWreOaZZ1BVVVv2NwO/38+wYcN47bXXGDp0KD6f7+fwBIDX62Xs2LG8/v'
28+
. 'rrxMbGasGmqqrodDrMZjNms7nXPsmyTEREBC+99BL33HMPbrc7bL2wZujz+VBkBZPZRHZ2dkhZIBDgzbfe4tzZs9xySzKbfvc7Nmx4nsEpg5'
29+
. 'k/fz6XL1/hjTf/QGxMLBD0eV6fD6VjdXVG4IIgYDQaARA7TE+SJJxOJ5IkIQgCJpMJg8GALMt4vV5EUeyIxIPG0ElEpz5ZlkP8ktFopL6+n'
30+
. 'qlTpxIIBHC5XEiShMfj0eqIooher8fn8xEIBPB6vTidTqxRUT3IDUuWJEnceuutTJo4ibVr17Ju3TpGjhwJgMvlorLya+bOnsM/3X03CxYsp'
31+
. 'KG+nsEpgxFFkYce+hcOHTrEd9//LyZzBH6/n7vuuouYmBgCgQD3338/UVFRVFdXU1payrfffhs0WUkiJyeHefPmkZSUREVFBSUlJbS2ttK/f3'
32+
. '9mzZrFhQsXmDx5Munp6TgcDvbs2cPnn38ePAqpqvbpCkVRmDFjBk1NTZSVlZGZmcns2bNJTU3l2rVrfPbZZxw/fpw1a9Zgs9mYP38+aWlp/Pv'
33+
. 'GjfgDgRBdYc1QJ+rIysxi7dq1jL1rLAUFBZw/fx6AqKgoxt45lk8Pfsrb77zNwIEDGDp0mCY7ZMgQcsePx+/zB1ei309+fj5FRUUUFRXh9Xqp'
34+
. 'rq5m4sSJfPDBB4waNQqPx4MgCKxevZr4+Hhqamq47777eP/990lISCA+Pp7169dTWlpKXl4eNTU1eDwetm7dypo1awh0G1QnOonLz88nJyeHk'
35+
. 'SNHsn37dpKSkqisrESWZZ5//nlycnI0nc3NzZw/f16zhD5XFoJAeno6AMufWI4syRQWFrJ582ZGjBjB0qWPMmPGr9lW/DZfHj1KYmLidYV6PW'
36+
. 'lpaRhNJk1Xpw8pKCjgww8/RBAEtm7dSklJCc899xxbtmxBp9Oxbds21q9fjyRJvPrqq+zdu5fly5dTXFwMwIkTJ3jkkUdoa2tDVVXmzJnDyy+'
37+
. '/zL59+/B6vWGHAkEf297eTnZ2NhEREaxYsYILFy5gtVoZNmwYLpcLh8PB4sWLOXjwIFu2bGFQYmKPnTn8Pq2qxMXFacmVK1eSl5dHYWEhly5dY'
38+
. 's+evcTFxTHvoYd45513sNvtIeK2WBtGg0FLG41GGhoa+PTTT4mOjiYmJob29naKi4u5/fbbSU5ORpIkDh48iF6vp1+/fly7do0DBw5wxx13aH'
39+
. '7pvffew+l0EhsbS0xMDAcOHOC7775j0qRJmi/sDUajkZMnT+Lz+di5cyebNm1i4sSJNDc3Y7fbiY6ORhRFzGYzUWH8Va9kyYqCrIQ2vnLlSqb'
40+
. '/83SWPraU3Xt2s3btWl568UX8AT+rV6/G5XJpdRVZQeW67xBFkZ9++glJkjRnbjAY+PHHHxEEAavVqm33neV6vV5z9IIgoCgKbW1tmgPvdOjX'
41+
. 'rl2jX79+NyQKwGQyUVtby6JFi6ipqWHmzJmUlJSwY8cOEhMT8fv9feoIS5bRaKChvqFHflJiEs3NzbQ72omNjcVsNrPh+Q1YrVaeLnha2/abm'
42+
. 'q/i811vXJZlEhMTsVgsmn/xer0MHz4cv99Pa2srer2+x2wKgqD5HVEUSU1N1cwtEAgQGRlJamoqly9f7nOgnTpOnjzJihUrmDp1KgsWLCA9PZ'
43+
. '2FCxdqZPUIqPsiy2QyceLEiZC8uro6it9+m9LS98nLy2Pjxo2oqorVamXjxo1YIiyseWYNDoeDs2fOIklBUgSCoUh8fDyrVq3CaDTidDrJys'
44+
. 'riiSeeYN++fTQ3N6PT6cKS1UmYLMs8/vjjZGdn43Q6MRgMFBQUEBERQXl5OaYOH+nz+fB6vfh8Pi0cEAQBn8/HvHnzKCwsJDo6GrvdTlVVFQ6'
45+
. 'HQzu+GQwGRFHE6/WGJa2X445AZWUlx48f58477wSCUTqqyoCEBIaPGE5VVRV+vx+TyYTZbOaFF15g06ZNLF36GN83fkdkx32XSjAeamtr49577'
46+
. '2XKlCk4HA6SkpL46quv2Lx5M2lpaQAhfkdVVQRBwGAwQEeQ7PV62bFjB42NjVitVgwGAytXruTy5ctkZWVhMBgoKSnB7/drxO/atUtLe71eFi1'
47+
. 'axMyZM3E4HMTExOByuXj33Xfx+XwcPXqUpUuXkpmZyaqnn8bXzTSF0RkZbXS7WhZQcTpdZGdl8oc33iA6Khq3282zzz5Lyw+tSIEAc+fMJT8/'
48+
. 'P0RZa2sr+fn5NDY1ER0djT8QwO/3859btpCcnMzSRx8ld8IEoqxWvvnmGyoqKpAVhZiYGLKzszl+/DgejwexYwdNHTKEuLg4PB4Pu3fvZsmSJ'
49+
. 'TgcDtLT03E6nXx57BiNTU2YTCZibDZyJ0wgMjIyZIWePXMGCO6I1TU1DE5JITMri9jYWFpaWvjjV3+kzR70hWazmcmTJ2PQ6zlQXg6hu6FdGJ2'
50+
. 'RsZdu/0ILqMiqgqPNzv33z2Tdv64jLq4fHo+H/Qf2k3xLMjk5OSFE/fDDD2x+8UX27fsYnU6PIEBAkvH7/WwtKmLw4MEs+s1iTOYIVEVBbzBgM'
51+
. 'pkQRQFFVvB4PERYIhAFsbMTBAIS9rY2hgwZwq6yMh5btoyvq6ow6PUIooDJZMag14MQXJVutxu6WY/RaERVVURRxGgyEggE8Pv8KIqCqBMxmc'
52+
. 'zodboOHQoetxsEgcjISK42hdyafqQHirqTBQI6UUd0VDS7d++mtaWVJUseYfLkycx6YFZITVmWOXzkCNu3b+PYsS+DMysK0GHzqqqi1+vR6XTo'
53+
. 'DUYyszK1sj4hCJw+Xa0lVYJBcTAGVK9n/rxz902jG1lFnX/fh6wuQSA4IEFEUSTaHU5stmgybruN9F+N5pbkZFRVpamxkZqaGmrPnsHe1kZkpB'
54+
. 'VRJ3aQIRCQJFRVJXPMGMwREXxx9GjwxH+ztwoqqKpCtM3GpEmTOHz4MD/9+CPiDa5Z/pzocs78qLa6Oj/sWwdR0PraQVxwl5EkCQQw6A0gCMiS'
55+
. 'hKIqGPQG7VDcFf6AhMFgwOvxBK9iOnadnwtFUXB7PFgiIrQ47C8I7a3DL69oboyer2i64pf3Wb2/z/o/Z4jQ19LLyeMAAAAASUVORK5CYII=';
56+
private const IMAGE_CONTENT = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAALGPC/'
57+
. 'xhBQAAAAZQTFRF////AAAAVcLTfgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsSAAALEgHS3X78'
58+
. 'AAAAB3RJTUUH0gQCEx05cqKA8gAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=';
59+
60+
61+
public function getFallbackLogo(): string
62+
{
63+
return self::IMAGE_CONTENT;
64+
}
65+
66+
public function getPoweredByImage(): string
67+
{
68+
return self::NEW_POWERED_IMAGE;
69+
}
70+
}

src/Domain/Messaging/Service/Manager/TemplateImageManager.php

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class TemplateImageManager
2828
public function __construct(
2929
private readonly TemplateImageRepository $templateImageRepository,
3030
private readonly ConfigProvider $configProvider,
31+
private readonly ImageProvider $imageProvider,
3132
) {
3233
}
3334

@@ -36,19 +37,77 @@ public function createImagesFromImagePaths(array $imagePaths, Template $template
3637
{
3738
$templateImages = [];
3839
foreach ($imagePaths as $path) {
40+
$this->removeExistingTemplateImage($template, $path);
41+
[$imageData, $width, $height, $detectedMimeType] = $this->extractImageData($path);
42+
3943
$image = new TemplateImage();
4044
$image->setTemplate($template);
4145
$image->setFilename($path);
42-
$image->setMimeType($this->guessMimeType($path));
43-
$image->setData(null);
46+
$image->setMimeType($detectedMimeType ?? $this->guessMimeType($path));
47+
$image->setWidth($width);
48+
$image->setHeight($height);
49+
$image->setData($imageData);
4450

4551
$this->templateImageRepository->persist($image);
4652
$templateImages[] = $image;
4753
}
4854

55+
$this->ensurePoweredByImageExists($template);
56+
4957
return $templateImages;
5058
}
5159

60+
private function removeExistingTemplateImage(Template $template, string $path): void
61+
{
62+
$templateId = $template->getId();
63+
if ($templateId === null) {
64+
return;
65+
}
66+
67+
$existing = $this->templateImageRepository->findByTemplateIdAndFilename($templateId, $path);
68+
if ($existing !== null) {
69+
$this->templateImageRepository->remove($existing);
70+
}
71+
}
72+
73+
/**
74+
* @return array{0: ?string, 1: ?int, 2: ?int, 3: ?string}
75+
*/
76+
private function extractImageData(string $path): array
77+
{
78+
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
79+
$contents = @file_get_contents($path);
80+
if ($contents === false || $contents === '') {
81+
return [null, null, null, null];
82+
}
83+
84+
$size = getimagesizefromstring($contents);
85+
if ($size === false || empty($size[0]) || empty($size[1])) {
86+
return [null, null, null, null];
87+
}
88+
89+
$mimeType = strtolower((string) $size['mime']);
90+
91+
return [base64_encode($contents), (int) $size[0], (int) $size[1], $mimeType];
92+
}
93+
94+
private function ensurePoweredByImageExists(Template $template): void
95+
{
96+
if ($this->templateImageRepository->poweredByImageExists($template)) {
97+
return;
98+
}
99+
100+
$image = new TemplateImage();
101+
$image->setTemplate($template);
102+
$image->setFilename('powerphplist.png');
103+
$image->setMimeType('image/png');
104+
$image->setWidth(70);
105+
$image->setHeight(30);
106+
$image->setData($this->imageProvider->getPoweredByImage());
107+
108+
$this->templateImageRepository->persist($image);
109+
}
110+
52111
private function guessMimeType(string $filename): string
53112
{
54113
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
@@ -57,9 +116,7 @@ private function guessMimeType(string $filename): string
57116

58117
public function extractAllImages(string $html): array
59118
{
60-
$fromRegex = array_keys(
61-
$this->extractTemplateImagesFromContent($html)
62-
);
119+
$fromRegex = array_keys($this->extractTemplateImagesFromContent($html));
63120

64121
$fromDom = $this->extractImagesFromHtml($html);
65122

@@ -68,7 +125,10 @@ public function extractAllImages(string $html): array
68125

69126
private function extractTemplateImagesFromContent(string $content): array
70127
{
71-
$regexp = sprintf('/"([^"]+\.(%s))"/Ui', implode('|', array_keys(self::IMAGE_MIME_TYPES)));
128+
$regexp = sprintf(
129+
'/"([^"]+\.(%s))"/Ui',
130+
implode('|', array_keys(self::IMAGE_MIME_TYPES))
131+
);
72132
preg_match_all($regexp, stripslashes($content), $images);
73133

74134
return array_count_values($images[1]);
@@ -170,9 +230,7 @@ private function decodeLogoImageData(?string $logoData): ?string
170230
return $imageContent;
171231
}
172232

173-
$fallbackContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAALGPC/'
174-
. 'xhBQAAAAZQTFRF////AAAAVcLTfgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsSAAALEgHS3X78'
175-
. 'AAAAB3RJTUUH0gQCEx05cqKA8gAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=', true);
233+
$fallbackContent = base64_decode($this->imageProvider->getFallbackLogo(), true);
176234

177235
return $fallbackContent !== false ? $fallbackContent : null;
178236
}

src/Domain/Messaging/Service/Manager/TemplateManager.php

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,17 @@
1414

1515
class TemplateManager
1616
{
17-
private TemplateRepository $templateRepository;
18-
private TemplateImageManager $templateImageManager;
19-
private TemplateLinkValidator $templateLinkValidator;
20-
private TemplateImageValidator $templateImageValidator;
21-
2217
public function __construct(
23-
TemplateRepository $templateRepository,
24-
TemplateImageManager $templateImageManager,
25-
TemplateLinkValidator $templateLinkValidator,
26-
TemplateImageValidator $templateImageValidator
18+
private readonly TemplateRepository $templateRepository,
19+
private readonly TemplateImageManager $templateImageManager,
20+
private readonly TemplateLinkValidator $templateLinkValidator,
21+
private readonly TemplateImageValidator $templateImageValidator
2722
) {
28-
$this->templateRepository = $templateRepository;
29-
$this->templateImageManager = $templateImageManager;
30-
$this->templateLinkValidator = $templateLinkValidator;
31-
$this->templateImageValidator = $templateImageValidator;
3223
}
3324

3425
public function create(CreateTemplateDto $createTemplateDto): Template
3526
{
36-
$template = (new Template($createTemplateDto->title))
27+
$template = (new Template(title: $createTemplateDto->title))
3728
->setText($createTemplateDto->text);
3829

3930
$content = $createTemplateDto->fileContent ?? $createTemplateDto->content;
@@ -49,11 +40,11 @@ public function create(CreateTemplateDto $createTemplateDto): Template
4940
$this->templateLinkValidator->validate($template->getContent() ?? '', $context);
5041

5142
$imageUrls = $this->templateImageManager->extractAllImages($template->getContent() ?? '');
52-
$this->templateImageValidator->validate($imageUrls, $context);
43+
$this->templateImageValidator->validate(value: $imageUrls, context: $context);
5344

5445
$this->templateRepository->persist($template);
5546

56-
$this->templateImageManager->createImagesFromImagePaths($imageUrls, $template);
47+
$this->templateImageManager->createImagesFromImagePaths(imagePaths: $imageUrls, template: $template);
5748

5849
return $template;
5950
}
@@ -83,10 +74,6 @@ public function update(Template $template, UpdateTemplateDto $updateTemplateDto)
8374
$imageUrls = $this->templateImageManager->extractAllImages($template->getContent() ?? '');
8475
$this->templateImageValidator->validate($imageUrls, $context);
8576

86-
foreach ($template->getImages() as $image) {
87-
$this->templateImageManager->delete($image);
88-
}
89-
9077
$this->templateImageManager->createImagesFromImagePaths($imageUrls, $template);
9178

9279
return $template;

tests/Unit/Domain/Messaging/Service/Manager/TemplateImageManagerTest.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
use PhpList\Core\Domain\Messaging\Model\Template;
99
use PhpList\Core\Domain\Messaging\Model\TemplateImage;
1010
use PhpList\Core\Domain\Messaging\Repository\TemplateImageRepository;
11+
use PhpList\Core\Domain\Messaging\Service\Manager\ImageProvider;
1112
use PhpList\Core\Domain\Messaging\Service\Manager\TemplateImageManager;
1213
use PHPUnit\Framework\MockObject\MockObject;
1314
use PHPUnit\Framework\TestCase;
1415

1516
class TemplateImageManagerTest extends TestCase
1617
{
18+
private const ONE_PIXEL_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAALGPC/'
19+
. 'xhBQAAAAZQTFRF////AAAAVcLTfgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsSAAALEgHS3X78'
20+
. 'AAAAB3RJTUUH0gQCEx05cqKA8gAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=';
21+
1722
private TemplateImageRepository&MockObject $templateImageRepository;
1823
private ConfigProvider&MockObject $configProvider;
1924
private TemplateImageManager $manager;
@@ -26,14 +31,16 @@ protected function setUp(): void
2631
$this->manager = new TemplateImageManager(
2732
templateImageRepository: $this->templateImageRepository,
2833
configProvider: $this->configProvider,
34+
imageProvider: $this->createMock(ImageProvider::class),
2935
);
3036
}
3137

3238
public function testCreateImagesFromImagePaths(): void
3339
{
3440
$template = $this->createMock(Template::class);
3541

36-
$this->templateImageRepository->expects($this->exactly(2))
42+
$this->templateImageRepository->method('poweredByImageExists')->willReturn(false);
43+
$this->templateImageRepository->expects($this->exactly(2 + 1))
3744
->method('persist')
3845
->with($this->isInstanceOf(TemplateImage::class));
3946

@@ -45,6 +52,49 @@ public function testCreateImagesFromImagePaths(): void
4552
}
4653
}
4754

55+
public function testCreateImagesFromImagePathsStoresImageDataAndReplacesExistingImage(): void
56+
{
57+
$template = $this->createMock(Template::class);
58+
$template->method('getId')->willReturn(77);
59+
60+
$existingImage = $this->createMock(TemplateImage::class);
61+
$capturedImage = null;
62+
63+
$tmpFile = tempnam(sys_get_temp_dir(), 'img_');
64+
if ($tmpFile === false) {
65+
$this->fail('Failed to create temporary file');
66+
}
67+
68+
file_put_contents($tmpFile, (string) base64_decode(self::ONE_PIXEL_PNG, true));
69+
70+
$this->templateImageRepository->method('poweredByImageExists')->willReturn(true);
71+
$this->templateImageRepository->expects($this->once())
72+
->method('findByTemplateIdAndFilename')
73+
->with(77, $tmpFile)
74+
->willReturn($existingImage);
75+
$this->templateImageRepository->expects($this->once())
76+
->method('remove')
77+
->with($existingImage);
78+
$this->templateImageRepository->expects($this->once())
79+
->method('persist')
80+
->with($this->callback(static function (TemplateImage $image) use (&$capturedImage): bool {
81+
$capturedImage = $image;
82+
return true;
83+
}));
84+
85+
try {
86+
$this->manager->createImagesFromImagePaths([$tmpFile], $template);
87+
} finally {
88+
unlink($tmpFile);
89+
}
90+
91+
$this->assertInstanceOf(TemplateImage::class, $capturedImage);
92+
$this->assertSame(1, $capturedImage->getWidth());
93+
$this->assertSame(1, $capturedImage->getHeight());
94+
$this->assertSame('image/png', $capturedImage->getMimeType());
95+
$this->assertSame(self::ONE_PIXEL_PNG, $capturedImage->getData());
96+
}
97+
4898
public function testGuessMimeType(): void
4999
{
50100
$reflection = new \ReflectionClass($this->manager);

0 commit comments

Comments
 (0)