Your IP : 18.117.152.51


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/dev2fun.imagecompress/lib/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/dev2fun.imagecompress/lib/Convert.php

<?php
/**
 * @author darkfriend <hi@darkfriend.ru>
 * @copyright dev2fun
 * @version 0.8.6
 */

namespace Dev2fun\ImageCompress;

use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Config\Option;
use Dev2funImageCompress;

IncludeModuleLangFile(__FILE__);

class Convert
{
    const HIT_CONVERT = 'hitConvert';
    const POST_CONVERT = 'postConvert';
    const LAZY_CONVERT = 'lazyConvert';
    const TYPE_WEBP = 'webp';
    const TYPE_AVIF = 'avif';

    /** @var string */
    private $MODULE_ID = 'dev2fun.imagecompress';
    /** @var string */
    public $LAST_ERROR;
    /** @var string[] support tag attributes */
    public $supportAttrs = [];
    /** @var string[]  */
    public $convertMode = [];
    /** @var int */
    public $cacheTime = 3600;
    /** @var string */
    public $algorithm = 'phpWebp';

    public static $supportContentType = [
        'image/jpeg',
        'image/png',
        'image/svg',
    ];

    public static $convertModes = [
        self::HIT_CONVERT,
        self::POST_CONVERT,
        self::LAZY_CONVERT,
    ];

    public static $convertClasses = [
        'cwebp' => '\Dev2fun\ImageCompress\Webp',
        'phpWebp' => '\Dev2fun\ImageCompress\WebpConvertPhp',
        'phpAvif' => '\Dev2fun\ImageCompress\AvifConvertPhp',
        'imagickAvif' => '\Dev2fun\ImageCompress\AvifConvertImagick',
    ];

    /** @var self */
    private static $instance;
    /** @var bool global state */
    public static $globalEnable = true;
    /** @var null|array */
    public static $domains = null;
    /** @var bool */
    public $enable = false;
    /** @var int */
    public $convertPerPage = 500;
    /** @var int */
    public $cacheTimeFindImages = -1;
    /** @var int */
    public $cacheTimeGetImages = -1;
    /** @var bool */
    public $cacheIncludeUserGroups = true;

    public $siteId;

    /**
     * @param string|null $siteId
     */
    public function __construct(?string $siteId = null)
    {
        if (!$siteId) {
            $siteId = \Dev2funImageCompress::getSiteId();
        }
        $this->siteId = $siteId;

        $this->enable = Option::get($this->MODULE_ID, 'convert_enable', 'N', $siteId) === 'Y';
        $supportAttrs = Option::get($this->MODULE_ID, 'convert_attributes', [], $siteId);
        if($supportAttrs) {
            $supportAttrs = \unserialize($supportAttrs, ['allowed_classes' => false]);
        }
        $this->supportAttrs = $supportAttrs;

        $convertMode = Option::get($this->MODULE_ID, 'convert_mode', [], $siteId);
        if($convertMode) {
            $convertMode = \unserialize($convertMode, ['allowed_classes' => false]);
        } else {
            $convertMode = [self::POST_CONVERT];
        }
        $this->convertMode = $convertMode;

        $this->algorithm = Option::get($this->MODULE_ID, 'convert_algorithm', 'phpWebp', $siteId);

        $this->cacheTime = Option::get($this->MODULE_ID, 'cache_time', 3600, $siteId);

        if (\in_array('lazyConvert', $this->convertMode)) {
            $this->convertPerPage = Option::get($this->MODULE_ID, 'convert_per_page', 500);
            $this->cacheTimeFindImages = Option::get($this->MODULE_ID, 'convert_cache_time_find_images', 3600*24);
            $this->cacheTimeGetImages = Option::get($this->MODULE_ID, 'convert_cache_time_get_images', 3600);
            $this->cacheIncludeUserGroups = Option::get($this->MODULE_ID, 'convert_cache_include_user_groups', 'Y') === 'Y';
        }
    }

    /**
     * @static
     * @return self
     */
    public static function getInstance()
    {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }

