Restrict page types to editors: hide sidebar and lock down the taxonomy - #729
Restrict page types to editors: hide sidebar and lock down the taxonomy#729ilicfilip wants to merge 6 commits into
Conversation
|
Test on Playground |
✅ Code Coverage Report
📊 File-level Coverage Changes (2 files)📉 Coverage Decreased
ℹ️ About this report
|
Resolve the conflict in classes/admin/class-editor.php by keeping only the capability check. Two things this branch carried are dropped: - The site-editor.php bail. #746 deliberately removed it and moved the "where should the sidebar appear" decision into JS, where PrplProgressPlannerSidebar returns null for wp_template and wp_template_part. Keeping the PHP bail would undo that. - The 'page' !== get_post_type() check. In the site-editor there is no global post context, so get_post_type() returns false at enqueue_block_editor_assets time and the script would never enqueue there, again undoing #746. The capability check is the part that is still needed: develop has no capability gating in Editor at all, so every user who can open the block editor gets the sidebar plus the localized lessons and page-types payload.
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
232 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
377 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
381 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
388 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/suggested-tasks/data-collector/class-unpublished-content.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
103 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/suggested-tasks/data-collector/class-yoast-orphaned-content.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
111 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_row()\n$query assigned unsafely at line 98. |
📁 classes/suggested-tasks/data-collector/class-terms-without-description.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
108 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 106. |
📁 classes/suggested-tasks/data-collector/class-terms-without-posts.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
120 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 118. |
📁 classes/activities/class-query.php (2 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
71 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $table_name used in $wpdb->query()\n$table_name assigned unsafely at line 58. |
163 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $where_args used in $wpdb->get_results()\n$where_args assigned unsafely at line 153. |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
|
Brought this up to date with What changedThe branch was 86 commits behind and conflicted in Kept: the Dropped: the
That also settles the open question in the original description about the Blog page type — with the post-type restriction gone, posts and pages behave the same, and where the sidebar renders is JS's call per #746. Nothing further needed on that. Net changeSix lines, one file: // Assigning a page-type is an editorial decision, so require the capability
// to edit others' posts. Authors editing their own posts don't get the sidebar.
if ( ! \current_user_can( 'edit_others_posts' ) ) {
return;
}One thing worth knowing before mergingThis changes who sees the sidebar, so it's a visible behaviour change for Authors and Contributors — they lose it entirely. On severity: Verification
There's no automated coverage of |
The sidebar guard in class-editor.php only hides the UI. The write path
stayed open: progress_planner_page_types is registered with
show_in_rest and no capabilities array, so assign_terms fell back to
edit_posts and any author could set a page type on their own post
through the standard REST post endpoint -- no plugin UI involved.
Set assign_terms to edit_others_posts. Saying what a page is *for* is a
site-level editorial decision, not something an author decides for their
own post.
Raising the capability alone would break ordinary editing, though. The
posts controller rejects the whole request with rest_cannot_assign_term
when the field is present and not permitted, so an author who merely
resubmits the page type an editor already set would lose every other
change in that save -- title and content included. The block editor
round-trips the field once it is dirty in editor state, so this is
reachable in normal use.
Add a rest_request_before_callbacks filter that strips the field from
writes by users who cannot assign it, so their save proceeds and the
existing page type is left untouched. That hook is used because the
taxonomy permission check runs in the controller's permissions_check(),
before rest_pre_insert_{$post_type} would fire. GET requests are left
alone so the taxonomy still works as a query filter.
Verified against a real install -- author resubmitting the same term,
author attempting to change it, author saving with no term, GET
filtering, and an editor changing the term.
|
Extended this to actually close the write path, not just hide the UI. Still ready to merge — 22/22 checks pass, Why the extra commitThe sidebar guard alone doesn't prevent anything. So The part that needed careRaising the capability on its own introduces a worse bug than it fixes. The posts controller rejects the whole request with That's reachable in normal use, since the block editor round-trips the taxonomy once it's dirty in editor state. So there's a Verified
Note on the CI runThe first run after this commit showed 4 failures. They're a pre-existing flake, not this change: ScopeAlso checked whether authors could reach anything else: the two plugin REST routes with |
Currently we add the PP Sidebar to all edit screens and for all users, which is wrong. Authors shouldn't be deciding which page serves which purpose — that's a site-level editorial decision.
This PR now does two things: hides the sidebar UI, and closes the write path behind it.
1. Don't enqueue the editor script for users who can't act on it
classes/admin/class-editor.php— requireedit_others_postsbefore enqueueing:2. Restrict the taxonomy itself
Hiding the UI alone doesn't stop anything.
progress_planner_page_typesis registered withshow_in_rest => trueand nocapabilitiesarray, soassign_termsfalls back toedit_posts. Any author could set a page type on their own post through the standardPOST /wp/v2/posts/{id}endpoint — no plugin UI involved. Verified on a real install:So
assign_termsis nowedit_others_posts, with term management staying atmanage_categories.3. Don't let the new restriction eat unrelated edits
Raising the capability on its own introduces a worse bug. The posts controller rejects the entire request with
rest_cannot_assign_termwhen the field is present and not permitted — so an author who merely resubmits the page type an editor already set loses every other change in that save:This is reachable in normal use: the block editor round-trips the taxonomy once it's dirty in editor state.
So this adds a
rest_request_before_callbacksfilter that strips the field from writes by users who can't assign it. Their save proceeds; the existing page type is left untouched. That hook is used because the taxonomy permission check runs inside the controller's*_permissions_check(), which fires beforerest_pre_insert_{$post_type}would. GET requests are left alone so the taxonomy still works as a query filter.Verified behaviour
Exercised against a real install:
200, title saved, page type intact200, title saved, page type unchanged200200, unaffected200, term updatedScope note
The original version of this PR also restricted the sidebar to the Page edit screen. Both halves of that had to go, because #746 landed in the meantime (merged 2026-01-21, one day after this branch's last update):
site-editor.phpbail. WIP: Make the editor sidebar show up in the site-editor #746 deliberately removed it and moved the "where should the sidebar appear" decision into JS, wherePrplProgressPlannerSidebarreturnsnullforwp_template/wp_template_part.'page' !== \get_post_type()check would break the same feature from the other side: there's no global post context in the site-editor, so it returnsfalseatenqueue_block_editor_assetstime and the script would never enqueue there.That also settles the open question in the original description about the Blog page type — with the post-type restriction gone, posts and pages behave the same.
Verification
composer check-cs -- --warning-severity=6— clean (exit 0)composer phpstan— no errorscomposer test— OK (398 tests, 1203 assertions)There's no automated coverage of
Editor::enqueue_editor_script()or the taxonomy caps in the committed suite, so the suite passing confirms nothing else regressed rather than validating these changes. The behaviour table above came from throwaway probes that were not committed — worth deciding separately whether the capability behaviour deserves permanent test coverage.cc @jdevalk