From fe29221da5c54a8283ae42388114f779adc8a712 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 25 Apr 2026 11:41:11 +0200 Subject: [PATCH 1/2] tweak: improve snippets rest api --- .../class-snippets-rest-controller.php | 145 +++++++++++++++++- 1 file changed, 138 insertions(+), 7 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index bf37e60bf..5abf07e48 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -10,6 +10,7 @@ use WP_REST_Response; use WP_REST_Server; use function Code_Snippets\activate_snippet; +use function Code_Snippets\clean_active_snippets_cache; use function Code_Snippets\code_snippets; use function Code_Snippets\deactivate_snippet; use function Code_Snippets\trash_snippet; @@ -363,13 +364,35 @@ public function delete_item( $request ) { /** * Activate one item in the collection. * + * Shared network snippets are tracked per-site via the `active_shared_network_snippets` + * option rather than the global `active` column on `ms_snippets`, so that activation + * by a site administrator only takes effect on the current site. Non-shared snippets + * (regular site snippets and exclusive network snippets) continue to use the + * underlying `activate_snippet()` helper. + * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function activate_item( WP_REST_Request $request ) { $item = $this->prepare_item_for_database( $request ); - $result = activate_snippet( $item->id, $item->network ); + $snippet = $item ? get_snippet( $item->id, $item->network ) : null; + + if ( ! $snippet || ! $snippet->id ) { + return new WP_Error( + 'rest_cannot_activate', + __( 'The snippet could not be found.', 'code-snippets' ), + [ 'status' => 404 ] + ); + } + + if ( $snippet->shared_network ) { + $this->set_shared_network_active( $snippet->id, true ); + $snippet->active = true; + return rest_ensure_response( $snippet ); + } + + $result = activate_snippet( $snippet->id, $snippet->network ); return $result instanceof Snippet ? rest_ensure_response( $result ) : @@ -383,13 +406,33 @@ public function activate_item( WP_REST_Request $request ) { /** * Deactivate one item in the collection. * + * Shared network snippets are tracked per-site via the `active_shared_network_snippets` + * option, so deactivation here only removes the snippet from the current site's active + * list rather than disabling it globally on `ms_snippets`. + * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function deactivate_item( WP_REST_Request $request ) { $item = $this->prepare_item_for_database( $request ); - $result = deactivate_snippet( $item->id, $item->network ); + $snippet = $item ? get_snippet( $item->id, $item->network ) : null; + + if ( ! $snippet || ! $snippet->id ) { + return new WP_Error( + 'rest_cannot_activate', + __( 'The snippet could not be found.', 'code-snippets' ), + [ 'status' => 404 ] + ); + } + + if ( $snippet->shared_network ) { + $this->set_shared_network_active( $snippet->id, false ); + $snippet->active = false; + return rest_ensure_response( $snippet ); + } + + $result = deactivate_snippet( $snippet->id, $snippet->network ); return $result instanceof Snippet ? rest_ensure_response( $result ) : @@ -400,6 +443,40 @@ public function deactivate_item( WP_REST_Request $request ) { ); } + /** + * Toggle a shared network snippet's active state for the current site only. + * + * Shared network snippets live in the network-wide `ms_snippets` table but are + * activated on a per-site basis via the `active_shared_network_snippets` site option. + * Mutating the option here keeps activation state isolated to the current site and + * avoids accidentally enabling the snippet for the entire network. + * + * @param int $snippet_id Snippet identifier. + * @param bool $active Whether the snippet should be active on the current site. + * + * @return void + */ + private function set_shared_network_active( int $snippet_id, bool $active ): void { + $active_shared_snippets = get_option( 'active_shared_network_snippets', [] ); + + if ( ! is_array( $active_shared_snippets ) ) { + $active_shared_snippets = []; + } + + $already_active = in_array( $snippet_id, $active_shared_snippets, true ); + + if ( $active === $already_active ) { + return; + } + + $active_shared_snippets = $active ? + array_merge( $active_shared_snippets, [ $snippet_id ] ) : + array_values( array_diff( $active_shared_snippets, [ $snippet_id ] ) ); + + update_option( 'active_shared_network_snippets', $active_shared_snippets ); + clean_active_snippets_cache( code_snippets()->db->ms_table ); + } + /** * Prepare an instance of the Export class from a request. * @@ -480,6 +557,60 @@ public function prepare_item_for_response( $item, $request ) { return rest_ensure_response( $response ); } + /** + * Determine whether a request targets network-scoped snippets. + * + * Only the literal boolean `true` (or its common string/integer equivalents) + * is treated as a network-scoped request. A missing or null `network` param + * means "site-scoped", and must not be escalated to the network capability. + * + * @param WP_REST_Request $request Full data about the request. + * + * @return bool + */ + private function is_network_scoped_request( $request ): bool { + if ( ! is_multisite() ) { + return false; + } + + if ( ! $request instanceof WP_REST_Request || ! $request->has_param( 'network' ) ) { + return false; + } + + $network = $request->get_param( 'network' ); + + if ( is_bool( $network ) ) { + return $network; + } + + if ( is_string( $network ) ) { + return in_array( strtolower( $network ), [ '1', 'true', 'yes' ], true ); + } + + return (bool) $network; + } + + /** + * Verify the current user has permission for the scope implied by the request. + * + * When the request payload sets `network=true`, the user must hold the network + * capability (e.g. `manage_network_options`) regardless of whether the request + * originated from the network admin context. This closes a privilege-escalation + * vector where a subsite administrator could forge `network=true` to operate + * on network-scoped snippets. + * + * @param WP_REST_Request $request Full data about the request. + * + * @return bool + */ + private function check_request_capability( $request ): bool { + if ( $this->is_network_scoped_request( $request ) ) { + return code_snippets()->user_can_manage_network_snippets(); + } + + return code_snippets()->current_user_can(); + } + /** * Check if a given request has access to get items. * @@ -488,7 +619,7 @@ public function prepare_item_for_response( $item, $request ) { * @return boolean */ public function get_items_permissions_check( $request ): bool { - return code_snippets()->current_user_can(); + return $this->check_request_capability( $request ); } /** @@ -499,7 +630,7 @@ public function get_items_permissions_check( $request ): bool { * @return boolean */ public function get_item_permissions_check( $request ): bool { - return $this->get_items_permissions_check( $request ); + return $this->check_request_capability( $request ); } /** @@ -510,7 +641,7 @@ public function get_item_permissions_check( $request ): bool { * @return boolean */ public function create_item_permissions_check( $request ): bool { - return code_snippets()->current_user_can(); + return $this->check_request_capability( $request ); } /** @@ -521,7 +652,7 @@ public function create_item_permissions_check( $request ): bool { * @return boolean */ public function update_item_permissions_check( $request ): bool { - return $this->create_item_permissions_check( $request ); + return $this->check_request_capability( $request ); } /** @@ -532,7 +663,7 @@ public function update_item_permissions_check( $request ): bool { * @return boolean */ public function delete_item_permissions_check( $request ): bool { - return $this->create_item_permissions_check( $request ); + return $this->check_request_capability( $request ); } /** From ad8887c4d5e47657571e284b354a55ae966e40e1 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 25 Apr 2026 11:43:41 +0200 Subject: [PATCH 2/2] remove redundant comments --- .../class-snippets-rest-controller.php | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index 5abf07e48..db31f74c8 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -364,12 +364,6 @@ public function delete_item( $request ) { /** * Activate one item in the collection. * - * Shared network snippets are tracked per-site via the `active_shared_network_snippets` - * option rather than the global `active` column on `ms_snippets`, so that activation - * by a site administrator only takes effect on the current site. Non-shared snippets - * (regular site snippets and exclusive network snippets) continue to use the - * underlying `activate_snippet()` helper. - * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response @@ -406,10 +400,6 @@ public function activate_item( WP_REST_Request $request ) { /** * Deactivate one item in the collection. * - * Shared network snippets are tracked per-site via the `active_shared_network_snippets` - * option, so deactivation here only removes the snippet from the current site's active - * list rather than disabling it globally on `ms_snippets`. - * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response @@ -446,11 +436,6 @@ public function deactivate_item( WP_REST_Request $request ) { /** * Toggle a shared network snippet's active state for the current site only. * - * Shared network snippets live in the network-wide `ms_snippets` table but are - * activated on a per-site basis via the `active_shared_network_snippets` site option. - * Mutating the option here keeps activation state isolated to the current site and - * avoids accidentally enabling the snippet for the entire network. - * * @param int $snippet_id Snippet identifier. * @param bool $active Whether the snippet should be active on the current site. * @@ -593,12 +578,6 @@ private function is_network_scoped_request( $request ): bool { /** * Verify the current user has permission for the scope implied by the request. * - * When the request payload sets `network=true`, the user must hold the network - * capability (e.g. `manage_network_options`) regardless of whether the request - * originated from the network admin context. This closes a privilege-escalation - * vector where a subsite administrator could forge `network=true` to operate - * on network-scoped snippets. - * * @param WP_REST_Request $request Full data about the request. * * @return bool