# jch-optimize/trunk/lib/src/ImageAttributes.php

JCH Optimize, version trunk. 217 lines.

- Page: https://pluginprobe.com/plugins/jch-optimize/trunk/code/lib/src/ImageAttributes.php
- Raw: https://pluginprobe.com/plugins/jch-optimize/trunk/raw/lib/src/ImageAttributes.php
- Modified: 2026-07-04T02:31:50+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/jch-optimize/trunk/code/lib/src/ImageAttributes.php#L10-L20`.

```php
<?php

/**
 * JCH Optimize - Performs several front-end optimizations for fast downloads
 *
 * @package   jchoptimize/joomla-platform
 * @author    Samuel Marshall <samuel@jch-optimize.net>
 * @copyright Copyright (c) 2024 Samuel Marshall / JCH Optimize
 * @license   GNU/GPLv3, or later. See LICENSE file
 *
 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
 */

namespace JchOptimize\Core;

use _JchOptimizeVendor\V91\GuzzleHttp\Psr7\UriResolver;
use _JchOptimizeVendor\V91\Laminas\EventManager\Event;
use _JchOptimizeVendor\V91\Psr\Http\Message\UriInterface;
use JchOptimize\Core\Exception\PregErrorException;
use JchOptimize\Core\Exception\RuntimeException;
use JchOptimize\Core\Html\Elements\Img;
use JchOptimize\Core\Html\HtmlElementBuilder;
use JchOptimize\Core\Html\HtmlManager;
use JchOptimize\Core\Platform\PathsInterface;
use JchOptimize\Core\SystemUri\SystemUri;
use JchOptimize\Core\Uri\AttributeClassifier;
use JchOptimize\Core\Uri\OriginClassifier;
use JchOptimize\Core\Uri\UriConverter;
use JchOptimize\Core\Uri\Utils;

class ImageAttributes
{
    private ?Img $currentImgObj = null;

    private array $imageSize = [];

    private bool $isImageAttrEnabled;

    public function __construct(
        Registry $params,
        private OriginClassifier $classifier,
        private PathsInterface $pathsUtils
    ) {
        $this->isImageAttrEnabled = (bool)$params->get('img_attributes_enable', '0');
    }

    public function getImageAttributes(array $images): array
    {
        $imageAttributes = [];

        foreach ($images[0] as $image) {
            if ($this->validateImage($image) === false) {
                $imageAttributes[] = $image;
                continue;
            }

            $imageAttributes[] = $this->addWidthHeightAttributes();

            $this->currentImgObj = null;
            $this->imageSize = [];
        }

        return $imageAttributes;
    }

    private function validateImage(mixed $image): bool
    {
        try {
            $imgObj = HtmlElementBuilder::load($image);
        } catch (PregErrorException) {
            return false;
        }

        if ($imgObj instanceof Img) {
            $this->currentImgObj = $imgObj;
        }

        $uri = $this->getUriFromImageObj($this->currentImgObj);

        if (!$uri instanceof UriInterface) {
            return false;
        }

        $uri = UriResolver::resolve(SystemUri::currentUri(), $uri);

        if (!$this->classifier->isInternal($uri)) {
            return false;
        }

        $imagePath = UriConverter::uriToFilePath($uri, $this->pathsUtils);

        if (!is_file($imagePath)) {
            return false;
        }

        $imageSize = getimagesize($imagePath);

        if (empty($imageSize) || !is_array($imageSize) || $imageSize[0] <= 1 || $imageSize[1] <= 1) {
            return false;
        }

        $this->imageSize = $imageSize;

        return true;
    }

    private function getUriFromImageObj(Img $imgObj): UriInterface|string|false
    {
        if ($imgObj->hasAttribute('src') && ($src = $imgObj->getSrc()) instanceof UriInterface) {
            return $src;
        } elseif ($imgObj->hasAttribute('data-src')) {
            $dataSrc = $imgObj->attributeValue('data-src');

            if (AttributeClassifier::isUriCandidate($dataSrc)) {
                return Utils::uriFor($dataSrc);
            }
        }

        return false;
    }

    private function addWidthHeightAttributes(): string
    {
        if ($this->currentImgObj->hasAttribute('width')) {
            try {
                return $this->addHeightAttributeFromWidth();
            } catch (RuntimeException $e) {
            }
        }

        if ($this->currentImgObj->hasAttribute('height')) {
            try {
                return $this->addWidthAttributeFromHeight();
            } catch (RuntimeException $e) {
            }
        }

        if ($this->isImageAttrEnabled) {
            $this->currentImgObj->width($this->imageSize[0]);
            $this->currentImgObj->height($this->imageSize[1]);
        } else {
            $this->currentImgObj->data('width', $this->imageSize[0]);
            $this->currentImgObj->data('height', $this->imageSize[1]);
        }

        return $this->currentImgObj->render();
    }

    private function addAttrFromExistingAttr(string $existingAttr): string
    {
        $existingAttrValue = $this->currentImgObj->attributeValue($existingAttr);

        if ($existingAttrValue !== false) {
            $existingAttrValue = $this->cleanAttrValue($existingAttrValue, $existingAttr);

            if ($existingAttrValue) {
                return $this->addOtherAttrByAspectRatio($existingAttrValue, $existingAttr);
            }
        }

        throw new RuntimeException('Attribute not found');
    }

    private function cleanAttrValue(mixed $value, $existingAttr): string
    {
        $cleanedAttrValue = (string)preg_replace('#[^0-9]#', '', $value, -1, $count);

        if ($count > 0) {
            $this->currentImgObj->attribute($existingAttr, $cleanedAttrValue);
        }

        return $cleanedAttrValue;
    }

    private function addHeightAttributeFromWidth(): string
    {
        return $this->addAttrFromExistingAttr('width');
    }

    private function addWidthAttributeFromHeight(): string
    {
        return $this->addAttrFromExistingAttr('height');
    }

    private function addOtherAttrByAspectRatio(string $existingAttrValue, string $existingAttr): string
    {
        if ($existingAttr == 'width') {
            $height = (string)round(
                ($this->imageSize[1] / $this->imageSize[0]) * (int)$existingAttrValue
            );
            $this->isImageAttrEnabled
                ? $this->currentImgObj->height($height) : $this->currentImgObj->data('height', $height);
        } else {
            $width = (string)round(
                ($this->imageSize[0] / $this->imageSize[1]) * (int)$existingAttrValue
            );
            $this->isImageAttrEnabled
                ? $this->currentImgObj->width($width) : $this->currentImgObj->data('width', $width);
        }

        return $this->currentImgObj->render();
    }

    public function loadImageAttributesCss(Event $event): void
    {
        if ($this->isImageAttrEnabled) {
            $style = HtmlElementBuilder::style()
                ->class('jchoptimize-image-attributes')
                ->addChild('img{max-width: 100%; height: auto;}');

            /** @var HtmlManager $htmlManager */
            $htmlManager = $event->getTarget();
            $htmlManager->appendChildToHead($style->render());
        }
    }
}

```
