PluginProbe
HEIC Support / 2.0.0
HEIC Support v2.0.0
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.0.0, at heic-support.php

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