PluginProbe
HEIC Support / 2.1.1
HEIC Support v2.1.1
2.3.0 2.2.1 trunk 1.0.0 1.0.1 2.0.0 2.1.0 2.1.1 2.1.3 2.1.4 2.2.0
heic-support / heic-support.php

heic-support.php in HEIC Support 2.1.1, at heic-support.php

522 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * HEIC Support
4 *
5 * @author Corey Salzano <csalzano@duck.com>
6 * @package HEIC_Support
7 */
8
9 defined( 'ABSPATH' ) || exit;
10
11 /**
12 * Plugin Name: HEIC Support
13 * Description: Allows .heic uploads to the Media Library. Creates .webp or .jpg copies of .heic images when they are uploaded.
14 * Plugin URI: https://breakfastco.xyz/heic-support/
15 * Author: Breakfast
16 * Author URI: https://breakfastco.xyz/
17 * Version: 2.1.1
18 * Text-domain: heic-support
19 * License: GPLv2
20 */
21
22 if ( ! class_exists( 'Heic_Support_Plugin' ) ) {
23 /**
24 * Heic_Support_Plugin
25 */
26 class Heic_Support_Plugin {
27 /**
28 * Adds filter and action hooks that power this plugin.
29 *
30 * @return void
31 */
32 public function add_hooks() {
33 // Allow .heic files to be uploaded into the Media Library.
34 add_filter( 'upload_mimes', array( $this, 'add_mimes' ) );
35
36 // Creates a copy of .heic images uploaded to the Media Library.
37 add_action( 'add_attachment', array( $this, 'create_copy' ), 12, 1 );
38
39 // Replace heic uploads without preserving the heic.
40 add_filter( 'wp_handle_upload_prefilter', array( $this, 'replace' ) );
41
42 // Populates width, height, and other attributes in meta key _wp_attachment_metadata.
43 add_filter( 'wp_generate_attachment_metadata', array( $this, 'populate_meta' ), 10, 3 );
44
45 // Adds settings to the dashboard at Settings → Media.
46 add_action( 'admin_init', array( $this, 'add_settings' ) );
47
48 // Adds a link to the plugins list that helps users find Settings → Media.
49 add_filter( 'plugin_action_links_heic-support/heic-support.php', array( $this, 'add_settings_link' ) );
50
51 // Deletes the test image when the plugin is uninstalled.
52 register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall' ) );
53 }
54
55 /**
56 * Allow .heic files to be uploaded into the Media Library.
57 *
58 * @param array $mimes Array of allowed mime types.
59 * @return array
60 */
61 public function add_mimes( $mimes ) {
62 if ( empty( $mimes['heic'] ) ) {
63 $mimes['heic'] = 'image/heic';
64 }
65 return $mimes;
66 }
67
68 /**
69 * Adds settings to the dashboard at Settings → Media.
70 *
71 * @return void
72 */
73 public function add_settings() {
74
75 $section = 'heic_support_section';
76 add_settings_section(
77 $section,
78 __( 'HEIC Support', 'heic-support' ),
79 array( $this, 'callback_section' ),
80 'media'
81 );
82
83 // Format setting registration.
84 register_setting(
85 'media',
86 'heic_support_format',
87 array(
88 'type' => 'string',
89 'description' => __( 'Convert .heic images to this format.', 'heic-support' ),
90 'sanitize_callback' => 'sanitize_text_field',
91 'show_in_rest' => true,
92 )
93 );
94
95 // Replace setting registration.
96 register_setting(
97 'media',
98 'heic_support_replace',
99 array(
100 'type' => 'boolean',
101 'description' => __( 'Replace .heic images uploaded to the Media Library instead of creating copies.', 'heic-support' ),
102 'sanitize_callback' => 'rest_sanitize_boolean',
103 'show_in_rest' => true,
104 )
105 );
106
107 // Is the plugin's primary feature going to work?
108 if ( ! class_exists( 'Imagick' ) ) {
109 // No. Do not output any of the options.
110 return;
111 }
112
113 // Format setting output.
114 add_settings_field(
115 'format',
116 __( 'Convert To', 'heic-support' ),
117 array( $this, 'callback_format_setting' ),
118 'media',
119 $section
120 );
121
122 // Replace setting output.
123 add_settings_field(
124 'replace',
125 __( 'Replace', 'heic-support' ),
126 array( $this, 'callback_replace_setting' ),
127 'media',
128 $section
129 );
130
131 // ImageMagick setting.
132 add_settings_field(
133 'imagemagick',
134 __( 'ImageMagick', 'heic-support' ),
135 array( $this, 'callback_imagemagick_setting' ),
136 'media',
137 $section
138 );
139
140 // Test setting.
141 add_settings_field(
142 'test',
143 __( 'Test', 'heic-support' ),
144 array( $this, 'callback_test_setting' ),
145 'media',
146 $section
147 );
148 }
149
150 /**
151 * add_settings_link
152 *
153 * @param array $links
154 * @return array
155 */
156 public function add_settings_link( $links ) {
157 $links[] = '<a href="' . admin_url( 'options-media.php' ) . '">' . __( 'Settings', 'heic-support' ) . '</a>';
158 return $links;
159 }
160
161 /**
162 * get_extension
163 *
164 * @return string
165 */
166 protected static function get_extension() {
167 $format = self::get_format();
168 if ( 'jpeg' === $format ) {
169 return apply_filters( 'heic_support_extension', 'jpg' );
170 }
171 return $format;
172 }
173
174 /**
175 * get_format
176 *
177 * @return string
178 */
179 protected static function get_format() {
180 $value = get_option( 'heic_support_format' );
181 if ( empty( $value ) ) {
182 $value = 'webp';
183 }
184 return apply_filters( 'heic_support_format', $value );
185 }
186
187 /**
188 * callback_imagemagick_setting
189 *
190 * @return void
191 */
192 public function callback_imagemagick_setting() {
193 echo esc_html( $this->imagemagick_version() );
194 }
195
196 /**
197 * Outputs HTML that renders the Format setting radio buttons.
198 *
199 * @return void
200 */
201 public function callback_format_setting() {
202 $value = self::get_format();
203 printf(
204 '<fieldset><label for="heic_support_webp"><input type="radio" id="heic_support_webp" name="heic_support_format" value="webp" %1$s/> %2$s</label><br />'
205 . '<label for="heic_support_jpeg"><input type="radio" id="heic_support_jpeg" name="heic_support_format" value="jpeg" %3$s/> %4$s</label></fieldset>',
206 checked( $value, 'webp', false ),
207 esc_html__( '.webp', 'heic-support' ),
208 checked( $value, 'jpeg', false ),
209 esc_html__( '.jpg', 'heic-support' )
210 );
211 }
212
213 /**
214 * Outputs HTML that renders the Replace ID settings checkbox.
215 *
216 * @return void
217 */
218 public function callback_replace_setting() {
219 $value = get_option( 'heic_support_replace' );
220 printf(
221 '<input type="checkbox" id="heic_support_replace" name="heic_support_replace" %s/> <label for="heic_support_replace">%s</label><p class="description">%s</p>',
222 checked( $value, '1', false ),
223 esc_html__( 'Replace .heic images uploaded to the Media Library instead of creating copies.', 'heic-support' ),
224 esc_html__( 'Does not preserve the original .heic files.', 'heic-support' )
225 );
226 }
227
228 public function callback_test_setting() {
229 if ( ! class_exists( 'Imagick' ) ) {
230 esc_html_e( 'ImageMagick is not installed on this server. This plugin only works on servers running ImageMagick. Some hosts require a switch be flipped before the program is available to a site.', 'heic-support' );
231 } else {
232 $imagick = new Imagick();
233 try {
234 if ( $imagick->readImage( __DIR__ . DIRECTORY_SEPARATOR . 'image4.heic' ) ) {
235 $imagick->setImageFormat( self::get_format() );
236
237 // Create a copy of the image.
238 $path = self::test_file_path();
239 $imagick->writeImage( $path );
240 $upload_dir = wp_upload_dir();
241 $name = basename( $path );
242 printf(
243 '<figure><img src="%s" width="%d" /><figcaption>%s .%s.</figcaption></figure>',
244 esc_attr( $upload_dir['url'] . '/' . $name ),
245 esc_attr( get_option( 'medium_size_w' ) ),
246 esc_html__( 'This plugin can convert .heic images. If you do not see an image, your browser may not support', 'heic-support' ),
247 esc_html( self::get_extension() )
248 );
249 }
250 } catch ( ImagickException $ie ) {
251 // "Fatal error: Uncaught ImagickException: no decode delegate for this image format `HEIC'".
252 $msg = 'no decode delegate for this image format `HEIC\'';
253 if ( false !== strpos( $ie->getMessage(), $msg ) ) {
254 esc_html_e( 'ImageMagick is installed, but does not support HEIC. The version might be too old, or perhaps your server is missing libheif. Installed version is ', 'heic-support' );
255 echo esc_html( $this->imagemagick_version() );
256 echo '</p>';
257 }
258 }
259 }
260 }
261
262 /**
263 * callback_section
264 *
265 * @return void
266 */
267 public function callback_section() {
268 // Is the plugin's primary feature going to work?
269 if ( ! class_exists( 'Imagick' ) ) {
270 // No.
271 printf(
272 /* translators: 1. Anchor element opening tag. 2. Anchor element closing tag. */
273 esc_html__( 'This server does not provide ImageMagick, and .heic images cannot be converted without it. Ask your web host to enable ImageMagick, or %1$sread our list of compatible hosts%2$s.', 'heic-support' ),
274 '<a href="https://breakfastco.xyz/heic-support/#hosting">',
275 '</a>'
276 );
277 return;
278 }
279 esc_html_e( 'Control how .heic images are handled during uploads.', 'heic-support' );
280 }
281
282 /**
283 * check_for_attachment_metadata
284 *
285 * @param array $data
286 * @param int $post_id
287 * @return array
288 */
289 public function check_for_attachment_metadata( $data, $post_id ) {
290 $transient_guid = get_transient( 'heic_support_guid_' . $post_id );
291 if ( ! empty( $transient_guid ) ) {
292 global $wpdb;
293 $wpdb->update( $wpdb->posts, array( 'guid' => $transient_guid ), array( 'ID' => $post_id ), array( '%s' ), array( '%d' ) );
294 delete_transient( 'heic_support_guid_' . $post_id );
295 }
296 $transient = get_transient( 'heic_support_metadata_' . $post_id );
297 if ( empty( $transient ) ) {
298 return $data;
299 }
300 delete_transient( 'heic_support_metadata_' . $post_id );
301 return $transient;
302 }
303
304 /**
305 * Filter callback on add_attachment. Creates a copy of .heic images
306 * uploaded to the Media Library.
307 *
308 * @param int $post_id The ID of a new attachment.
309 * @return void
310 */
311 public function create_copy( $post_id ) {
312 // Is the Replace feature enabled? If so, abort the copy.
313 $replace = filter_var( get_option( 'heic_support_replace' ), FILTER_VALIDATE_BOOLEAN );
314 if ( $replace ) {
315 // Yes. Replace is enabled. Abort.
316 return;
317 }
318
319 // Is ImageMagick running?
320 if ( ! class_exists( 'Imagick' ) ) {
321 // No.
322 return;
323 }
324 $file_path = get_attached_file( $post_id );
325 if ( false === $file_path ) {
326 return;
327 }
328 // Is the attachment an heic?
329 if ( 'heic' !== pathinfo( $file_path, PATHINFO_EXTENSION ) ) {
330 // No.
331 return;
332 }
333 $imagick = new Imagick();
334 try {
335 if ( $imagick->readImage( $file_path ) ) {
336 $imagick->setImageFormat( self::get_format() );
337 // Create a path to a copy of the image.
338 $name = basename( $file_path, '.heic' ) . '.' . self::get_extension();
339 $upload_dir = wp_upload_dir();
340 $imagick->writeImage( $upload_dir['path'] . DIRECTORY_SEPARATOR . $name );
341
342 /**
343 * The Media Library loads these files, but not the Editor.
344 *
345 * @link https://developer.wordpress.org/reference/functions/media_sideload_image/#more-information
346 */
347 if ( ! function_exists( 'media_sideload_image' ) ) {
348 require_once ABSPATH . 'wp-admin/includes/media.php';
349 require_once ABSPATH . 'wp-admin/includes/file.php';
350 require_once ABSPATH . 'wp-admin/includes/image.php';
351 }
352 $copy_post_id = media_sideload_image( $upload_dir['url'] . '/' . $name, 0 /* post_parent */, get_the_title( $post_id ), 'id' );
353 if ( ! is_wp_error( $copy_post_id ) ) {
354 update_post_meta( $copy_post_id, '_heic_support_copy_of', $post_id );
355 update_post_meta( $post_id, '_heic_support_copy_of', $copy_post_id );
356 }
357 }
358 } catch ( ImagickException $ie ) {
359 // "Fatal error: Uncaught ImagickException: no decode delegate for this image format `HEIC'".
360 // The version of Imagick does not support heic
361 return;
362 }
363 }
364
365 /**
366 * Returns the ImageMagick version string.
367 *
368 * @return string
369 */
370 protected function imagemagick_version() {
371 if ( ! class_exists( 'Imagick' ) ) {
372 return '';
373 }
374 return Imagick::getVersion()['versionString'];
375 }
376
377 /**
378 * When .heic files are added to the Media Library, populate their width,
379 * height, and other attributes that live in meta key _wp_attachment_metadata.
380 *
381 * @param array $metadata An array of attachment meta data.
382 * @param int $attachment_id Current attachment ID.
383 * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment
384 * or 'update' when the metadata was updated.
385 * @return array
386 */
387 public function populate_meta( $metadata, $attachment_id, $context ) {
388 // Is ImageMagick running?
389 if ( ! class_exists( 'Imagick' ) ) {
390 // No.
391 return $metadata;
392 }
393 $file_path = get_attached_file( $attachment_id );
394 if ( false === $file_path ) {
395 return $metadata;
396 }
397 // Is the attachment an heic?
398 if ( 'heic' !== pathinfo( $file_path, PATHINFO_EXTENSION ) ) {
399 // No.
400 return $metadata;
401 }
402 // Are the width and height missing?
403 if ( false === $metadata ) {
404 $metadata = array();
405 }
406 if ( ! empty( $metadata['width'] ) && ! empty( $metadata['height'] ) ) {
407 // No.
408 return $metadata;
409 }
410 $imagick = new Imagick();
411 try {
412 if ( $imagick->readImage( $file_path ) ) {
413 $new_values = $imagick->getImageGeometry();
414 $new_values['sizes'] = array();
415 $new_values['file'] = get_post_meta( $attachment_id, '_wp_attached_file', true );
416 $metadata = wp_parse_args( $metadata, $new_values );
417 return $metadata;
418 }
419 } catch ( ImagickException $ie ) {
420 // "Fatal error: Uncaught ImagickException: no decode delegate for this image format `HEIC'".
421 // The version of Imagick does not support heic
422 return $metadata;
423 }
424 }
425
426 /**
427 * Replaces uploaded .heic files with equivalents during uploads.
428 *
429 * @param array $file An array of data for a single file.
430 * @return array
431 */
432 public function replace( $file ) {
433 // Does $file look like an uploaded file?
434 if ( empty( $file['tmp_name'] ) || empty( $file['name'] ) ) {
435 return $file;
436 }
437
438 // Is this image even an heic?
439 $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] );
440 if ( empty( $wp_filetype['type'] ) || 'image/heic' !== $wp_filetype['type'] ) {
441 // No.
442 return $file;
443 }
444
445 // Is ImageMagick available?
446 if ( ! class_exists( 'Imagick' ) ) {
447 // No.
448 return $file;
449 }
450
451 // Is this replace feature enabled?
452 $replace = filter_var( get_option( 'heic_support_replace' ), FILTER_VALIDATE_BOOLEAN );
453 if ( ! $replace ) {
454 // No. The feature is not enabled.
455 return $file;
456 }
457
458 $imagick = new Imagick();
459 try {
460 if ( $imagick->readImage( $file['tmp_name'] ) ) {
461 $format = self::get_format();
462 $imagick->setImageFormat( $format );
463 $file['type'] = apply_filters( 'heic_support_mime', 'image/' . $format );
464 $file['name'] = basename( $file['name'], '.heic' ) . '.' . self::get_extension();
465 $imagick->writeImage( $file['tmp_name'] );
466 $file['size'] = wp_filesize( $file['tmp_name'] );
467 }
468 } catch ( ImagickException $ie ) {
469 // "Fatal error: Uncaught ImagickException: no decode delegate for this image format `HEIC'".
470 // The version of Imagick does not support heic
471 return $file;
472 }
473
474 return $file;
475 }
476
477 /**
478 * Returns a file path to the copy of the test image.
479 *
480 * @return string
481 */
482 protected static function test_file_path() {
483 $upload_dir = wp_upload_dir();
484 return $upload_dir['path'] . DIRECTORY_SEPARATOR
485 . 'heic-support-image4.' . self::get_extension();
486 }
487
488 /**
489 * Deletes the test image when the plugin is uninstalled.
490 *
491 * @return void
492 */
493 public static function uninstall() {
494 if ( ! is_multisite() ) {
495 $path = self::test_file_path();
496 if ( file_exists( $path ) ) {
497 wp_delete_file( $path );
498 }
499 } else {
500 $sites = get_sites(
501 array(
502 'network' => 1,
503 'limit' => 1000,
504 )
505 );
506 foreach ( $sites as $site ) {
507 switch_to_blog( $site->blog_id );
508
509 $path = self::test_file_path();
510 if ( file_exists( $path ) ) {
511 wp_delete_file( $path );
512 }
513
514 restore_current_blog();
515 }
516 }
517 }
518 }
519 }
520 $heic_support_plugin = new Heic_Support_Plugin();
521 $heic_support_plugin->add_hooks();
522