diff --git a/classes/controllers/FrmEntriesAJAXSubmitController.php b/classes/controllers/FrmEntriesAJAXSubmitController.php index 201cf50d79..0af56c5fdf 100644 --- a/classes/controllers/FrmEntriesAJAXSubmitController.php +++ b/classes/controllers/FrmEntriesAJAXSubmitController.php @@ -42,7 +42,7 @@ public static function ajax_create() { $form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' ); - if ( ! $form_id ) { + if ( ! $form_id || is_array( $form_id ) ) { echo json_encode( $response ); wp_die(); } diff --git a/classes/controllers/FrmEntriesController.php b/classes/controllers/FrmEntriesController.php index e68c5a7a9a..2da2467fd6 100644 --- a/classes/controllers/FrmEntriesController.php +++ b/classes/controllers/FrmEntriesController.php @@ -772,7 +772,8 @@ public static function destroy() { public static function process_entry( $errors = '', $ajax = false ) { $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' ); - if ( FrmAppHelper::is_admin() || empty( $_POST ) || ! $form_id || ! isset( $_POST['item_key'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( FrmAppHelper::is_admin() || empty( $_POST ) || ! $form_id || is_array( $form_id ) || ! isset( $_POST['item_key'] ) ) { return; } diff --git a/classes/helpers/FrmAppHelper.php b/classes/helpers/FrmAppHelper.php index fa9234fec0..1c78f55227 100644 --- a/classes/helpers/FrmAppHelper.php +++ b/classes/helpers/FrmAppHelper.php @@ -929,7 +929,7 @@ public static function sanitize_request( $sanitize_method, &$values ) { foreach ( $temp_values as $k => $val ) { if ( isset( $sanitize_method[ $k ] ) ) { - $values[ $k ] = call_user_func( $sanitize_method[ $k ], $val ); + $values[ $k ] = is_array( $val ) ? '' : call_user_func( $sanitize_method[ $k ], $val ); } } } @@ -1900,11 +1900,16 @@ public static function save_combined_js() { * * @since 1.07.10 * - * @param bool|int|string $value The value to compare. + * @param array|bool|int|string $value The value to compare. * * @return bool */ public static function is_true( $value ) { + if ( is_array( $value ) ) { + // An array is never a true flag, and casting one to a string triggers a warning. + return false; + } + return true === $value || '1' === (string) $value || 'true' === $value || 'yes' === $value; } diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 02e8038ff9..22d4d64df1 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -1291,7 +1291,8 @@ public static function replace_shortcodes( $html, $form, $title = false, $descri } elseif ( $code === 'form_description' ) { $replace_with = FrmAppHelper::use_wpautop( $form->description ); } elseif ( $code === 'entry_key' && ! empty( $_GET ) && isset( $_GET['entry'] ) ) { - $replace_with = FrmAppHelper::simple_get( 'entry', 'sanitize_title' ); + $entry_key = FrmAppHelper::simple_get( 'entry', 'sanitize_title' ); + $replace_with = is_array( $entry_key ) ? '' : $entry_key; } else { $replace_with = ''; } diff --git a/classes/models/FrmEntryValidate.php b/classes/models/FrmEntryValidate.php index 4172b40080..c474f55a9b 100644 --- a/classes/models/FrmEntryValidate.php +++ b/classes/models/FrmEntryValidate.php @@ -478,10 +478,10 @@ public static function validate_field_types( &$errors, $posted_field, $value, $a } /** - * @param array $errors - * @param object $field - * @param string $value - * @param array $args + * @param array $errors + * @param object $field + * @param array|string $value + * @param array $args * * @return void */ @@ -494,7 +494,7 @@ public static function validate_phone_field( &$errors, $field, $value, $args ) { $pattern = self::phone_format( $field ); - if ( ! preg_match( $pattern, $value ) ) { + if ( is_array( $value ) || ! preg_match( $pattern, $value ) ) { $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' ); } } diff --git a/classes/models/FrmFormState.php b/classes/models/FrmFormState.php index 33005ec6a9..b2d5e3be02 100644 --- a/classes/models/FrmFormState.php +++ b/classes/models/FrmFormState.php @@ -133,7 +133,8 @@ public static function maybe_render_state_field( $form ) { private static function get_state_from_request() { $encrypted_state = FrmAppHelper::get_post_param( 'frm_state', '', 'sanitize_text_field' ); - if ( ! $encrypted_state ) { + if ( ! $encrypted_state || ! is_string( $encrypted_state ) ) { + // An array value (frm_state[]=x) would be a TypeError in openssl_decrypt. return false; } diff --git a/classes/models/fields/FrmFieldType.php b/classes/models/fields/FrmFieldType.php index b3e5e84a7b..002a29985d 100644 --- a/classes/models/fields/FrmFieldType.php +++ b/classes/models/fields/FrmFieldType.php @@ -1450,7 +1450,8 @@ protected function prepare_esc_value() { } if ( is_array( $value ) ) { - $value = implode( ', ', $value ); + // Flatten first. A nested array in the request would otherwise trigger an "Array to string conversion" warning. + $value = implode( ', ', FrmAppHelper::array_flatten( $value, 'reset' ) ); } return str_contains( $value, '<' ) ? htmlentities( $value ) : $value; diff --git a/tests/phpunit/entries/test_FrmEntriesController.php b/tests/phpunit/entries/test_FrmEntriesController.php index aa1011b373..c763993727 100644 --- a/tests/phpunit/entries/test_FrmEntriesController.php +++ b/tests/phpunit/entries/test_FrmEntriesController.php @@ -122,4 +122,23 @@ public function test_spam_reason_in_sidebar() { $this->assertStringNotContainsString( 'Spam_source', $html ); $this->assertStringNotContainsString( 'Test_sample', $html ); } + + /** + * @covers FrmEntriesController::process_entry + */ + public function test_process_entry_ignores_array_form_id() { + global $frm_vars; + + $form_id = $this->factory->form->create(); + $_POST['form_id'] = array( (string) $form_id ); + $_POST['item_key'] = 'abc'; + + try { + FrmEntriesController::process_entry(); + } finally { + unset( $_POST['form_id'], $_POST['item_key'] ); + } + + $this->assertFalse( isset( $frm_vars['form_params'][ $form_id ] ), 'An array form_id should stop before the form is processed.' ); + } } diff --git a/tests/phpunit/entries/test_FrmEntryValidate.php b/tests/phpunit/entries/test_FrmEntryValidate.php index 5a39c3b176..ae38a3d6d1 100644 --- a/tests/phpunit/entries/test_FrmEntryValidate.php +++ b/tests/phpunit/entries/test_FrmEntryValidate.php @@ -32,6 +32,23 @@ public function test_validate() { remove_filter( 'frm_validate_entry', $add_a_custom_error ); } + /** + * @covers FrmEntryValidate::validate_phone_field + */ + public function test_validate_phone_field_rejects_array_value() { + $field = $this->factory->field->create_and_get( + array( + 'type' => 'phone', + 'form_id' => $this->factory->form->create(), + ) + ); + $errors = array(); + + FrmEntryValidate::validate_phone_field( $errors, $field, array( 'x' ), array( 'id' => $field->id ) ); + + $this->assertArrayHasKey( 'field' . $field->id, $errors ); + } + /** * @since 7.0 * diff --git a/tests/phpunit/fields/test_FrmFieldType.php b/tests/phpunit/fields/test_FrmFieldType.php index d5d13315fd..683f698c6f 100644 --- a/tests/phpunit/fields/test_FrmFieldType.php +++ b/tests/phpunit/fields/test_FrmFieldType.php @@ -38,6 +38,22 @@ public function test_html_min_number() { $this->assertStringContainsString( ' step="any"', $form ); } + /** + * @covers FrmFieldType::prepare_esc_value + */ + public function test_prepare_esc_value_flattens_nested_array() { + $field_type = new FrmFieldText( + array( + 'id' => 1, + 'value' => array( 'a', array( 'b', array( 'c' ) ) ), + ) + ); + + $value = $this->run_private_method( array( $field_type, 'prepare_esc_value' ) ); + + $this->assertSame( 'a, b, c', $value ); + } + public function test_sanitize_value() { $this->set_current_user_to_1(); $frm_field_type = new FrmFieldDefault(); diff --git a/tests/phpunit/forms/test_FrmFormState.php b/tests/phpunit/forms/test_FrmFormState.php new file mode 100644 index 0000000000..5817f2e177 --- /dev/null +++ b/tests/phpunit/forms/test_FrmFormState.php @@ -0,0 +1,20 @@ +run_private_method( array( 'FrmFormState', 'get_state_from_request' ) ); + + unset( $_POST['frm_state'] ); + + $this->assertFalse( $result ); + } +} diff --git a/tests/phpunit/forms/test_FrmFormsHelper.php b/tests/phpunit/forms/test_FrmFormsHelper.php index 929857e96e..65ce7bf487 100644 --- a/tests/phpunit/forms/test_FrmFormsHelper.php +++ b/tests/phpunit/forms/test_FrmFormsHelper.php @@ -632,4 +632,22 @@ public function test_count_fields_without_submit() { $this->assertSame( 2, FrmFormsHelper::count_fields_without_submit( $fields ) ); $this->assertSame( 0, FrmFormsHelper::count_fields_without_submit( array() ) ); } + + /** + * @covers FrmFormsHelper::replace_shortcodes + */ + public function test_replace_shortcodes_ignores_array_entry_param() { + $form = new stdClass(); + $form->name = 'Test'; + $form->form_key = 'test'; + $_GET['entry'] = array( 'x' ); + + try { + $html = FrmFormsHelper::replace_shortcodes( '[entry_key]', $form ); + } finally { + unset( $_GET['entry'] ); + } + + $this->assertSame( '', $html ); + } } diff --git a/tests/phpunit/misc/test_FrmAppHelper.php b/tests/phpunit/misc/test_FrmAppHelper.php index 84d0864862..18c1606048 100644 --- a/tests/phpunit/misc/test_FrmAppHelper.php +++ b/tests/phpunit/misc/test_FrmAppHelper.php @@ -251,6 +251,39 @@ public function test_sanitize_request() { $this->assertSame( $values['content'], wp_kses_post( $values['content'] ) ); } + /** + * @covers FrmAppHelper::sanitize_request + */ + public function test_sanitize_request_ignores_array_value() { + $values = array( 'frm_action' => array( 'x' ) ); + + FrmAppHelper::sanitize_request( array( 'frm_action' => 'sanitize_title' ), $values ); + + $this->assertSame( '', $values['frm_action'] ); + } + + /** + * @covers FrmAppHelper::is_true + */ + public function test_is_true_array_value_is_false_without_warning() { + $warnings = array(); + + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler + set_error_handler( + function ( $errno, $errstr ) use ( &$warnings ) { + $warnings[] = $errstr; + return true; + } + ); + + $result = FrmAppHelper::is_true( array( 'x' ) ); + + restore_error_handler(); + + $this->assertFalse( $result ); + $this->assertSame( array(), $warnings ); + } + public function test_kses() { $start_value = ''; $safe_value = 'Hello, click here';