    /**
     * Get algorithm class
     * @param string $algorithm
     * @return Webp|WebpConvertPhp|AvifConvertImagick|AvifConvertPhp
     * @throws \ErrorException
     */
    public static function getAlgInstance(string $algorithm)
    {
        switch ($algorithm) {
            case 'cwebp':
                $obj = Webp::getInstance();
                break;
            case 'phpWebp':
                $obj = WebpConvertPhp::getInstance();
                break;
            case 'imagickAvif':
                $obj = AvifConvertImagick::getInstance();
                break;
            case 'phpAvif':
                $obj = AvifConvertPhp::getInstance();
                break;
            default:
                throw new \ErrorException("Not found algorithm \"{$algorithm}\"");
        }

        return $obj;
//        return self::$optiClasses[$algorithm]::getInstance(); // PHP7+
    }

    /**
     * @param string $algorithm
     * @return string
     */
    public function getImageTypeByAlgorithm(string $algorithm): string
    {
        switch ($algorithm) {
            case 'cwebp':
            case 'phpWebp':
                return 'webp';
            case 'imagickAvif':
            case 'phpAvif':
                return 'avif';
        }

        return '';
    }

    /**
     * Get exclude pages
     * @param string|null $siteId
     * @return array
     */
    public static function getSettingsExcludePage(?string $siteId = null)
    {
        if (!$siteId) {
            $siteId = Dev2funImageCompress::getSiteId();
        }
        $pages = Option::get(\Dev2funImageCompress::MODULE_ID, 'exclude_pages', '', $siteId);
        if ($pages) {
            $pages = \json_decode($pages, true);
        } else {
            $pages = [];
        }
        if (!in_array('#(\/bitrix\/.*)#', $pages)) {
            array_unshift($pages, '#(\/bitrix\/.*)#');
        }
        return $pages;
    }

    /**
     * Get exclude files
     * @param string|null $siteId
     * @return array
     */
    public static function getSettingsExcludeFiles(?string $siteId = null)
    {
        if (!$siteId) {
            $siteId = Dev2funImageCompress::getSiteId();
        }
        $files = Option::get(\Dev2funImageCompress::MODULE_ID, 'exclude_files', '', $siteId);
        if ($files) {
            $files = \json_decode($files, true);
        } else {
            $files = [];
        }
        return $files;
    }

    /**
     * Save exclude pages
     * @param array $sFields
     * @param string $siteId
     * @return bool
     * @throws \Bitrix\Main\ArgumentOutOfRangeException
     */
    public static function saveSettingsExcludePage(array $sFields = [], string $siteId = 's1')
    {
        if($sFields) {
            $sFields = array_unique($sFields);
            foreach ($sFields as $key => $field) {
                if (empty($field)) {
                    unset($sFields[$key]);
                }
            }
        } elseif(!\is_array($sFields)) {
            $sFields = [];
        }
        Option::set(
            \Dev2funImageCompress::MODULE_ID,
            'exclude_pages',
            \json_encode(\array_values($sFields)),
            $siteId
        );
        return true;
    }

