File: //proc/self/cwd/wp-content/plugins/crop-thumbnails/functions/save.php
<?php
namespace crop_thumbnails;
add_action('after_setup_theme', function() {
//Add the ajax action for entring the cropping function.
add_action( 'wp_ajax_cptSaveThumbnail', [CptSaveThumbnail::class, 'saveThumbnailAjaxWrap'], 10);
//Add the crop_thumbnails_do_crop filter for the default behaiviour of the plugin.
//You may exchange it with your own function, by removing the default filter and store your own.
add_filter( 'crop_thumbnails_do_crop', [CptSaveThumbnail::class, 'filter_doWpCrop'], 10, 5);
//add the function to determine if an old file should be deleted
add_filter( 'crop_thumbnails_should_delete_old_file', [CptSaveThumbnail::class, 'filter_shouldDeleteOldFile'], 10, 4);
}, 10);
class CptSaveThumbnail {
protected static $debug = [];
public static function checkRestPermission() {
$return = false;
$cropThumbnailSettings = $GLOBALS['CROP_THUMBNAILS_HELPER']->getOptions();
if(current_user_can('edit_files')) {
$return = true;
}
if(current_user_can('upload_files') && empty($cropThumbnailSettings['user_permission_only_on_edit_files'])) {
$return = true;
}
return $return;
}
/**
* Handle-function called via ajax request.
* Check and crop multiple images. Update with wp_update_attachment_metadata if needed.
* Input parameters:
* * $_REQUEST['selection'] - json-object - data of the selection/crop
* * $_REQUEST['sourceImageId'] - int - the ID of the original image
* * $_REQUEST['activeImageSizes'] - json-array - array with data of the images to crop
* The main code is wraped via try-catch - the errorMessage will send back to JavaScript for displaying in an alert-box.
* @return array JSON-Object with the result of the cropping
*/
public static function saveThumbnail($request) {
if(!function_exists('wp_crop_image')) require_once(ABSPATH . 'wp-admin/includes/image.php');
$jsonResult = [];
$settings = $GLOBALS['CROP_THUMBNAILS_HELPER']->getOptions();
try {
$input = self::getValidatedInput($request);
self::addDebug('validated input data');
self::addDebug($input);
$sourceImgPath = get_attached_file( $input->sourceImageId );
if(empty($sourceImgPath)) {
throw new \Exception(__("ERROR: Can't find original image file!",'crop-thumbnails'), 1);
}
$imageMetadata = wp_get_attachment_metadata($input->sourceImageId, true);//get the attachement metadata of the post
if(empty($imageMetadata)) {
throw new \Exception(__("ERROR: Can't find original image metadata!",'crop-thumbnails'), 1);
}
//from DB
$dbImageSizes = $GLOBALS['CROP_THUMBNAILS_HELPER']->getImageSizes();
/**
* will be filled with the new image-url if the image format isn't in the attachements metadata,
* and Wordpress doesn't know about the image file
*/
$changedImageName = [];
$_processing_error = [];
$_activeImageSizes = apply_filters('crop_thumbnails_active_image_sizes', $input->activeImageSizes);
foreach($_activeImageSizes as $activeImageSize) {
if(!self::isImageSizeValid($activeImageSize,$dbImageSizes)) {
self::addDebug("Image size not valid.");
continue;
}
$croppedSize = self::getCroppedSize($activeImageSize, $imageMetadata, $input);
$currentFilePath = self::generateFilename($sourceImgPath, $imageMetadata, $croppedSize['width'], $croppedSize['height'], $activeImageSize->crop);
self::addDebug("filename: ".$currentFilePath);
$currentFilePathInfo = pathinfo($currentFilePath);
$currentFilePathInfo['basename'] = wp_basename($currentFilePath);//uses the i18n version of the file-basename
$temporaryCopyFile = $GLOBALS['CROP_THUMBNAILS_HELPER']->getUploadDir().DIRECTORY_SEPARATOR.$currentFilePathInfo['basename'];
do_action('crop_thumbnails_before_crop', $input, $croppedSize, $temporaryCopyFile, $currentFilePath);
$resultWpCropImage = apply_filters('crop_thumbnails_do_crop', null, $input, $croppedSize, $temporaryCopyFile, $currentFilePath);
do_action('crop_thumbnails_after_crop', $input, $croppedSize, $temporaryCopyFile, $currentFilePath, $resultWpCropImage);
$oldFile_toDelete = '';
if(empty($imageMetadata['sizes'][$activeImageSize->name])) {
//image-size not yet existant
self::addDebug('Image filename has changed ('.$activeImageSize->name . ')');
$changedImageName[ $activeImageSize->name ] = true;
} elseif( apply_filters('crop_thumbnails_should_delete_old_file',
false,//default value
$imageMetadata['sizes'][$activeImageSize->name],
$activeImageSize,
$currentFilePath
) ) {
//the old file of this image-size needs to be deleted
$oldFile_toDelete = $imageMetadata['sizes'][$activeImageSize->name]['file'];
$changedImageName[ $activeImageSize->name ] = true;
}
$_error = false;
if(empty($resultWpCropImage) || is_wp_error($resultWpCropImage)) {
$_processing_error[$activeImageSize->name][] = sprintf(__("Can't generate filesize '%s'.",'crop-thumbnails'), $activeImageSize->name);
$_error = true;
} else {
if(!empty($oldFile_toDelete)) {
self::addDebug("delete old image:".$oldFile_toDelete);
@unlink($currentFilePathInfo['dirname'].DIRECTORY_SEPARATOR.$oldFile_toDelete);
}
if(!@rename($resultWpCropImage, $currentFilePath)) {
$_processing_error[$activeImageSize->name][] = __("Can't copy temporary file to media library.", 'crop-thumbnails');
$_error = true;
}
if(file_exists($resultWpCropImage) && !@unlink($resultWpCropImage)) {
$_processing_error[$activeImageSize->name][] = __("Can't delete temporary file.", 'crop-thumbnails');
$_error = true;
}
}
if(!$_error) {
//update metadata --> otherwise new sizes will not be updated
$imageMetadata = self::createNewMetadata($imageMetadata, $activeImageSize->name, $currentFilePathInfo, $croppedSize['width'], $croppedSize['height'], $input);
} else {
self::addDebug('error on '.$currentFilePathInfo['basename']);
self::addDebug($_processing_error);
}
}//END foreach
//we have to update the posts metadate
//otherwise new sizes will not be updated
$imageMetadata = apply_filters('crop_thumbnails_before_update_metadata', $imageMetadata, $input->sourceImageId);
wp_update_attachment_metadata( $input->sourceImageId, $imageMetadata);
self::addDebug('metadata updated:');
self::addDebug($imageMetadata);
//generate result;
if(!empty($changedImageName)) {
//there was a change in the image-formats
foreach($changedImageName as $key=>$value) {
$newImageLocation = wp_get_attachment_image_src($input->sourceImageId, $key);
$changedImageName[ $key ] = $newImageLocation[0];
}
$jsonResult['changedImageName'] = $changedImageName;
}
if(!empty($_processing_error)) {//one or more errors happend when generating thumbnails
$jsonResult['processingErrors'] = $_processing_error;
}
if(!empty($settings['debug_data'])) {
$jsonResult['debug'] = self::getDebug();
}
$jsonResult['success'] = time();//time for cache-breaker
return $jsonResult;
} catch (\Exception $e) {
if(!empty($settings['debug_data'])) {
$jsonResult['debug'] = self::getDebug();
}
$jsonResult['error'] = $e->getMessage();
return $jsonResult;
}
}
/**
* Filter function to determine whether we should delete old thumbnail file.
*
* We should delete when any of these happens:
* - the old size hasn't got the right image-size/image-ratio
* - the new image has a different file path
*
* Otherwise, nobody will ever delete it correctly.
*
* @param bool $baseResult Filter base value
* @param array $oldSizeMetadata The old image size from the database
* @param object $activeImageSize The image size that should be used
* @param string $activeImageFilePath Full path to the new image
* @return bool
*/
public static function filter_shouldDeleteOldFile($baseResult, $oldSizeMetadata, $activeImageSize, $activeImageFilePath) {
$result = absint($oldSizeMetadata['width']) !== absint($activeImageSize->width)
|| absint($oldSizeMetadata['height']) !== absint($activeImageSize->height)
|| wp_basename($activeImageFilePath) !== $oldSizeMetadata['file'];
//error_log('filter_shouldDeleteOldFile: '. ($result ? 'YES' : 'NO') );
return $result;
}
/**
* This is the place where crop-thumbnails crops the images - using the wordpress default function.
*
* @param bool $baseResult Filter base value
* @param object $input Input object
* @param object $croppedSize Target size of the result image
* @param object $temporaryCopyFile Target file-path
* @param object $currentFilePath Additional file-path of the current image
*
*/
public static function filter_doWpCrop($baseResult, $input, $croppedSize, $temporaryCopyFile, $currentFilePath) {
$input = apply_filters('crop_thumbnails_optimize_input_before_crop', $input);
$src = wp_get_original_image_path($input->sourceImageId);
return \wp_crop_image( // * @return string|WP_Error|false New filepath on success, WP_Error or false on failure.
$src, // * @param string|int $src The source file or Attachment ID.
$input->selection->x, // * @param int $src_x The start x position to crop from.
$input->selection->y, // * @param int $src_y The start y position to crop from.
$input->selection->x2 - $input->selection->x, // * @param int $src_w The width to crop.
$input->selection->y2 - $input->selection->y, // * @param int $src_h The height to crop.
$croppedSize['width'], // * @param int $dst_w The destination width.
$croppedSize['height'], // * @param int $dst_h The destination height.
false, // * @param int $src_abs Optional. If the source crop points are absolute.
$temporaryCopyFile // * @param string $dst_file Optional. The destination file to write to.
);
}
/**
* Get the end-size of the cropped image in pixels.
* Attention: these sizes are used to name the file.
* @param object $activeImageSize The image size that should be used
* @param [type] $imageMetadata [description]
* @param [type] $input [description]
* @return {[type] [description]
*/
public static function getCroppedSize($activeImageSize,$imageMetadata,$input) {
//set target size of the cropped image
$croppedWidth = $activeImageSize->width;
$croppedHeight = $activeImageSize->height;
try {
if($activeImageSize->width===9999) {
$croppedWidth = intval($imageMetadata['width']);
} elseif($activeImageSize->height===9999) {
$croppedHeight = intval($imageMetadata['height']);
} elseif(intval($activeImageSize->width)===0 && intval($activeImageSize->height)===0) {
$croppedWidth = $input->selection->x2 - $input->selection->x;
$croppedHeight = $input->selection->y2 - $input->selection->y;
} elseif(intval($activeImageSize->width)===0) {
$croppedWidth = intval(( intval($imageMetadata['width']) / intval($imageMetadata['height']) ) * $activeImageSize->height);
$croppedHeight = $activeImageSize->height;
} elseif(intval($activeImageSize->height)===0) {
$croppedWidth = $activeImageSize->width;
$croppedHeight = intval(( intval($imageMetadata['height']) / intval($imageMetadata['width']) ) * $activeImageSize->width);
}
/* --- no need to use that ---
if(!$activeImageSize->crop) {
$croppedWidth = $input->selection->x2 - $input->selection->x;
$croppedHeight = $input->selection->y2 - $input->selection->y;
}*/
} catch(\Exception $e) {
$croppedWidth = 10;
$croppedHeight = 10;
}
return ['width' => $croppedWidth, 'height'=> $croppedHeight];
}
protected static function addDebug($text) {
self::$debug[] = $text;
}
protected static function getDebug() {
if(!empty(self::$debug)) {
return self::$debug;
}
return [];
}
/**
* Update the metadata for one image-size.
*
* @param array $imageMetadata the image-metadata base array to modify
* @param string $imageSizeName the name of the image-size
* @param array $currentFilePathInfo pathinfo of the new thumbnail/image-size
* @param int $croppedWidth the new width of the image
* @param int $croppedHeight the new height of the image
* @param object $croppingInput the input data for the cropping (to store the crop-informations)
* @return array the modified $imageMetadata
*/
protected static function createNewMetadata($imageMetadata, $imageSizeName, $currentFilePathInfo, $croppedWidth, $croppedHeight, $croppingInput) {
$fullFilePath = trailingslashit($currentFilePathInfo['dirname']) . $currentFilePathInfo['basename'];
$fileTypeInformations = wp_check_filetype($fullFilePath);
$newValues = [];
$newValues['file'] = $currentFilePathInfo['basename'];
$newValues['width'] = intval($croppedWidth);
$newValues['height'] = intval($croppedHeight);
$newValues['mime-type'] = $fileTypeInformations['type'];
$newValues['cpt_last_cropping_data'] = [
'version' => 2,//the version of the following data --> version 1 had no version information
/**
* version 1 had no version information and contained the following data:
* 'x' => $croppingInput->selection->x,
* 'y' => $croppingInput->selection->y,
* 'x2' => $croppingInput->selection->x2,
* 'y2' => $croppingInput->selection->y2,
* 'original_width' => $imageMetadata['width'],
* 'original_height' => $imageMetadata['height'],
*
* version 2 contains the following data:
* 'version' => 2,
* 'x' => $croppingInput->selection->x,
* 'y' => $croppingInput->selection->y,
* 'x2' => $croppingInput->selection->x2,
* 'y2' => $croppingInput->selection->y2
* original_width and original_height are not stored anymore, as the plugin will always use the original image for cropping
*/
'x' => $croppingInput->selection->x,
'y' => $croppingInput->selection->y,
'x2' => $croppingInput->selection->x2,
'y2' => $croppingInput->selection->y2
];
$oldValues = [];
if(empty($imageMetadata['sizes'])) {
$imageMetadata['sizes'] = [];
}
if(!empty($imageMetadata['sizes'][$imageSizeName])) {
$oldValues = $imageMetadata['sizes'][$imageSizeName];
}
$imageMetadata['sizes'][$imageSizeName] = array_merge($oldValues,$newValues);
do_action('crop_thumbnails_after_save_new_thumb', $fullFilePath, $imageSizeName, $imageMetadata['sizes'][$imageSizeName] );
return apply_filters('crop_thumbnails_create_new_metadata', $imageMetadata, $imageSizeName, $currentFilePathInfo, $croppedWidth, $croppedHeight, $croppingInput );
}
/**
* @param object data of the new ImageSize the user want to crop
* @param array all available ImageSizes
* @return boolean true if the newImageSize is in the list of ImageSizes and dimensions are correct
*/
protected static function isImageSizeValid(&$submitted,$dbData) {
if(empty($submitted->name)) {
return false;
}
if(empty($dbData[$submitted->name])) {
return false;
}
//restore the default data just to make sure nothing is compromited
$submitted->crop = empty($dbData[$submitted->name]['crop']) ? 0 : 1;
$submitted->width = $dbData[$submitted->name]['width'];
$submitted->height = $dbData[$submitted->name]['height'];
//eventually we want to test some more later
return true;
}
/**
* Some basic validations and value transformations
* @return object JSON-Object with submitted data
* @throw Exception if the security validation fails
*/
protected static function getValidatedInput($request) {
$input = json_decode( $request->get_body(), false );
if(empty($input) || empty($input->crop_thumbnails)) {
throw new \Exception(__('ERROR: Submitted data is incomplete.','crop-thumbnails'), 1);
}
$input = $input->crop_thumbnails;
$input = apply_filters('crop_thumbnails_before_get_validated_input', $input);
if(empty($input->selection) || empty($input->sourceImageId) || !isset($input->activeImageSizes)) {
throw new \Exception(__('ERROR: Submitted data is incomplete.','crop-thumbnails'), 1);
}
if(!self::isUserPermitted($input->sourceImageId)) {
throw new \Exception(__("You are not permitted to crop the thumbnails.",'crop-thumbnails'), 1);
}
if(!isset($input->selection->x) || !isset($input->selection->y) || !isset($input->selection->x2) || !isset($input->selection->y2)) {
throw new \Exception(__('ERROR: Submitted data is incomplete.','crop-thumbnails'), 1);
}
$input->selection->x = intval($input->selection->x);
$input->selection->y = intval($input->selection->y);
$input->selection->x2 = intval($input->selection->x2);
$input->selection->y2 = intval($input->selection->y2);
if($input->selection->x < 0) $input->selection->x = 0;
if($input->selection->y < 0) $input->selection->y = 0;
if($input->selection->x2 < 0) $input->selection->x2 = 0;
if($input->selection->y2 < 0) $input->selection->y2 = 0;
$input->sourceImageId = intval($input->sourceImageId);
$_tmp = get_post($input->sourceImageId);//need to be its own var - cause of old php versions
if(empty($_tmp)) {
throw new \Exception(__("ERROR: Can't find original image in database!",'crop-thumbnails'), 1);
}
return apply_filters('crop_thumbnails_after_get_validated_input', $input);
}
/**
* Generate the Filename (and path) of the thumbnail based on width and height the same way as WordPress do.
* @see generate_filename in wp-includes/class-wp-image-editor.php
* @param string $file Path to the original (full-size) file.
* @param array $imageMetadata the WordPress image-metadata array
* @param int $width width of the new image
* @param int $height height of the new image
* @param boolean $crop is this a cropped image-size
* @return string path to the new image
*/
protected static function generateFilename( $file, $imageMetadata, $w, $h, $crop ){
$info = pathinfo($file);
$dir = $info['dirname'];
$ext = $info['extension'];
/**
* since WordPress 5.8 the image extension / MIME type may differ from that of the
* original file so we'll use the below hook to check if any defaults are overwritten.
*/
$fileTypeInformations = wp_check_filetype($file);
$outputFormats = apply_filters('image_editor_output_format', [], $file, $fileTypeInformations['type']);
if(isset($outputFormats[$fileTypeInformations['type']])) {
$ext = array_search($outputFormats[$fileTypeInformations['type']], wp_get_mime_types(), true);
}
$name = wp_basename($file, '.'.$ext);
if(!empty($imageMetadata['original_image'])) {
$name = wp_basename($imageMetadata['original_image'], '.'.$ext);
}
$suffix = $w.'x'.$h;
$destfilename = $dir.'/'.$name.'-'.$suffix.'.'.$ext;
return apply_filters('crop_thumbnails_filename', $destfilename, $file, $w, $h, $crop, $info, $imageMetadata);
}
/**
* Check if the user is permitted to crop the thumbnails.
*
* You may override the default result of this function by using the filter 'crop_thumbnails_user_permission_check'.
*
* @param int $imageId The ID of the image that should be cropped (not used in default test)
* @return boolean true if the user is permitted
*/
public static function isUserPermitted($imageId) {
$return = self::checkRestPermission();
return apply_filters('crop_thumbnails_user_permission_check', $return, $imageId);
}
}