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