get_route() ) ) { return $response; } $method = strtoupper( $request->get_method() ); if ( 'GET' === $method ) { return self::normalize_get_input_transport( $response, $request ); } if ( 'DELETE' === $method ) { return self::normalize_delete_input_transport( $response, $request ); } return $response; } /** * Normalize the GET input_json query carrier. * * @param mixed $response Result to send to the client, or null. * @param \WP_REST_Request $request Current REST request. * @return mixed */ private static function normalize_get_input_transport( $response, \WP_REST_Request $request ) { $query = $request->get_query_params(); if ( ! array_key_exists( 'input_json', $query ) ) { return $response; } if ( array_key_exists( 'input', $query ) ) { return self::invalid_input_transport(); } $input = self::decode_input_object( $query['input_json'], self::MAX_GET_INPUT_BYTES ); if ( is_wp_error( $input ) ) { return $input; } unset( $query['input_json'] ); $query['input'] = $input; $request->set_query_params( $query ); return $response; } /** * Normalize the DELETE application/json body carrier. * * @param mixed $response Result to send to the client, or null. * @param \WP_REST_Request $request Current REST request. * @return mixed */ private static function normalize_delete_input_transport( $response, \WP_REST_Request $request ) { $body = $request->get_body(); $query = $request->get_query_params(); if ( '' === trim( $body ) ) { if ( array_key_exists( 'input_json', $query ) ) { return self::invalid_input_transport(); } return $response; } if ( array_key_exists( 'input', $query ) || array_key_exists( 'input_json', $query ) ) { return self::invalid_input_transport(); } $content_type = $request->get_content_type(); if ( ! is_array( $content_type ) || 'application/json' !== $content_type['value'] ) { return self::invalid_input_transport(); } if ( strlen( $body ) > self::MAX_DELETE_INPUT_BYTES ) { return self::invalid_input_transport(); } $shape = json_decode( $body, false, 32 ); $decoded = json_decode( $body, true, 32 ); if ( JSON_ERROR_NONE !== json_last_error() || ! $shape instanceof \stdClass || array( 'input' ) !== array_keys( get_object_vars( $shape ) ) || ! $shape->input instanceof \stdClass || ! is_array( $decoded ) ) { return self::invalid_input_transport(); } $input = $decoded['input']; if ( ! is_array( $input ) ) { return self::invalid_input_transport(); } $query['input'] = $input; $request->set_query_params( $query ); return $response; } /** * Decode a bounded JSON object without coercing scalar values. * * @param mixed $raw Raw JSON value. * @param int $max_bytes Maximum encoded size. * @return array|\WP_Error */ private static function decode_input_object( $raw, int $max_bytes ) { if ( ! is_string( $raw ) || strlen( $raw ) > $max_bytes ) { return self::invalid_input_transport(); } $decoded = json_decode( $raw, true, 32 ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $decoded ) || '{' !== substr( ltrim( $raw ), 0, 1 ) ) { return self::invalid_input_transport(); } return $decoded; } /** * Return the closed, non-reflective transport error. * * @return \WP_Error */ private static function invalid_input_transport(): \WP_Error { return new \WP_Error( 'mainwp_abilities_invalid_input_transport', __( 'The ability input transport is invalid.', 'mainwp' ), array( 'status' => 400 ) ); } /** * Register MainWP ability categories. * * @return void */ public static function register_categories(): void { if ( ! function_exists( 'wp_register_ability_category' ) ) { return; } // Sites category. wp_register_ability_category( 'mainwp-sites', array( 'label' => __( 'MainWP Sites', 'mainwp' ), 'description' => __( 'Abilities for managing MainWP child sites including listing, syncing, and monitoring.', 'mainwp' ), ) ); // Updates category. wp_register_ability_category( 'mainwp-updates', array( 'label' => __( 'MainWP Updates', 'mainwp' ), 'description' => __( 'Abilities for managing updates across MainWP child sites including core, plugins, and themes.', 'mainwp' ), ) ); // Clients category. wp_register_ability_category( 'mainwp-clients', array( 'label' => __( 'MainWP Clients', 'mainwp' ), 'description' => __( 'Abilities for managing MainWP clients including creation, updates, and client-site relationships.', 'mainwp' ), ) ); // Tags category. wp_register_ability_category( 'mainwp-tags', array( 'label' => __( 'MainWP Tags', 'mainwp' ), 'description' => __( 'Abilities for managing MainWP tags for organizing sites and clients.', 'mainwp' ), ) ); // Batch category. wp_register_ability_category( 'mainwp-batch', array( 'label' => __( 'MainWP Batch Operations', 'mainwp' ), 'description' => __( 'Abilities for monitoring queued batch operations including sync, update, and site management tasks.', 'mainwp' ), ) ); } /** * Register all MainWP abilities. * * @return void */ public static function register_abilities(): void { // Site abilities. MainWP_Abilities_Sites::register(); // Update abilities. MainWP_Abilities_Updates::register(); // Client abilities. MainWP_Abilities_Clients::register(); // Tag abilities. MainWP_Abilities_Tags::register(); // Batch abilities. MainWP_Abilities_Batch::register(); // NOTE: Extensions register their own abilities in their own codebases. // Total core abilities: 62 (30 sites + 13 updates + 11 clients + 7 tags + 1 batch). } }