    /**
     * Check page on exclude
     * @return bool
     */
    public static function isExcludePage(): bool
    {
        global $APPLICATION;
        $arExcluded = self::getSettingsExcludePage();
        if($arExcluded) {
            $curPage = $APPLICATION->GetCurPage();
            if ($curPage === '/') {
                $curPage = 'index.php';
            }
            if (in_array(ltrim($curPage, '/'), $arExcluded)) {
                return true;
            }
            foreach ($arExcluded as $exc) {
                if (preg_match($exc, $curPage)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check page on exclude
     * @param string $file
     * @return bool
     */
    public static function isExcludeFile($file): bool
    {
        $file = ltrim($file, '/');
        $arExcluded = self::getSettingsExcludeFiles();

        $arExcludedRegExp = array_filter($arExcluded, function($item) {
            return strpos($item, '#') === 0;
        });

        foreach ($arExcludedRegExp as $item) {
            if (preg_match($item, $file)) {
                return true;
            }
        }

        return in_array($file, $arExcluded);
    }

    /**
     * Save exclude files
     * @param array $sFields
     * @param string $siteId
     * @return void
     * @throws \Bitrix\Main\ArgumentOutOfRangeException
     */
    public static function saveSettingsExcludeFile(array $sFields = [], string $siteId = 's1')
    {
        if(!$sFields || !is_array($sFields)) {
            return;
        }
        foreach ($sFields as $key => $field) {
            if (empty($field)) {
                unset($sFields[$key]);
            }
        }
        Option::set(
            \Dev2funImageCompress::MODULE_ID,
            'exclude_files',
            json_encode(array_values($sFields)),
            $siteId
        );
    }

    /**
     * Start process convert
     * @param array $arFile
     * @param array $options
     * @return bool|null|string
     */
    public function process(array $arFile, array $options=[])
    {
        if(!static::$globalEnable || !$this->enable) {
            return false;
        }
        $res = false;

        $event = new \Bitrix\Main\Event($this->MODULE_ID, "OnBeforeConvertImage", [&$arFile]);
        $event->send();
        if($arFile === false) {
            return false;
        }

        if(!static::checkWebpSupport()) {
            return false;
        }

        if (!\in_array($arFile["CONTENT_TYPE"], static::$supportContentType)) {
            return false;
        }

        $alg = Option::get($this->MODULE_ID, 'convert_algorithm', 'phpWebp', $this->siteId);
        $algInstance = static::getAlgInstance($alg);
        if (!$algInstance->isOptim()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => $alg]);
            return $res;
        }

        $upload_dir = Option::get('main', 'upload_dir', 'upload');
        $res = "/$upload_dir/{$arFile["SUBDIR"]}/{$arFile["FILE_NAME"]}";

        // исключение файла из списка исключений
        if (static::isExcludeFile($res)) {
            return false;
        }

        $strFilePath = $_SERVER["DOCUMENT_ROOT"] . $res;
        if (!\is_file($strFilePath)) {
            return false;
        }

        //        $upload_dir = Option::get('main', 'upload_dir', 'upload');
        //        $src = "{$_SERVER["DOCUMENT_ROOT"]}/$upload_dir/{$arFile["SUBDIR"]}/{$arFile["FILE_NAME"]}";
        //        $srcWebp = "/{$upload_dir}/resize_cache/webp/{$arFile["SUBDIR"]}/{$arFile['FILE_NAME']}.webp";

        return $algInstance->convert(
            $arFile,
            \array_merge(
                [
                    'changeChmod' => $this->getChmod(
                        Option::get($this->MODULE_ID, 'change_chmod', 777)
                    ),
                ],
                $options
            )
        );
    }

    /**
     * @param string[] $arFiles
     * @param array $options
     * @return array|false
     * @throws \Bitrix\Main\ArgumentNullException
     * @throws \Bitrix\Main\ArgumentOutOfRangeException
     * @throws \ErrorException
     */
    public function postProcess(array $arFiles, array $options=[])
    {
        if(!static::$globalEnable || !$this->enable || !static::checkWebpSupport()) {
            return false;
        }

        $arFilesReplace = [];
        foreach ($arFiles as $file) {
            $resFile = $this->convertFile($file, $options);
            if (!$resFile) {
                continue;
            }
            $arFilesReplace[$file] = $resFile;
        }

        return $arFilesReplace;
    }

    /**
     * @param string $file
     * @param array $options
     * @return bool|string|null
     * @throws \ErrorException
     */
    public function convertFile(string $file, array $options = [])
    {
        $alg = Option::get($this->MODULE_ID, 'convert_algorithm', 'phpWebp', $this->siteId);
        $algInstance = static::getAlgInstance($alg);

        if (!$algInstance->isOptim()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => $alg]);
            return false;
        }

        if($file) {
            $fileScheme = \parse_url($file, \PHP_URL_SCHEME);
            if($fileScheme==='data') {
                return null;
            }
        }

        $event = new \Bitrix\Main\Event($this->MODULE_ID, "OnBeforePostConvertImage", [&$file]);
        $event->send();

        if(!$file) {
            return null;
        }

        // исключение файла из списка исключений
        if (static::isExcludeFile($file)) {
            return null;
        }

        $absFile = "{$_SERVER["DOCUMENT_ROOT"]}{$file}";
        if(!\is_file($absFile)) {
            return null;
        }
        $fileInfo = \pathinfo($absFile);
        $arFile = [
            'CONTENT_TYPE' => \mime_content_type($absFile),
            'SUBDIR' => \str_replace($_SERVER["DOCUMENT_ROOT"], '', $fileInfo['dirname']),
            'FILE_NAME' => $fileInfo['basename'],
            'ABS_PATH' => $absFile,
        ];

        if (!\in_array($arFile["CONTENT_TYPE"], static::$supportContentType)) {
            return null;
        }
        if (!\is_file($absFile)) {
            return null;
        }
        return $algInstance->convert(
            $arFile,
            \array_merge(
                [
                    'changeChmod' => $this->getChmod(
                        Option::get($this->MODULE_ID, 'change_chmod', 777)
                    ),
                ],
                $options
            )
        );
    }

