diff --git a/src/php/Application/Presenter/MetadataPresenter.php b/src/php/Application/Presenter/MetadataPresenter.php index 1daba853..746d4fe0 100644 --- a/src/php/Application/Presenter/MetadataPresenter.php +++ b/src/php/Application/Presenter/MetadataPresenter.php @@ -101,19 +101,30 @@ public function generate( WP_Post $post, array $existing_metadata = array() ): a $metadata = $existing_metadata; - $metadata['@context'] = 'https://schema.org'; - $metadata['@type'] = 'LiveBlogPosting'; - $metadata['headline'] = get_the_title( $post ); - $metadata['url'] = get_permalink( $post ); - $metadata['datePublished'] = get_post_datetime( $post, 'date', 'gmt' )->format( 'c' ); - $metadata['dateModified'] = get_post_datetime( $post, 'modified', 'gmt' )->format( 'c' ); - - // Add coverage times for LiveBlogPosting (helps with Google's "LIVE" badge). - $metadata['coverageStartTime'] = $metadata['datePublished']; - - // Add coverageEndTime only if the liveblog is archived. - if ( LiveblogPost::STATE_ARCHIVED === $liveblog_state ) { - $metadata['coverageEndTime'] = $metadata['dateModified']; + $metadata['@context'] = 'https://schema.org'; + $metadata['@type'] = 'LiveBlogPosting'; + $metadata['headline'] = get_the_title( $post ); + $metadata['url'] = get_permalink( $post ); + + // Unpublished posts (drafts, pending, auto-drafts) store a `0000-00-00 00:00:00` + // GMT date, for which get_post_datetime() returns false. Guard against calling + // format() on false to avoid a fatal when such a post is previewed. + $published_datetime = get_post_datetime( $post, 'date', 'gmt' ); + if ( false !== $published_datetime ) { + $metadata['datePublished'] = $published_datetime->format( 'c' ); + + // Add coverage times for LiveBlogPosting (helps with Google's "LIVE" badge). + $metadata['coverageStartTime'] = $metadata['datePublished']; + } + + $modified_datetime = get_post_datetime( $post, 'modified', 'gmt' ); + if ( false !== $modified_datetime ) { + $metadata['dateModified'] = $modified_datetime->format( 'c' ); + + // Add coverageEndTime only if the liveblog is archived. + if ( LiveblogPost::STATE_ARCHIVED === $liveblog_state ) { + $metadata['coverageEndTime'] = $metadata['dateModified']; + } } $metadata['liveBlogUpdate'] = $blog_updates; diff --git a/tests/Integration/SchemaMetadataTest.php b/tests/Integration/SchemaMetadataTest.php index 5200fb9a..6292e3b5 100644 --- a/tests/Integration/SchemaMetadataTest.php +++ b/tests/Integration/SchemaMetadataTest.php @@ -399,6 +399,57 @@ public function test_entries_present_for_password_protected_post_with_password() $this->assertNotEmpty( $metadata['liveBlogUpdate'] ); } + /** + * Test that metadata generation does not fatal for an unpublished post. + * + * Drafts (and pending/auto-draft posts) store a `0000-00-00 00:00:00` GMT + * date, for which get_post_datetime() returns false. Previously this caused + * a fatal `Call to a member function format() on false` when such a post was + * previewed. See https://github.com/Automattic/liveblog/issues/919. + * + * @covers ::generate + */ + public function test_metadata_omits_dates_for_unpublished_post(): void { + $draft_id = self::factory()->post->create( + array( + 'post_title' => 'Draft Liveblog', + 'post_status' => 'draft', + ) + ); + update_post_meta( $draft_id, LiveblogConfiguration::KEY, 'enable' ); + + // Confirm the fixture reproduces the original conditions: the GMT date is + // unavailable, so get_post_datetime() returns false. + $this->assertFalse( get_post_datetime( $draft_id, 'date', 'gmt' ) ); + + $metadata_presenter = Container::instance()->metadata_presenter(); + $metadata = $metadata_presenter->generate( get_post( $draft_id ), array() ); + + // The metadata is still generated (proving the date code did not fatal)... + $this->assertEquals( 'LiveBlogPosting', $metadata['@type'] ); + + // ...but the date-derived properties are omitted rather than fatalling. + $this->assertArrayNotHasKey( 'datePublished', $metadata ); + $this->assertArrayNotHasKey( 'dateModified', $metadata ); + $this->assertArrayNotHasKey( 'coverageStartTime', $metadata ); + } + + /** + * Test that a published post still includes the date-derived properties. + * + * @covers ::generate + */ + public function test_metadata_includes_dates_for_published_post(): void { + $this->insert_entry( array( 'content' => '
Test entry
' ) ); + + $metadata_presenter = Container::instance()->metadata_presenter(); + $metadata = $metadata_presenter->generate( get_post( $this->post_id ), array() ); + + $this->assertArrayHasKey( 'datePublished', $metadata ); + $this->assertArrayHasKey( 'dateModified', $metadata ); + $this->assertArrayHasKey( 'coverageStartTime', $metadata ); + } + /** * Insert a liveblog entry. *