PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
gutenberg / lib / media / animated-gif-to-video.php

animated-gif-to-video.php in Gutenberg trunk, at lib/media/animated-gif-to-video.php

105 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Animated GIF → video: clean up the companion files of a converted GIF.
4 *
5 * When client-side media processing is enabled, an opaque animated GIF is
6 * stored as a normal image attachment (it stays a single media library item).
7 * The GIF is also transcoded to a video (MP4/WebM) and a static first-frame
8 * poster, both sideloaded as *companion files* of that same attachment — like
9 * the HEIC original — and recorded in the attachment metadata under the
10 * `animated_video` and `animated_video_poster` keys. They are never separate
11 * attachments. Transparent GIFs are not converted (a `<video>` cannot
12 * reproduce GIF transparency), so they have no companion.
13 *
14 * The swap to a video is handled in the editor: an uploaded GIF whose companion
15 * video is available is switched to the Video block's "GIF" variation, which
16 * serializes a normal `<video autoplay loop muted playsinline>` and so renders
17 * natively on the front end with no render-time filtering. The author can
18 * restore the original GIF from the block toolbar. The only thing left for PHP
19 * is removing the sideloaded companions when their attachment is deleted, which
20 * core's wp_delete_attachment_files() does not know about.
21 *
22 * @package gutenberg
23 */
24
25 /**
26 * Returns the absolute path to one of an attachment's animated-GIF companion
27 * files (the converted video or its poster), if recorded.
28 *
29 * The path is rebuilt from the attachment's own (trusted) directory plus the
30 * recorded basename, so the stored metadata cannot point anywhere else.
31 *
32 * @param int $attachment_id Attachment ID.
33 * @param string $meta_key Metadata key holding the companion basename
34 * ('animated_video' or 'animated_video_poster').
35 * @return string|null Absolute file path, or null when there is no companion.
36 */
37 function gutenberg_get_animated_gif_companion_path( int $attachment_id, string $meta_key ): ?string {
38 $metadata = wp_get_attachment_metadata( $attachment_id, true );
39
40 if ( empty( $metadata[ $meta_key ] ) || ! is_string( $metadata[ $meta_key ] ) ) {
41 return null;
42 }
43
44 // Only ever trust the basename of the recorded value; strip any path
45 // components so the metadata can't reference another directory.
46 $name = wp_basename( $metadata[ $meta_key ] );
47
48 if ( '' === $name ) {
49 return null;
50 }
51
52 $attached_file = get_attached_file( $attachment_id, true );
53
54 if ( ! $attached_file ) {
55 return null;
56 }
57
58 return path_join( dirname( $attached_file ), $name );
59 }
60
61 /**
62 * Deletes a sideloaded animated-GIF companion file from disk.
63 *
64 * Deletion is delegated to wp_delete_file_from_directory(), which confirms the
65 * path resolves strictly inside the uploads directory before unlinking, so this
66 * can only ever remove a sideloaded companion. Mirrors the HEIC companion
67 * cleanup in lib/media/load.php.
68 *
69 * @param string|null $path Absolute path to the companion file, or null.
70 */
71 function gutenberg_delete_animated_gif_companion_file( ?string $path ): void {
72 if ( ! $path || ! file_exists( $path ) ) {
73 return;
74 }
75
76 $uploads = wp_get_upload_dir();
77
78 if ( empty( $uploads['basedir'] ) ) {
79 return;
80 }
81
82 wp_delete_file_from_directory( $path, $uploads['basedir'] );
83 }
84
85 /**
86 * Deletes the companion video and poster when their GIF attachment is deleted.
87 *
88 * The companions are sideloaded next to the GIF and recorded in
89 * $metadata['animated_video'] and $metadata['animated_video_poster']. WordPress
90 * core's wp_delete_attachment_files() does not know about them, so without this
91 * hook they would linger on disk after the attachment is deleted.
92 *
93 * @param int $post_id Attachment ID being deleted.
94 */
95 function gutenberg_delete_animated_gif_video( int $post_id ): void {
96 gutenberg_delete_animated_gif_companion_file(
97 gutenberg_get_animated_gif_companion_path( $post_id, 'animated_video' )
98 );
99 gutenberg_delete_animated_gif_companion_file(
100 gutenberg_get_animated_gif_companion_path( $post_id, 'animated_video_poster' )
101 );
102 }
103
104 add_action( 'delete_attachment', 'gutenberg_delete_animated_gif_video' );
105