    /**
     * Событие на ресайзе (OnAfterResizeImage)
     * @param array $arFile
     * @param array $arInfo
     * @param array $callbackData
     * @param string $cacheImageFile
     * @param string $cacheImageFileTmp
     * @param array $arImageSize
     * @return bool
     * @throws \ErrorException
     */
    public static function CompressImageCacheOnConvertEvent(
        $arFile,
        $arInfo,
        &$callbackData,
        &$cacheImageFile,
        &$cacheImageFileTmp,
        &$arImageSize
    )
    {
        if(
            !static::$globalEnable
            || !self::getInstance()->enable
            || static::isExcludePage()
            || !static::checkWebpSupport()
        ) {
            return false;
        }

        $urlFile = \parse_url($cacheImageFile);
        $uploadDir = Option::get('main', 'upload_dir', 'upload');

        $urlFile['path'] = \str_replace("/$uploadDir/",'',$urlFile['path']);
        $urlFile['path'] = \str_replace("/{$arFile['FILE_NAME']}",'',$urlFile['path']);

        $arFileConvert = [
            'CONTENT_TYPE' => $arFile['CONTENT_TYPE'],
            'SUBDIR' => $urlFile['path'],
            'FILE_NAME' => $arFile['FILE_NAME'],
        ];

        $event = new \Bitrix\Main\Event(self::getInstance()->MODULE_ID, "OnBeforeConvertImageResize", [&$arFileConvert]);
        $event->send();
        if($arFile === false) {
            return false;
        }

        // исключение файла из списка исключений
        $resFile = "/$uploadDir/{$arFile["SUBDIR"]}/{$arFile["FILE_NAME"]}";
        if (static::isExcludeFile($resFile)) {
            return false;
        }

        // исключение файла из-за типа файла
        if (!\in_array($arFileConvert["CONTENT_TYPE"], static::$supportContentType)) {
            return false;
        }

        $alg = Option::get(self::getInstance()->MODULE_ID, 'convert_algorithm', 'phpWebp', \Dev2funImageCompress::getSiteId());
        $algInstance = static::getAlgInstance($alg);
        if (!$algInstance->isOptim()) {
            return false;
        }

        $strFilePath = $_SERVER["DOCUMENT_ROOT"] . $resFile;
        if (!\is_file($strFilePath)) {
            return false;
        }

        $webpPath = $algInstance->convert(
            $arFileConvert,
            [
                'changeChmod' => self::getInstance()->getChmod(
                    Option::get(self::getInstance()->MODULE_ID, 'change_chmod', 777)
                ),
            ]
        );

        if($webpPath) {
            $cacheImageFile = $webpPath;
            return true;
        }

        return false;
    }

    /**
     * Compress image by fileID
     * @param int $intFileID
     * @return bool|null|string
     */
    public function convertImageByID(int $intFileID)
    {
        if(
            !self::$globalEnable
            || !self::getInstance()->enable
            || !\in_array('hitConvert', self::getInstance()->convertMode)
        ) {
            return false;
        }

        if(!$intFileID) {
            return null;
        }

        // исключение страницы из списка исключений
        if (self::isExcludePage()) {
            return false;
        }

        $arFile = \CFile::GetByID($intFileID)->GetNext();

        //        if ($this->enableImageResize) {
        //            $this->resize($intFileID, $strFilePath);
        //        }

        return $this->process($arFile);
    }

