diff --git a/classes/controllers/FrmMcpSkillEnvController.php b/classes/controllers/FrmMcpSkillEnvController.php index 8eaf6aa579..1503b5b1a2 100644 --- a/classes/controllers/FrmMcpSkillEnvController.php +++ b/classes/controllers/FrmMcpSkillEnvController.php @@ -52,6 +52,18 @@ public static function download() { self::verify_nonce( self::DOWNLOAD_ACTION ); + if ( FrmAppHelper::get_post_param( 'frm_mcp_settings_shown', 0, 'absint' ) ) { + $settings = FrmAppHelper::get_settings(); + $settings->mcp = FrmAppHelper::get_post_param( 'frm_mcp', array(), 'absint' ) ? 1 : 0; + $settings->store(); + FrmMcpController::reset(); + } + + // MCP may have been enabled after init skipped loading the adapter. + if ( FrmMcpController::is_enabled() ) { + FrmMcpCompat::load_adapter(); + } + if ( ! FrmMcpController::is_enabled() || ! FrmMcpCompat::is_usable() || diff --git a/tests/phpunit/abilities/test_FrmMcpSkillEnvController.php b/tests/phpunit/abilities/test_FrmMcpSkillEnvController.php index 2dc46ad84e..7871bf2ee0 100644 --- a/tests/phpunit/abilities/test_FrmMcpSkillEnvController.php +++ b/tests/phpunit/abilities/test_FrmMcpSkillEnvController.php @@ -5,6 +5,90 @@ */ class test_FrmMcpSkillEnvController extends FrmUnitTest { + /** + * Check submitted toggle states and authorization boundaries for downloads. + * + * @return void + */ + public function test_download_saves_submitted_mcp_setting() { + foreach ( self::get_download_scenarios() as $scenario ) { + $this->check_download_settings( $scenario[0], $scenario[1], $scenario[2], $scenario[3], $scenario[4] ); + } + } + + /** + * Downloading saves only the submitted MCP toggle after authorization. + * + * Application Passwords are disabled to stop before the file response exits. + * + * @param int $initial Saved MCP value before downloading. + * @param array $posted Submitted MCP settings. + * @param int $expected Saved MCP value after downloading. + * @param string $role Role of the downloading user. + * @param bool $valid_nonce Whether the download nonce is valid. + * + * @return void + */ + private function check_download_settings( $initial, $posted, $expected, $role, $valid_nonce ) { + $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $admin_id ); + FrmAppHelper::maybe_add_permissions(); + $user_id = $this->factory->user->create( array( 'role' => $role ) ); + wp_set_current_user( $user_id ); + + $settings = FrmAppHelper::get_settings(); + $original_mcp = $settings->mcp; + $original_post = $_POST; + $settings->mcp = $initial; + $settings->store(); + FrmMcpController::reset(); + $this->assertSame( (bool) $initial, FrmMcpController::is_enabled(), 'The initial MCP setting should be cached.' ); + $original_currency = $settings->currency; + + $_POST = $posted; + $_POST[ FrmMcpSkillEnvController::DOWNLOAD_ACTION . '_nonce' ] = $valid_nonce ? wp_create_nonce( FrmMcpSkillEnvController::DOWNLOAD_ACTION ) : 'invalid'; + add_filter( 'wp_is_application_passwords_available', '__return_false' ); + + try { + FrmMcpSkillEnvController::download(); + $this->fail( 'The download should stop before creating a credential.' ); + } catch ( WPDieException $exception ) { + $this->assertSame( 403, $exception->getCode(), 'The blocked download should return a forbidden response.' ); + $this->assertSame( $expected, $settings->mcp, 'The submitted toggle should be saved only after authorization.' ); + $this->assertSame( $expected, get_option( 'frm_options' )->mcp, 'The MCP setting should persist for the next request.' ); + $this->assertSame( (bool) $expected, FrmMcpController::is_enabled(), 'The cached MCP setting should reflect the saved value.' ); + $this->assertSame( $original_currency, $settings->currency, 'Other settings should remain unchanged.' ); + $this->assertSame( array(), FrmMcpSkillEnvController::get_passwords( $user_id ), 'A blocked download should not create a credential.' ); + } finally { + $_POST = $original_post; + remove_filter( 'wp_is_application_passwords_available', '__return_false' ); + $settings->mcp = $original_mcp; + $settings->store(); + FrmMcpController::reset(); + } + } + + /** + * Submitted toggle states and authorization boundaries for downloads. + * + * @return array + */ + private static function get_download_scenarios() { + $enabled = array( + 'frm_mcp_settings_shown' => 1, + 'frm_mcp' => array( 1 ), + ); + + return array( + 'unsaved enabled toggle' => array( 0, $enabled, 1, 'administrator', true ), + 'unchecked toggle' => array( 1, array( 'frm_mcp_settings_shown' => 1 ), 0, 'administrator', true ), + 'no settings submitted' => array( 1, array(), 1, 'administrator', true ), + 'no settings marker' => array( 0, array( 'frm_mcp' => array( 1 ) ), 0, 'administrator', true ), + 'invalid nonce' => array( 0, $enabled, 0, 'administrator', false ), + 'unauthorized user' => array( 0, $enabled, 0, 'subscriber', true ), + ); + } + /** * Values containing shell syntax stay literal when the skill sources the file. *