diff --git a/includes/acf-value-functions.php b/includes/acf-value-functions.php index 37820b70..ecedee0b 100644 --- a/includes/acf-value-functions.php +++ b/includes/acf-value-functions.php @@ -178,6 +178,11 @@ function acf_format_value( $value, $post_id, $field, $escape_html = false ) { return $store->get( $cache_name ); } + // Snapshot the raw value so we can detect a missed type-specific + // dispatcher below. If the filter chain leaves the value untouched and the + // field has a non-id return_format, run the formatter directly (#527). + $raw_value = $value; + /** * Filters the $value for use in a template function. * @@ -190,6 +195,32 @@ function acf_format_value( $value, $post_id, $field, $escape_html = false ) { */ $value = apply_filters( 'acf/format_value', $value, $post_id, $field, $escape_html ); + // Safety net: if the filter chain left the value unchanged and the field + // expects a formatted output, the type callback was skipped. Run the field + // type's format_value() directly so Image/File fields honor their + // return_format setting (#527). + if ( + $value === $raw_value + && is_numeric( $value ) + && ! is_float( $value ) + && (string) (int) $value === (string) $value + && ! empty( $field['type'] ) + && ! empty( $field['return_format'] ) + && 'id' !== $field['return_format'] + ) { + $type = acf_get_field_type( $field['type'] ); + if ( $type && method_exists( $type, 'format_value' ) ) { + try { + $candidate = $type->format_value( $value, $post_id, $field ); + } catch ( \Throwable $e ) { + $candidate = $value; + } + if ( ! is_numeric( $candidate ) ) { + $value = $candidate; + } + } + } + // Update store. $store->set( $cache_name, $value ); diff --git a/tests/php/includes/fields/test-class-acf-field-file.php b/tests/php/includes/fields/test-class-acf-field-file.php index 8ab5d65b..0544abdd 100644 --- a/tests/php/includes/fields/test-class-acf-field-file.php +++ b/tests/php/includes/fields/test-class-acf-field-file.php @@ -137,4 +137,32 @@ public function test_array_format_returns_expected_structure() { $this->assertArrayHasKey( 'url', $result ); $this->assertEquals( $this->attachment_id, $result['ID'] ); } + + /** + * Regression test for #527: get_field() (which routes through + * acf_format_value() and the acf/format_value filter chain) must respect + * every documented return_format on File fields, mirroring the Image fix. + * + * @dataProvider return_format_provider + * + * @param string $return_format The return format setting. + */ + public function test_get_field_pipeline_respects_return_format( $return_format ) { + $field = $this->get_field( array( 'return_format' => $return_format ) ); + + // The production path get_field() uses. + $result = acf_format_value( $this->attachment_id, $this->post_id, $field ); + + if ( 'url' === $return_format ) { + $this->assertIsString( $result ); + $this->assertStringContainsString( 'test-file.pdf', $result ); + } elseif ( 'array' === $return_format ) { + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'ID', $result ); + $this->assertArrayHasKey( 'url', $result ); + $this->assertEquals( $this->attachment_id, $result['ID'] ); + } else { + $this->assertEquals( $this->attachment_id, $result ); + } + } } diff --git a/tests/php/includes/fields/test-class-acf-field-image.php b/tests/php/includes/fields/test-class-acf-field-image.php index 02d9780e..98cab459 100644 --- a/tests/php/includes/fields/test-class-acf-field-image.php +++ b/tests/php/includes/fields/test-class-acf-field-image.php @@ -155,4 +155,37 @@ public function test_array_format_returns_expected_structure() { $this->assertArrayHasKey( 'url', $result ); $this->assertEquals( $this->attachment_id, $result['ID'] ); } + + /** + * Regression test for #527: get_field() (which routes through + * acf_format_value() and the acf/format_value filter chain) must respect + * every documented return_format. Previously, only direct calls to + * format_value()/format_value_for_rest() were covered, leaving the real + * filter-chain path untested. + * + * @dataProvider return_format_provider + * + * @param string $return_format The return format setting. + */ + public function test_get_field_pipeline_respects_return_format( $return_format ) { + $field = $this->get_field( array( 'return_format' => $return_format ) ); + + // The production path get_field() uses: acf_format_value() invokes the + // acf/format_value filter chain, which dispatches to acf_field_image:: + // format_value via the type=name=key variation in + // _acf_apply_hook_variations(). + $result = acf_format_value( $this->attachment_id, $this->post_id, $field ); + + if ( 'url' === $return_format ) { + $this->assertIsString( $result ); + $this->assertStringContainsString( 'test-image.jpg', $result ); + } elseif ( 'array' === $return_format ) { + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'ID', $result ); + $this->assertArrayHasKey( 'url', $result ); + $this->assertEquals( $this->attachment_id, $result['ID'] ); + } else { + $this->assertEquals( $this->attachment_id, $result ); + } + } }