    /**
     * Resize image file
     * @param int $fileId
     * @param string $strFilePath
     * @return bool
     * @deprecated
     */
    public function resize(int $fileId, string $strFilePath): bool
    {
        if(!static::$globalEnable || !$strFilePath || !static::getInstance()->enable) {
            return false;
        }

        $width = Option::get($this->MODULE_ID, 'resize_image_width', '', $this->siteId);
        $height = Option::get($this->MODULE_ID, 'resize_image_height', '', $this->siteId);
        $algorithm = Option::get($this->MODULE_ID, 'resize_image_algorithm', '', $this->siteId);
        if (!$algorithm) $algorithm = BX_RESIZE_IMAGE_PROPORTIONAL;

        $destinationFile = $_SERVER['DOCUMENT_ROOT'] . "/upload/{$this->MODULE_ID}/" . basename($strFilePath);
        $res = \CFile::ResizeImageFile(
            $strFilePath,
            $destinationFile,
            [
                'width' => $width,
                'height' => $height,
            ],
            $algorithm
        );
        if ($res) {
            \chmod($destinationFile, 0777);
            \copy($destinationFile, $strFilePath);
//            Compress::getInstance()->saveWidthHeight($fileId, $strFilePath);
//            unlink($destinationFile);
        }
        return $res;
    }

    /**
     * Handler for OnGetFileSRC
     * @param array $arFile
     * @return bool|null
     */
    public static function CompressImageOnConvertEvent(array $arFile)
    {
        if(
            !static::$globalEnable
            || !static::getInstance()->enable
            || !\in_array('hitConvert', static::getInstance()->convertMode)
        ) {
            return false;
        }

        if(self::isExcludePage()) {
            return false;
        }

        return self::getInstance()->process($arFile);
    }

    /**
     * @return string
     */
    public function getSupportAttributesString(): string
    {
        return $this->supportAttrs
            ? \trim(\implode('|', $this->supportAttrs)).'|'
            : '';
    }

