namespace, '/mass-action/(?P[a-z_]+)', array( array( 'methods' => 'POST', 'callback' => array( $this, 'run' ), 'permission_callback' => array( $this, 'can_run' ), ), ) ); } /** * The entity-level gate: may you edit — or, for `delete`, delete — this kind of record at all. * * THIS USED TO ASK ONLY WHETHER THE ENTITY EXISTED, under a docblock that called it "are you signed * in to the CRM at all". Nothing here checked that, so a signed-out visitor reached the service, * which refused — but only after validating their payload and answering them in detail. The * refusal belongs before any processing, which is what a permission callback is for. * * It asks the SAME entity-level question {@see MassAction::run()} asks, with the same mapping of * action to capability, so an allowed user sees no change. The service keeps its own check, and the * per-RECORD decision still happens there: a permission callback cannot see the ids. * * @param WP_REST_Request $request The request. */ public function can_run( WP_REST_Request $request ): bool|WP_Error { $entity_type = sanitize_key( (string) $request['entity_type'] ); if ( ! $this->writer->knows( $entity_type ) ) { return $this->forbidden(); } $body = (array) $request->get_json_params(); $capability = 'delete' === ( $body['action'] ?? '' ) ? 'delete' : 'edit'; return Capabilities::user_can( $capability, $entity_type ) ? true : $this->forbidden(); } /** * @param WP_REST_Request $request The request. */ public function run( WP_REST_Request $request ): WP_REST_Response|WP_Error { $body = (array) $request->get_json_params(); $result = $this->actions->run( sanitize_key( (string) $request['entity_type'] ), sanitize_key( (string) ( $body['action'] ?? '' ) ), (array) ( $body['ids'] ?? array() ), (array) ( $body['values'] ?? array() ) ); return $result instanceof WP_Error ? $result : $this->ok( $result ); } }