1 | <?php |
||
2 | |||
3 | /** |
||
4 | * responsive-images-css |
||
5 | * |
||
6 | * @category Jkphl |
||
7 | * @package Jkphl\Respimgcss |
||
8 | * @subpackage Jkphl\Respimgcss\Infrastructure |
||
9 | * @author Joschi Kuphal <[email protected]> / @jkphl |
||
10 | * @copyright Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl |
||
11 | * @license http://opensource.org/licenses/MIT The MIT License (MIT) |
||
12 | */ |
||
13 | |||
14 | /*********************************************************************************** |
||
15 | * The MIT License (MIT) |
||
16 | * |
||
17 | * Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl |
||
18 | * |
||
19 | * Permission is hereby granted, free of charge, to any person obtaining a copy of |
||
20 | * this software and associated documentation files (the "Software"), to deal in |
||
21 | * the Software without restriction, including without limitation the rights to |
||
22 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
||
23 | * the Software, and to permit persons to whom the Software is furnished to do so, |
||
24 | * subject to the following conditions: |
||
25 | * |
||
26 | * The above copyright notice and this permission notice shall be included in all |
||
27 | * copies or substantial portions of the Software. |
||
28 | * |
||
29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
30 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
||
31 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
||
32 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
||
33 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
||
34 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
35 | ***********************************************************************************/ |
||
36 | |||
37 | namespace Jkphl\Respimgcss\Infrastructure; |
||
38 | |||
39 | use Jkphl\Respimgcss\Application\Contract\LengthFactoryInterface; |
||
40 | use Jkphl\Respimgcss\Application\Contract\SourceSizeListInterface; |
||
41 | use Jkphl\Respimgcss\Application\Model\SourceSize; |
||
42 | use Jkphl\Respimgcss\Application\Model\SourceSizeMediaCondition; |
||
43 | use Jkphl\Respimgcss\Domain\Contract\AbsoluteLengthInterface; |
||
44 | use Jkphl\Respimgcss\Domain\Contract\ImageCandidateInterface; |
||
45 | use Jkphl\Respimgcss\Domain\Contract\ImageCandidateSetInterface; |
||
46 | use Jkphl\Respimgcss\Domain\Contract\SourceSizeImageCandidateMatch; |
||
47 | use Jkphl\Respimgcss\Domain\Model\Css\MediaCondition; |
||
48 | use Jkphl\Respimgcss\Domain\Model\ImageCandidateMatch; |
||
49 | use Jkphl\Respimgcss\Ports\InvalidArgumentException; |
||
50 | |||
51 | /** |
||
52 | * Sizes list |
||
53 | * |
||
54 | * @package Jkphl\Respimgcss |
||
55 | * @subpackage Jkphl\Respimgcss\Infrastructure |
||
56 | * @see http://w3c.github.io/html/semantics-embedded-content.html#ref-for-viewport-based-selection%E2%91%A0 |
||
57 | * @see http://w3c.github.io/html/semantics-embedded-content.html#valid-source-size-list |
||
58 | * @see http://w3c.github.io/html/semantics-embedded-content.html#parse-a-sizes-attribute |
||
59 | * @see https://www.sitepoint.com/community/t/pixels-or-percentages-for-media-queries/37487 |
||
60 | */ |
||
61 | class SourceSizeList extends \ArrayObject implements SourceSizeListInterface |
||
62 | { |
||
63 | /** |
||
64 | * Length factory |
||
65 | * |
||
66 | * @var LengthFactoryInterface |
||
67 | */ |
||
68 | protected $lengthFactory; |
||
69 | |||
70 | /** |
||
71 | * Source size list constructor |
||
72 | * |
||
73 | * @param SourceSize[] $sourceSizes Source sizes |
||
74 | * @param LengthFactoryInterface $lengthFactory Length factory |
||
75 | * |
||
76 | * @throws InvalidArgumentException If the source size is invalid |
||
77 | */ |
||
78 | 11 | public function __construct(array $sourceSizes, LengthFactoryInterface $lengthFactory) |
|
79 | { |
||
80 | // Run through all source sizes |
||
81 | 11 | foreach ($sourceSizes as $sourceSize) { |
|
82 | // If the source size is invalid |
||
83 | 9 | if (!($sourceSize instanceof SourceSize)) { |
|
84 | 1 | throw new InvalidArgumentException( |
|
85 | 1 | InvalidArgumentException::INVALID_SOURCE_SIZE_STR, |
|
86 | 9 | InvalidArgumentException::INVALID_SOURCE_SIZE |
|
87 | ); |
||
88 | } |
||
89 | } |
||
90 | |||
91 | 10 | usort($sourceSizes, [$this, 'sortSourceSizes']); |
|
92 | 10 | parent::__construct($sourceSizes); |
|
93 | |||
94 | 10 | $this->lengthFactory = $lengthFactory; |
|
95 | 10 | } |
|
96 | |||
97 | /** |
||
98 | * Find the optimum image candidate for a particular breakpoint |
||
99 | * |
||
100 | * @param ImageCandidateSetInterface $imageCandidates Image candidates |
||
101 | * @param AbsoluteLengthInterface $breakpoint Breakpoint |
||
102 | * @param float $density Density |
||
103 | * |
||
104 | * @return SourceSizeImageCandidateMatch|null Image candidate match |
||
105 | */ |
||
106 | 6 | public function findImageCandidate( |
|
107 | ImageCandidateSetInterface $imageCandidates, |
||
108 | AbsoluteLengthInterface $breakpoint, |
||
109 | float $density |
||
110 | ): ?SourceSizeImageCandidateMatch { |
||
111 | // Run through the source sizes (from biggest to smallest) |
||
112 | /** @var SourceSize $sourceSize */ |
||
113 | 6 | $lastMinimumWidth = null; |
|
114 | 6 | foreach ($this->getArrayCopy() as $sourceSize) { |
|
115 | 5 | $mediaCondition = $sourceSize->getMediaCondition(); |
|
116 | |||
117 | // If the current breakpoint and density matches the source size condition |
||
118 | 5 | if ($mediaCondition->matches($breakpoint, $density, $lastMinimumWidth)) { |
|
119 | 5 | $maximum = $this->getSourceSizeMaximumWidth($mediaCondition, $lastMinimumWidth); |
|
120 | 5 | return $this->findImageCandidateForSourceSize( |
|
121 | 5 | $sourceSize, $imageCandidates, $density, $breakpoint, $maximum |
|
122 | ); |
||
123 | } |
||
124 | 3 | $lastMinimumWidth = $mediaCondition->getMinimumWidth(); |
|
125 | } |
||
126 | |||
127 | 1 | return null; |
|
128 | } |
||
129 | |||
130 | /** |
||
131 | * Get the maximum width for a source size |
||
132 | * |
||
133 | * @param SourceSizeMediaCondition $condition Source size media condition |
||
134 | * @param float|null $lastMinimumWidth Last source size minimum width |
||
135 | * |
||
136 | * @return AbsoluteLengthInterface Maximum width |
||
137 | */ |
||
138 | 5 | protected function getSourceSizeMaximumWidth( |
|
139 | SourceSizeMediaCondition $condition, |
||
140 | float $lastMinimumWidth = null |
||
141 | ): ?AbsoluteLengthInterface { |
||
142 | 5 | $maximumWidth = $this->considerLastMinimumWidth($condition->getMaximumWidth(), $lastMinimumWidth); |
|
143 | 5 | if ($maximumWidth === null) { |
|
144 | 4 | return null; |
|
145 | } |
||
146 | 4 | if ($lastMinimumWidth !== null) { |
|
147 | 3 | $maximumWidth = max(0, min($maximumWidth, $lastMinimumWidth - 1)); |
|
148 | } |
||
149 | |||
150 | 4 | return $this->lengthFactory->createAbsoluteLength($maximumWidth); |
|
151 | } |
||
152 | |||
153 | /** |
||
154 | * Consider the last minimum width in case the maximum width is undefined |
||
155 | * |
||
156 | * @param int|null $maximumWidth Maximum width |
||
157 | * @param float|null $lastMinimumWidth Last minimum width |
||
158 | * |
||
159 | * @return float|null Maximum width |
||
160 | */ |
||
161 | 5 | protected function considerLastMinimumWidth(int $maximumWidth = null, float $lastMinimumWidth = null): ?float |
|
162 | { |
||
163 | 5 | if (($maximumWidth === null) && ($lastMinimumWidth !== null)) { |
|
164 | 3 | $maximumWidth = max(0, $lastMinimumWidth - 1); |
|
165 | } |
||
166 | |||
167 | 5 | return $maximumWidth; |
|
168 | } |
||
169 | |||
170 | /** |
||
171 | * Find an image candidate for a particular source size |
||
172 | * |
||
173 | * @param SourceSize $sourceSize Matching source size |
||
174 | * @param ImageCandidateSetInterface $imageCandidates Image candidates |
||
175 | * @param float $density Density |
||
176 | * @param AbsoluteLengthInterface $minWidth Minimum viewport width |
||
177 | * @param AbsoluteLengthInterface|null $maxWidth Maximum viewport width |
||
178 | * |
||
179 | * @return SourceSizeImageCandidateMatch|null |
||
180 | */ |
||
181 | 5 | protected function findImageCandidateForSourceSize( |
|
182 | SourceSize $sourceSize, |
||
183 | ImageCandidateSetInterface $imageCandidates, |
||
184 | float $density, |
||
185 | AbsoluteLengthInterface $minWidth, |
||
186 | AbsoluteLengthInterface $maxWidth = null |
||
187 | ): ?SourceSizeImageCandidateMatch { |
||
188 | // If there's no upper limit: Use the largest image candidate in any case |
||
189 | 5 | if ($maxWidth === null) { |
|
190 | 4 | return $this->createLargestImageCandidateMatch($sourceSize, $imageCandidates); |
|
191 | } |
||
192 | |||
193 | // Run through all image candidates for the effective minimum image, current source size and current breakpoint |
||
194 | 4 | return $this->findImageCandidateForMinImageWidth( |
|
195 | 4 | $sourceSize, |
|
196 | 4 | $imageCandidates, |
|
197 | 4 | max($sourceSize->getValue()->getValue($minWidth), $sourceSize->getValue()->getValue($maxWidth)) * $density |
|
0 ignored issues
–
show
|
|||
198 | ); |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Create and return a match for the largest image candidate |
||
203 | * |
||
204 | * @param SourceSize $sourceSize Source size |
||
205 | * @param ImageCandidateSetInterface $imageCandidates Image candidates |
||
206 | * |
||
207 | * @return SourceSizeImageCandidateMatch|null Largest image candidate match |
||
208 | */ |
||
209 | 4 | protected function createLargestImageCandidateMatch( |
|
210 | SourceSize $sourceSize, |
||
211 | ImageCandidateSetInterface $imageCandidates |
||
212 | ): ?SourceSizeImageCandidateMatch { |
||
213 | 4 | if (count($imageCandidates)) { |
|
214 | 3 | $largestImageCandidate = $imageCandidates[count($imageCandidates) - 1]; |
|
215 | |||
216 | 3 | return $this->createImageCandidateMatch($sourceSize, $largestImageCandidate); |
|
217 | } |
||
218 | |||
219 | 1 | return null; |
|
220 | } |
||
221 | |||
222 | /** |
||
223 | * Create a source size image candidate match |
||
224 | * |
||
225 | * @param SourceSize $sourceSize Matching source size |
||
226 | * @param ImageCandidateInterface $imageCandidate Image candidata |
||
227 | * |
||
228 | * @return SourceSizeImageCandidateMatch Source size image candidate match |
||
229 | */ |
||
230 | 3 | protected function createImageCandidateMatch( |
|
231 | SourceSize $sourceSize, |
||
232 | ImageCandidateInterface $imageCandidate |
||
233 | ): SourceSizeImageCandidateMatch { |
||
234 | 3 | $mediaCondition = new MediaCondition('', $sourceSize->getMediaCondition()->getValue()); |
|
235 | |||
236 | 3 | return new ImageCandidateMatch($mediaCondition, $imageCandidate); |
|
237 | } |
||
238 | |||
239 | /** |
||
240 | * Find an image candidate for a particular source size |
||
241 | * |
||
242 | * @param SourceSize $sourceSize Matching source size |
||
243 | * @param ImageCandidateSetInterface $imageCandidates Image candidates |
||
244 | * @param float $minImageWidth Minimum image width |
||
245 | * |
||
246 | * @return SourceSizeImageCandidateMatch|null Image candidate |
||
247 | */ |
||
248 | 4 | protected function findImageCandidateForMinImageWidth( |
|
249 | SourceSize $sourceSize, |
||
250 | ImageCandidateSetInterface $imageCandidates, |
||
251 | float $minImageWidth |
||
252 | ): ?SourceSizeImageCandidateMatch { |
||
253 | // Run through all image candidates |
||
254 | /** @var ImageCandidateInterface $imageCandidate */ |
||
255 | 4 | foreach ($imageCandidates as $imageCandidate) { |
|
256 | 4 | if ($imageCandidate->getValue() >= $minImageWidth) { |
|
257 | 4 | return $this->createImageCandidateMatch($sourceSize, $imageCandidate); |
|
258 | } |
||
259 | } |
||
260 | |||
261 | 1 | return null; |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * Compare and sort two source sizes against each other |
||
266 | * |
||
267 | * @param SourceSize $sourceSize1 Source size 1 |
||
268 | * @param SourceSize $sourceSize2 Source size 2 |
||
269 | * |
||
270 | * @return int Sort order |
||
271 | */ |
||
272 | 4 | protected function sortSourceSizes(SourceSize $sourceSize1, SourceSize $sourceSize2): int |
|
273 | { |
||
274 | 4 | $hasConditions1 = $sourceSize1->hasConditions(); |
|
275 | 4 | $hasConditions2 = $sourceSize2->hasConditions(); |
|
276 | |||
277 | // If one of the sources sizes doesn't have conditions: Default source size (move to the end) |
||
278 | 4 | if ($hasConditions1 !== $hasConditions2) { |
|
279 | 3 | return $hasConditions1 ? -1 : 1; |
|
280 | } |
||
281 | |||
282 | 4 | return $hasConditions1 ? $this->sortSourceSizesByWidth($sourceSize1, $sourceSize2) : 0; |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * Compare and sort two source sizes by width |
||
287 | * |
||
288 | * @param SourceSize $sourceSize1 Source size 1 |
||
289 | * @param SourceSize $sourceSize2 Source size 2 |
||
290 | * |
||
291 | * @return int Sort order |
||
292 | */ |
||
293 | 4 | protected function sortSourceSizesByWidth(SourceSize $sourceSize1, SourceSize $sourceSize2): int |
|
294 | { |
||
295 | // Sort by minimum width |
||
296 | 4 | $minWidth1 = $sourceSize1->getMediaCondition()->getMinimumWidth(); |
|
297 | 4 | $minWidth2 = $sourceSize2->getMediaCondition()->getMinimumWidth(); |
|
298 | 4 | if ($minWidth1 !== $minWidth2) { |
|
299 | 3 | return $this->sortSourceSizesByDifferingValues($minWidth1, $minWidth2); |
|
300 | } |
||
301 | |||
302 | // Sort by maximum width |
||
303 | 2 | $maxWidth1 = $sourceSize1->getMediaCondition()->getMaximumWidth(); |
|
304 | 2 | $maxWidth2 = $sourceSize2->getMediaCondition()->getMaximumWidth(); |
|
305 | 2 | if ($maxWidth1 !== $maxWidth2) { |
|
306 | 1 | return $this->sortSourceSizesByDifferingValues($maxWidth1, $maxWidth2); |
|
307 | } |
||
308 | |||
309 | // Sort by resolution |
||
310 | 1 | return $this->sortSourceSizesByResolution($sourceSize1, $sourceSize2); |
|
311 | } |
||
312 | |||
313 | /** |
||
314 | * Sort by differing values |
||
315 | * |
||
316 | * @param float|null $value1 Value 1 |
||
317 | * @param float|null $value2 Value 2 |
||
318 | * |
||
319 | * @return int Sort order |
||
320 | */ |
||
321 | 4 | protected function sortSourceSizesByDifferingValues(float $value1 = null, float $value2 = null): int |
|
322 | { |
||
323 | 4 | if ($value1 === null) { |
|
324 | 1 | return -1; |
|
325 | } |
||
326 | 4 | if ($value2 === null) { |
|
327 | 1 | return 1; |
|
328 | } |
||
329 | |||
330 | 4 | return ($value1 > $value2) ? -1 : 1; |
|
331 | } |
||
332 | |||
333 | /** |
||
334 | * Compare and sort two source sizes by resolution |
||
335 | * |
||
336 | * @param SourceSize $sourceSize1 Source size 1 |
||
337 | * @param SourceSize $sourceSize2 Source size 2 |
||
338 | * |
||
339 | * @return int Sort order |
||
340 | */ |
||
341 | 1 | protected function sortSourceSizesByResolution(SourceSize $sourceSize1, SourceSize $sourceSize2): int |
|
342 | { |
||
343 | // Sort by minimum resolution |
||
344 | 1 | $minResolution1 = $sourceSize1->getMediaCondition()->getMinimumResolution(); |
|
345 | 1 | $minResolution2 = $sourceSize2->getMediaCondition()->getMinimumResolution(); |
|
346 | 1 | if ($minResolution1 !== $minResolution2) { |
|
347 | 1 | return $this->sortSourceSizesByDifferingValues($minResolution1, $minResolution2); |
|
348 | } |
||
349 | |||
350 | // Sort by maximum resolution |
||
351 | 1 | $maxResolution1 = $sourceSize1->getMediaCondition()->getMaximumResolution(); |
|
352 | 1 | $maxResolution2 = $sourceSize2->getMediaCondition()->getMaximumResolution(); |
|
353 | 1 | if ($maxResolution1 !== $maxResolution2) { |
|
354 | 1 | return $this->sortSourceSizesByDifferingValues($maxResolution1, $maxResolution2); |
|
355 | } |
||
356 | |||
357 | 1 | return 0; |
|
358 | } |
||
359 | } |
||
360 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.