    /**
     * Handler for post converter
     * @param string $content
     * @return bool|null
     */
    public static function PostConverterEvent(string &$content)
    {
        global $APPLICATION, $USER;
        if(!$content) {
            return $content;
        }

        if(
            !self::$globalEnable
            || !static::getInstance()->enable
            || (
                !\in_array('postConvert', self::getInstance()->convertMode)
                && !\in_array('lazyConvert', self::getInstance()->convertMode)
            )
        ) {
            return $content;
        }

        if(self::isExcludePage()) {
            return $content;
        }

        $curUri = $APPLICATION->GetCurUri();
        $userGroups = 'guest';
        $includeUserGroups = self::getInstance()->cacheIncludeUserGroups;
        if ($includeUserGroups && $USER->IsAuthorized()) {
            $userGroups = $USER->GetGroups();
        }

        $cacheId = [
            'v0.1.0',
            'scanImages',
            self::getInstance()->convertMode,
            \Dev2funImageCompress::getSiteId(),
            $userGroups,
            $curUri,
        ];
        $cacheId = implode('|',$cacheId);
        $arFiles = LazyConvert::cache(
        //                3600*1,
            self::getInstance()->cacheTimeGetImages,
            $cacheId,
            '/scan-images',
            function() use ($content) {
                $arFiles = [];
                \preg_match_all('/([^"\'=\s]+\.(?:jpe?g|png))/mi', $content, $matchInlineImages);
                if(!empty($matchInlineImages[1])) {
                    $arFiles = $matchInlineImages[1];
                }

                \preg_match_all('/url\(([^"\'=\s]+\.(?:jpe?g|png))\)/mi', $content, $matchInlineImages);
                if(!empty($matchInlineImages[1])) {
                    $arFiles = \array_merge(
                        $arFiles,
                        $matchInlineImages[1]
                    );
                }

                if ($arFiles) {
                    $arFiles = \array_unique($arFiles);
                }

                if (\in_array(self::LAZY_CONVERT, self::getInstance()->convertMode)) {
                    foreach ($arFiles as $kFile => &$file) {
                        $preparedFile = self::getNormalizePathFile($file);
                        if ($preparedFile === null) {
                            unset($arFiles[$kFile]);
                            continue;
                        }
                        if ($file !== $preparedFile) {
                            $file = $preparedFile;
                        }
                    }
                    unset($file);
                }

                return $arFiles;
            }
        );

        if (!$arFiles) {
            return $content;
        }

        if (\in_array(self::LAZY_CONVERT, self::getInstance()->convertMode)) {

            $jsonFiles = json_encode($arFiles);
            $cacheId = [
                'v0.6.10',
                'findImages',
                $jsonFiles,
            ];
            $cacheId = implode('|',$cacheId);
            LazyConvert::cache(
                self::getInstance()->cacheTimeFindImages,
                    $cacheId,
                '/find-images',
                function () use ($arFiles) {
                    $currentFiles = LazyConvert::findFiles($arFiles);
//                    DebugHelper::print_pre($currentFiles, 1);
                    $connection = \Bitrix\Main\Application::getInstance()->getConnection();
                    $rows = [];
                    foreach ($arFiles as $file) {
                        $isUrl = !empty(parse_url($file, PHP_URL_HOST));
                        if ($isUrl) {
                            $md5 = md5_file($file);
                        } else {
                            $md5 = md5_file($_SERVER['DOCUMENT_ROOT'].$file);
                        }

                        if (empty($currentFiles[$md5])) {
                            $rows[] = [
//                                'SITE_ID' => \Dev2funImageCompress::getSiteId(),
                                'IMAGE_PATH' => $file,
                                'IMAGE_HASH' => $md5,
                                'DATE_CREATE' => new SqlExpression("NOW()"),
                                'IMAGE_IGNORE' => 'N',
                                //                            'IMAGE_PROCESSED' => 'N',
                            ];
                        }
                    }
                    $sql = MySqlHelper::getInsertIgnoreMulti(
                        ImageCompressImagesTable::getTableName(),
                        $rows
                    );
//                    $connection->getSqlHelper()->prepareMerge(
//                        ImageCompressImagesTable::getTableName(),
//                        [],
//                        $rows,
//                    );
                    $connection->queryExecute($sql);
                    return true;
                },
                false
            );

            if (!static::checkWebpSupport()) {
                return $content;
            }

            $cacheId = [
                'v0.1.5',
                'getImages',
                $jsonFiles,
            ];
            $cacheId = implode('|',$cacheId);
            $arFileReplace = LazyConvert::cache(
//                3600*1,
                self::getInstance()->cacheTimeGetImages,
                $cacheId,
                '/get-images',
                function () use ($arFiles) {
                    $filter = [
//                        'IMAGE_PROCESSED' => 'Y',
                        'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.IMAGE_PROCESSED' => 'Y',
                        'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.IMAGE_TYPE' => self::getInstance()->getImageTypeByAlgorithm(
                            self::getInstance()->algorithm
                        ),
                        'IMAGE_IGNORE' => 'N',
                    ];
                    $imagesHash = [];
                    foreach ($arFiles as $file) {
                        $isUrl = !empty(parse_url($file, PHP_URL_HOST));
                        if ($isUrl) {
                            $imagesHash[] = md5_file($file);
                        } else {
                            $imagesHash[] = md5_file($_SERVER['DOCUMENT_ROOT'].$file);
                        }
//                        $imagesHash[] = md5_file($_SERVER['DOCUMENT_ROOT'].$file);
//                        $rows[] = [
//                            'SITE_ID' => \Dev2funImageCompress::getSiteId(),
//                            'IMAGE_PATH' => $file,
//                            'IMAGE_HASH' => md5_file($_SERVER['DOCUMENT_ROOT'].$file),
//                        ];
                    }
                    if (!$imagesHash) {
                        return [];
                    }
                    $filter[] = [
                        'IMAGE_HASH', 'in', $imagesHash,
//                            'logic' => Filter::LOGIC_OR,
//                            ['IMAGE_HASH', 'in', ''],
                    ];
                    $images = ImageCompressImagesTable::getList([
                            'select' => [
                                '*',
                                'CONVERTED_IMAGE_PATH' => 'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.CONVERTED_IMAGE.IMAGE_PATH',
                                'CONVERTED_IMAGE_ID' => 'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.CONVERTED_IMAGE.ID',
                                'CONVERTED_IMAGE_HASH' => 'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.CONVERTED_IMAGE.ORIGINAL_IMAGE_HASH',
                                'IMAGE_TYPE' => 'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.CONVERTED_IMAGE.IMAGE_TYPE',
//                                'IMAGE_PROCESSED' => 'Dev2fun\ImageCompress\ImageCompressImagesToConvertedTable:IMAGE.IMAGE_PROCESSED',
//                                'REF_WEBP_' => 'WEBP_IMAGE',
//                                'WEBP_' => 'WEBP_PATH',
                            ],
                            'filter' => $filter,
                        ])
                        ->fetchAll();

                    $result = [];
                    foreach ($images as $image) {
                        $result[$image['IMAGE_PATH']] = $image['CONVERTED_IMAGE_PATH'];
                    }

                    return $result;
                }
            );

            if($arFileReplace) {
                $content = \strtr($content, $arFileReplace);
            }

        } else {
            if (!static::checkWebpSupport()) {
                return $content;
            }

            $arFileReplace = self::getInstance()->postProcess($arFiles);
            if($arFileReplace) {
                $event = new \Bitrix\Main\Event(
                    self::getInstance()->MODULE_ID,
                    "OnBeforePostConvertReplaceImage",
                    [&$arFileReplace]
                );
                $event->send();

                if($arFileReplace) {
                    $content = \strtr($content, $arFileReplace);
                }
            }
        }

        return $content;
    }

