[BUGFIX] Eliminate gremlins in image cropper
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Imaging / ImageManipulation / CropVariant.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Imaging\ImageManipulation;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Resource\FileInterface;
19
20 class CropVariant
21 {
22 /**
23 * @var string
24 */
25 protected $id;
26
27 /**
28 * @var string
29 */
30 protected $title;
31
32 /**
33 * @var Area
34 */
35 protected $cropArea;
36
37 /**
38 * @var Ratio[]
39 */
40 protected $allowedAspectRatios;
41
42 /**
43 * @var string
44 */
45 protected $selectedRatio;
46
47 /**
48 * @var Area|null
49 */
50 protected $focusArea;
51
52 /**
53 * @var Area[]|null
54 */
55 protected $coverAreas;
56
57 /**
58 * @param string $id
59 * @param string $title
60 * @param Area $cropArea
61 * @param Ratio[] $allowedAspectRatios
62 * @param string|null $selectedRatio
63 * @param Area|null $focusArea
64 * @param Area[]|null $coverAreas
65 * @throws InvalidConfigurationException
66 */
67 public function __construct(
68 string $id,
69 string $title,
70 Area $cropArea,
71 array $allowedAspectRatios = null,
72 string $selectedRatio = null,
73 Area $focusArea = null,
74 array $coverAreas = null
75 ) {
76 $this->id = $id;
77 $this->title = $title;
78 $this->cropArea = $cropArea;
79 if ($allowedAspectRatios) {
80 $this->setAllowedAspectRatios(...$allowedAspectRatios);
81 if ($selectedRatio && isset($this->allowedAspectRatios[$selectedRatio])) {
82 $this->selectedRatio = $selectedRatio;
83 } else {
84 $this->selectedRatio = current($this->allowedAspectRatios)->getId();
85 }
86 }
87 $this->focusArea = $focusArea;
88 if ($coverAreas !== null) {
89 $this->setCoverAreas(...$coverAreas);
90 }
91 }
92
93 /**
94 * @param string $id
95 * @param array $config
96 * @return CropVariant
97 * @throws InvalidConfigurationException
98 */
99 public static function createFromConfiguration(string $id, array $config): CropVariant
100 {
101 try {
102 return new self(
103 $id,
104 $config['title'] ?? '',
105 Area::createFromConfiguration($config['cropArea']),
106 isset($config['allowedAspectRatios']) ? Ratio::createMultipleFromConfiguration($config['allowedAspectRatios']) : null,
107 $config['selectedRatio'] ?? null,
108 isset($config['focusArea']) ? Area::createFromConfiguration($config['focusArea']) : null,
109 isset($config['coverAreas']) ? Area::createMultipleFromConfiguration($config['coverAreas']) : null
110 );
111 } catch (\Throwable $throwable) {
112 throw new InvalidConfigurationException(sprintf('Invalid type in configuration for crop variant: %s', $throwable->getMessage()), 1485278693, $throwable);
113 }
114 }
115
116 /**
117 * @return array
118 * @internal
119 */
120 public function asArray(): array
121 {
122 $allowedAspectRatiosAsArray = [];
123 foreach ($this->allowedAspectRatios as $id => $allowedAspectRatio) {
124 $allowedAspectRatiosAsArray[$id] = $allowedAspectRatio->asArray();
125 }
126 if ($this->coverAreas !== null) {
127 $coverAreasAsArray = [];
128 foreach ($this->coverAreas as $coverArea) {
129 $coverAreasAsArray[] = $coverArea->asArray();
130 }
131 }
132 return [
133 'id' => $this->id,
134 'title' => $this->title,
135 'cropArea' => $this->cropArea->asArray(),
136 'allowedAspectRatios' => $allowedAspectRatiosAsArray,
137 'selectedRatio' => $this->selectedRatio,
138 'focusArea' => $this->focusArea ? $this->focusArea->asArray() : null,
139 'coverAreas' => $coverAreasAsArray ?? null,
140 ];
141 }
142
143 /**
144 * @return string
145 */
146 public function getId(): string
147 {
148 return $this->id;
149 }
150
151 /**
152 * @return Area
153 */
154 public function getCropArea(): Area
155 {
156 return $this->cropArea;
157 }
158
159 /**
160 * @return Area|null
161 */
162 public function getFocusArea()
163 {
164 return $this->focusArea;
165 }
166
167 /**
168 * @param FileInterface $file
169 * @return CropVariant
170 */
171 public function applyRatioRestrictionToSelectedCropArea(FileInterface $file): CropVariant
172 {
173 if (!$this->selectedRatio) {
174 return $this;
175 }
176 $newVariant = clone $this;
177 $newArea = $this->cropArea->makeAbsoluteBasedOnFile($file);
178 $newArea = $newArea->applyRatioRestriction($this->allowedAspectRatios[$this->selectedRatio]);
179 $newVariant->cropArea = $newArea->makeRelativeBasedOnFile($file);
180 return $newVariant;
181 }
182
183 /**
184 * @param Ratio[] $ratios
185 * @throws InvalidConfigurationException
186 */
187 protected function setAllowedAspectRatios(Ratio ...$ratios)
188 {
189 $this->allowedAspectRatios = [];
190 foreach ($ratios as $ratio) {
191 $this->addAllowedAspectRatio($ratio);
192 }
193 }
194
195 /**
196 * @param Ratio $ratio
197 * @throws InvalidConfigurationException
198 */
199 protected function addAllowedAspectRatio(Ratio $ratio)
200 {
201 if (isset($this->allowedAspectRatios[$ratio->getId()])) {
202 throw new InvalidConfigurationException(sprintf('Ratio with with duplicate ID (%s) is configured. Make sure all configured ratios have different ids.', $ratio->getId()), 1485274618);
203 }
204 $this->allowedAspectRatios[$ratio->getId()] = $ratio;
205 }
206
207 /**
208 * @param Area[] $areas
209 * @throws InvalidConfigurationException
210 */
211 protected function setCoverAreas(Area ...$areas)
212 {
213 $this->coverAreas = [];
214 foreach ($areas as $area) {
215 $this->addCoverArea($area);
216 }
217 }
218
219 /**
220 * @param Area $area
221 * @throws InvalidConfigurationException
222 */
223 protected function addCoverArea(Area $area)
224 {
225 $this->coverAreas[] = $area;
226 }
227 }