PluginProbe
Imsanity / 2.8.4
Imsanity v2.8.4
2.9.4 2.9.3 2.9.2 trunk 2.4.3 2.5.0 2.6.0 2.7.0 2.7.2 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.9.0 2.9.1
imsanity / imsanity.php

imsanity.php in Imsanity 2.8.4, at imsanity.php

380 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main file for Imsanity plugin.
4 *
5 * This file includes the core of Imsanity and the top-level image handler.
6 *
7 * @link https://wordpress.org/plugins/imsanity/
8 * @package Imsanity
9 */
10
11 /*
12 Plugin Name: Imsanity
13 Plugin URI: https://wordpress.org/plugins/imsanity/
14 Description: Imsanity stops insanely huge image uploads
15 Author: Exactly WWW
16 Domain Path: /languages
17 Version: 2.8.4
18 Requires at least: 6.2
19 Requires PHP: 7.4
20 Author URI: https://ewww.io/about/
21 License: GPLv3
22 */
23
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 define( 'IMSANITY_VERSION', '2.8.4' );
29 define( 'IMSANITY_SCHEMA_VERSION', '1.1' );
30
31 define( 'IMSANITY_DEFAULT_MAX_WIDTH', 1920 );
32 define( 'IMSANITY_DEFAULT_MAX_HEIGHT', 1920 );
33 define( 'IMSANITY_DEFAULT_BMP_TO_JPG', true );
34 define( 'IMSANITY_DEFAULT_PNG_TO_JPG', false );
35 define( 'IMSANITY_DEFAULT_QUALITY', 82 );
36
37 define( 'IMSANITY_SOURCE_POST', 1 );
38 define( 'IMSANITY_SOURCE_LIBRARY', 2 );
39 define( 'IMSANITY_SOURCE_OTHER', 4 );
40
41 if ( ! defined( 'IMSANITY_AJAX_MAX_RECORDS' ) ) {
42 define( 'IMSANITY_AJAX_MAX_RECORDS', 250 );
43 }
44
45 /**
46 * The full path of the main plugin file.
47 *
48 * @var string IMSANITY_PLUGIN_FILE
49 */
50 define( 'IMSANITY_PLUGIN_FILE', __FILE__ );
51 /**
52 * The path of the main plugin file, relative to the plugins/ folder.
53 *
54 * @var string IMSANITY_PLUGIN_FILE_REL
55 */
56 define( 'IMSANITY_PLUGIN_FILE_REL', plugin_basename( __FILE__ ) );
57
58 /**
59 * Load translations for Imsanity.
60 */
61 function imsanity_init() {
62 load_plugin_textdomain( 'imsanity', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
63 }
64
65 /**
66 * Import supporting libraries.
67 */
68 require_once plugin_dir_path( __FILE__ ) . 'libs/utils.php';
69 require_once plugin_dir_path( __FILE__ ) . 'settings.php';
70 require_once plugin_dir_path( __FILE__ ) . 'ajax.php';
71 require_once plugin_dir_path( __FILE__ ) . 'media.php';
72 if ( defined( 'WP_CLI' ) && WP_CLI ) {
73 require_once plugin_dir_path( __FILE__ ) . 'class-imsanity-cli.php';
74 }
75
76 /**
77 * Use the EWWW IO debugging functions (if available).
78 *
79 * @param string $message A message to send to the debugger.
80 */
81 function imsanity_debug( $message ) {
82 if ( function_exists( 'ewwwio_debug_message' ) ) {
83 if ( ! is_string( $message ) ) {
84 if ( function_exists( 'print_r' ) ) {
85 $message = print_r( $message, true );
86 } else {
87 $message = 'not a string, print_r disabled';
88 }
89 }
90 ewwwio_debug_message( $message );
91 if ( function_exists( 'ewww_image_optimizer_debug_log' ) ) {
92 ewww_image_optimizer_debug_log();
93 }
94 }
95 }
96
97 /**
98 * Inspects the request and determines where the upload came from.
99 *
100 * @return IMSANITY_SOURCE_POST | IMSANITY_SOURCE_LIBRARY | IMSANITY_SOURCE_OTHER
101 */
102 function imsanity_get_source() {
103 imsanity_debug( __FUNCTION__ );
104 $id = array_key_exists( 'post_id', $_REQUEST ) ? (int) $_REQUEST['post_id'] : ''; // phpcs:ignore WordPress.Security.NonceVerification
105 $action = ! empty( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
106 imsanity_debug( "getting source for id=$id and action=$action" );
107
108 // Uncomment this (and remove the trailing .) to temporarily check the full $_SERVER vars.
109 // imsanity_debug( $_SERVER );.
110 $referer = '';
111 if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
112 $referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
113 imsanity_debug( "http_referer: $referer" );
114 }
115
116 $request_uri = wp_referer_field( false );
117 imsanity_debug( "request URI: $request_uri" );
118
119 // A post_id indicates image is attached to a post.
120 if ( $id > 0 ) {
121 imsanity_debug( 'from a post (id)' );
122 return IMSANITY_SOURCE_POST;
123 }
124
125 // If the referrer is the post editor, that's a good indication the image is attached to a post.
126 if ( false !== strpos( $referer, '/post.php' ) ) {
127 imsanity_debug( 'from a post.php' );
128 return IMSANITY_SOURCE_POST;
129 }
130 // If the referrer is the (new) post editor, that's a good indication the image is attached to a post.
131 if ( false !== strpos( $referer, '/post-new.php' ) ) {
132 imsanity_debug( 'from a new post' );
133 return IMSANITY_SOURCE_POST;
134 }
135
136 // Post_id of 0 is 3.x otherwise use the action parameter.
137 if ( 0 === $id || 'upload-attachment' === $action ) {
138 imsanity_debug( 'from the library' );
139 return IMSANITY_SOURCE_LIBRARY;
140 }
141
142 // We don't know where this one came from but $_REQUEST['_wp_http_referer'] may contain info.
143 imsanity_debug( 'unknown source' );
144 return IMSANITY_SOURCE_OTHER;
145 }
146
147 /**
148 * Given the source, returns the max width/height.
149 *
150 * @example: list( $w, $h ) = imsanity_get_max_width_height( IMSANITY_SOURCE_LIBRARY );
151 * @param int $source One of IMSANITY_SOURCE_POST | IMSANITY_SOURCE_LIBRARY | IMSANITY_SOURCE_OTHER.
152 */
153 function imsanity_get_max_width_height( $source ) {
154 $w = (int) imsanity_get_option( 'imsanity_max_width', IMSANITY_DEFAULT_MAX_WIDTH );
155 $h = (int) imsanity_get_option( 'imsanity_max_height', IMSANITY_DEFAULT_MAX_HEIGHT );
156
157 switch ( $source ) {
158 case IMSANITY_SOURCE_POST:
159 break;
160 case IMSANITY_SOURCE_LIBRARY:
161 $w = (int) imsanity_get_option( 'imsanity_max_width_library', $w );
162 $h = (int) imsanity_get_option( 'imsanity_max_height_library', $h );
163 break;
164 default:
165 $w = (int) imsanity_get_option( 'imsanity_max_width_other', $w );
166 $h = (int) imsanity_get_option( 'imsanity_max_height_other', $h );
167 break;
168 }
169
170 // NOTE: filters MUST return an array of 2 items, or the defaults will be used.
171 return apply_filters( 'imsanity_get_max_width_height', array( $w, $h ), $source );
172 }
173
174 /**
175 * Handler after a file has been uploaded. If the file is an image, check the size
176 * to see if it is too big and, if so, resize and overwrite the original.
177 *
178 * @param Array $params The parameters submitted with the upload.
179 */
180 function imsanity_handle_upload( $params ) {
181
182 // If "noresize" is included in the filename then we will bypass imsanity scaling.
183 if ( strpos( $params['file'], 'noresize' ) !== false ) {
184 return $params;
185 }
186
187 if ( apply_filters( 'imsanity_skip_image', false, $params['file'] ) ) {
188 return $params;
189 }
190
191 // If preferences specify so then we can convert an original bmp or png file into jpg.
192 if ( ( 'image/bmp' === $params['type'] || 'image/x-ms-bmp' === $params['type'] ) && imsanity_get_option( 'imsanity_bmp_to_jpg', IMSANITY_DEFAULT_BMP_TO_JPG ) ) {
193 $params = imsanity_convert_to_jpg( 'bmp', $params );
194 }
195
196 if ( 'image/png' === $params['type'] && imsanity_get_option( 'imsanity_png_to_jpg', IMSANITY_DEFAULT_PNG_TO_JPG ) ) {
197 $params = imsanity_convert_to_jpg( 'png', $params );
198 }
199
200 // Make sure this is a type of image that we want to convert and that it exists.
201 $oldpath = $params['file'];
202
203 // Let folks filter the allowed mime-types for resizing.
204 $allowed_types = apply_filters( 'imsanity_allowed_mimes', array( 'image/png', 'image/gif', 'image/jpeg' ), $oldpath );
205 if ( is_string( $allowed_types ) ) {
206 $allowed_types = array( $allowed_types );
207 } elseif ( ! is_array( $allowed_types ) ) {
208 $allowed_types = array();
209 }
210
211 if (
212 ( ! is_wp_error( $params ) ) &&
213 is_file( $oldpath ) &&
214 is_readable( $oldpath ) &&
215 is_writable( $oldpath ) &&
216 filesize( $oldpath ) > 0 &&
217 in_array( $params['type'], $allowed_types, true )
218 ) {
219
220 // figure out where the upload is coming from.
221 $source = imsanity_get_source();
222
223 $maxw = IMSANITY_DEFAULT_MAX_WIDTH;
224 $maxh = IMSANITY_DEFAULT_MAX_HEIGHT;
225 $max_width_height = imsanity_get_max_width_height( $source );
226 if ( is_array( $max_width_height ) && 2 === count( $max_width_height ) ) {
227 list( $maxw, $maxh ) = $max_width_height;
228 }
229 $maxw = (int) $maxw;
230 $maxh = (int) $maxh;
231
232 list( $oldw, $oldh ) = getimagesize( $oldpath );
233
234 if ( ( $oldw > $maxw + 1 && $maxw > 0 ) || ( $oldh > $maxh + 1 && $maxh > 0 ) ) {
235 $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
236
237 $ftype = imsanity_quick_mimetype( $oldpath );
238 $orientation = imsanity_get_orientation( $oldpath, $ftype );
239 // If we are going to rotate the image 90 degrees during the resize, swap the existing image dimensions.
240 if ( 6 === (int) $orientation || 8 === (int) $orientation ) {
241 $old_oldw = $oldw;
242 $oldw = $oldh;
243 $oldh = $old_oldw;
244 }
245
246 if ( $maxw > 0 && $maxh > 0 && $oldw >= $maxw && $oldh >= $maxh && ( $oldh > $maxh || $oldw > $maxw ) && apply_filters( 'imsanity_crop_image', false ) ) {
247 $neww = $maxw;
248 $newh = $maxh;
249 } else {
250 list( $neww, $newh ) = wp_constrain_dimensions( $oldw, $oldh, $maxw, $maxh );
251 }
252
253 global $ewww_preempt_editor;
254 if ( ! isset( $ewww_preempt_editor ) ) {
255 $ewww_preempt_editor = false;
256 }
257 $original_preempt = $ewww_preempt_editor;
258 $ewww_preempt_editor = true;
259 $resizeresult = imsanity_image_resize( $oldpath, $neww, $newh, apply_filters( 'imsanity_crop_image', false ), null, null, $quality );
260 $ewww_preempt_editor = $original_preempt;
261
262 if ( $resizeresult && ! is_wp_error( $resizeresult ) ) {
263 $newpath = $resizeresult;
264
265 if ( is_file( $newpath ) && filesize( $newpath ) < filesize( $oldpath ) ) {
266 // We saved some file space. remove original and replace with resized image.
267 unlink( $oldpath );
268 rename( $newpath, $oldpath );
269 } elseif ( is_file( $newpath ) ) {
270 // The resized image is actually bigger in filesize (most likely due to jpg quality).
271 // Keep the old one and just get rid of the resized image.
272 unlink( $newpath );
273 }
274 } elseif ( false === $resizeresult ) {
275 return $params;
276 } elseif ( is_wp_error( $resizeresult ) ) {
277 // resize didn't work, likely because the image processing libraries are missing.
278 // remove the old image so we don't leave orphan files hanging around.
279 unlink( $oldpath );
280
281 $params = wp_handle_upload_error(
282 $oldpath,
283 sprintf(
284 /* translators: 1: error message 2: link to support forums */
285 esc_html__( 'Imsanity was unable to resize this image for the following reason: %1$s. If you continue to see this error message, you may need to install missing server components. If you think you have discovered a bug, please report it on the Imsanity support forum: %2$s', 'imsanity' ),
286 $resizeresult->get_error_message(),
287 'https://wordpress.org/support/plugin/imsanity'
288 )
289 );
290 } else {
291 return $params;
292 }
293 }
294 }
295 clearstatcache();
296 return $params;
297 }
298
299
300 /**
301 * Read in the image file from the params and then save as a new jpg file.
302 * if successful, remove the original image and alter the return
303 * parameters to return the new jpg instead of the original
304 *
305 * @param string $type Type of the image to be converted: 'bmp' or 'png'.
306 * @param array $params The upload parameters.
307 * @return array altered params
308 */
309 function imsanity_convert_to_jpg( $type, $params ) {
310
311 if ( apply_filters( 'imsanity_disable_convert', false, $type, $params ) ) {
312 return $params;
313 }
314
315 $img = null;
316
317 if ( 'bmp' === $type ) {
318 if ( ! function_exists( 'imagecreatefrombmp' ) ) {
319 return $params;
320 }
321 $img = imagecreatefrombmp( $params['file'] );
322 } elseif ( 'png' === $type ) {
323 // Prevent converting PNG images with alpha/transparency, unless overridden by the user.
324 if ( apply_filters( 'imsanity_skip_alpha', imsanity_has_alpha( $params['file'] ), $params['file'] ) ) {
325 return $params;
326 }
327 if ( ! function_exists( 'imagecreatefrompng' ) ) {
328 return wp_handle_upload_error( $params['file'], esc_html__( 'Imsanity requires the GD library to convert PNG images to JPG', 'imsanity' ) );
329 }
330
331 $input = imagecreatefrompng( $params['file'] );
332 // convert png transparency to white.
333 $img = imagecreatetruecolor( imagesx( $input ), imagesy( $input ) );
334 imagefill( $img, 0, 0, imagecolorallocate( $img, 255, 255, 255 ) );
335 imagealphablending( $img, true );
336 imagecopy( $img, $input, 0, 0, 0, 0, imagesx( $input ), imagesy( $input ) );
337 } else {
338 return wp_handle_upload_error( $params['file'], esc_html__( 'Unknown image type specified in imsanity_convert_to_jpg', 'imsanity' ) );
339 }
340
341 // We need to change the extension from the original to .jpg so we have to ensure it will be a unique filename.
342 $uploads = wp_upload_dir();
343 $oldfilename = wp_basename( $params['file'] );
344 $newfilename = wp_basename( str_ireplace( '.' . $type, '.jpg', $oldfilename ) );
345 $newfilename = wp_unique_filename( $uploads['path'], $newfilename );
346
347 $quality = imsanity_get_option( 'imsanity_quality', IMSANITY_DEFAULT_QUALITY );
348
349 if ( imagejpeg( $img, $uploads['path'] . '/' . $newfilename, $quality ) ) {
350 // Conversion succeeded: remove the original bmp & remap the params.
351 unlink( $params['file'] );
352
353 $params['file'] = $uploads['path'] . '/' . $newfilename;
354 $params['url'] = $uploads['url'] . '/' . $newfilename;
355 $params['type'] = 'image/jpeg';
356 } else {
357 unlink( $params['file'] );
358
359 return wp_handle_upload_error(
360 $oldfilename,
361 /* translators: %s: the image mime type */
362 sprintf( esc_html__( 'Imsanity was unable to process the %s file. If you continue to see this error you may need to disable the conversion option in the Imsanity settings.', 'imsanity' ), $type )
363 );
364 }
365
366 return $params;
367 }
368
369 // Add filter to hook into uploads.
370 add_filter( 'wp_handle_upload', 'imsanity_handle_upload' );
371 // Run necessary actions on init (loading translations mostly).
372 add_action( 'plugins_loaded', 'imsanity_init' );
373
374 // Adds a column to the media library list view to display optimization results.
375 add_filter( 'manage_media_columns', 'imsanity_media_columns' );
376 // Outputs the actual column information for each attachment.
377 add_action( 'manage_media_custom_column', 'imsanity_custom_column', 10, 2 );
378 // Checks for WebP support and adds it to the allowed mime types.
379 add_filter( 'imsanity_allowed_mimes', 'imsanity_add_webp_support' );
380