Your IP : 3.134.104.244


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/Compress.php

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

namespace Dev2fun\ImageCompress;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Config\Option;

IncludeModuleLangFile(__FILE__);

class Compress
{
    private $jpegoptim = false;
    private $pngoptim = false;
    private $MODULE_ID = 'dev2fun.imagecompress';
    private $tableName = 'b_d2f_imagecompress_files';

    private
        $algorithmJpeg = '',
        $algorithmPng = '';

    private
        $jpegOptimPath = '',
        $pngOptimPath = '';

    private
        $enableElement = false,
        $enableSection = false,
        $enableResize = false,
        $enableSave = false,
        $enableImageResize = false,
        $jpegProgress = false;

    private
        $jpegOptimCompress,
        $pngOptimCompress;

    public $enableJpeg = false;
    public $enablePng = false;
    public $enablePdf = false;
    public $enableGif = false;
    public $enableSvg = false;

    /** @var string[] */
    private static $optiClasses = [
        'jpegoptim' => '\Dev2fun\ImageCompress\Jpegoptim',
        'optipng' => '\Dev2fun\ImageCompress\Optipng',
        'ps2pdf' => '\Dev2fun\ImageCompress\Ps2Pdf',
        'svg' => '\Dev2fun\ImageCompress\Svg',
        'gif' => '\Dev2fun\ImageCompress\Gif',
    ];

    private static $instance;

    /** @var bool state */
    protected static $enable = true;

    /** @var string[] */
    public static $supportContentType = [
        'image/jpeg',
        'image/png',
        'application/pdf',
        'image/svg',
        'image/gif',
    ];

    /** @var string */
    public $LAST_ERROR;

    /** @var bool */
    protected static $hasDisk;

    public function __construct()
    {
//        if (!$siteId) {
//            $siteId = \Dev2funImageCompress::getSiteId();
//        }
//        $this->siteId = $siteId;

        $this->algorithmJpeg = Option::get($this->MODULE_ID, 'opti_algorithm_jpeg', 'jpegoptim');
        $this->algorithmPng = Option::get($this->MODULE_ID, 'opti_algorithm_png', 'optipng');

        $this->pngOptimPath = Option::get($this->MODULE_ID, 'path_to_optipng', '');
        $this->jpegOptimPath = Option::get($this->MODULE_ID, 'path_to_jpegoptim', '');

        $this->enableJpeg = Option::get($this->MODULE_ID, 'enable_jpeg', 'N') === 'Y';
        $this->enablePng = Option::get($this->MODULE_ID, 'enable_png', 'N') === 'Y';
        $this->enablePdf = Option::get($this->MODULE_ID, 'enable_pdf', 'N') === 'Y';
        $this->enableGif = Option::get($this->MODULE_ID, 'enable_gif', 'N') === 'Y';
        $this->enableSvg = Option::get($this->MODULE_ID, 'enable_svg', 'N') === 'Y';

        $this->enableElement = (Option::get($this->MODULE_ID, 'enable_element', 'N') === 'Y');
        $this->enableSection = (Option::get($this->MODULE_ID, 'enable_section', 'N') === 'Y');
        $this->enableResize = (Option::get($this->MODULE_ID, 'enable_resize', 'N') === 'Y');
        $this->enableSave = (Option::get($this->MODULE_ID, 'enable_save', 'N') === 'Y');

        $this->jpegOptimCompress = Option::get($this->MODULE_ID, 'jpegoptim_compress', 80);
        $this->pngOptimCompress = Option::get($this->MODULE_ID, 'optipng_compress', 3);

        $this->jpegProgress = (Option::get($this->MODULE_ID, 'jpeg_progressive', 'N') === 'Y');
        $this->enableImageResize = (Option::get($this->MODULE_ID, 'resize_image_enable', 'N') === 'Y');
    }

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

