<?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); } }