    /**
     * @return array
     */
    public static function getDomains(): array
    {
        if (self::$domains === null) {
            $sites = \Dev2funImageCompress::getSites();
            $domains = [];
            foreach ($sites as $site) {
                $domains[] = $site['SERVER_NAME'];
                $siteDomains = !empty($site['DOMAINS'])
                    ? explode("\n", $site['DOMAINS'])
                    : [];
                if ($siteDomains) {
                    foreach ($siteDomains as $siteDomain) {
                        $domains[] = $siteDomain;
                    }
                }
            }
            if ($domains) {
                $domains = array_unique($domains);
            }
            self::$domains = $domains;
        }
        return self::$domains;
    }

    /**
     * Return normalized path to file or null for exclude
     * @param string $file
     * @return string|null
     */
    public static function getNormalizePathFile(string $file): ?string
    {
        $url = \parse_url($file);
        if (empty($url['host'])) {
            return $file;
        }
        if ($url['host'] === 'data') {
            return null;
        }
        $hosts = array_filter(
            self::getDomains(),
            function($hostVal) use ($file) {
                return strpos($file, $hostVal) !== false;
            }
        );
        if (!$hosts) {
            return $file;
        }
        $replacer = [
            '://',
            '//',
            $url['host'],
        ];
        if (!empty($url['scheme'])) {
            $replacer[] = $url['scheme'];
        }
        return str_replace($replacer, '', $file);
    }

    /**
     * Get normalize file size
     * @param int $fileSize
     * @param int $digits
     * @return string
     */
    public function getNiceFileSize(int $fileSize, int $digits = 2)
    {
        $sizes = ["TB", "GB", "MB", "KB", "B"];
        $total = \count($sizes);
        while ($total-- && $fileSize > 1024) {
            $fileSize /= 1024;
        }
        return \round($fileSize, $digits) . " " . $sizes[$total];
    }

    /**
     * Get normalize chmod value
     * @param int $num
     * @return int
     */
    public function getChmod(int $num)
    {
        if (!$num) return 0777;
        switch ($num) {
            case 644:
                $num = 0644;
                break;
            case 660:
                $num = 0660;
                break;
            case 664:
                $num = 0664;
                break;
            case 666:
                $num = 0666;
                break;
            case 700:
                $num = 0700;
                break;
            case 744:
                $num = 0744;
                break;
            case 755:
                $num = 0755;
                break;
            case 775:
                $num = 0775;
                break;
            case 777:
                $num = 0777;
                break;
            default:
                $num = 0777;
        }
        return $num;
    }

    /**
     * Set state module
     * @param bool $enable
     */
    public static function setEnable(bool $enable)
    {
        static::$globalEnable = $enable;
    }

    /**
     * Get current state module
     * @return bool
     */
    public static function getEnable(): bool
    {
        return static::$globalEnable;
    }

    /**
     * Check current path on support webp
     * @return bool
     */
    public static function checkSupportWebpCurrentPath(): bool
    {
        global $APPLICATION;
        return !\preg_match('#\/bitrix\/admin\/#', $APPLICATION->GetCurPage());
    }

    /**
     * Check header accept on support webp
     * @return bool
     */
    public static function checkSupportWebpAccept(): bool
    {
        return \strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false;
    }

