From 5a3ac1945021d17e2cfd6204418d5e205f98c6e1 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Wed, 19 Jul 2023 19:03:37 +0200 Subject: [PATCH] Footnotes: store in revisions (#52686) --- .../block-library/src/footnotes/index.php | 126 ++++++++++++++++++ .../specs/editor/various/footnotes.spec.js | 86 +++++++++++- 2 files changed, 210 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php index e4bd8dcdeaac2e..d3bee136725947 100644 --- a/packages/block-library/src/footnotes/index.php +++ b/packages/block-library/src/footnotes/index.php @@ -78,3 +78,129 @@ function register_block_core_footnotes() { ); } add_action( 'init', 'register_block_core_footnotes' ); + +add_action( + 'wp_after_insert_post', + /** + * Saves the footnotes meta value to the revision. + * + * @param int $revision_id The revision ID. + */ + function( $revision_id ) { + $post_id = wp_is_post_revision( $revision_id ); + + if ( $post_id ) { + $footnotes = get_post_meta( $post_id, 'footnotes', true ); + + if ( $footnotes ) { + // Can't use update_post_meta() because it doesn't allow revisions. + update_metadata( 'post', $revision_id, 'footnotes', $footnotes ); + } + } + } +); + +add_action( + '_wp_put_post_revision', + /** + * Keeps track of the revision ID for "rest_after_insert_{$post_type}". + * + * @param int $revision_id The revision ID. + */ + function( $revision_id ) { + global $_gutenberg_revision_id; + $_gutenberg_revision_id = $revision_id; + } +); + +foreach ( array( 'post', 'page' ) as $post_type ) { + add_action( + "rest_after_insert_{$post_type}", + /** + * This is a specific fix for the REST API. The REST API doesn't update + * the post and post meta in one go (through `meta_input`). While it + * does fix the `wp_after_insert_post` hook to be called correctly after + * updating meta, it does NOT fix hooks such as post_updated and + * save_post, which are normally also fired after post meta is updated + * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is + * added to the `post_updated` action, which means the meta is not + * available at the time, so we have to add it afterwards through the + * `"rest_after_insert_{$post_type}"` action. + * + * @param WP_Post $post The post object. + */ + function( $post ) { + global $_gutenberg_revision_id; + + if ( $_gutenberg_revision_id ) { + $revision = get_post( $_gutenberg_revision_id ); + $post_id = $revision->post_parent; + + // Just making sure we're updating the right revision. + if ( $post->ID === $post_id ) { + $footnotes = get_post_meta( $post_id, 'footnotes', true ); + + if ( $footnotes ) { + // Can't use update_post_meta() because it doesn't allow revisions. + update_metadata( 'post', $_gutenberg_revision_id, 'footnotes', $footnotes ); + } + } + } + } + ); +} + +add_action( + 'wp_restore_post_revision', + /** + * Restores the footnotes meta value from the revision. + * + * @param int $post_id The post ID. + * @param int $revision_id The revision ID. + */ + function( $post_id, $revision_id ) { + $footnotes = get_post_meta( $revision_id, 'footnotes', true ); + + if ( $footnotes ) { + update_post_meta( $post_id, 'footnotes', $footnotes ); + } else { + delete_post_meta( $post_id, 'footnotes' ); + } + }, + 10, + 2 +); + +add_filter( + '_wp_post_revision_fields', + /** + * Adds the footnotes field to the revision. + * + * @param array $fields The revision fields. + * + * @return array The revision fields. + */ + function( $fields ) { + $fields['footnotes'] = __( 'Footnotes' ); + return $fields; + } +); + +add_filter( + 'wp_post_revision_field_footnotes', + /** + * Gets the footnotes field from the revision. + * + * @param string $revision_field The field value, but $revision->$field + * (footnotes) does not exist. + * @param string $field The field name, in this case "footnotes". + * @param object $revision The revision object to compare against. + * + * @return string The field value. + */ + function( $revision_field, $field, $revision ) { + return get_metadata( 'post', $revision->ID, $field, true ); + }, + 10, + 3 +); diff --git a/test/e2e/specs/editor/various/footnotes.spec.js b/test/e2e/specs/editor/various/footnotes.spec.js index 376962b5c99ba7..ad3ab149095d2e 100644 --- a/test/e2e/specs/editor/various/footnotes.spec.js +++ b/test/e2e/specs/editor/various/footnotes.spec.js @@ -3,9 +3,11 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -async function getFootnotes( page ) { +async function getFootnotes( page, withoutSave = false ) { // Save post so we can check meta. - await page.click( 'button:text("Save draft")' ); + if ( ! withoutSave ) { + await page.click( 'button:text("Save draft")' ); + } await page.waitForSelector( 'button:text("Saved")' ); const footnotes = await page.evaluate( () => { return window.wp.data @@ -278,4 +280,84 @@ test.describe( 'Footnotes', () => { }, ] ); } ); + + test( 'works with revisions', async ( { editor, page } ) => { + await editor.canvas.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( 'first paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'second paragraph' ); + + await editor.showBlockToolbar(); + await editor.clickBlockToolbarButton( 'More' ); + await page.locator( 'button:text("Footnote")' ).click(); + + await page.keyboard.type( 'first footnote' ); + + const id1 = await editor.canvas.evaluate( () => { + return document.activeElement.id; + } ); + + await editor.canvas.click( 'p:text("first paragraph")' ); + + await editor.showBlockToolbar(); + await editor.clickBlockToolbarButton( 'More' ); + await page.locator( 'button:text("Footnote")' ).click(); + + await page.keyboard.type( 'second footnote' ); + + const id2 = await editor.canvas.evaluate( () => { + return document.activeElement.id; + } ); + + // This also saves the post! + expect( await getFootnotes( page ) ).toMatchObject( [ + { + content: 'second footnote', + id: id2, + }, + { + content: 'first footnote', + id: id1, + }, + ] ); + + await editor.canvas.click( 'p:text("first paragraph")' ); + + await editor.showBlockToolbar(); + await editor.clickBlockToolbarButton( 'Move down' ); + + // This also saves the post! + expect( await getFootnotes( page ) ).toMatchObject( [ + { + content: 'first footnote', + id: id1, + }, + { + content: 'second footnote', + id: id2, + }, + ] ); + + // Open revisions. + await editor.openDocumentSettingsSidebar(); + await page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Post' } ) + .click(); + await page.locator( 'a:text("2 Revisions")' ).click(); + await page.locator( '.revisions-controls .ui-slider-handle' ).focus(); + await page.keyboard.press( 'ArrowLeft' ); + await page.locator( 'input:text("Restore This Revision")' ).click(); + + expect( await getFootnotes( page, true ) ).toMatchObject( [ + { + content: 'second footnote', + id: id2, + }, + { + content: 'first footnote', + id: id1, + }, + ] ); + } ); } );