    /**
     * Get algorithm class
     * @param string $algorithm
     * @return null|Jpegoptim|Optipng|Ps2Pdf|Webp|Gif|Svg
     */
    public static function getAlgInstance(string $algorithm)
    {
        $obj = null;
        switch ($algorithm) {
            case 'jpegoptim':
                $obj = \Dev2fun\ImageCompress\Jpegoptim::getInstance();
                break;
            case 'optipng':
                $obj = \Dev2fun\ImageCompress\Optipng::getInstance();
                break;
            case 'ps2pdf':
                $obj = \Dev2fun\ImageCompress\Ps2Pdf::getInstance();
                break;
            case 'webp':
            case 'cwebp':
                $obj = \Dev2fun\ImageCompress\Webp::getInstance();
                break;
            case 'gif':
            case 'gifsicle':
                $obj = \Dev2fun\ImageCompress\Gif::getInstance();
                break;
            case 'svg':
            case 'svgo':
                $obj = \Dev2fun\ImageCompress\Svg::getInstance();
                break;
        }
        return $obj;
//		return self::$optiClasses[$algorithm]::getInstance(); // PHP7+
    }

    public function isPNGOptim()
    {
        if (!$this->pngoptim) {
            $this->pngoptim = Check::isPNGOptim($this->algorithmPng);
            if (!$this->pngoptim) $this->LAST_ERROR = Check::$lastError;
//            exec($this->pngOptimPath.'/optipng -v',$s);
//            if($s) $this->pngoptim = true;
        }
        return $this->pngoptim;
    }

    public function isJPEGOptim()
    {
        if (!$this->jpegoptim) {
            $this->jpegoptim = Check::isJPEGOptim($this->algorithmJpeg);
            if (!$this->jpegoptim) $this->LAST_ERROR = Check::$lastError;
//            exec($this->jpegOptimPath.'/jpegoptim --version',$s);
//            if($s) $this->jpegoptim = true;
        }
        return $this->jpegoptim;
    }

