From 1e3e4d7e1032eb09c73f50475171c9c348bd3b3a Mon Sep 17 00:00:00 2001 From: Filip Ilic Date: Wed, 17 Dec 2025 13:10:55 +0100 Subject: [PATCH 1/2] enqueue only on "page edit" screen and for editors and admins --- classes/admin/class-editor.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classes/admin/class-editor.php b/classes/admin/class-editor.php index 449bd4a2ae..dbbd84bdeb 100644 --- a/classes/admin/class-editor.php +++ b/classes/admin/class-editor.php @@ -34,6 +34,11 @@ public function enqueue_editor_script() { return; } + // Only load on Page edit screen and if the user can edit others posts. + if ( 'page' !== \get_post_type() || ! \current_user_can( 'edit_others_posts' ) ) { + return; + } + $page_types = \progress_planner()->get_page_types()->get_page_types(); // Check if the page-type is set in the URL (user is coming from the Settings page). From abd325dd0ae47822444c187771972d86360cc528 Mon Sep 17 00:00:00 2001 From: Filip Ilic Date: Tue, 25 Aug 2026 15:06:19 +0200 Subject: [PATCH 2/2] Restrict assigning page types to users who can edit others' posts 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. --- classes/class-page-types.php | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/classes/class-page-types.php b/classes/class-page-types.php index 2b58d751d0..08c3ae449f 100644 --- a/classes/class-page-types.php +++ b/classes/class-page-types.php @@ -34,6 +34,9 @@ public function __construct() { \add_action( 'init', [ $this, 'maybe_add_terms' ] ); \add_action( 'init', [ $this, 'maybe_update_terms' ] ); + // Drop the page-type from REST post writes by users who can't assign it. + \add_filter( 'rest_request_before_callbacks', [ $this, 'remove_page_type_if_not_allowed' ], 10, 3 ); + // Add hook when updating the `page_on_front` option. \add_action( 'update_option_page_on_front', [ $this, 'update_option_page_on_front' ] ); @@ -63,10 +66,58 @@ public function create_taxonomy() { 'rewrite' => [ 'slug' => 'site-type' ], 'show_in_rest' => true, 'show_in_menu' => false, + // Assigning a page-type says what a page is *for*, which is a site-level + // editorial decision rather than something an author does on their own + // post. Without this, assign_terms falls back to `edit_posts` and any + // author can set it through the standard REST post endpoint. + 'capabilities' => [ + 'manage_terms' => 'manage_categories', + 'edit_terms' => 'manage_categories', + 'delete_terms' => 'manage_categories', + 'assign_terms' => 'edit_others_posts', + ], ] ); } + /** + * Remove the page-type from a REST post write when the user can't assign it. + * + * Without this, WP rejects the whole request with `rest_cannot_assign_term`, so an + * author who resubmits the page-type an editor already set loses every other change + * in that same save (title, content). Silently dropping the field keeps their edit + * working while leaving the existing page-type untouched. + * + * Hooked on `rest_request_before_callbacks` because the taxonomy permission check + * lives in the posts controller's `*_permissions_check()`, which runs before + * `rest_pre_insert_{$post_type}` would fire. + * + * @param \WP_REST_Response|\WP_HTTP_Response|\WP_Error|mixed $response The response. + * @param array $handler The route handler. + * @param \WP_REST_Request $request The request. + * + * @return \WP_REST_Response|\WP_HTTP_Response|\WP_Error|mixed + */ + public function remove_page_type_if_not_allowed( $response, $handler, $request ) { + if ( null === $request[ self::TAXONOMY_NAME ] ) { + return $response; + } + + // Only touch writes; a GET can legitimately carry the field as a query filter. + if ( ! \in_array( $request->get_method(), [ 'POST', 'PUT', 'PATCH' ], true ) ) { + return $response; + } + + $taxonomy = \get_taxonomy( self::TAXONOMY_NAME ); + if ( ! $taxonomy || \current_user_can( $taxonomy->cap->assign_terms ) ) { + return $response; + } + + unset( $request[ self::TAXONOMY_NAME ] ); + + return $response; + } + /** * Maybe add terms to the `progress_planner_page_types` taxonomy. *