<?php

if (!function_exists('download_url')) {
    require_once ABSPATH . 'wp-admin/includes/file.php';
}
use ShortPixel\ShortpixelLogger\ShortPixelLogger as Log;
class ShortPixelAPI
{
    const STATUS_SUCCESS = 1;
    const STATUS_UNCHANGED = 0;
    const STATUS_ERROR = -1;
    const STATUS_FAIL = -2;
    const STATUS_QUOTA_EXCEEDED = -3;
    const STATUS_SKIP = -4;
    const STATUS_NOT_FOUND = -5;
    const STATUS_NO_KEY = -6;
    const STATUS_RETRY = -7;
    const STATUS_SEARCHING = -8;
    const STATUS_QUEUE_FULL = -404;
    const STATUS_MAINTENANCE = -500;
    const ERR_FILE_NOT_FOUND = -2;
    const ERR_TIMEOUT = -3;
    const ERR_SAVE = -4;
    const ERR_SAVE_BKP = -5;
    const ERR_INCORRECT_FILE_SIZE = -6;
    const ERR_DOWNLOAD = -7;
    const ERR_PNG2JPG_MEMORY = -8;
    const ERR_POSTMETA_CORRUPT = -9;
    const ERR_UNKNOWN = -999;
    private $_settings;
    private $_apiEndPoint;
    private $_apiDumpEndPoint;
    public function __construct($settings)
    {
        $this->_settings = $settings;
        $this->_apiEndPoint = $this->_settings->httpProto . '://' . SHORTPIXEL_API . '/v2/reducer.php';
        $this->_apiDumpEndPoint = $this->_settings->httpProto . '://' . SHORTPIXEL_API . '/v2/cleanup.php';
    }
    protected function prepareRequest($requestParameters, $Blocking = false)
    {
        $arguments = array('method' => 'POST', 'timeout' => 15, 'redirection' => 3, 'sslverify' => false, 'httpversion' => '1.0', 'blocking' => $Blocking, 'headers' => array(), 'body' => json_encode($requestParameters), 'cookies' => array());
        if ($this->_settings->httpProto !== 'https') {
            unset($arguments['sslverify']);
        }
        return $arguments;
    }
    public function doDumpRequests($URLs)
    {
        if (!count($URLs)) {
            return false;
        }
        $ret = wp_remote_post($this->_apiDumpEndPoint, $this->prepareRequest(array('plugin_version' => SHORTPIXEL_IMAGE_OPTIMISER_VERSION, 'key' => $this->_settings->apiKey, 'urllist' => $URLs)));
        return $ret;
    }
    public function doRequests($URLs, $Blocking, $itemHandler, $compressionType = false, $refresh = false)
    {
        if (!count($URLs)) {
            $meta = $itemHandler->getMeta();
            $thumbsMissing = $meta->getThumbsMissing();
            if (is_array($thumbsMissing) && count($thumbsMissing)) {
                $added = array();
                $files = " (";
                foreach ($meta->getThumbsMissing() as $miss) {
                    if (isset($added[$miss])) {
                        continue;
                    }
                    $files .= $miss . ", ";
                    $added[$miss] = true;
                }
                if (strrpos($files, ', ')) {
                    $files = substr_replace($files, ')', strrpos($files, ', '));
                }
                throw new Exception(__('Image files are missing.', 'shortpixel-image-optimiser') . (strlen($files) > 1 ? $files : ''));
            } else {
                throw new Exception(__('Image files are missing.', 'shortpixel-image-optimiser'));
            }
        }
        $apiKey = $this->_settings->apiKey;
        if (strlen($apiKey) < 20) {
            $this->_settings->verifiedKey = false;
            Log::addWarn('Invalid API Key');
            throw new Exception(__('Invalid API Key', 'shortpixel-image-optimiser'));
        }
        $convertTo = array();
        if ($this->_settings->createWebp) {
            $convertTo[] = urlencode("+webp");
        }
        if ($this->_settings->createAvif) {
            $convertTo[] = urlencode('+avif');
        }
        if (count($convertTo) > 0) {
            $convertTo = implode('|', $convertTo);
        } else {
            $convertTo = '';
        }
        $requestParameters = array('plugin_version' => SHORTPIXEL_IMAGE_OPTIMISER_VERSION, 'key' => $apiKey, 'lossy' => $compressionType === false ? $this->_settings->compressionType : $compressionType, 'cmyk2rgb' => $this->_settings->CMYKtoRGBconversion, 'keep_exif' => $this->_settings->keepExif ? "1" : "0", 'convertto' => $convertTo, 'resize' => $this->_settings->resizeImages ? 1 + 2 * ($this->_settings->resizeType == 'inner' ? 1 : 0) : 0, 'resize_width' => $this->_settings->resizeWidth, 'resize_height' => $this->_settings->resizeHeight, 'urllist' => $URLs);
        if ($this->_settings->downloadArchive == 7 && class_exists('PharData')) {
            $requestParameters['group'] = $itemHandler->getId();
        }
        if ($refresh) {
            $requestParameters['refresh'] = 1;
        }
        $response = wp_remote_post($this->_apiEndPoint, $this->prepareRequest($requestParameters, $Blocking));
        Log::addDebug('ShortPixel API Request sent', $requestParameters);
        if ($Blocking) {
            if (is_object($response) && get_class($response) == 'WP_Error') {
                $errorMessage = $response->errors['http_request_failed'][0];
                $errorCode = 503;
            } elseif (isset($response['response']['code']) && $response['response']['code'] != 200) {
                $errorMessage = $response['response']['code'] . " - " . $response['response']['message'];
                $errorCode = $response['response']['code'];
            }
            if (isset($errorMessage)) {
                $itemHandler->incrementRetries(1, $errorCode, $errorMessage);
                return array("response" => array("code" => $errorCode, "message" => $errorMessage));
            }
            return $response;
        }
        return $response;
    }
    public function parseResponse($response)
    {
        $data = $response['body'];
        $data = json_decode($data);
        return (array) $data;
    }
    public function processImage($URLs, $PATHs, $itemHandler = null)
    {
        return $this->processImageRecursive($URLs, $PATHs, $itemHandler, 0);
    }
    private function processImageRecursive($URLs, $PATHs, $itemHandler = null, $startTime = 0)
    {
        $PATHs = self::CheckAndFixImagePaths($PATHs);
        if ($PATHs === false || isset($PATHs['error'])) {
            $missingFiles = '';
            if (isset($PATHs['error'])) {
                foreach ($PATHs['error'] as $errPath) {
                    $missingFiles .= (strlen($missingFiles) ? ', ' : '') . basename(stripslashes($errPath));
                }
            }
            $msg = __('The file(s) do not exist on disk: ', 'shortpixel-image-optimiser') . $missingFiles;
            $itemHandler->setError(self::ERR_FILE_NOT_FOUND, $msg);
            return array("Status" => self::STATUS_SKIP, "Message" => $msg, "Silent" => $itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? 1 : 0);
        }
        if ($startTime == 0) {
            $startTime = time();
        }
        $apiRetries = $this->_settings->apiRetries;
        if (time() - $startTime > SHORTPIXEL_MAX_EXECUTION_TIME2) {
            if ($apiRetries > SHORTPIXEL_MAX_API_RETRIES) {
                $itemHandler->incrementRetries(1, self::ERR_TIMEOUT, __('Timed out while processing.', 'shortpixel-image-optimiser'));
                $this->_settings->apiRetries = 0;
                return array("Status" => self::STATUS_SKIP, "Message" => ($itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? __('Image ID', 'shortpixel-image-optimiser') : __('Media ID', 'shortpixel-image-optimiser')) . ": " . $itemHandler->getId() . ' ' . __('Skip this image, try the next one.', 'shortpixel-image-optimiser'));
            } else {
                $apiRetries++;
                $this->_settings->apiRetries = $apiRetries;
                return array("Status" => self::STATUS_RETRY, "Message" => __('Timed out while processing.', 'shortpixel-image-optimiser') . ' (pass ' . $apiRetries . ')', "Count" => $apiRetries);
            }
        }
        $meta = $itemHandler->getMeta();
        $compressionType = $meta->getCompressionType() !== null ? $meta->getCompressionType() : $this->_settings->compressionType;
        try {
            $response = $this->doRequests($URLs, true, $itemHandler, $compressionType);
        } catch (Exception $e) {
            Log::addError('Api DoRequest Thrown ' . $e->getMessage());
            $response = array();
        }
        if ($response['response']['code'] != 200) {
            return array("Status" => self::STATUS_FAIL, "Message" => __('There was an error and your request was not processed.', 'shortpixel-image-optimiser') . (isset($response['response']['message']) ? ' (' . $response['response']['message'] . ')' : ''), "Code" => $response['response']['code']);
        }
        $APIresponse = $this->parseResponse($response);
        if (isset($APIresponse[0])) {
            foreach ($APIresponse as $imageObject) {
                if (isset($imageObject->Status) && ($imageObject->Status->Code == 0 || $imageObject->Status->Code == 1)) {
                    sleep(1);
                    return $this->processImageRecursive($URLs, $PATHs, $itemHandler, $startTime);
                }
            }
            $firstImage = $APIresponse[0];
            switch ($firstImage->Status->Code) {
                case 2:
                    if (!isset($firstImage->Status->QuotaExceeded)) {
                        $this->_settings->quotaExceeded = 0;
                    }
                    return $this->handleSuccess($APIresponse, $PATHs, $itemHandler, $compressionType);
                default:
                    $incR = 1;
                    if (!file_exists($PATHs[0])) {
                        $err = array("Status" => self::STATUS_NOT_FOUND, "Message" => "File not found on disk. " . ($itemHandler->getType() == ShortPixelMetaFacade::CUSTOM_TYPE ? "Image" : "Media") . " ID: " . $itemHandler->getId(), "Code" => self::ERR_FILE_NOT_FOUND);
                        $incR = 3;
                    } elseif (isset($APIresponse[0]->Status->Message)) {
                        $err = array("Status" => self::STATUS_FAIL, "Code" => isset($APIresponse[0]->Status->Code) ? $APIresponse[0]->Status->Code : self::ERR_UNKNOWN, "Message" => __('There was an error and your request was not processed.', 'shortpixel-image-optimiser') . " (" . wp_basename($APIresponse[0]->OriginalURL) . ": " . $APIresponse[0]->Status->Message . ")");
                    } else {
                        $err = array("Status" => self::STATUS_FAIL, "Message" => __('There was an error and your request was not processed.', 'shortpixel-image-optimiser'), "Code" => isset($APIresponse[0]->Status->Code) ? $APIresponse[0]->Status->Code : self::ERR_UNKNOWN);
                    }
                    $itemHandler->incrementRetries($incR, $err["Code"], $err["Message"]);
                    $meta = $itemHandler->getMeta();
                    if ($meta->getRetries() >= SHORTPIXEL_MAX_FAIL_RETRIES) {
                        $meta->setStatus($APIresponse[0]->Status->Code);
                        $meta->setMessage($APIresponse[0]->Status->Message);
                        $itemHandler->updateMeta($meta);
                    }
                    return $err;
            }
        }
        if (!isset($APIresponse['Status'])) {
            WpShortPixel::log("API Response Status unfound : " . json_encode($APIresponse));
            return array("Status" => self::STATUS_FAIL, "Message" => __('Unrecognized API response. Please contact support.', 'shortpixel-image-optimiser'), "Code" => self::ERR_UNKNOWN, "Debug" => ' (SERVER RESPONSE: ' . json_encode($response) . ')');
        } else {
            switch ($APIresponse['Status']->Code) {
                case -403:
                    @delete_option('bulkProcessingStatus');
                    $this->_settings->quotaExceeded = 1;
                    return array("Status" => self::STATUS_QUOTA_EXCEEDED, "Message" => __('Quota exceeded.', 'shortpixel-image-optimiser'));
                    break;
                case -404:
                    return array("Status" => self::STATUS_QUEUE_FULL, "Message" => $APIresponse['Status']->Message);
                case -500:
                    return array("Status" => self::STATUS_MAINTENANCE, "Message" => $APIresponse['Status']->Message);
            }
            if (is_numeric($APIresponse['Status']->Code)) {
                return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse['Status']->Message);
            } else {
                return array("Status" => self::STATUS_FAIL, "Message" => $APIresponse[0]->Status->Message);
            }
        }
    }
    public function setPreferredProtocol($url, $reset = false)
    {
        if ($this->_settings->downloadProto == '' || $reset) {
            $testURL = 'http://' . SHORTPIXEL_API . '/img/connection-test-image.png';
            $result = download_url($testURL, 10);
            $this->_settings->downloadProto = is_wp_error($result) ? 'https' : 'http';
        }
        return $this->_settings->downloadProto == 'http' ? str_replace('https://', 'http://', $url) : str_replace('http://', 'https://', $url);
    }
    function fromArchive($path, $optimizedUrl, $optimizedSize, $originalSize, $webpUrl, $avifUrl)
    {
        $webpTempFile = "NA";
        if ($webpUrl !== "NA") {
            $webpTempFile = $path . '/' . wp_basename($webpUrl);
            $webpTempFile = file_exists($webpTempFile) ? $webpTempFile : 'NA';
        }
        $avifTempFile = false;
        if ($avifUrl !== "NA") {
            $avifTempFile = $path . '/' . wp_basename($avifUrl);
            $avifTempFile = file_exists($avifTempFile) ? $avifTempFile : 'NA';
        }
        if ($originalSize == $optimizedSize) {
            return array("Status" => self::STATUS_UNCHANGED, "Message" => "File wasn't optimized so we do not download it.", "WebP" => $webpTempFile, 'Avif' => $avifTempFile);
        }
        $correctFileSize = $optimizedSize;
        $tempFile = $path . '/' . wp_basename($optimizedUrl);
        if (file_exists($tempFile)) {
            if (filesize($tempFile) != $correctFileSize) {
                $size = filesize($tempFile);
                @unlink($tempFile);
                @unlink($webpTempFile);
                @unlink($avifTempFile);
                $returnMessage = array("Status" => self::STATUS_ERROR, "Code" => self::ERR_INCORRECT_FILE_SIZE, "Message" => sprintf(__('Error in archive - incorrect file size (downloaded: %s, correct: %s )', 'shortpixel-image-optimiser'), $size, $correctFileSize));
            } else {
                $returnMessage = array("Status" => self::STATUS_SUCCESS, "Message" => $tempFile, "WebP" => $webpTempFile, 'Avif' => $avifTempFile);
            }
        } else {
            $returnMessage = array("Status" => self::STATUS_ERROR, "Code" => self::ERR_FILE_NOT_FOUND, "Message" => __('Unable to locate downloaded file', 'shortpixel-image-optimiser') . " " . $tempFile);
        }
        return $returnMessage;
    }
    private function handleDownload($optimizedUrl, $optimizedSize, $originalSize, $webpUrl, $avifUrl)
    {
        $downloadTimeout = max(ini_get('max_execution_time') - 10, 15);
        $webpTempFile = "NA";
        if ($webpUrl !== "NA") {
            $webpURL = $this->setPreferredProtocol(urldecode($webpUrl));
            $webpTempFile = download_url($webpURL, $downloadTimeout);
            $webpTempFile = is_wp_error($webpTempFile) ? "NA" : $webpTempFile;
        }
        $avifTempFile = "NA";
        if ($avifUrl !== "NA") {
            $avifUrl = $this->setPreferredProtocol(urldecode($avifUrl));
            $avifTempFile = download_url($avifUrl, $downloadTimeout);
            $avifTempFile = is_wp_error($avifTempFile) ? "NA" : $avifTempFile;
        }
        if ($originalSize == $optimizedSize) {
            return array("Status" => self::STATUS_UNCHANGED, "Message" => "File wasn't optimized so we do not download it.", "WebP" => $webpTempFile, 'Avif' => $avifTempFile);
        }
        $correctFileSize = $optimizedSize;
        $fileURL = $this->setPreferredProtocol(urldecode($optimizedUrl));
        $tempFile = download_url($fileURL, $downloadTimeout);
        Log::addInfo('Downloading file: ' . json_encode($tempFile));
        if (is_wp_error($tempFile)) {
            $fileURL = $this->setPreferredProtocol(urldecode($optimizedUrl), true);
            $tempFile = download_url($fileURL, $downloadTimeout);
        }
        $returnMessage = array("Status" => self::STATUS_SUCCESS, "Message" => $tempFile, "WebP" => $webpTempFile, 'Avif' => $avifTempFile);
        if (is_wp_error($tempFile)) {
            @unlink($tempFile);
            @unlink($webpTempFile);
            @unlink($avifTempFile);
            $returnMessage = array("Status" => self::STATUS_ERROR, "Code" => self::ERR_DOWNLOAD, "Message" => __('Error downloading file', 'shortpixel-image-optimiser') . " ({$optimizedUrl}) " . $tempFile->get_error_message());
        } elseif (!file_exists($tempFile)) {
            $returnMessage = array("Status" => self::STATUS_ERROR, "Code" => self::ERR_FILE_NOT_FOUND, "Message" => __('Unable to locate downloaded file', 'shortpixel-image-optimiser') . " " . $tempFile);
        } elseif (filesize($tempFile) != $correctFileSize) {
            $size = filesize($tempFile);
            @unlink($tempFile);
            @unlink($webpTempFile);
            @unlink($avifTempFile);
            $returnMessage = array("Status" => self::STATUS_ERROR, "Code" => self::ERR_INCORRECT_FILE_SIZE, "Message" => sprintf(__('Error downloading file - incorrect file size (downloaded: %s, correct: %s )', 'shortpixel-image-optimiser'), $size, $correctFileSize));
        }
        Log::addDebug('HandleDownload ReturnMSG ', $returnMessage);
        return $returnMessage;
    }
    public static function backupImage($mainPath, $PATHs)
    {
        if (apply_filters('shortpixel_skip_backup', false, $mainPath, $PATHs)) {
            return array("Status" => self::STATUS_SUCCESS);
        }
        $PATHs = apply_filters('shortpixel/backup/paths', $PATHs, $mainPath);
        $fullSubDir = ShortPixelMetaFacade::returnSubDir($mainPath);
        $source = $PATHs;
        $fs = \wpSPIO()->filesystem();
        if (!file_exists(SHORTPIXEL_BACKUP_FOLDER) && !ShortPixelFolder::createBackUpFolder()) {
            Log::addWarn('Backup folder does not exist and it cannot be created');
            return array("Status" => self::STATUS_FAIL, "Message" => __('Backup folder does not exist and it cannot be created', 'shortpixel-image-optimiser'));
        }
        ShortPixelFolder::createBackUpFolder(SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir);
        foreach ($source as $fileID => $filePATH) {
            $file = $fs->getFile($filePATH);
            $bkFilePath = $fs->getBackupDirectory($file);
            $bkFile = $fs->getFile($bkFilePath . $file->getFileName());
            $destination[$fileID] = $bkFile;
        }
        if (is_writable(SHORTPIXEL_BACKUP_FOLDER)) {
            foreach ($destination as $fileID => $destination_file) {
                if (!$destination_file->exists()) {
                    $source_file = $fs->getFile($source[$fileID]);
                    $result = $source_file->copy($destination_file);
                    if (!$result) {
                        $msg = sprintf(__('Cannot save file <i>%s</i> in backup directory', 'shortpixel-image-optimiser'), $destination_file->getFullPath());
                        return array("Status" => self::STATUS_FAIL, "Message" => $msg);
                    }
                }
            }
            return array("Status" => self::STATUS_SUCCESS);
        } else {
            $msg = __('Cannot save file in backup directory', 'shortpixel-image-optimiser');
            Log::addWarn('Backup directory not writable');
            return array("Status" => self::STATUS_FAIL, "Message" => $msg);
        }
    }
    private function createArchiveTempFolder($archiveBasename)
    {
        $archiveTempDir = get_temp_dir() . '/' . $archiveBasename;
        $fs = \wpSPIO()->filesystem();
        $tempDir = $fs->getDirectory($archiveTempDir);
        if ($tempDir->exists() && time() - filemtime($archiveTempDir) < max(30, SHORTPIXEL_MAX_EXECUTION_TIME) + 10) {
            Log::addWarn("CONFLICT. Folder already exists and is modified in the last minute. Current IP:" . $_SERVER['REMOTE_ADDR']);
            return array("Status" => self::STATUS_RETRY, "Code" => 1, "Message" => "Pending");
        }
        $tempDir->check();
        if (!$tempDir->exists()) {
            return array("Status" => self::STATUS_ERROR, "Code" => self::ERR_SAVE, "Message" => "Could not create temporary folder.");
        }
        return array("Status" => self::STATUS_SUCCESS, "Dir" => $tempDir);
    }
    private function downloadArchive($archive, $compressionType, $first = true)
    {
        if ($archive->ArchiveStatus->Code == 1 || $archive->ArchiveStatus->Code == 0) {
            return array("Status" => self::STATUS_RETRY, "Code" => 1, "Message" => "Pending");
        } elseif ($archive->ArchiveStatus->Code == 2) {
            $suffix = $compressionType == 0 ? "-lossless" : "";
            $archiveURL = "Archive" . ($compressionType == 0 ? "Lossless" : "") . "URL";
            $archiveSize = "Archive" . ($compressionType == 0 ? "Lossless" : "") . "Size";
            $archiveTemp = $this->createArchiveTempFolder(wp_basename($archive->{$archiveURL}, '.tar'));
            if ($archiveTemp["Status"] == self::STATUS_SUCCESS) {
                $archiveTempDir = $archiveTemp["Dir"];
            } else {
                return $archiveTemp;
            }
            $downloadResult = $this->handleDownload($archive->{$archiveURL}, $archive->{$archiveSize}, 0, 'NA');
            if ($downloadResult['Status'] == self::STATUS_SUCCESS) {
                $archiveFile = $downloadResult['Message'];
                if (filesize($archiveFile) !== $archive->{$archiveSize}) {
                    @unlink($archiveFile);
                    ShortpixelFolder::deleteFolder($archiveTempDir);
                    return array("Status" => self::STATUS_RETRY, "Code" => 1, "Message" => "Pending");
                }
                $pharData = new PharData($archiveFile);
                try {
                    if (SHORTPIXEL_DEBUG === true) {
                        $info = "Current IP:" . $_SERVER['REMOTE_ADDR'] . "ARCHIVE CONTENTS: COUNT " . $pharData->count() . ", ";
                        foreach ($pharData as $file) {
                            $info .= $file . ", ";
                        }
                        WPShortPixel::log($info);
                    }
                    $pharData->extractTo($archiveTempDir, null, true);
                    WPShortPixel::log("ARCHIVE EXTRACTED " . json_encode(scandir($archiveTempDir)));
                    @unlink($archiveFile);
                } catch (Exception $ex) {
                    @unlink($archiveFile);
                    ShortpixelFolder::deleteFolder($archiveTempDir);
                    return array("Status" => self::STATUS_ERROR, "Code" => $ex->getCode(), "Message" => $ex->getMessage());
                }
                return array("Status" => self::STATUS_SUCCESS, "Code" => 2, "Message" => "Success", "Path" => $archiveTempDir);
            } else {
                WPShortPixel::log("ARCHIVE ERROR (" . $archive->{$archiveURL} . "): " . json_encode($downloadResult));
                if ($first && $downloadResult['Code'] == self::ERR_INCORRECT_FILE_SIZE) {
                    WPShortPixel::log("RETRYING AFTER ARCHIVE ERROR");
                    return $this->downloadArchive($archive, $compressionType, false);
                }
                @rmdir($archiveTempDir);
                return array("Status" => $downloadResult['Status'], "Code" => $downloadResult['Code'], "Message" => $downloadResult['Message']);
            }
        }
        return false;
    }
    private function handleSuccess($APIresponse, $PATHs, $itemHandler, $compressionType)
    {
        Log::addDebug('Shortpixel API : Handling Success!');
        $counter = $savedSpace = $originalSpace = $optimizedSpace = 0;
        $NoBackup = true;
        if ($compressionType) {
            $fileType = "LossyURL";
            $fileSize = "LossySize";
        } else {
            $fileType = "LosslessURL";
            $fileSize = "LosslessSize";
        }
        $webpType = "WebP" . $fileType;
        $avifType = 'AVIF' . $fileType;
        $archive = $this->_settings->downloadArchive == 7 && class_exists('PharData') && isset($APIresponse[count($APIresponse) - 1]->ArchiveStatus) ? $this->downloadArchive($APIresponse[count($APIresponse) - 1], $compressionType) : false;
        if ($archive !== false && $archive['Status'] !== self::STATUS_SUCCESS) {
            return $archive;
        }
        foreach ($APIresponse as $fileData) {
            if (!isset($fileData->Status)) {
                continue;
            }
            if ($fileData->Status->Code == 2) {
                if ($counter == 0) {
                    $percentImprovement = $fileData->PercentImprovement;
                } else {
                    $this->_settings->thumbsCount = $this->_settings->thumbsCount + 1;
                }
                $webpUrl = isset($fileData->{$webpType}) ? $fileData->{$webpType} : 'NA';
                $avifUrl = isset($fileData->{$avifType}) ? $fileData->{$avifType} : 'NA';
                if ($archive) {
                    $downloadResult = $this->fromArchive($archive['Path'], $fileData->{$fileType}, $fileData->{$fileSize}, $fileData->OriginalSize, $webpUrl, $avifUrl);
                } else {
                    $downloadResult = $this->handleDownload($fileData->{$fileType}, $fileData->{$fileSize}, $fileData->OriginalSize, $webpUrl, $avifUrl);
                }
                $tempFiles[$counter] = $downloadResult;
                if ($downloadResult['Status'] == self::STATUS_SUCCESS) {
                } elseif ($downloadResult['Status'] == self::STATUS_UNCHANGED) {
                    $originalSpace += $fileData->OriginalSize;
                    $optimizedSpace += $fileData->{$fileSize};
                } else {
                    self::cleanupTemporaryFiles($archive, $tempFiles);
                    return array("Status" => $downloadResult['Status'], "Code" => $downloadResult['Code'], "Message" => $downloadResult['Message']);
                }
            } else {
                $tempFiles[$counter] = "";
            }
            $counter++;
        }
        $mainPath = $itemHandler->getMeta()->getPath();
        if ($this->_settings->backupImages) {
            $backupStatus = self::backupImage($mainPath, $PATHs);
            if ($backupStatus['Status'] == self::STATUS_FAIL) {
                $itemHandler->incrementRetries(1, self::ERR_SAVE_BKP, $backupStatus["Message"]);
                self::cleanupTemporaryFiles($archive, empty($tempFiles) ? array() : $tempFiles);
                Log::addError('Failed to create image backup!', array('status' => $backupStatus));
                return array("Status" => self::STATUS_FAIL, "Code" => "backup-fail", "Message" => "Failed to back the image up.");
            }
            $NoBackup = false;
        }
        $writeFailed = 0;
        $width = $height = null;
        $do_resize = $this->_settings->resizeImages;
        $retinas = 0;
        $thumbsOpt = 0;
        $thumbsOptList = array();
        $settings = \wpSPIO()->settings();
        $fs = \wpSPIO()->fileSystem();
        if (!empty($tempFiles)) {
            foreach ($tempFiles as $tempFileID => $tempFile) {
                if (!is_array($tempFile)) {
                    continue;
                }
                $targetFile = $fs->getFile($PATHs[$tempFileID]);
                $isRetina = ShortPixelMetaFacade::isRetina($targetFile->getFullPath());
                if (($tempFile['Status'] == self::STATUS_UNCHANGED || $tempFile['Status'] == self::STATUS_SUCCESS) && !$isRetina && $targetFile->getFullPath() !== $mainPath) {
                    $thumbsOpt++;
                    $thumbsOptList[] = self::MB_basename($targetFile->getFullPath());
                }
                if ($tempFile['Status'] == self::STATUS_SUCCESS) {
                    $tempFilePATH = $fs->getFile($tempFile["Message"]);
                    if ($tempFilePATH->exists() && (!$targetFile->exists() || $targetFile->is_writable())) {
                        $tempFilePATH->move($targetFile);
                        if (ShortPixelMetaFacade::isRetina($targetFile->getFullPath())) {
                            $retinas++;
                        }
                        if ($do_resize && $itemHandler->getMeta()->getPath() == $targetFile->getFullPath()) {
                            $size = getimagesize($PATHs[$tempFileID]);
                            $width = $size[0];
                            $height = $size[1];
                        }
                        $fileData = $APIresponse[$tempFileID];
                        $savedSpace += $fileData->OriginalSize - $fileData->{$fileSize};
                        $originalSpace += $fileData->OriginalSize;
                        $optimizedSpace += $fileData->{$fileSize};
                        Log::addInfo("HANDLE SUCCESS: Image " . $PATHs[$tempFileID] . " original size: " . $fileData->OriginalSize . " optimized: " . $fileData->{$fileSize});
                        if ((1 - $APIresponse[$tempFileID]->{$fileSize} / $APIresponse[$tempFileID]->OriginalSize) * 100 < 5) {
                            $this->_settings->under5Percent++;
                        }
                    } else {
                        if ($archive && SHORTPIXEL_DEBUG === true) {
                            if (!$tempFilePATH->exists()) {
                                Log::addWarn("MISSING FROM ARCHIVE. tempFilePath: " . $tempFilePATH->getFullPath() . " with ID: {$tempFileID}");
                            } elseif (!$targetFile->is_writable()) {
                                Log::addWarn("TARGET NOT WRITABLE: " . $targetFile->getFullPath());
                            }
                        }
                        $writeFailed++;
                    }
                }
                $tempWebpFilePATH = $fs->getFile($tempFile["WebP"]);
                if ($tempWebpFilePATH->exists()) {
                    $targetWebPFileCompat = $fs->getFile($targetFile->getFileDir() . $targetFile->getFileName() . '.webp');
                    $targetWebPFile = $fs->getFile($targetFile->getFileDir() . $targetFile->getFileBase() . '.webp');
                    if (SHORTPIXEL_USE_DOUBLE_WEBP_EXTENSION || $targetWebPFile->exists()) {
                        $tempWebpFilePATH->move($targetWebPFileCompat);
                    } else {
                        $tempWebpFilePATH->move($targetWebPFile);
                    }
                }
                $tempAvifFilePATH = $fs->getFile($tempFile["Avif"]);
                if ($tempAvifFilePATH->exists()) {
                    $targetWebPFile = $fs->getFile($targetFile->getFileDir() . $targetFile->getFileBase() . '.avif');
                    $tempAvifFilePATH->move($targetWebPFile);
                }
            }
            self::cleanupTemporaryFiles($archive, $tempFiles);
            if ($writeFailed > 0) {
                Log::addDebug('Archive files missing (expected paths, response)', array($PATHs, $APIresponse));
                $msg = sprintf(__('Optimized version of %s file(s) couldn\'t be updated.', 'shortpixel-image-optimiser'), $writeFailed);
                $itemHandler->incrementRetries(1, self::ERR_SAVE, $msg);
                $this->_settings->bulkProcessingStatus = "error";
                return array("Status" => self::STATUS_FAIL, "Code" => "write-fail", "Message" => $msg);
            }
        } elseif (0 + $fileData->PercentImprovement < 5) {
            $this->_settings->under5Percent++;
        }
        $this->_settings->savedSpace += $savedSpace;
        $this->_settings->fileCount += count($APIresponse);
        $this->_settings->totalOriginal += $originalSpace;
        $this->_settings->totalOptimized += $optimizedSpace;
        $meta = $itemHandler->getMeta();
        if ($meta->getThumbsTodo()) {
            $percentImprovement = $meta->getImprovementPercent();
        }
        $png2jpg = $meta->getPng2Jpg();
        $png2jpg = is_array($png2jpg) ? $png2jpg['optimizationPercent'] : 0;
        $meta->setMessage($originalSpace ? number_format(100.0 * (1.0 - $optimizedSpace / $originalSpace), 2) : "Couldn't compute thumbs optimization percent. Main image: " . $percentImprovement);
        WPShortPixel::log("HANDLE SUCCESS: Image optimization: " . $meta->getMessage());
        $meta->setCompressionType($compressionType);
        $meta->setCompressedSize(@filesize($meta->getPath()));
        $meta->setKeepExif($this->_settings->keepExif);
        $meta->setCmyk2rgb($this->_settings->CMYKtoRGBconversion);
        $meta->setTsOptimized(date("Y-m-d H:i:s"));
        $meta->setThumbsOptList(is_array($meta->getThumbsOptList()) ? array_unique(array_merge($meta->getThumbsOptList(), $thumbsOptList)) : $thumbsOptList);
        $meta->setThumbsOpt($meta->getThumbsTodo() || $this->_settings->processThumbnails ? count($meta->getThumbsOptList()) : 0);
        $meta->setRetinasOpt($retinas);
        if (null !== $this->_settings->excludeSizes) {
            $meta->setExcludeSizes($this->_settings->excludeSizes);
        }
        $meta->setThumbsTodo(false);
        if ($width && $height) {
            $meta->setActualWidth($width);
            $meta->setActualHeight($height);
        }
        $meta->setRetries($meta->getRetries() + 1);
        $meta->setBackup(!$NoBackup);
        $meta->setStatus(2);
        if ($do_resize) {
            $resizeWidth = $settings->resizeWidth;
            $resizeHeight = $settings->resizeHeight;
            if ($resizeWidth == $width || $resizeHeight == $height) {
                $meta->setResizeWidth($width);
                $meta->setResizeHeight($height);
                $meta->setResize(true);
            } else {
                $meta->setResize(false);
            }
        }
        $itemHandler->updateMeta($meta);
        $itemHandler->optimizationSucceeded();
        $itemHandler->deleteItemCache();
        Log::addDebug("HANDLE SUCCESS: Metadata saved.");
        if (!$originalSpace) {
            throw new Exception("OriginalSpace = 0. APIResponse" . json_encode($APIresponse));
        }
        $this->_settings->apiRetries = 0;
        return array("Status" => self::STATUS_SUCCESS, "Message" => 'Success: No pixels remained unsqueezed :-)', "PercentImprovement" => $originalSpace ? number_format(100.0 * (1.0 - (1.0 - $png2jpg / 100.0) * $optimizedSpace / $originalSpace), 2) : "Couldn't compute thumbs optimization percent. Main image: " . $percentImprovement);
    }
    protected static function cleanupTemporaryFiles($archive, $tempFiles)
    {
        if ($archive) {
            ShortpixelFolder::deleteFolder($archive['Path']);
        } else {
            if (!empty($tempFiles) && is_array($tempFiles)) {
                foreach ($tempFiles as $tmpFile) {
                    $filepath = isset($tmpFile['Message']) ? $tmpFile['Message'] : false;
                    if ($filepath) {
                        $file = \wpSPIO()->filesystem()->getFile($filepath);
                        if ($file->exists()) {
                            $file->delete();
                        }
                    }
                }
            }
        }
    }
    public static function MB_basename($Path, $suffix = false)
    {
        $Separator = " qq ";
        $qqPath = preg_replace("/[^ ]/u", $Separator . "\$0" . $Separator, $Path);
        if (!$qqPath) {
            $pathAr = explode(DIRECTORY_SEPARATOR, $Path);
            $fileName = end($pathAr);
            $pos = strpos($fileName, $suffix);
            if ($pos !== false) {
                return substr($fileName, 0, $pos);
            }
            return $fileName;
        }
        $suffix = preg_replace("/[^ ]/u", $Separator . "\$0" . $Separator, $suffix);
        $Base = basename($qqPath, $suffix);
        $Base = str_replace($Separator, "", $Base);
        return $Base;
    }
    public static function CheckAndFixImagePaths($PATHs)
    {
        $ErrorCount = 0;
        $Tmp = explode("/", SHORTPIXEL_UPLOADS_BASE);
        $TmpCount = count($Tmp);
        $StichString = $Tmp[$TmpCount - 2] . "/" . $Tmp[$TmpCount - 1];
        $missingFiles = array();
        foreach ($PATHs as $Id => $File) {
            if (!apply_filters('shortpixel_image_exists', file_exists($File), $File, null)) {
                $NewFile = SHORTPIXEL_UPLOADS_BASE . substr($File, strpos($File, $StichString) + strlen($StichString));
                if (file_exists($NewFile)) {
                    $PATHs[$Id] = $NewFile;
                } else {
                    $NewFile = SHORTPIXEL_UPLOADS_BASE . "/" . $File;
                    if (file_exists($NewFile)) {
                        $PATHs[$Id] = $NewFile;
                    } else {
                        $missingFiles[] = $File;
                        $ErrorCount++;
                    }
                }
            }
        }
        if ($ErrorCount > 0) {
            return array("error" => $missingFiles);
        } else {
            return $PATHs;
        }
    }
    public static function getCompressionTypeName($compressionType)
    {
        if (is_array($compressionType)) {
            return array_map(array('ShortPixelAPI', 'getCompressionTypeName'), $compressionType);
        }
        return 0 + $compressionType == 2 ? 'glossy' : (0 + $compressionType == 1 ? 'lossy' : 'lossless');
    }
    public static function getCompressionTypeCode($compressionName)
    {
        return $compressionName == 'glossy' ? 2 : ($compressionName == 'lossy' ? 1 : 0);
    }
}