11<?php
22/**
3- * Records and exposes the lineage root for attachments edited via
3+ * Records and exposes the edit root for attachments edited via
44 * `/wp/v2/media/{id}/edit`.
55 *
66 * Each call to core's `edit_media_item` creates a new child attachment.
7- * On that path, we record the id at the top of the lineage in a
8- * dedicated postmeta key (`_wp_attachment_original_id`) so the Media
9- * Editor modal can offer a "Restore original" action without walking
10- * the chain on every read, and so cleanup on parent delete can use
11- * an indexed query.
7+ * On that path, we record the id of the edit root ( the attachment at
8+ * the top of the edit chain) in a dedicated postmeta key
9+ * (`_wp_attachment_edit_root_id`) so the Media Editor modal can offer
10+ * a "Restore original" action without walking the chain on every
11+ * read, and so cleanup on edit root delete can use an indexed query.
1212 *
1313 * This applies to every edit that goes through `/edit` — including
1414 * the image block's crop tools in the post editor — on any site
2020 */
2121
2222/**
23- * Postmeta key for the lineage root id.
23+ * Postmeta key for the edit root id.
2424 *
2525 * Underscore-prefixed so it's treated as private/internal and not
2626 * exposed by the default postmeta REST surface.
2727 */
28- const GUTENBERG_ORIGINAL_ATTACHMENT_ID_META_KEY = '_wp_attachment_original_id ' ;
28+ const GUTENBERG_EDIT_ROOT_ATTACHMENT_ID_META_KEY = '_wp_attachment_edit_root_id ' ;
2929
3030/**
31- * Get the lineage root (original) attachment id for an attachment.
31+ * Get the edit root attachment id for an attachment.
3232 *
33- * Returns the id recorded in `_wp_attachment_original_id ` when the
33+ * Returns the id recorded in `_wp_attachment_edit_root_id ` when the
3434 * attachment was created by editing another one, or the attachment's
3535 * own id when it has no edit lineage. Callers that want to tell
36- * "has an original " apart from "is its own original " should compare
36+ * "has an edit root " apart from "is its own edit root " should compare
3737 * the result against the id they passed in.
3838 *
39- * In core this would be `wp_get_original_attachment_id ()`.
39+ * In core this would be `wp_get_edit_root_attachment_id ()`.
4040 *
4141 * @param int $attachment_id Attachment id to resolve.
42- * @return int The original attachment id, or `$attachment_id` when there is no lineage.
42+ * @return int The edit root attachment id, or `$attachment_id` when there is no lineage.
4343 */
44- function gutenberg_get_original_attachment_id ( $ attachment_id ) {
45- $ original_id = (int ) get_post_meta (
44+ function gutenberg_get_edit_root_attachment_id ( $ attachment_id ) {
45+ $ edit_root_id = (int ) get_post_meta (
4646 $ attachment_id ,
47- GUTENBERG_ORIGINAL_ATTACHMENT_ID_META_KEY ,
47+ GUTENBERG_EDIT_ROOT_ATTACHMENT_ID_META_KEY ,
4848 true
4949 );
5050
51- return $ original_id > 0 ? $ original_id : (int ) $ attachment_id ;
51+ return $ edit_root_id > 0 ? $ edit_root_id : (int ) $ attachment_id ;
5252}
5353
5454/**
55- * Persist the original attachment id when /edit creates a new edited
55+ * Persist the edit root attachment id when /edit creates a new edited
5656 * attachment.
5757 *
5858 * Hooks `wp_edited_image_metadata` (fired by core inside
5959 * `WP_REST_Attachments_Controller::edit_media_item`, after the new
6060 * attachment id has been inserted but before its image metadata is
61- * stored). The new child's original is its parent's original if the
61+ * stored). The new child's edit root is its parent's edit root if the
6262 * parent has one, otherwise the parent itself.
6363 *
6464 * @param array $new_image_meta Metadata for the new edited attachment (unused).
6565 * @param int $new_attachment_id The new attachment id.
6666 * @param int $attachment_id The parent (source) attachment id.
6767 * @return array Filtered metadata (unchanged).
6868 */
69- function gutenberg_record_original_attachment_id ( $ new_image_meta , $ new_attachment_id , $ attachment_id ) {
70- // The new child's original is its parent's original , or the parent
71- // itself when the parent has no lineage of its own.
72- $ original_id = gutenberg_get_original_attachment_id ( $ attachment_id );
69+ function gutenberg_record_edit_root_attachment_id ( $ new_image_meta , $ new_attachment_id , $ attachment_id ) {
70+ // The new child's edit root is its parent's edit root , or the
71+ // parent itself when the parent has no lineage of its own.
72+ $ edit_root_id = gutenberg_get_edit_root_attachment_id ( $ attachment_id );
7373
7474 update_post_meta (
7575 $ new_attachment_id ,
76- GUTENBERG_ORIGINAL_ATTACHMENT_ID_META_KEY ,
77- $ original_id
76+ GUTENBERG_EDIT_ROOT_ATTACHMENT_ID_META_KEY ,
77+ $ edit_root_id
7878 );
7979
8080 return $ new_image_meta ;
8181}
82- add_filter ( 'wp_edited_image_metadata ' , 'gutenberg_record_original_attachment_id ' , 10 , 3 );
82+ add_filter ( 'wp_edited_image_metadata ' , 'gutenberg_record_edit_root_attachment_id ' , 10 , 3 );
8383
8484/**
85- * Registers `original_attachment` as a REST field on the attachment
86- * schema.
85+ * Registers `edit_root` as a REST field on the attachment schema.
8786 *
88- * The value is the id of the lineage root the attachment was edited
87+ * The value is the id of the edit root the attachment was edited
8988 * from, or `0` when it has no edit lineage — mirroring
9089 * `featured_media`, where `0` means "none". Registering the field
9190 * (rather than injecting it in a response filter) exposes it in the
@@ -100,48 +99,48 @@ function gutenberg_record_original_attachment_id( $new_image_meta, $new_attachme
10099 * `view` context and never carries this field. Clients must refetch
101100 * the new attachment with `?context=edit`.
102101 */
103- function gutenberg_register_original_attachment_field () {
102+ function gutenberg_register_edit_root_field () {
104103 register_rest_field (
105104 'attachment ' ,
106- 'original_attachment ' ,
105+ 'edit_root ' ,
107106 array (
108- 'get_callback ' => 'gutenberg_get_original_attachment_field ' ,
107+ 'get_callback ' => 'gutenberg_get_edit_root_field ' ,
109108 'schema ' => array (
110- 'description ' => __ ( 'The ID of the original attachment this attachment was edited from, or 0 when it was not created by editing another attachment. ' , 'gutenberg ' ),
109+ 'description ' => __ ( 'The ID of the edit root attachment this attachment was edited from, or 0 when it was not created by editing another attachment. ' , 'gutenberg ' ),
111110 'type ' => 'integer ' ,
112111 'context ' => array ( 'edit ' ),
113112 'readonly ' => true ,
114113 ),
115114 )
116115 );
117116}
118- add_action ( 'rest_api_init ' , 'gutenberg_register_original_attachment_field ' );
117+ add_action ( 'rest_api_init ' , 'gutenberg_register_edit_root_field ' );
119118
120119/**
121- * Resolves the `original_attachment ` field value for a REST response.
120+ * Resolves the `edit_root ` field value for a REST response.
122121 *
123- * Trusts the stored meta rather than verifying the original still
122+ * Trusts the stored meta rather than verifying the edit root still
124123 * exists: the `delete_attachment` cleanup below keeps it accurate,
125- * and a consumer fetching a dangling id (e.g. an original sitting in
124+ * and a consumer fetching a dangling id (e.g. an edit root sitting in
126125 * the trash) simply gets no record.
127126 *
128127 * @param array $attachment Prepared attachment response data.
129- * @return int The lineage root id, or 0 when there is no lineage.
128+ * @return int The edit root id, or 0 when there is no lineage.
130129 */
131- function gutenberg_get_original_attachment_field ( $ attachment ) {
130+ function gutenberg_get_edit_root_field ( $ attachment ) {
132131 $ attachment_id = isset ( $ attachment ['id ' ] ) ? (int ) $ attachment ['id ' ] : 0 ;
133132 if ( $ attachment_id <= 0 ) {
134133 return 0 ;
135134 }
136135
137- $ original_id = gutenberg_get_original_attachment_id ( $ attachment_id );
136+ $ edit_root_id = gutenberg_get_edit_root_attachment_id ( $ attachment_id );
138137
139- return $ original_id === $ attachment_id ? 0 : $ original_id ;
138+ return $ edit_root_id === $ attachment_id ? 0 : $ edit_root_id ;
140139}
141140
142141/**
143- * Filter `rest_prepare_attachment` to add the `wp:original-attachment`
144- * link for attachments with an edit lineage.
142+ * Filter `rest_prepare_attachment` to add the `wp:edit-root` link for
143+ * attachments with an edit lineage.
145144 *
146145 * `register_rest_field` has no mechanism for links, so the link rides
147146 * a response filter while the field itself is registered above. Only
@@ -152,53 +151,53 @@ function gutenberg_get_original_attachment_field( $attachment ) {
152151 * @param WP_REST_Request $request REST request.
153152 * @return WP_REST_Response
154153 */
155- function gutenberg_add_original_attachment_link ( $ response , $ post , $ request ) {
154+ function gutenberg_add_edit_root_link ( $ response , $ post , $ request ) {
156155 if ( 'edit ' !== $ request ['context ' ] ) {
157156 return $ response ;
158157 }
159158
160- $ original_id = gutenberg_get_original_attachment_id ( $ post ->ID );
161- if ( $ original_id === (int ) $ post ->ID ) {
159+ $ edit_root_id = gutenberg_get_edit_root_attachment_id ( $ post ->ID );
160+ if ( $ edit_root_id === (int ) $ post ->ID ) {
162161 return $ response ;
163162 }
164163
165164 // Mirror `featured_media`: an embeddable link so clients can
166- // hydrate the original attachment with `?_embed`. Core fires
165+ // hydrate the edit root attachment with `?_embed`. Core fires
167166 // `rest_prepare_attachment` twice per attachment — once from the
168167 // posts controller's `prepare_item_for_response()` and again from
169168 // the attachments controller wrapping it — and `add_link()`
170169 // appends unconditionally, so guard against adding it twice.
171- $ rel = 'https://api.w.org/original-attachment ' ;
170+ $ rel = 'https://api.w.org/edit-root ' ;
172171 $ links = $ response ->get_links ();
173172 if ( ! isset ( $ links [ $ rel ] ) ) {
174173 $ response ->add_link (
175174 $ rel ,
176- rest_url ( 'wp/v2/media/ ' . $ original_id ),
175+ rest_url ( 'wp/v2/media/ ' . $ edit_root_id ),
177176 array ( 'embeddable ' => true )
178177 );
179178 }
180179
181180 return $ response ;
182181}
183- add_filter ( 'rest_prepare_attachment ' , 'gutenberg_add_original_attachment_link ' , 10 , 3 );
182+ add_filter ( 'rest_prepare_attachment ' , 'gutenberg_add_edit_root_link ' , 10 , 3 );
184183
185184/**
186- * Clear `_wp_attachment_original_id ` from descendants when their
187- * original is deleted.
185+ * Clear `_wp_attachment_edit_root_id ` from descendants when their
186+ * edit root is deleted.
188187 *
189188 * Without this, descendants would carry a dangling pointer at a
190189 * recycled attachment id. The REST filter trusts this meta without
191- * verifying the original still exists, so this cleanup is what keeps
190+ * verifying the edit root still exists, so this cleanup is what keeps
192191 * the exposed field accurate and prevents it from pointing at an
193192 * unrelated attachment if the deleted id is later reused.
194193 *
195194 * Runs on hard delete only: with `MEDIA_TRASH`, trashing fires no
196195 * `delete_attachment`, so descendants keep pointing at a trashed
197- * original (whose URL still resolves) until the trash is purged.
196+ * edit root (whose URL still resolves) until the trash is purged.
198197 *
199198 * @param int $attachment_id Attachment id being deleted.
200199 */
201- function gutenberg_clear_original_attachment_id_on_delete ( $ attachment_id ) {
200+ function gutenberg_clear_edit_root_attachment_id_on_delete ( $ attachment_id ) {
202201 $ attachment_id = (int ) $ attachment_id ;
203202 if ( $ attachment_id <= 0 ) {
204203 return ;
@@ -211,9 +210,9 @@ function gutenberg_clear_original_attachment_id_on_delete( $attachment_id ) {
211210 delete_metadata (
212211 'post ' ,
213212 0 ,
214- GUTENBERG_ORIGINAL_ATTACHMENT_ID_META_KEY ,
213+ GUTENBERG_EDIT_ROOT_ATTACHMENT_ID_META_KEY ,
215214 $ attachment_id ,
216215 true
217216 );
218217}
219- add_action ( 'delete_attachment ' , 'gutenberg_clear_original_attachment_id_on_delete ' );
218+ add_action ( 'delete_attachment ' , 'gutenberg_clear_edit_root_attachment_id_on_delete ' );
0 commit comments