    /**
     * Get result check webp support
     * @return bool
     */
    public static function checkWebpSupport(): bool
    {
        if(!static::checkSupportWebpCurrentPath()) {
            return false;
        }

        $curBrowserAgent = $_SERVER["HTTP_USER_AGENT"] ?? '';
        $supportBrowsers = [
            'chrome',
            'opera',
        ];
        $event = new \Bitrix\Main\Event(
            self::getInstance()->MODULE_ID,
            'OnBeforeCheckWebpBrowserSupport',
            [&$supportBrowsers, &$curBrowserAgent]
        );
        $event->send();

        if (!$curBrowserAgent) {
            return true;
        }

        $result = \in_array(self::getBrowserAgentName($curBrowserAgent), $supportBrowsers)
            || self::checkSupportWebpAccept();

        $event = new \Bitrix\Main\Event(self::getInstance()->MODULE_ID, "OnAfterCheckWebpSupport", [$result]);
        $event->send();
        if ($event->getResults()) {
            foreach ($event->getResults() as $evenResult) {
                if ($evenResult->getType() == \Bitrix\Main\EventResult::SUCCESS) {
                    $result = (bool) $evenResult->getParameters();
                }
            }
        }

        return (bool)$result;
    }

    /**
     * Get browser name
     * @param string|null $userAgent
     * @return string
     */
    public static function getBrowserAgentName(?string $userAgent): string
    {
        $result = 'Other';
        if(!$userAgent) {
            return $result;
        }
        $userAgent = \mb_strtolower($userAgent);
        switch ($userAgent) {
            case \strpos($userAgent, 'opera')!==false || \strpos($userAgent, 'opr/')!==false:
                $result = 'opera';
                break;
            case \strpos($userAgent, 'edge')!==false:
                $result = 'edge';
                break;
            case \strpos($userAgent, 'chrome')!==false:
                $result = 'chrome';
                break;
            case \strpos($userAgent, 'safari')!==false:
                $result = 'safari';
                break;
            case \strpos($userAgent, 'firefox')!==false:
                $result = 'firefox';
                break;
            case \strpos($userAgent, 'msie')!==false || \strpos($userAgent, 'trident/7')!==false:
                $result = 'msie';
                break;
        }

        return $result;
    }

    /**
     * Get original src by webp src
     * @param string $srcWebp
     * @param string|null $siteId
     * @return string
     */
    public static function getOriginalSrc(string $srcWebp, ?string $siteId = null): string
    {
        if (!$siteId) {
            $siteId = Dev2funImageCompress::getSiteId();
        }
        $alg = Option::get(self::getInstance()->MODULE_ID, 'convert_algorithm', 'phpWebp', $siteId);
        $algInstance = static::getAlgInstance($alg);
        return $algInstance ? $algInstance->getOriginalSrc($srcWebp) : '';
    }

    /**
     * Convert image by absolute or relative path
     * @param string $file
     * @param array $options
     * @return bool|string|null
     */
    public function convertImageByPath(string $file, array $options=[])
    {
        if(!static::$globalEnable || !$this->enable || !$file) {
            return null;
        }

        $fileScheme = \parse_url($file, \PHP_URL_SCHEME);
        if ($fileScheme === 'data') {
            return null;
        }

        if (strpos($file, $_SERVER["DOCUMENT_ROOT"]) !== 0) {
            $absFile = "{$_SERVER["DOCUMENT_ROOT"]}$file";
        } else {
            $absFile = $file;
        }

        if(!\is_file($absFile)) {
            return null;
        }
        $fileInfo = \pathinfo($absFile);
        $arFile = [
            'CONTENT_TYPE' => \mime_content_type($absFile),
            'SUBDIR' => \str_replace($_SERVER["DOCUMENT_ROOT"], '', $fileInfo['dirname']),
            'FILE_NAME' => $fileInfo['basename'],
            'ABS_PATH' => $absFile,
        ];

        return $this->process($arFile, $options);
    }

//    public function getList(
//        array $arOrder = [],
//        array $arFilter = [],
//        int $limit = 100,
//        int $offset = 0
//    ) {
//        global $DB;
//        $strSql = $this->queryBuilder($arOrder, $arFilter);
//        //        if($limit) {
//        //            $strSql .= ' LIMIT '.$limit;
//        //        }
//        //
//        //        if($offset) {
//        //            $strSql .= ' OFFSET '.$offset;
//        //        }
//        return $DB->Query($strSql, false, "FILE: " . __FILE__ . "<br> LINE: " . __LINE__);
//    }
}