diff --git a/src/Import/Importer.php b/src/Import/Importer.php index af548eb..398579d 100644 --- a/src/Import/Importer.php +++ b/src/Import/Importer.php @@ -307,20 +307,7 @@ final class Importer { ); } - $postarr = array( - 'post_type' => $this->post_type, - 'post_status' => $options['post_status'], - 'post_author' => $options['post_author'], - 'post_title' => $data['title'], - 'post_content' => $data['content'], - 'post_excerpt' => $data['excerpt'], - 'meta_input' => $this->build_meta_input( $data, $feed_url ), - ); - - if ( ! empty( $data['timestamp'] ) ) { - $postarr['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $data['timestamp'] ); - $postarr['post_date'] = get_date_from_gmt( $postarr['post_date_gmt'] ); - } + $postarr = $this->build_postarr( $data, $feed_url, $options ); if ( $existing_id ) { $postarr['ID'] = $existing_id; @@ -454,6 +441,37 @@ final class Importer { return $data; } + /** + * Build the wp_insert_post/wp_update_post argument array for an episode. + * + * When the feed item provides a publication timestamp, that timestamp + * becomes the post's post_date/post_date_gmt on both create and refresh; + * otherwise WordPress falls back to the import time. + * + * @param array $data Normalized item data. + * @param string $feed_url Feed URL. + * @param array $options Sanitized import options. + * @return array Post argument array. + */ + private function build_postarr( array $data, string $feed_url, array $options ): array { + $postarr = array( + 'post_type' => $this->post_type, + 'post_status' => $options['post_status'], + 'post_author' => $options['post_author'], + 'post_title' => $data['title'], + 'post_content' => $data['content'], + 'post_excerpt' => $data['excerpt'], + 'meta_input' => $this->build_meta_input( $data, $feed_url ), + ); + + if ( ! empty( $data['timestamp'] ) ) { + $postarr['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $data['timestamp'] ); + $postarr['post_date'] = get_date_from_gmt( $postarr['post_date_gmt'] ); + } + + return $postarr; + } + /** * Build protected post metadata for an episode. * diff --git a/tests/SmokeTest.php b/tests/SmokeTest.php index 7619cbd..ac31c52 100644 --- a/tests/SmokeTest.php +++ b/tests/SmokeTest.php @@ -40,4 +40,97 @@ final class SmokeTest extends TestCase { $importer = new RSS2CPT\Import\Importer(); $this->assertSame( 300, $importer->filter_feed_cache_lifetime( 43200, 'https://example.test/feed.xml' ) ); } + + /** + * Verify the imported post_date/post_date_gmt come from the feed timestamp + * on both create and update paths, with no timezone drift. + */ + public function test_postarr_uses_feed_timestamp_when_present(): void { + $importer = new RSS2CPT\Import\Importer(); + + $post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' ); + $post_type->setAccessible( true ); + $post_type->setValue( $importer, 'podcast_episode' ); + + $method = ( new \ReflectionClass( $importer ) )->getMethod( 'build_postarr' ); + $method->setAccessible( true ); + + $timestamp = 1700000000; // 2023-11-14T22:13:20Z, a known UTC moment. + $data = self::sample_item_data( $timestamp ); + $expected_gmt = gmdate( 'Y-m-d H:i:s', $timestamp ); + + $postarr = $method->invoke( + $importer, + $data, + 'https://example.test/feed.xml', + array( + 'post_status' => 'publish', + 'post_author' => 1, + ) + ); + + $this->assertSame( $expected_gmt, $postarr['post_date_gmt'] ); + $this->assertSame( get_date_from_gmt( $expected_gmt ), $postarr['post_date'] ); + } + + /** + * Verify that a feed item without a timestamp leaves post_date unset, + * letting WordPress default to the import time on insert/update. + */ + public function test_postarr_omits_post_date_when_feed_timestamp_missing(): void { + $importer = new RSS2CPT\Import\Importer(); + + $post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' ); + $post_type->setAccessible( true ); + $post_type->setValue( $importer, 'podcast_episode' ); + + $method = ( new \ReflectionClass( $importer ) )->getMethod( 'build_postarr' ); + $method->setAccessible( true ); + + $postarr = $method->invoke( + $importer, + self::sample_item_data( 0 ), + 'https://example.test/feed.xml', + array( + 'post_status' => 'publish', + 'post_author' => 1, + ) + ); + + $this->assertArrayNotHasKey( 'post_date', $postarr ); + $this->assertArrayNotHasKey( 'post_date_gmt', $postarr ); + } + + /** + * Build a complete sample item-data array. + * + * Both build_postarr() and the helper it calls, build_meta_input(), read + * every field that map_item() produces. Supplying the full set keeps the + * tests focused on the post_date behavior we are pinning down. + * + * @param int $timestamp Optional feed timestamp (zero means "no date"). + * @return array + */ + private static function sample_item_data( int $timestamp ): array { + return array( + 'title' => 'Episode', + 'content' => 'Body', + 'excerpt' => 'Teaser', + 'timestamp' => $timestamp, + 'link' => 'https://example.test/episode', + 'audio_url' => 'https://example.test/episode.mp3', + 'audio_type' => 'audio/mpeg', + 'audio_length' => 0, + 'duration' => '', + 'duration_seconds' => 0, + 'season' => 0, + 'episode' => 0, + 'episode_type' => '', + 'explicit' => '0', + 'source_id' => 'src-1', + 'source_key' => str_repeat( 'a', 64 ), + 'creator' => '', + 'fingerprint' => str_repeat( 'b', 64 ), + ); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e6b8cb5..663efee 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -31,5 +31,29 @@ function wp_parse_args( $args, $defaults = array() ) { return array_merge( $defaults, $args ); } +/** + * Minimal get_date_from_gmt stub matching WordPress's MySQL-string behavior. + * + * @param string $date_string GMT timestamp in MySQL date format. + * @return string Timestamp converted to the site's local timezone. + */ +function get_date_from_gmt( $date_string ) { + return get_gmt_from_date( $date_string ); +} + +/** + * Minimal get_gmt_from_date stub: the inverse of get_date_from_gmt. + * + * @param string $date_string Local timestamp in MySQL date format. + * @return string Timestamp converted to GMT (UTC). + */ +function get_gmt_from_date( $date_string ) { + $timestamp = strtotime( $date_string . ' UTC' ); + if ( false === $timestamp ) { + return $date_string; + } + return gmdate( 'Y-m-d H:i:s', $timestamp ); +} + require_once dirname( __DIR__ ) . '/src/Options.php'; require_once dirname( __DIR__ ) . '/src/Import/Importer.php';