🐛fix: Use feed publication date as post_date on import
Extracted the wp_insert_post/wp_update_post argument construction into a private build_postarr() helper so the post_date/post_date_gmt derivation from the feed timestamp can be exercised in isolation. Added smoke tests that pin down the create-path behavior (feed timestamp becomes post_date) and the fallback path (no feed date leaves post_date unset for WordPress to fill in at import time). Added minimal get_date_from_gmt and get_gmt_from_date stubs to the test bootstrap so the new tests run without WordPress loaded.
This commit is contained in:
+32
-14
@@ -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<string,mixed> $data Normalized item data.
|
||||
* @param string $feed_url Feed URL.
|
||||
* @param array<string,mixed> $options Sanitized import options.
|
||||
* @return array<string,mixed> 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.
|
||||
*
|
||||
|
||||
@@ -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<string,mixed>
|
||||
*/
|
||||
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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user