Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Footnotes: store in revisions #52686

Merged
merged 9 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions packages/block-library/src/footnotes/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is causing PHP test failures on the core backport. Not 100% sure why that's happening but might be good to check if the revision exists/is an object before trying to access one of its properties?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#52879 fixes it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @tellthemachines already created PR for this #52870


// 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
);
86 changes: 84 additions & 2 deletions test/e2e/specs/editor/various/footnotes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
},
] );
} );
} );