    public function compressJPG(string $strFilePath)
    {
        if(!static::$enable || !$this->enableJpeg) {
            return null;
        }
        $res = false;
        if (!$this->isJPEGOptim()) {
            if (empty($this->LAST_ERROR)) {
                $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => 'jpegoptim']);
            }
            return $res;
        }
        if (\file_exists($strFilePath)) {
            $algInstance = static::getAlgInstance($this->algorithmJpeg);
            $res = $algInstance->compressJPG(
                $strFilePath,
                $this->jpegOptimCompress,
                [
                    'progressiveJpeg' => $this->jpegProgress,
                    'changeChmod' => $this->getChmod(Option::get($this->MODULE_ID, 'change_chmod', 777)),
                ]
            );
        }
        return $res;
    }

    public function compressPNG(string $strFilePath)
    {
        if(!static::$enable || !$this->enablePng) {
            return null;
        }
        $res = false;
        if (!$this->isPNGOptim()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => 'optipng']);
            return $res;
        }
        if (\file_exists($strFilePath)) {
            $res = static::getAlgInstance($this->algorithmPng)->compressPNG(
                $strFilePath,
                $this->pngOptimCompress,
                [
                    'changeChmod' => $this->getChmod(
                        Option::get($this->MODULE_ID, 'change_chmod', 777)
                    ),
                ]
            );
        }
        return $res;
    }

    public function compressPdf(string $strFilePath)
    {
        if(!static::$enable || !$this->enablePdf) {
            return null;
        }
        $res = false;
        $algInstance = static::getAlgInstance('ps2pdf');
        if (!$algInstance->isPdfOptim()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => 'ps2pdf']);
            return $res;
        }
        if (\file_exists($strFilePath)) {
            $res = $algInstance->compress(
                $strFilePath,
                [
                    'changeChmod' => $this->getChmod(
                        Option::get($this->MODULE_ID, 'change_chmod', 777)
                    ),
                ]
            );
        }
        return $res;
    }

    /**
     * Запускает процесс
     * @param string $strFilePath
     * @param string|null $alg
     * @param array $options
     * @return bool|null
     * @throws \Bitrix\Main\ArgumentNullException
     * @throws \Bitrix\Main\ArgumentOutOfRangeException
     */
    public function process(string $strFilePath, ?string $alg=null, array $options=[])
    {
        if(!static::$enable) return null;
        $res = false;
        if(!$alg) {
            return $res;
        }
        $algInstance = static::getAlgInstance($alg);
        if (!$algInstance->isOptim()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_NO_MODULE', ['#MODULE#' => $alg]);
            return $res;
        }
        if (!$algInstance->isActive()) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_ERROR_NO_ACTIVE', ['#TYPE#' => $alg]);
            return $res;
        }
        if (\file_exists($strFilePath)) {
            $res = $algInstance->compress(
                $strFilePath,
                \array_merge(
                    [
                        'changeChmod' => $this->getChmod(
                            Option::get($this->MODULE_ID, 'change_chmod', 777)
                        ),
                    ],
                    $options
                )
            );
        }
        return $res;
    }

    /**
     * Compress image by fileID
     * @param int $intFileID
     * @return bool|null
     */
    public function compressImageByID(int $intFileID)
    {
        $res = false;
        if(!static::$enable || !$intFileID) {
            return null;
        }

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

        $arFile = \CFile::GetByID($intFileID)->GetNext();
        if (!\in_array($arFile["CONTENT_TYPE"], static::$supportContentType)) {
            return null;
        }

        if (!Check::isActiveByMimeType($arFile["CONTENT_TYPE"])) {
            $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_ERROR_NO_ACTIVE', ['#TYPE#' => $arFile["CONTENT_TYPE"]]);
            return null;
        }

        $strFilePath = $_SERVER["DOCUMENT_ROOT"] . \CFile::GetPath($intFileID);

        if (\file_exists($strFilePath)) {
            $oldSize = $arFile["FILE_SIZE"]; // filesize($strFilePath);
            if ($this->enableImageResize) {
                $this->resize($intFileID, $strFilePath);
            }
            switch ($arFile["CONTENT_TYPE"]) {
                case 'image/jpeg':
                    $isCompress = $this->compressJPG($strFilePath);
                    break;
                case 'image/png':
                    $isCompress = $this->compressPNG($strFilePath);
                    break;
                case 'application/pdf':
                    $isCompress = $this->compressPdf($strFilePath);
                    break;
                case 'image/svg':
                    $isCompress = $this->process(
                        $strFilePath,
                        Option::get($this->MODULE_ID, 'opti_algorithm_svg', '')
                    );
                    break;
                case 'image/gif':
                    $isCompress = $this->process(
                        $strFilePath,
                        Option::get($this->MODULE_ID, 'opti_algorithm_gif', '')
                    );
                    break;
                default:
                    $this->LAST_ERROR = Loc::getMessage('DEV2FUN_IMAGECOMPRESS_CONTENT_TYPE', [
                        '#TYPE#' => $arFile["CONTENT_TYPE"],
                    ]);
                    return null;
            }

            if ($isCompress) {
                \clearstatcache(true, $strFilePath);
                $newSize = filesize($strFilePath);
                if ($newSize != $oldSize) {
                    //					$DB->Query("UPDATE b_file SET FILE_SIZE='" . $DB->ForSql($newSize, 255) . "' WHERE ID=" . intval($intFileID));
                    $this->saveSizeBitrix($intFileID, $newSize);
                }
                $arFields = [
                    'FILE_ID' => $intFileID,
                    'SIZE_BEFORE' => $oldSize,
                    'SIZE_AFTER' => $newSize,
                ];
                $rs = ImageCompressTable::getById($intFileID);
                if ($rs->getSelectedRowsCount() <= 0) {
                    $res = ImageCompressTable::add($arFields);
                } else {
                    $res = ImageCompressTable::update($intFileID, $arFields);
                }
            } else {
                $this->LAST_ERROR = '';
            }
        } else {
            $res = $this->addCompressTable($intFileID, [
                'FILE_ID' => $intFileID,
                'SIZE_BEFORE' => 0,
                'SIZE_AFTER' => 0,
            ]);
        }
        return $res;
    }

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

        $width = Option::get($this->MODULE_ID, 'resize_image_width', '');
        $height = Option::get($this->MODULE_ID, 'resize_image_height', '');
        $algorithm = Option::get($this->MODULE_ID, 'resize_image_algorithm', '');
        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);
            if ($fileId) {
                $this->saveWidthHeight($fileId, $strFilePath);
            }
            \unlink($destinationFile);
        }
        return $res;
    }

    /**
     * @param int $fileId
     * @param string $filepath
     * @return \CDBResult|false|null
     */
    public function saveWidthHeight(int $fileId, string $filepath)
    {
        global $DB;
        \clearstatcache(true, $filepath);
        $arImageSize = \CFile::GetImageSize($filepath);
        return $DB->Query("UPDATE b_file SET HEIGHT='" . \round((float)$arImageSize[1]) . "', WIDTH='" . \round((float)$arImageSize[0]) . "' WHERE ID=" . $fileId);
    }

    /**
     * Save filesize in table b_file
     * @param int $fileId
     * @param int $newSize
     * @return mixed
     */
    public function saveSizeBitrix(int $fileId, int $newSize)
    {
        global $DB;
        $this->updateSizeDiskModule($fileId, $newSize);
        return $DB->Query("UPDATE b_file SET FILE_SIZE='" . $DB->ForSql($newSize, 255) . "' WHERE ID=" . (int)$fileId);
    }

    /**
     * @param int $intFileID
     * @param array $arFields
     * @return \Bitrix\Main\ORM\Data\AddResult|\Bitrix\Main\ORM\Data\UpdateResult
     * @throws \Bitrix\Main\ArgumentException
     * @throws \Bitrix\Main\ObjectPropertyException
     * @throws \Bitrix\Main\SystemException
     */
    public function addCompressTable(int $intFileID, array $arFields)
    {
        $rs = ImageCompressTable::getById($intFileID);

        if ($rs->getSelectedRowsCount() <= 0) {
            $res = ImageCompressTable::add($arFields);
        } else {
            $res = ImageCompressTable::update($intFileID, $arFields);
        }
        return $res;
    }

    /**
     * Сжатие картинок на событии в разделах
     * @param array $arFields
     */
    public static function CompressImageOnSectionEvent(&$arFields)
    {
        if(!static::$enable) return;
        $instance = self::getInstance();
        if ($instance->enableSection && !empty($arFields['PICTURE']) && is_numeric($arFields['PICTURE'])) {
            $rsSection = \CIBlockSection::GetByID($arFields["ID"]);
            $arSection = $rsSection->GetNext();
            $instance->compressImageByID($arSection['PICTURE']);
        }
    }

    /**
     * Сжатие картинок на событии в элементах
     * @param array $arFields
     */
    public static function CompressImageOnElementEvent(&$arFields)
    {
        if(!static::$enable) return;
        $instance = self::getInstance();
        if (!$instance->enableElement) {
            return;
        }

        if ((int)$arFields["PREVIEW_PICTURE_ID"] > 0) {
            $instance->compressImageByID($arFields["PREVIEW_PICTURE_ID"]);
        }

        if ((int)$arFields["DETAIL_PICTURE_ID"] > 0) {
            $instance->compressImageByID($arFields["DETAIL_PICTURE_ID"]);
        }

        $arEl = false;

        if ($arFields["PROPERTY_VALUES"]) {
            foreach ($arFields["PROPERTY_VALUES"] as $key => $values) {
                foreach ($values as $v) {
                    if (\is_array($v)) {

//                        $contentTypeList = [
//                            'image/jpeg',
//                            'image/png',
//                            'application/pdf',
//                        ];
                        if (
                            !isset($v['VALUE']['type'])
                            || (isset($v['VALUE']['type']) && !\in_array($v['VALUE']['type'], static::$supportContentType))
                            || !Check::isActiveByMimeType($v['VALUE']['type'])
                        ) {
                            continue;
                        }

                        if (!$arEl) {
                            $rsEl = \CIBlockElement::GetByID($arFields["ID"]);
                            if ($obEl = $rsEl->GetNextElement()) {
                                $arEl = $obEl->GetFields();
                                $arEl["PROPERTIES"] = $obEl->GetProperties();
                            }
                        }

                        foreach ($arEl["PROPERTIES"] as $arProp) {
                            if ($arProp["ID"] == $key) {
                                if ($arProp["MULTIPLE"] !== 'N') {
                                    foreach ($arProp["VALUE"] as $intFileID) {
                                        $instance->compressImageByID($intFileID);
                                    }
                                } else {
                                    $instance->compressImageByID($arProp["VALUE"]);
                                }
                            }
                        }

                    }
                }
            }
        }
    }

    /**
     * Сжатие картинок на событии в сохранения в таблице
     * CFile::SaveFile()
     */
    public static function CompressImageOnFileEvent(&$arFile, $strFileName, $strSavePath, $bForceMD5, $bSkipExt)
    {
        if(!static::$enable) return;
        $instance = self::getInstance();
        if (!$instance->enableSave) return;

//        $contentTypeList = [
//            'image/jpeg',
//            'image/png',
//            'application/pdf',
//        ];
        if (!\in_array($arFile["type"], static::$supportContentType)) {
            return;
        }

        if(!Check::isActiveByMimeType($arFile["type"])) {
            return null;
        }

        // resize
        $instance->resize(0, $arFile["tmp_name"]);

        switch ($arFile["type"]) {
            case 'image/jpeg':
                $isCompress = $instance->compressJPG($arFile["tmp_name"]);
                break;
            case 'image/png':
                $isCompress = $instance->compressPNG($arFile["tmp_name"]);
                break;
            case 'application/pdf':
                $isCompress = $instance->compressPdf($arFile["tmp_name"]);
                break;
            case 'image/svg':
                $isCompress = $instance->process(
                    $arFile["tmp_name"],
                    Option::get($instance->MODULE_ID, 'opti_algorithm_svg', '')
                );
                break;
            case 'image/gif':
                $isCompress = $instance->process(
                    $arFile["tmp_name"],
                    Option::get($instance->MODULE_ID, 'opti_algorithm_gif', '')
                );
                break;
        }
        if ($isCompress) {
            $arFile["size"] = \filesize($arFile["tmp_name"]);
        }

    }

    /**
     * Delete picture
     * @param array $arFile
     * @return void
     * @throws \Exception
     */
    public static function CompressImageOnFileDeleteEvent(array $arFile)
    {
        if(!static::$enable) return;
        ImageCompressTable::delete($arFile['ID']);
    }

    /**
     * @param array $arFile
     * @param array $arParams
     * @param $callbackData
     * @param $cacheImageFile
     * @param $cacheImageFileTmp
     * @param $arImageSize
     * @return void|null
     * @throws \Bitrix\Main\ArgumentNullException
     * @throws \Bitrix\Main\ArgumentOutOfRangeException
     */
    public static function CompressImageOnResizeEvent(
        $arFile,
        $arParams,
        &$callbackData,
        &$cacheImageFile,
        &$cacheImageFileTmp,
        &$arImageSize
    ) {
        if(!static::$enable) return;
        $instance = self::getInstance();
        if (!$instance->enableResize) return;

//        $contentTypeList = [
//            'image/jpeg',
//            'image/png',
//            'application/pdf',
//        ];
        if (
            !\in_array($arFile["CONTENT_TYPE"], static::$supportContentType)
            || !Check::isActiveByMimeType($arFile["CONTENT_TYPE"])
        ) {
            return null;
        }
        switch ($arFile["CONTENT_TYPE"]) {
            case 'image/jpeg':
                $instance->compressJPG($cacheImageFileTmp);
                break;
            case 'image/png':
                $instance->compressPNG($cacheImageFileTmp);
                break;
            case 'application/pdf':
                $instance->compressPdf($cacheImageFileTmp);
                break;
            case 'image/svg':
                $instance->process(
                    $cacheImageFileTmp,
                    Option::get($instance->MODULE_ID, 'opti_algorithm_svg', '')
                );
                break;
            case 'image/gif':
                $instance->process(
                    $cacheImageFileTmp,
                    Option::get($instance->MODULE_ID, 'opti_algorithm_gif', '')
                );
                break;
        }
    }

    /**
     * @param array $arOrder
     * @param array $arFilter
     * @return string
     */
    public function queryBuilder(array $arOrder = [], array $arFilter = []): string
    {
        global $DB;
        $arSqlSearch = [];
        $arSqlOrder = [];
        $strSqlSearch = "";

        if (\is_array($arFilter)) {
            foreach ($arFilter as $key => $val) {
                $key = \strtoupper($key);

                $strOperation = '';
                if (\substr($key, 0, 1) === "@") {
                    $key = \substr($key, 1);
                    $strOperation = "IN";
                    $arIn = \is_array($val) ? $val : \explode(',', $val);
                    $val = '';
                    foreach ($arIn as $v) {
                        $val .= ($val <> '' ? ',' : '') . "'" . $DB->ForSql(\trim($v)) . "'";
                    }
                } elseif (\substr($val, 0, 1) === ">") {
                    $val = \substr($val, 1);
                    $strOperation = ">";
                    $arIn = \is_array($val) ? $val : \explode(',', $val);
                    $val = '';
                    foreach ($arIn as $v) {
                        $val .= ($val <> '' ? ',' : '') . "'" . $DB->ForSql(\trim($v)) . "'";
                    }
                } elseif (\substr($val, 0, 1) === "<") {
                    $val = \substr($val, 1);
                    $strOperation = "<";
                    $arIn = \is_array($val) ? $val : \explode(',', $val);
                    $val = '';
                    foreach ($arIn as $v) {
                        $val .= ($val <> '' ? ',' : '') . "'" . $DB->ForSql(\trim($v)) . "'";
                    }
                } else {
                    $val = $DB->ForSql($val);
                }

                if ($val === '') {
                    continue;
                }

                switch ($key) {
                    case "MODULE_ID":
                    case "ID":
                    case "EXTERNAL_ID":
                    case "SUBDIR":
                    case "FILE_NAME":
                    case "FILE_SIZE":
                    case "ORIGINAL_NAME":
                    case "CONTENT_TYPE":
                        if ($strOperation === "IN")
                            $arSqlSearch[] = "f." . $key . " IN (" . $val . ")";
                        elseif ($strOperation === ">")
                            $arSqlSearch[] = "f." . $key . " > " . $val . "";
                        elseif ($strOperation === "<")
                            $arSqlSearch[] = "f." . $key . " < " . $val . "";
                        else
                            $arSqlSearch[] = "f." . $key . " = '" . $val . "'";
                        break;
                    case "COMRESSED":
                        if ($val === "Y")
                            $arSqlSearch[] = "tf.FILE_ID > 0";
                        else
                            $arSqlSearch[] = "tf.FILE_ID is NULL";
                        break;
                    case "COMPRESSED":
                        $arSqlSearch[] = "tf.COMPRESSED = $val";
                        break;
                }
            }
        }

        if (!empty($arSqlSearch)) {
            $strSqlSearch = " WHERE (" . \implode(") AND (", $arSqlSearch) . ")";
        }

        if (\is_array($arOrder)) {
            static $aCols = [
                "ID" => 1,
                "TIMESTAMP_X" => 1,
                "MODULE_ID" => 1,
                "HEIGHT" => 1,
                "WIDTH" => 1,
                "FILE_SIZE" => 1,
                "CONTENT_TYPE" => 1,
                "SUBDIR" => 1,
                "FILE_NAME" => 1,
                "ORIGINAL_NAME" => 1,
                "EXTERNAL_ID" => 1,
            ];
            foreach ($arOrder as $by => $ord) {
                $by = \strtoupper($by);
                if (\array_key_exists($by, $aCols))
                    $arSqlOrder[] = "f." . $by . " " . (\strtoupper($ord) === "DESC" ? "DESC" : "ASC");
            }
        }
        if (empty($arSqlOrder)) {
            $arSqlOrder[] = "f.ID ASC";
        }
        $strSqlOrder = " ORDER BY " . \implode(", ", $arSqlOrder);

        $strSql =
            "SELECT f.*, " . $DB->DateToCharFunction("f.TIMESTAMP_X") . " as TIMESTAMP_X, tf.* " .
            "FROM b_file f " .
            "LEFT JOIN {$this->tableName} as tf ON f.ID = tf.FILE_ID" .
            $strSqlSearch .
            $strSqlOrder;

        return $strSql;
    }

    /**
     * @param array $arOrder
     * @param array $arFilter
     * @param int $limit
     * @param int $offset
     * @return \CDBResult|false|null
     */
    public function getFileList(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__);
    }

    /**
     * @param int|null $fileSize
     * @param int $digits
     * @return string
     */
    public function getNiceFileSize(?int $fileSize, int $digits = 2): string
    {
        if (!$fileSize) {
            return '';
        }
        $sizes = ["TB", "GB", "MB", "KB", "B"];
        $total = \count($sizes);
        while ($total-- && $fileSize > 1024) {
            $fileSize /= 1024;
        }
        return \round($fileSize, $digits) . " " . $sizes[$total];
    }

    /**
     * @param int $num
     * @return int
     */
    public function getChmod($num)
    {
        if (!$num) return 0777;
        switch ((int)$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::$enable = $enable;
    }

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

    /**
     * @param int $fileId
     * @param int $size
     * @return void
     * @throws \Bitrix\Main\LoaderException
     */
    public function updateSizeDiskModule(int $fileId, int $size)
    {
        if (!self::availableDiskModule()) {
            return;
        }
        global $DB;
        $DB->Query("UPDATE b_disk_object SET SIZE='" . $DB->ForSql($size, 255) . "' WHERE FILE_ID=" . $fileId);
        $DB->Query("UPDATE b_disk_version SET SIZE='" . $DB->ForSql($size, 255) . "' WHERE FILE_ID=" . $fileId);
    }

    /**
     * @return bool
     * @throws \Bitrix\Main\LoaderException
     */
    public static function availableDiskModule(): bool
    {
        if (self::$hasDisk === null) {
            self::$hasDisk = \Bitrix\Main\Loader::includeModule('disk');
        }
        return self::$hasDisk;
    }

    /**
     * Get IBLOCK_ID by file id
     * @param int $fileId
     * @return int|null
     */
    public function getIblockIdByFileId(int $fileId): ?int
    {
        global $DB;
        $sql = <<<SQL
    SELECT p.IBLOCK_ID FROM b_iblock_property p 
        INNER JOIN b_iblock_element_property e ON e.IBLOCK_PROPERTY_ID=p.ID 
        WHERE p.PROPERTY_TYPE='F' AND e.VALUE={$fileId}
    UNION
    SELECT b.IBLOCK_ID FROM b_iblock_element b WHERE b.PREVIEW_PICTURE={$fileId} OR b.DETAIL_PICTURE={$fileId}
    UNION
    SELECT s.IBLOCK_ID FROM b_iblock_section s WHERE s.PICTURE={$fileId}
SQL;
        $result = $DB->Query($sql);
        if ($result) {
            return (int)($result->Fetch()['IBLOCK_ID'] ?? 0);
        }
        return null;
    }

    /**
     * @return string|null
     */
    public function getError()
    {
        switch ($this->LAST_ERROR) {
            case 'no_active':
                return Loc::getMessage('DEV2FUN_IMAGECOMPRESS_ERROR_NO_ACTIVE');
        }

        return $this->LAST_ERROR;
    }
}