PluginProbe
Gutenberg / 16.6.0
Gutenberg v16.6.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 12.6.0 All 403 releases
gutenberg / build / block-library / blocks / footnotes.php

footnotes.php in Gutenberg 16.6.0, at build/block-library/blocks/footnotes.php

287 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/footnotes` block.
4 *
5 * @package WordPress
6 */
7
8 /**
9 * Renders the `core/footnotes` block on the server.
10 *
11 * @since 6.3.0
12 *
13 * @param array $attributes Block attributes.
14 * @param string $content Block default content.
15 * @param WP_Block $block Block instance.
16 *
17 * @return string Returns the HTML representing the footnotes.
18 */
19 function gutenberg_render_block_core_footnotes( $attributes, $content, $block ) {
20 // Bail out early if the post ID is not set for some reason.
21 if ( empty( $block->context['postId'] ) ) {
22 return '';
23 }
24
25 if ( post_password_required( $block->context['postId'] ) ) {
26 return;
27 }
28
29 $footnotes = get_post_meta( $block->context['postId'], 'footnotes', true );
30
31 if ( ! $footnotes ) {
32 return;
33 }
34
35 $footnotes = json_decode( $footnotes, true );
36
37 if ( ! is_array( $footnotes ) || count( $footnotes ) === 0 ) {
38 return '';
39 }
40
41 $wrapper_attributes = get_block_wrapper_attributes();
42
43 $block_content = '';
44
45 foreach ( $footnotes as $footnote ) {
46 $block_content .= sprintf(
47 '<li id="%1$s">%2$s <a href="#%1$s-link">↩︎</a></li>',
48 $footnote['id'],
49 $footnote['content']
50 );
51 }
52
53 return sprintf(
54 '<ol %1$s>%2$s</ol>',
55 $wrapper_attributes,
56 $block_content
57 );
58 }
59
60 /**
61 * Registers the `core/footnotes` block on the server.
62 *
63 * @since 6.3.0
64 */
65 function gutenberg_register_block_core_footnotes() {
66 foreach ( array( 'post', 'page' ) as $post_type ) {
67 register_post_meta(
68 $post_type,
69 'footnotes',
70 array(
71 'show_in_rest' => true,
72 'single' => true,
73 'type' => 'string',
74 )
75 );
76 }
77 register_block_type_from_metadata(
78 __DIR__ . '/footnotes',
79 array(
80 'render_callback' => 'gutenberg_render_block_core_footnotes',
81 )
82 );
83 }
84 add_action( 'init', 'gutenberg_register_block_core_footnotes', 20 );
85
86 /**
87 * Saves the footnotes meta value to the revision.
88 *
89 * @since 6.3.0
90 *
91 * @param int $revision_id The revision ID.
92 */
93 function gutenberg_save_footnotes_meta( $revision_id ) {
94 $post_id = wp_is_post_revision( $revision_id );
95
96 if ( $post_id ) {
97 $footnotes = get_post_meta( $post_id, 'footnotes', true );
98
99 if ( $footnotes ) {
100 // Can't use update_post_meta() because it doesn't allow revisions.
101 update_metadata( 'post', $revision_id, 'footnotes', wp_slash( $footnotes ) );
102 }
103 }
104 }
105 add_action( 'wp_after_insert_post', 'gutenberg_save_footnotes_meta' );
106
107 /**
108 * Keeps track of the revision ID for "rest_after_insert_{$post_type}".
109 *
110 * @since 6.3.0
111 *
112 * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
113 *
114 * @param int $revision_id The revision ID.
115 */
116 function gutenberg_keep_footnotes_revision_id( $revision_id ) {
117 global $wp_temporary_footnote_revision_id;
118 $wp_temporary_footnote_revision_id = $revision_id;
119 }
120 add_action( '_wp_put_post_revision', 'gutenberg_keep_footnotes_revision_id' );
121
122 /**
123 * This is a specific fix for the REST API. The REST API doesn't update
124 * the post and post meta in one go (through `meta_input`). While it
125 * does fix the `wp_after_insert_post` hook to be called correctly after
126 * updating meta, it does NOT fix hooks such as post_updated and
127 * save_post, which are normally also fired after post meta is updated
128 * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is
129 * added to the `post_updated` action, which means the meta is not
130 * available at the time, so we have to add it afterwards through the
131 * `"rest_after_insert_{$post_type}"` action.
132 *
133 * @since 6.3.0
134 *
135 * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
136 *
137 * @param WP_Post $post The post object.
138 */
139 function gutenberg_add_footnotes_revisions_to_post_meta( $post ) {
140 global $wp_temporary_footnote_revision_id;
141
142 if ( $wp_temporary_footnote_revision_id ) {
143 $revision = get_post( $wp_temporary_footnote_revision_id );
144
145 if ( ! $revision ) {
146 return;
147 }
148
149 $post_id = $revision->post_parent;
150
151 // Just making sure we're updating the right revision.
152 if ( $post->ID === $post_id ) {
153 $footnotes = get_post_meta( $post_id, 'footnotes', true );
154
155 if ( $footnotes ) {
156 // Can't use update_post_meta() because it doesn't allow revisions.
157 update_metadata( 'post', $wp_temporary_footnote_revision_id, 'footnotes', wp_slash( $footnotes ) );
158 }
159 }
160 }
161 }
162
163 add_action( 'rest_after_insert_post', 'gutenberg_add_footnotes_revisions_to_post_meta' );
164 add_action( 'rest_after_insert_page', 'gutenberg_add_footnotes_revisions_to_post_meta' );
165
166 /**
167 * Restores the footnotes meta value from the revision.
168 *
169 * @since 6.3.0
170 *
171 * @param int $post_id The post ID.
172 * @param int $revision_id The revision ID.
173 */
174 function gutenberg_restore_footnotes_from_revision( $post_id, $revision_id ) {
175 $footnotes = get_post_meta( $revision_id, 'footnotes', true );
176
177 if ( $footnotes ) {
178 update_post_meta( $post_id, 'footnotes', wp_slash( $footnotes ) );
179 } else {
180 delete_post_meta( $post_id, 'footnotes' );
181 }
182 }
183 add_action( 'wp_restore_post_revision', 'gutenberg_restore_footnotes_from_revision', 10, 2 );
184
185 /**
186 * Adds the footnotes field to the revision.
187 *
188 * @since 6.3.0
189 *
190 * @param array $fields The revision fields.
191 * @return array The revision fields.
192 */
193 function gutenberg_add_footnotes_to_revision( $fields ) {
194 $fields['footnotes'] = __( 'Footnotes' );
195 return $fields;
196 }
197 add_filter( '_wp_post_revision_fields', 'gutenberg_add_footnotes_to_revision' );
198
199 /**
200 * Gets the footnotes field from the revision.
201 *
202 * @since 6.3.0
203 *
204 * @param string $revision_field The field value, but $revision->$field
205 * (footnotes) does not exist.
206 * @param string $field The field name, in this case "footnotes".
207 * @param object $revision The revision object to compare against.
208 * @return string The field value.
209 */
210 function gutenberg_get_footnotes_from_revision( $revision_field, $field, $revision ) {
211 return get_metadata( 'post', $revision->ID, $field, true );
212 }
213 add_filter( '_wp_post_revision_field_footnotes', 'gutenberg_get_footnotes_from_revision', 10, 3 );
214
215 /**
216 * The REST API autosave endpoint doesn't save meta, so we can use the
217 * `wp_creating_autosave` when it updates an exiting autosave, and
218 * `_wp_put_post_revision` when it creates a new autosave.
219 *
220 * @since 6.3.0
221 *
222 * @param int|array $autosave The autosave ID or array.
223 */
224 function gutenberg__wp_rest_api_autosave_meta( $autosave ) {
225 // Ensure it's a REST API request.
226 if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
227 return;
228 }
229
230 $body = rest_get_server()->get_raw_data();
231 $body = json_decode( $body, true );
232
233 if ( ! isset( $body['meta']['footnotes'] ) ) {
234 return;
235 }
236
237 // `wp_creating_autosave` passes the array,
238 // `_wp_put_post_revision` passes the ID.
239 $id = is_int( $autosave ) ? $autosave : $autosave['ID'];
240
241 if ( ! $id ) {
242 return;
243 }
244
245 update_post_meta( $id, 'footnotes', wp_slash( $body['meta']['footnotes'] ) );
246 }
247 // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L391C1-L391C1.
248 add_action( 'wp_creating_autosave', 'gutenberg__wp_rest_api_autosave_meta' );
249 // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L398.
250 // Then https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/revision.php#L367.
251 add_action( '_wp_put_post_revision', 'gutenberg__wp_rest_api_autosave_meta' );
252
253 /**
254 * This is a workaround for the autosave endpoint returning early if the
255 * revision field are equal. The problem is that "footnotes" is not real
256 * revision post field, so there's nothing to compare against.
257 *
258 * This trick sets the "footnotes" field (value doesn't matter), which will
259 * cause the autosave endpoint to always update the latest revision. That should
260 * be fine, it should be ok to update the revision even if nothing changed. Of
261 * course, this is temporary fix.
262 *
263 * @since 6.3.0
264 *
265 * @param WP_Post $prepared_post The prepared post object.
266 * @param WP_REST_Request $request The request object.
267 *
268 * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L365-L384.
269 * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L219.
270 */
271 function gutenberg__wp_rest_api_force_autosave_difference( $prepared_post, $request ) {
272 // We only want to be altering POST requests.
273 if ( $request->get_method() !== 'POST' ) {
274 return $prepared_post;
275 }
276
277 // Only alter requests for the '/autosaves' route.
278 if ( substr( $request->get_route(), -strlen( '/autosaves' ) ) !== '/autosaves' ) {
279 return $prepared_post;
280 }
281
282 $prepared_post->footnotes = '[]';
283 return $prepared_post;
284 }
285
286 add_filter( 'rest_pre_insert_post', 'gutenberg__wp_rest_api_force_autosave_difference', 10, 2 );
287