diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index bf37e60bf..db31f74c8 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; @@ -369,7 +370,23 @@ public function delete_item( $request ) { */ 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 ) : @@ -389,7 +406,23 @@ public function activate_item( WP_REST_Request $request ) { */ 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 +433,35 @@ public function deactivate_item( WP_REST_Request $request ) { ); } + /** + * Toggle a shared network snippet's active state for the current site only. + * + * @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 +542,54 @@ 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. + * + * @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 +598,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 +609,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 +620,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 +631,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 +642,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 ); } /**