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