Skip to content

feat: register WordPress abilities for the copy actions - #1

Draft
roborourke wants to merge 3 commits into
trunkfrom
claude/wizardly-babbage-k3pdy9
Draft

roborourke wants to merge 3 commits into
trunkfrom
claude/wizardly-babbage-k3pdy9

Conversation

@roborourke

Copy link
Copy Markdown
Collaborator

Context

  • The plugin exposes its features through admin links, bulk actions and a single REST route, so nothing outside the admin can clone a post, copy it to a new draft, or drive Rewrite & Republish. WordPress 6.9 ships the Abilities API, which is the standard way to make plugin features available to the REST API, MCP servers and AI agents. This PR registers the four main copy actions as abilities.

Summary

This PR can be summarized in the following changelog entry:

  • Adds abilities for cloning a post, copying it to a new draft, rewriting it and republishing it, so those actions can be used through the Abilities API.

Relevant technical choices:

  • A new Yoast\WP\Duplicate_Post\Abilities class registers a duplicate-post ability category and four abilities: duplicate-post/clone, duplicate-post/copy-to-new-draft, duplicate-post/rewrite and duplicate-post/republish. It is wired by hand in the Duplicate_Post bootstrap class, like the other services.
  • Every ability takes a single post_id and returns the resulting post's ID, title, status and edit link. duplicate-post/republish accepts either the ID of the Rewrite & Republish copy or the ID of the post it was made from.
  • The permission callbacks reuse Permissions_Helper, so the copy_posts capability, the enabled post types and the Rewrite & Republish constraints apply exactly as they do in the admin. Republishing additionally requires edit_post on the original, which is the same check Post_Republisher makes. The duplicate_post_allow filter is honoured too, so a site that blocks duplication of a post blocks it here as well.
  • The copies are created through Post_Duplicator rather than the legacy duplicate_post_create_duplicate(), because admin-functions.php bails out when the request is not an admin request and abilities run outside the admin. To keep the behaviour the same, Post_Duplicator::get_configured_options() was added: it maps the saved copy settings onto the duplicator's options array, and the abilities then copy taxonomies and meta with those options.
  • One consequence of that choice: copying children, attachments and comments is implemented by the admin-only handlers on duplicate_post_after_duplicated, so those three settings do not apply to copies made through an ability. That action is deliberately not fired from the abilities — in an admin request its handlers are attached and would copy the meta a second time. Happy to move those handlers into src/ in a follow-up if you would rather have full parity.
  • src/abilities.php was added to the existing temporary exclusion for Yoast.NamingConventions.ValidHookName.WrongPrefix, because it applies the existing, non-namespaced duplicate_post_allow filter. No thresholds were raised: composer check-cs-thresholds still reports 57/57 errors and 0/0 warnings.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  • Install the plugin from this branch on a WordPress 6.9 or newer site and make sure your user has the "copy_posts" permission (Settings → Duplicate Post → Permissions).
  • Publish a post, and note its ID (it is the post number in the URL when you edit it).
  • In a terminal, list the abilities: wp eval 'foreach ( wp_get_abilities() as $ability ) { echo $ability->get_name(), PHP_EOL; }'. The four duplicate-post/... abilities should be listed.
  • Run wp eval 'var_dump( wp_get_ability( "duplicate-post/clone" )->execute( array( "post_id" => 123 ) ) );' --user=1, replacing 123 with your post ID. A copy should appear in the posts list, with the title prefix and suffix and the other copy settings applied, just like the "Clone" row action.
  • Do the same with duplicate-post/copy-to-new-draft: the copy should always be a draft.
  • Run duplicate-post/rewrite on the published post: a Rewrite & Republish copy should be created, and the post should show "Rewrite & Republish" is in progress. Open the copy, change its content, save it as a draft, then run duplicate-post/republish with the copy's ID: the original post should now show the rewritten content and the copy should be gone.
  • Repeat the first duplicate-post/clone call as a user without the copy_posts permission (--user=<subscriber>): it should fail with a permission error instead of creating a copy.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

The abilities apply the copy settings and the enabled post types, so they are worth trying on a custom post type and on a post with taxonomy terms and custom fields.

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

  • See the steps above.

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • Copy creation. Post_Duplicator gained a new method but none of its existing methods changed, and the admin links, bulk actions and Rewrite & Republish flows are untouched.

UI changes

  • This PR changes the UI in the plugin. I have added the 'UI change' label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities
  • I have added unittests to verify the code works as intended

composer test, composer lint and composer check-cs-thresholds all pass. The integration tests in tests/WP/Abilities_Test.php could not be run in the environment this was written in, because neither Docker nor a database was available for composer test-wp-env — they still need to be verified by CI or a reviewer.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label and noted the work hours.

Fixes Yoast#544


🤖 Generated with Claude Code

https://claude.ai/code/session_01LxVAxTfaqRjd8faeWp37tx


Generated by Claude Code

Adds Post_Duplicator::get_configured_options(), which maps the saved copy
settings onto the options array the duplicator takes. The legacy admin
duplication path reads the same settings, so code that has to create a copy
outside an admin request can now do so with the same settings applied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxVAxTfaqRjd8faeWp37tx
Registers a `duplicate-post` ability category and four abilities on the
Abilities API: clone, copy-to-new-draft, rewrite and republish. They run the
existing services, so the copy settings, the enabled post types, the
`copy_posts` capability, the Rewrite & Republish constraints and the
`duplicate_post_allow` filter all apply as they do in the admin.

The abilities file is added to the temporary exclusion for the hook prefix
sniff, because it applies the existing `duplicate_post_allow` filter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxVAxTfaqRjd8faeWp37tx
Comment thread src/post-duplicator.php
Comment thread src/post-duplicator.php
Bakes the shared `duplicate_post_copy` prefix into the helper, so it takes the
post element to copy instead of a full option name and can only read the copy
settings it is meant for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxVAxTfaqRjd8faeWp37tx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add WP abilities for key plugin functions

2 participants