PluginProbe
JCH Optimize / trunk
JCH Optimize vtrunk
6.0.2 6.0.1 trunk 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.10.0 2.2.0 2.2.1 All 58 releases
jch-optimize / lib / src / ImageAttributes.php

ImageAttributes.php in JCH Optimize trunk, at lib/src/ImageAttributes.php

217 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * JCH Optimize - Performs several front-end optimizations for fast downloads
5 *
6 * @package jchoptimize/joomla-platform
7 * @author Samuel Marshall <samuel@jch-optimize.net>
8 * @copyright Copyright (c) 2024 Samuel Marshall / JCH Optimize
9 * @license GNU/GPLv3, or later. See LICENSE file
10 *
11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
12 */
13
14 namespace JchOptimize\Core;
15
16 use _JchOptimizeVendor\V91\GuzzleHttp\Psr7\UriResolver;
17 use _JchOptimizeVendor\V91\Laminas\EventManager\Event;
18 use _JchOptimizeVendor\V91\Psr\Http\Message\UriInterface;
19 use JchOptimize\Core\Exception\PregErrorException;
20 use JchOptimize\Core\Exception\RuntimeException;
21 use JchOptimize\Core\Html\Elements\Img;
22 use JchOptimize\Core\Html\HtmlElementBuilder;
23 use JchOptimize\Core\Html\HtmlManager;
24 use JchOptimize\Core\Platform\PathsInterface;
25 use JchOptimize\Core\SystemUri\SystemUri;
26 use JchOptimize\Core\Uri\AttributeClassifier;
27 use JchOptimize\Core\Uri\OriginClassifier;
28 use JchOptimize\Core\Uri\UriConverter;
29 use JchOptimize\Core\Uri\Utils;
30
31 class ImageAttributes
32 {
33 private ?Img $currentImgObj = null;
34
35 private array $imageSize = [];
36
37 private bool $isImageAttrEnabled;
38
39 public function __construct(
40 Registry $params,
41 private OriginClassifier $classifier,
42 private PathsInterface $pathsUtils
43 ) {
44 $this->isImageAttrEnabled = (bool)$params->get('img_attributes_enable', '0');
45 }
46
47 public function getImageAttributes(array $images): array
48 {
49 $imageAttributes = [];
50
51 foreach ($images[0] as $image) {
52 if ($this->validateImage($image) === false) {
53 $imageAttributes[] = $image;
54 continue;
55 }
56
57 $imageAttributes[] = $this->addWidthHeightAttributes();
58
59 $this->currentImgObj = null;
60 $this->imageSize = [];
61 }
62
63 return $imageAttributes;
64 }
65
66 private function validateImage(mixed $image): bool
67 {
68 try {
69 $imgObj = HtmlElementBuilder::load($image);
70 } catch (PregErrorException) {
71 return false;
72 }
73
74 if ($imgObj instanceof Img) {
75 $this->currentImgObj = $imgObj;
76 }
77
78 $uri = $this->getUriFromImageObj($this->currentImgObj);
79
80 if (!$uri instanceof UriInterface) {
81 return false;
82 }
83
84 $uri = UriResolver::resolve(SystemUri::currentUri(), $uri);
85
86 if (!$this->classifier->isInternal($uri)) {
87 return false;
88 }
89
90 $imagePath = UriConverter::uriToFilePath($uri, $this->pathsUtils);
91
92 if (!is_file($imagePath)) {
93 return false;
94 }
95
96 $imageSize = getimagesize($imagePath);
97
98 if (empty($imageSize) || !is_array($imageSize) || $imageSize[0] <= 1 || $imageSize[1] <= 1) {
99 return false;
100 }
101
102 $this->imageSize = $imageSize;
103
104 return true;
105 }
106
107 private function getUriFromImageObj(Img $imgObj): UriInterface|string|false
108 {
109 if ($imgObj->hasAttribute('src') && ($src = $imgObj->getSrc()) instanceof UriInterface) {
110 return $src;
111 } elseif ($imgObj->hasAttribute('data-src')) {
112 $dataSrc = $imgObj->attributeValue('data-src');
113
114 if (AttributeClassifier::isUriCandidate($dataSrc)) {
115 return Utils::uriFor($dataSrc);
116 }
117 }
118
119 return false;
120 }
121
122 private function addWidthHeightAttributes(): string
123 {
124 if ($this->currentImgObj->hasAttribute('width')) {
125 try {
126 return $this->addHeightAttributeFromWidth();
127 } catch (RuntimeException $e) {
128 }
129 }
130
131 if ($this->currentImgObj->hasAttribute('height')) {
132 try {
133 return $this->addWidthAttributeFromHeight();
134 } catch (RuntimeException $e) {
135 }
136 }
137
138 if ($this->isImageAttrEnabled) {
139 $this->currentImgObj->width($this->imageSize[0]);
140 $this->currentImgObj->height($this->imageSize[1]);
141 } else {
142 $this->currentImgObj->data('width', $this->imageSize[0]);
143 $this->currentImgObj->data('height', $this->imageSize[1]);
144 }
145
146 return $this->currentImgObj->render();
147 }
148
149 private function addAttrFromExistingAttr(string $existingAttr): string
150 {
151 $existingAttrValue = $this->currentImgObj->attributeValue($existingAttr);
152
153 if ($existingAttrValue !== false) {
154 $existingAttrValue = $this->cleanAttrValue($existingAttrValue, $existingAttr);
155
156 if ($existingAttrValue) {
157 return $this->addOtherAttrByAspectRatio($existingAttrValue, $existingAttr);
158 }
159 }
160
161 throw new RuntimeException('Attribute not found');
162 }
163
164 private function cleanAttrValue(mixed $value, $existingAttr): string
165 {
166 $cleanedAttrValue = (string)preg_replace('#[^0-9]#', '', $value, -1, $count);
167
168 if ($count > 0) {
169 $this->currentImgObj->attribute($existingAttr, $cleanedAttrValue);
170 }
171
172 return $cleanedAttrValue;
173 }
174
175 private function addHeightAttributeFromWidth(): string
176 {
177 return $this->addAttrFromExistingAttr('width');
178 }
179
180 private function addWidthAttributeFromHeight(): string
181 {
182 return $this->addAttrFromExistingAttr('height');
183 }
184
185 private function addOtherAttrByAspectRatio(string $existingAttrValue, string $existingAttr): string
186 {
187 if ($existingAttr == 'width') {
188 $height = (string)round(
189 ($this->imageSize[1] / $this->imageSize[0]) * (int)$existingAttrValue
190 );
191 $this->isImageAttrEnabled
192 ? $this->currentImgObj->height($height) : $this->currentImgObj->data('height', $height);
193 } else {
194 $width = (string)round(
195 ($this->imageSize[0] / $this->imageSize[1]) * (int)$existingAttrValue
196 );
197 $this->isImageAttrEnabled
198 ? $this->currentImgObj->width($width) : $this->currentImgObj->data('width', $width);
199 }
200
201 return $this->currentImgObj->render();
202 }
203
204 public function loadImageAttributesCss(Event $event): void
205 {
206 if ($this->isImageAttrEnabled) {
207 $style = HtmlElementBuilder::style()
208 ->class('jchoptimize-image-attributes')
209 ->addChild('img{max-width: 100%; height: auto;}');
210
211 /** @var HtmlManager $htmlManager */
212 $htmlManager = $event->getTarget();
213 $htmlManager->appendChildToHead($style->render());
214 }
215 }
216 }
217