diff --git a/.eslintignore b/.eslintignore
index 872f242f9..9b5e68df4 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,5 @@
assets/js/bulk-export.js
-assets/js/cover-image.js
+assets/js/cover-media.js
assets/js/export-table.js
assets/js/json.js
assets/js/meta-boxes.js
diff --git a/admin/apple-actions/class-api-action.php b/admin/apple-actions/class-api-action.php
index fce96fbe3..e73787e8d 100644
--- a/admin/apple-actions/class-api-action.php
+++ b/admin/apple-actions/class-api-action.php
@@ -100,5 +100,6 @@ protected function delete_post_meta( $post_id ): void {
delete_post_meta( $post_id, 'apple_news_api_created_at' );
delete_post_meta( $post_id, 'apple_news_api_modified_at' );
delete_post_meta( $post_id, 'apple_news_api_share_url' );
+ delete_post_meta( $post_id, 'apple_news_article_checksum' );
}
}
diff --git a/admin/apple-actions/index/class-channel.php b/admin/apple-actions/index/class-channel.php
index 48934e1c7..c3a67f3e3 100644
--- a/admin/apple-actions/index/class-channel.php
+++ b/admin/apple-actions/index/class-channel.php
@@ -26,17 +26,21 @@ class Channel extends API_Action {
*/
public function perform() {
$channel = get_transient( 'apple_news_channel' );
+
if ( false === $channel ) {
+ $channel = '';
+
if ( $this->is_api_configuration_valid() ) {
try {
$channel = $this->get_api()->get_channel( $this->get_setting( 'api_channel' ) );
} catch ( Request_Exception $e ) {
- $channel = '';
+ // Do nothing.
+ unset( $e );
}
}
- }
- set_transient( 'apple_news_channel', $channel, 300 );
+ set_transient( 'apple_news_channel', $channel, 300 );
+ }
if ( '' === $channel ) {
// Unable to get channel information. This likely means the user entered their API credentials incorrectly.
diff --git a/admin/apple-actions/index/class-export.php b/admin/apple-actions/index/class-export.php
index b12a91754..ab5b88430 100644
--- a/admin/apple-actions/index/class-export.php
+++ b/admin/apple-actions/index/class-export.php
@@ -13,6 +13,7 @@
require_once dirname( __DIR__, 3 ) . '/includes/apple-exporter/autoload.php';
use Apple_Actions\Action;
+use Apple_Exporter\Components\Embed_Web_Video;
use Apple_Exporter\Exporter;
use Apple_Exporter\Exporter_Content;
use Apple_Exporter\Exporter_Content_Settings;
@@ -121,34 +122,101 @@ public function fetch_exporter() {
$excerpt = has_excerpt( $post ) ? wp_strip_all_tags( $post->post_excerpt ) : '';
// Get the cover configuration.
- $post_thumb = null;
- $cover_meta_id = get_post_meta( $this->id, 'apple_news_coverimage', true );
- $cover_caption = get_post_meta( $this->id, 'apple_news_coverimage_caption', true );
- if ( ! empty( $cover_meta_id ) ) {
- if ( empty( $cover_caption ) ) {
- $cover_caption = wp_get_attachment_caption( $cover_meta_id );
+ $post_thumb = null;
+ $fall_back_to_image = false;
+
+ $cover_provider = get_post_meta( $this->id, 'apple_news_cover_media_provider', true );
+ if ( ! is_string( $cover_provider ) || ! $cover_provider ) {
+ $cover_provider = 'image';
+ }
+
+ if ( 'embedwebvideo' === $cover_provider ) {
+ $cover_url = get_post_meta( $this->id, 'apple_news_cover_embedwebvideo_url', true );
+
+ // Test against accepted providers.
+ if ( preg_match( Embed_Web_Video::YOUTUBE_MATCH, $cover_url, $cover_matches ) ) {
+ $post_thumb = [
+ 'caption' => '',
+ 'url' => 'https://www.youtube.com/embed/' . end( $cover_matches ),
+ ];
+ } elseif ( preg_match( Embed_Web_Video::VIMEO_MATCH, $cover_url, $cover_matches ) ) {
+ $post_thumb = [
+ 'caption' => '',
+ 'url' => 'https://player.vimeo.com/video/' . end( $cover_matches ),
+ ];
+ } elseif ( preg_match( Embed_Web_Video::DAILYMOTION_MATCH, $cover_url, $cover_matches ) ) {
+ $post_thumb = [
+ 'caption' => '',
+ 'url' => 'https://geo.dailymotion.com/player.html?video=' . end( $cover_matches ),
+ ];
+ } else {
+ $fall_back_to_image = true;
}
- $post_thumb = [
- 'caption' => ! empty( $cover_caption ) ? $cover_caption : '',
- 'url' => wp_get_attachment_url( $cover_meta_id ),
- ];
- } else {
- $thumb_id = get_post_thumbnail_id( $this->id );
- $post_thumb_url = wp_get_attachment_url( $thumb_id );
- if ( empty( $cover_caption ) ) {
- $cover_caption = wp_get_attachment_caption( $thumb_id );
+ }
+
+ if ( 'video_id' === $cover_provider ) {
+ $video_id = get_post_meta( $this->id, 'apple_news_cover_video_id', true );
+ $video_url = (string) wp_get_attachment_url( $video_id );
+
+ if ( $video_url ) {
+ $post_thumb = [
+ 'caption' => '',
+ 'url' => $video_url,
+ ];
+ } else {
+ $fall_back_to_image = true;
}
- if ( ! empty( $post_thumb_url ) ) {
- // If the post thumb URL is root-relative, convert it to fully-qualified.
- if ( str_starts_with( $post_thumb_url, '/' ) ) {
- $post_thumb_url = home_url( $post_thumb_url );
- }
+ }
- // Compile the post_thumb object using the URL and caption from the featured image.
+ if ( 'video_url' === $cover_provider ) {
+ $file_url = get_post_meta( $this->id, 'apple_news_cover_video_url', true );
+ $check = wp_check_filetype( $file_url );
+
+ if ( isset( $check['ext'] ) && 'mp4' === $check['ext'] ) {
+ $post_thumb = [
+ 'caption' => '',
+ 'url' => $file_url,
+ ];
+ } else {
+ $fall_back_to_image = true;
+ }
+ }
+
+ // Provide fallback behavior so there's still a cover, e.g. if the URL becomes unsupported later.
+ if ( $fall_back_to_image ) {
+ $cover_url = '';
+ $cover_provider = 'image';
+ }
+
+ if ( 'image' === $cover_provider ) {
+ $cover_meta_id = get_post_meta( $this->id, 'apple_news_coverimage', true );
+ $cover_caption = get_post_meta( $this->id, 'apple_news_coverimage_caption', true );
+ if ( ! empty( $cover_meta_id ) ) {
+ if ( empty( $cover_caption ) ) {
+ $cover_caption = wp_get_attachment_caption( $cover_meta_id );
+ }
$post_thumb = [
'caption' => ! empty( $cover_caption ) ? $cover_caption : '',
- 'url' => $post_thumb_url,
+ 'url' => wp_get_attachment_url( $cover_meta_id ),
];
+ } else {
+ $thumb_id = get_post_thumbnail_id( $this->id );
+ $post_thumb_url = wp_get_attachment_url( $thumb_id );
+ if ( empty( $cover_caption ) ) {
+ $cover_caption = wp_get_attachment_caption( $thumb_id );
+ }
+ if ( ! empty( $post_thumb_url ) ) {
+ // If the post thumb URL is root-relative, convert it to fully-qualified.
+ if ( str_starts_with( $post_thumb_url, '/' ) ) {
+ $post_thumb_url = home_url( $post_thumb_url );
+ }
+
+ // Compile the post_thumb object using the URL and caption from the featured image.
+ $post_thumb = [
+ 'caption' => ! empty( $cover_caption ) ? $cover_caption : '',
+ 'url' => $post_thumb_url,
+ ];
+ }
}
}
@@ -160,6 +228,11 @@ public function fetch_exporter() {
];
}
+ // Attach the final provider slug.
+ if ( is_array( $post_thumb ) ) {
+ $post_thumb['provider'] = $cover_provider;
+ }
+
// Build the byline.
$byline = $this->format_byline( $post );
@@ -208,15 +281,39 @@ public function fetch_exporter() {
*/
$excerpt = apply_filters( 'apple_news_exporter_excerpt', $excerpt, $post->ID );
+ $cover_url = ! empty( $post_thumb['url'] ) ? $post_thumb['url'] : null;
+
+ if ( isset( $post_thumb['provider'] ) && 'image' === $post_thumb['provider'] ) {
+ /**
+ * Filters the cover image URL of an article before it is sent to Apple News.
+ *
+ * The cover image URL is used for the Cover component, if it is active.
+ *
+ * @deprecated 2.7.0 Use the `apple_news_exporter_cover_url` filter, which includes all provider types.
+ *
+ * @param string|null $url The cover image URL for the post.
+ * @param int $post_id The ID of the post.
+ */
+ $cover_url = apply_filters_deprecated(
+ 'apple_news_exporter_post_thumb',
+ [ $cover_url, $post->ID ],
+ '2.7.0',
+ 'apple_news_exporter_cover_url',
+ );
+ }
+
/**
- * Filters the cover image URL of an article before it is sent to Apple News.
+ * Filters the URL to the cover media of an article before it is sent to Apple News.
*
- * The cover image URL is used for the Cover component, if it is active.
+ * The URL may be an image URL, video file URL, or a URL to an embeddable external video, depending on the provider
+ * of cover media for the article.
*
- * @param string|null $url The cover image URL for the post.
- * @param int $post_id The ID of the post.
+ * @param string|null $url The cover media URL for the post.
+ * @param string|null $provider The provider of the cover media. Possible values include 'image', 'video_url',
+ * 'video_id', and 'embedwebvideo'.
+ * @param int $post_id The ID of the post.
*/
- $cover_url = apply_filters( 'apple_news_exporter_post_thumb', ! empty( $post_thumb['url'] ) ? $post_thumb['url'] : null, $post->ID );
+ $cover_url = apply_filters( 'apple_news_exporter_cover_url', $cover_url, $post_thumb['provider'] ?? null, $post->ID );
/**
* Filters the byline of an article before it is sent to Apple News.
@@ -296,8 +393,9 @@ public function fetch_exporter() {
$cover_caption = apply_filters( 'apple_news_exporter_cover_caption', $cover_caption, $post->ID );
$post_thumb = [
- 'caption' => $cover_caption,
- 'url' => $cover_url,
+ 'provider' => $cover_provider,
+ 'caption' => $cover_caption,
+ 'url' => $cover_url,
];
} else {
$post_thumb = null;
@@ -606,6 +704,7 @@ private function get_content( $post ) {
$content = apply_filters( 'the_content', $content ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
// Clean up the HTML a little.
+ $content = $this->exclude_selectors( $content );
$content = $this->remove_tags( $content );
$content = $this->remove_entities( $content );
@@ -643,6 +742,52 @@ private function remove_entities( $content ) {
return str_replace( '&', '&', $content );
}
+ /**
+ * Remove excluded selectors from the content.
+ *
+ * @param string $content The content to be filtered.
+ * @return string
+ */
+ private function exclude_selectors( $content ) {
+ $raw_selectors = $this->get_setting( 'excluded_selectors' );
+
+ $selectors = explode( ',', $raw_selectors );
+ $selectors = array_map( 'trim', $selectors );
+ $selectors = array_filter( $selectors );
+
+ if ( count( $selectors ) === 0 ) {
+ return $content;
+ }
+
+ libxml_use_internal_errors( true );
+ $dom = new \DOMDocument();
+ $dom->loadHTML( '' . $content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
+ $xpath = new \DOMXPath( $dom );
+ libxml_clear_errors();
+
+ foreach ( $selectors as $selector ) {
+ $nodes = [];
+
+ if ( str_starts_with( $selector, '#' ) ) {
+ $nodes = $xpath->query( '//*[@id="' . substr( $selector, 1 ) . '"]' );
+ }
+
+ if ( str_starts_with( $selector, '.' ) ) {
+ $nodes = $xpath->query( '//*[contains(concat(" ", normalize-space(@class), " "), " ' . substr( $selector, 1 ) . ' ")]' );
+ }
+
+ if ( is_iterable( $nodes ) ) {
+ foreach ( $nodes as $node ) {
+ $node->parentNode->removeChild( $node ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+ }
+ }
+ }
+
+ $content = $dom->saveHTML();
+
+ return $content;
+ }
+
/**
* Loads settings for the Exporter_Content from the WordPress post metadata.
*
diff --git a/admin/class-admin-apple-bulk-export-page.php b/admin/class-admin-apple-bulk-export-page.php
index 669ebf01a..a670d221e 100644
--- a/admin/class-admin-apple-bulk-export-page.php
+++ b/admin/class-admin-apple-bulk-export-page.php
@@ -41,8 +41,8 @@ public function __construct( $settings ) {
add_action( 'admin_menu', [ $this, 'register_page' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'register_assets' ] );
- add_action( 'wp_ajax_push_post', [ $this, 'ajax_push_post' ] );
- add_filter( 'admin_title', [ $this, 'set_title' ] );
+ add_action( 'wp_ajax_apple_news_push_post', [ $this, 'ajax_push_post' ] );
+ add_action( 'wp_ajax_apple_news_delete_post', [ $this, 'ajax_delete_post' ] );
}
/**
@@ -66,45 +66,52 @@ public function register_page() {
);
}
- /**
- * Fix the title since WordPress doesn't set one.
- *
- * @param string $admin_title The title to be filtered.
- * @access public
- * @return string The title for the screen.
- */
- public function set_title( $admin_title ) {
- $screen = get_current_screen();
- if ( 'admin_page_apple_news_bulk_export' === $screen->base ) {
- $admin_title = __( 'Bulk Export', 'apple-news' ) . $admin_title;
- }
-
- return $admin_title;
- }
-
/**
* Builds the plugin submenu page.
*
* @access public
*/
public function build_page() {
- $ids = isset( $_GET['ids'] ) ? sanitize_text_field( wp_unslash( $_GET['ids'] ) ) : null; // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected, WordPress.Security.NonceVerification.Recommended
- if ( ! $ids ) {
+ $post_ids = isset( $_GET['post_ids'] ) ? sanitize_text_field( wp_unslash( $_GET['post_ids'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+
+ if ( ! $post_ids ) {
wp_safe_redirect( esc_url_raw( menu_page_url( $this->plugin_slug . '_index', false ) ) ); // phpcs:ignore WordPressVIPMinimum.Security.ExitAfterRedirect.NoExit
+
if ( ! defined( 'APPLE_NEWS_UNIT_TESTS' ) || ! APPLE_NEWS_UNIT_TESTS ) {
exit;
}
}
- // Populate $articles array with a set of valid posts.
- $articles = [];
- foreach ( explode( '.', $ids ) as $id ) {
- $post = get_post( absint( $id ) );
+ // Allow only specific actions.
+ if ( ! in_array( $action, [ 'apple_news_push_post', 'apple_news_delete_post' ], true ) ) {
+ wp_die( esc_html__( 'Invalid action.', 'apple-news' ), '', [ 'response' => 400 ] );
+ }
+
+ // Populate articles array with a set of valid posts.
+ $apple_posts = [];
+ foreach ( explode( ',', $post_ids ) as $post_id ) {
+ $post = get_post( (int) $post_id );
+
if ( ! empty( $post ) ) {
- $articles[] = $post;
+ $apple_posts[] = $post;
}
}
+ // Override text within the partial depending on the action.
+ $apple_page_title = match ( $action ) {
+ 'apple_news_push_post' => __( 'Bulk Export Articles', 'apple-news' ),
+ 'apple_news_delete_post' => __( 'Bulk Delete Articles', 'apple-news' ),
+ };
+ $apple_page_description = match ( $action ) {
+ 'apple_news_push_post' => __( 'The following articles will be exported. Articles that are already published will be updated.', 'apple-news' ),
+ 'apple_news_delete_post' => __( 'The following articles will be deleted.', 'apple-news' ),
+ };
+ $apple_submit_text = match ( $action ) {
+ 'apple_news_push_post' => __( 'Publish All', 'apple-news' ),
+ 'apple_news_delete_post' => __( 'Delete All', 'apple-news' ),
+ };
+
require_once __DIR__ . '/partials/page-bulk-export.php';
}
@@ -123,13 +130,7 @@ public function ajax_push_post() {
// Ensure the post exists and that it's published.
$post = get_post( $id );
if ( empty( $post ) ) {
- echo wp_json_encode(
- [
- 'success' => false,
- 'error' => __( 'This post no longer exists.', 'apple-news' ),
- ]
- );
- wp_die();
+ wp_send_json_error( __( 'This post no longer exists.', 'apple-news' ) );
}
// Check capabilities.
@@ -137,27 +138,17 @@ public function ajax_push_post() {
/** This filter is documented in admin/class-admin-apple-post-sync.php */
apply_filters( 'apple_news_publish_capability', self::get_capability_for_post_type( 'publish_posts', $post->post_type ) )
) ) {
- echo wp_json_encode(
- [
- 'success' => false,
- 'error' => __( 'You do not have permission to publish to Apple News', 'apple-news' ),
- ]
- );
- wp_die();
+ wp_send_json_error( __( 'You do not have permission to publish to Apple News', 'apple-news' ) );
}
if ( 'publish' !== $post->post_status ) {
- echo wp_json_encode(
- [
- 'success' => false,
- 'error' => sprintf(
- // translators: token is a post ID.
- __( 'Article %s is not published and cannot be pushed to Apple News.', 'apple-news' ),
- $id
- ),
- ]
+ wp_send_json_error(
+ sprintf(
+ /* translators: %s: post ID */
+ __( 'Article %s is not published and cannot be pushed to Apple News.', 'apple-news' ),
+ $id
+ )
);
- wp_die();
}
$action = new Apple_Actions\Index\Push( $this->settings, $id );
@@ -168,22 +159,56 @@ public function ajax_push_post() {
}
if ( $errors ) {
- echo wp_json_encode(
- [
- 'success' => false,
- 'error' => $errors,
- ]
- );
- } else {
- echo wp_json_encode(
- [
- 'success' => true,
- ]
- );
+ wp_send_json_error( $errors );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Handles the ajax action to delete a post from Apple News.
+ *
+ * @access public
+ */
+ public function ajax_delete_post() {
+ // Check the nonce.
+ check_ajax_referer( self::ACTION );
+
+ // Sanitize input data.
+ $id = isset( $_GET['id'] ) ? (int) $_GET['id'] : -1;
+
+ $post = get_post( $id );
+
+ if ( empty( $post ) ) {
+ wp_send_json_error( __( 'This post no longer exists.', 'apple-news' ) );
+ }
+
+ /** This filter is documented in admin/class-admin-apple-post-sync.php */
+ $cap = apply_filters( 'apple_news_delete_capability', self::get_capability_for_post_type( 'delete_posts', $post->post_type ) );
+
+ // Check capabilities.
+ if ( ! current_user_can( $cap ) ) {
+ wp_send_json_error( __( 'You do not have permission to delete posts from Apple News', 'apple-news' ) );
+ }
+
+ $errors = null;
+
+ // Try to sync only if the post has a remote ID. Ref `Admin_Apple_Post_Sync::do_delete()`.
+ if ( get_post_meta( $id, 'apple_news_api_id', true ) ) {
+ $action = new Apple_Actions\Index\Delete( $this->settings, $id );
+
+ try {
+ $errors = $action->perform();
+ } catch ( Apple_Actions\Action_Exception $e ) {
+ $errors = $e->getMessage();
+ }
+ }
+
+ if ( $errors ) {
+ wp_send_json_error( $errors );
}
- // This is required to terminate immediately and return a valid response.
- wp_die();
+ wp_send_json_success();
}
/**
diff --git a/admin/class-admin-apple-index-page.php b/admin/class-admin-apple-index-page.php
index ccef3af76..80a99e9be 100644
--- a/admin/class-admin-apple-index-page.php
+++ b/admin/class-admin-apple-index-page.php
@@ -110,7 +110,7 @@ public function admin_page() {
*
* @since 0.4.0
* @access public
- * @return mixed The result of the requested action.
+ * @return mixed|void The result of the requested action.
*/
public function page_router() {
$id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : null; // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected, WordPress.Security.NonceVerification.Recommended
@@ -130,22 +130,56 @@ public function page_router() {
return $this->export_action( $id );
case self::namespace_action( 'reset' ):
return $this->reset_action( $id );
- case self::namespace_action( 'push' ): // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
- if ( ! $id ) {
+ case self::namespace_action( 'push' ):
+ if ( $id ) {
+ $this->push_action( $id );
+ } else {
$url = menu_page_url( $this->plugin_slug . '_bulk_export', false );
- if ( isset( $_GET['article'] ) ) { // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected, WordPress.Security.NonceVerification.Recommended
- $ids = is_array( $_GET['article'] ) ? array_map( 'absint', $_GET['article'] ) : absint( $_GET['article'] ); // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected, WordPress.Security.NonceVerification.Recommended
- $url .= '&ids=' . implode( '.', $ids );
+
+ if ( isset( $_GET['article'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $post_ids = is_array( $_GET['article'] ) ? array_map( 'intval', $_GET['article'] ) : (int) $_GET['article']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $url = add_query_arg(
+ [
+ 'action' => 'apple_news_push_post',
+ 'post_ids' => implode( ',', $post_ids ),
+ ],
+ $url,
+ );
}
+
wp_safe_redirect( esc_url_raw( $url ) ); // phpcs:ignore WordPressVIPMinimum.Security.ExitAfterRedirect.NoExit
+
if ( ! defined( 'APPLE_NEWS_UNIT_TESTS' ) || ! APPLE_NEWS_UNIT_TESTS ) {
exit;
}
- } else {
- return $this->push_action( $id );
}
+
+ break;
case self::namespace_action( 'delete' ):
- return $this->delete_action( $id );
+ if ( $id ) {
+ $this->delete_action( $id );
+ } else {
+ $url = menu_page_url( $this->plugin_slug . '_bulk_export', false );
+
+ if ( isset( $_GET['article'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $post_ids = is_array( $_GET['article'] ) ? array_map( 'intval', $_GET['article'] ) : (int) $_GET['article']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $url = add_query_arg(
+ [
+ 'action' => 'apple_news_delete_post',
+ 'post_ids' => implode( ',', $post_ids ),
+ ],
+ $url,
+ );
+ }
+
+ wp_safe_redirect( esc_url_raw( $url ) ); // phpcs:ignore WordPressVIPMinimum.Security.ExitAfterRedirect.NoExit
+
+ if ( ! defined( 'APPLE_NEWS_UNIT_TESTS' ) || ! APPLE_NEWS_UNIT_TESTS ) {
+ exit;
+ }
+ }
+
+ break;
}
}
@@ -401,7 +435,7 @@ private function delete_action( $id ) {
$action = new Apple_Actions\Index\Delete( $this->settings, $id );
try {
$action->perform();
- $this->notice_success( __( 'Your article has been removed from apple news.', 'apple-news' ) );
+ $this->notice_success( __( 'Your article has been removed from Apple News.', 'apple-news' ) );
} catch ( Apple_Actions\Action_Exception $e ) {
$this->notice_error( $e->getMessage() );
}
diff --git a/admin/class-admin-apple-meta-boxes.php b/admin/class-admin-apple-meta-boxes.php
index 69c90c785..05ddc358f 100644
--- a/admin/class-admin-apple-meta-boxes.php
+++ b/admin/class-admin-apple-meta-boxes.php
@@ -173,17 +173,21 @@ public static function save_post_meta( $post_id ) {
// Save straightforward fields.
$fields = [
- 'apple_news_coverimage' => 'integer',
- 'apple_news_coverimage_caption' => 'textarea',
- 'apple_news_is_hidden' => 'string',
- 'apple_news_is_paid' => 'string',
- 'apple_news_is_preview' => 'string',
- 'apple_news_is_sponsored' => 'string',
- 'apple_news_pullquote' => 'string',
- 'apple_news_pullquote_position' => 'string',
- 'apple_news_slug' => 'string',
- 'apple_news_suppress_video_url' => 'boolean',
- 'apple_news_use_image_component' => 'boolean',
+ 'apple_news_coverimage' => 'integer',
+ 'apple_news_coverimage_caption' => 'textarea',
+ 'apple_news_cover_embedwebvideo_url' => 'string',
+ 'apple_news_cover_media_provider' => 'string',
+ 'apple_news_cover_video_id' => 'integer',
+ 'apple_news_cover_video_url' => 'string',
+ 'apple_news_is_hidden' => 'string',
+ 'apple_news_is_paid' => 'string',
+ 'apple_news_is_preview' => 'string',
+ 'apple_news_is_sponsored' => 'string',
+ 'apple_news_pullquote' => 'string',
+ 'apple_news_pullquote_position' => 'string',
+ 'apple_news_slug' => 'string',
+ 'apple_news_suppress_video_url' => 'boolean',
+ 'apple_news_use_image_component' => 'boolean',
];
foreach ( $fields as $meta_key => $type ) {
switch ( $type ) {
diff --git a/admin/class-admin-apple-news-list-table.php b/admin/class-admin-apple-news-list-table.php
index 50345a283..bcaeec206 100644
--- a/admin/class-admin-apple-news-list-table.php
+++ b/admin/class-admin-apple-news-list-table.php
@@ -330,8 +330,9 @@ public function get_bulk_actions() {
return apply_filters(
'apple_news_bulk_actions',
[
- Admin_Apple_Index_Page::namespace_action( 'push' ) => __( 'Publish/Update', 'apple-news' ),
- ]
+ Admin_Apple_Index_Page::namespace_action( 'push' ) => __( 'Publish/Update', 'apple-news' ),
+ Admin_Apple_Index_Page::namespace_action( 'delete' ) => __( 'Delete', 'apple-news' ),
+ ],
);
}
diff --git a/admin/class-admin-apple-news.php b/admin/class-admin-apple-news.php
index 24cd3d419..ceb9d5846 100644
--- a/admin/class-admin-apple-news.php
+++ b/admin/class-admin-apple-news.php
@@ -25,6 +25,7 @@
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-delete.php';
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-get-published-state.php';
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-get-settings.php';
+require_once dirname( __DIR__ ) . '/includes/REST/apple-news-is-valid-cover-media.php';
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-modify-post.php';
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-publish.php';
require_once dirname( __DIR__ ) . '/includes/REST/apple-news-sections.php';
@@ -99,44 +100,57 @@ public function __construct() {
// Define custom postmeta fields to register.
$postmeta = [
- 'apple_news_api_created_at' => [
+ 'apple_news_api_created_at' => [
'default' => '',
],
- 'apple_news_api_id' => [
+ 'apple_news_api_id' => [
'default' => '',
],
- 'apple_news_api_modified_at' => [
+ 'apple_news_api_modified_at' => [
'default' => '',
],
- 'apple_news_api_revision' => [
+ 'apple_news_api_revision' => [
'default' => '',
],
- 'apple_news_api_share_url' => [
+ 'apple_news_api_share_url' => [
'default' => '',
],
- 'apple_news_coverimage' => [
+ 'apple_news_cover_media_provider' => [
+ 'default' => 'image',
+ ],
+ 'apple_news_coverimage' => [
+ 'default' => 0,
+ 'type' => 'integer',
+ ],
+ 'apple_news_coverimage_caption' => [
+ 'default' => '',
+ ],
+ 'apple_news_cover_video_id' => [
'default' => 0,
'type' => 'integer',
],
- 'apple_news_coverimage_caption' => [
+ 'apple_news_cover_video_url' => [
+ 'default' => '',
+ ],
+ 'apple_news_cover_embedwebvideo_url' => [
'default' => '',
],
- 'apple_news_is_hidden' => [
+ 'apple_news_is_hidden' => [
'default' => '',
],
- 'apple_news_is_paid' => [
+ 'apple_news_is_paid' => [
'default' => '',
],
- 'apple_news_is_preview' => [
+ 'apple_news_is_preview' => [
'default' => '',
],
- 'apple_news_is_sponsored' => [
+ 'apple_news_is_sponsored' => [
'default' => '',
],
- 'apple_news_maturity_rating' => [
+ 'apple_news_maturity_rating' => [
'default' => '',
],
- 'apple_news_metadata' => [
+ 'apple_news_metadata' => [
'default' => '',
'sanitize_callback' => function ( $value ) {
return ! empty( $value ) && is_string( $value ) ? json_decode( $value, true ) : $value;
@@ -146,16 +160,16 @@ public function __construct() {
],
'type' => 'string',
],
- 'apple_news_pullquote' => [
+ 'apple_news_pullquote' => [
'default' => '',
],
- 'apple_news_pullquote_position' => [
+ 'apple_news_pullquote_position' => [
'default' => '',
],
- 'apple_news_slug' => [
+ 'apple_news_slug' => [
'default' => '',
],
- 'apple_news_sections' => [
+ 'apple_news_sections' => [
'default' => [],
'show_in_rest' => [
'schema' => [
@@ -167,11 +181,11 @@ public function __construct() {
],
'type' => 'array',
],
- 'apple_news_suppress_video_url' => [
+ 'apple_news_suppress_video_url' => [
'default' => false,
'type' => 'boolean',
],
- 'apple_news_use_image_component' => [
+ 'apple_news_use_image_component' => [
'default' => false,
'type' => 'boolean',
],
diff --git a/admin/class-admin-apple-post-sync.php b/admin/class-admin-apple-post-sync.php
index 1ae6b8f30..02db97931 100644
--- a/admin/class-admin-apple-post-sync.php
+++ b/admin/class-admin-apple-post-sync.php
@@ -149,8 +149,10 @@ public function do_publish( $id, $post ) {
// Proceed based on the current settings for auto publish and update.
$updated = get_post_meta( $id, 'apple_news_api_id', true );
- if ( ( $updated && 'yes' !== $this->settings->api_autosync_update )
- || ( ! $updated && 'yes' !== $this->settings->api_autosync ) ) {
+ if (
+ ( $updated && 'yes' !== $this->settings->api_autosync_update )
+ || ( ! $updated && 'yes' !== $this->settings->api_autosync )
+ ) {
return;
}
diff --git a/admin/partials/cover-image.php b/admin/partials/cover-image.php
deleted file mode 100644
index 0dd222447..000000000
--- a/admin/partials/cover-image.php
+++ /dev/null
@@ -1,46 +0,0 @@
-ID, 'apple_news_coverimage', true );
-$apple_cover_image_caption = get_post_meta( $post->ID, 'apple_news_coverimage_caption', true );
-
-?>
-
-
-
-
-
diff --git a/admin/partials/cover-media.php b/admin/partials/cover-media.php
new file mode 100644
index 000000000..f69004695
--- /dev/null
+++ b/admin/partials/cover-media.php
@@ -0,0 +1,142 @@
+ID, 'apple_news_cover_media_provider', true );
+$apple_cover_image_id = get_post_meta( $post->ID, 'apple_news_coverimage', true );
+$apple_cover_image_caption = get_post_meta( $post->ID, 'apple_news_coverimage_caption', true );
+$apple_cover_video_id = get_post_meta( $post->ID, 'apple_news_cover_video_id', true );
+
+?>
+
+
+
+
+ >
+
+
+
+
+ >
+
+
+
+
+ >
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/partials/metabox-publish.php b/admin/partials/metabox-publish.php
index 6cd9a7809..0f5df950e 100644
--- a/admin/partials/metabox-publish.php
+++ b/admin/partials/metabox-publish.php
@@ -134,8 +134,8 @@
-
-
+
+
settings->get( 'api_autosync' )
diff --git a/admin/partials/page-bulk-export.php b/admin/partials/page-bulk-export.php
index 790ca252a..dd5747a20 100644
--- a/admin/partials/page-bulk-export.php
+++ b/admin/partials/page-bulk-export.php
@@ -2,17 +2,20 @@
/**
* Publish to Apple News partials: Bulk Export page template
*
- * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
- *
- * @global array $articles
- *
* @package Apple_News
*/
+// Expect these variables to be defined in Admin_Apple_Bulk_Export_Page::build_page() but make sure they're set.
+$apple_page_title ??= __( 'Bulk Export', 'apple-news' );
+$apple_page_description ??= __( 'The following articles will be affected.', 'apple-news' );
+$apple_posts ??= [];
+$apple_submit_text ??= __( 'Go', 'apple-news' );
+
?>
-
-
+
+
+
-
+
post_title ); ?>
@@ -39,5 +42,5 @@
?>
-
+
diff --git a/admin/settings/class-admin-apple-settings-section-advanced.php b/admin/settings/class-admin-apple-settings-section-advanced.php
index fda8cd0b9..ae0359ee0 100644
--- a/admin/settings/class-admin-apple-settings-section-advanced.php
+++ b/admin/settings/class-admin-apple-settings-section-advanced.php
@@ -32,22 +32,27 @@ public function __construct( $page ) {
// Add the settings.
$this->settings = [
- 'component_alerts' => [
+ 'component_alerts' => [
'label' => __( 'Component Alerts', 'apple-news' ),
'type' => [ 'none', 'warn', 'fail' ],
'description' => __( 'If a post has a component that is unsupported by Apple News, choose "none" to generate no alert, "warn" to provide an admin warning notice, or "fail" to generate a notice and stop publishing.', 'apple-news' ),
],
- 'use_remote_images' => [
+ 'use_remote_images' => [
'label' => __( 'Use Remote Images?', 'apple-news' ),
'type' => [ 'yes', 'no' ],
'description' => __( 'Allow the Apple News API to retrieve images remotely rather than bundle them. This setting is recommended if you are having any issues with publishing images. If your images are not publicly accessible, such as on a development site, you cannot use this feature.', 'apple-news' ),
],
- 'full_bleed_images' => [
+ 'full_bleed_images' => [
'label' => __( 'Use Full-Bleed Images?', 'apple-news' ),
'type' => [ 'yes', 'no' ],
'description' => __( 'If set to yes, images that are centered or have no alignment will span edge-to-edge rather than being constrained within the body margins.', 'apple-news' ),
],
- 'html_support' => [
+ 'deduplicate_cover_media' => [
+ 'label' => __( 'Deduplicate Cover Media?', 'apple-news' ),
+ 'type' => [ 'yes', 'no' ],
+ 'description' => __( 'If set to yes, any image, video, or other content selected as an article\'s Cover Media will not appear again in the article body in Apple News.', 'apple-news' ),
+ ],
+ 'html_support' => [
'label' => __( 'Enable HTML support?', 'apple-news' ),
'type' => [ 'yes', 'no' ],
'description' => sprintf(
@@ -57,7 +62,7 @@ public function __construct( $page ) {
''
),
],
- 'in_article_position' => [
+ 'in_article_position' => [
'label' => __( 'Position of In Article Module', 'apple-news' ),
'type' => 'number',
'min' => 3,
@@ -65,32 +70,68 @@ public function __construct( $page ) {
'step' => 1,
'description' => __( 'If you have configured an In Article module via Customize JSON, the position that the module should be inserted into. Defaults to 3, which is after the third content block in the article body (e.g., the third paragraph).', 'apple-news' ),
],
- 'aside_component_class' => [
+ 'aside_component_class' => [
'label' => __( 'Aside Content CSS Class', 'apple-news' ),
'type' => 'text',
+ 'size' => 100,
'description' => __( 'Enter a CSS class name that will be used to generate the Aside component. Do not prefix with a period.', 'apple-news' ),
'required' => false,
],
+ 'recipe_component_class' => [
+ 'label' => __( 'Recipe Content CSS Class', 'apple-news' ),
+ 'type' => 'text',
+ 'size' => 100,
+ 'description' => __( 'Enter a CSS class name that will be used to generate the Recipe component. Do not prefix with a period.', 'apple-news' ),
+ 'required' => false,
+ ],
+ 'recipe_component_use_schema' => [
+ 'label' => __( 'Build Recipe Component From Structured Data', 'apple-news' ),
+ 'type' => [ 'yes', 'no' ],
+ 'description' => __( 'Set to yes if you want the recipe JSON-LD to replace the HTML markup inside the specified CSS class. Set to no if you want to render the HTML markup inside the specified CSS class as-is.', 'apple-news' ),
+ ],
+ 'excluded_selectors' => [
+ 'label' => __( 'Selectors', 'apple-news' ),
+ 'type' => 'text',
+ 'size' => 100,
+ 'description' => sprintf(
+ /* translators: %s: tag */
+ __( 'Enter a comma-separated list of CSS class or ID selectors, like %s. Elements in post content matching these selectors will be removed from the content published to Apple News.', 'apple-news' ),
+ '.my-class, #my-id
',
+ ),
+ 'required' => false,
+ ],
];
// Add the groups.
$this->groups = [
- 'alerts' => [
+ 'alerts' => [
'label' => __( 'Alerts', 'apple-news' ),
'settings' => [ 'component_alerts' ],
],
- 'images' => [
+ 'images' => [
'label' => __( 'Image Settings', 'apple-news' ),
'settings' => [ 'use_remote_images', 'full_bleed_images' ],
],
- 'format' => [
+ 'cover' => [
+ 'label' => __( 'Cover Media Settings', 'apple-news' ),
+ 'settings' => [ 'deduplicate_cover_media' ],
+ ],
+ 'format' => [
'label' => __( 'Format Settings', 'apple-news' ),
'settings' => [ 'html_support', 'in_article_position' ],
],
- 'aside' => [
+ 'aside' => [
'label' => __( 'Aside Component', 'apple-news' ),
'settings' => [ 'aside_component_class' ],
],
+ 'recipe' => [
+ 'label' => __( 'Recipe Component', 'apple-news' ),
+ 'settings' => [ 'recipe_component_class', 'recipe_component_use_schema' ],
+ ],
+ 'selectors' => [
+ 'label' => __( 'Excluded Elements', 'apple-news' ),
+ 'settings' => [ 'excluded_selectors' ],
+ ],
];
parent::__construct( $page );
diff --git a/admin/settings/class-admin-apple-settings-section.php b/admin/settings/class-admin-apple-settings-section.php
index a96aab801..2954abbd8 100644
--- a/admin/settings/class-admin-apple-settings-section.php
+++ b/admin/settings/class-admin-apple-settings-section.php
@@ -135,6 +135,7 @@ class Admin_Apple_Settings_Section extends Apple_News {
'b' => [],
'strong' => [],
'i' => [],
+ 'code' => [],
'em' => [],
'a' => [
'href' => [],
diff --git a/apple-news.php b/apple-news.php
index 4d0f01a8c..d0f1f05de 100644
--- a/apple-news.php
+++ b/apple-news.php
@@ -3,7 +3,7 @@
* Plugin Name: Publish To Apple News
* Plugin URI: http://github.com/alleyinteractive/apple-news
* Description: Export and sync posts to Apple format.
- * Version: 2.6.3
+ * Version: 2.7.0
* Author: Alley
* Author URI: https://alley.com
* Text Domain: apple-news
diff --git a/assets/js/admin-settings/index.jsx b/assets/js/admin-settings/index.jsx
index ada1ca8f0..dcb463a47 100644
--- a/assets/js/admin-settings/index.jsx
+++ b/assets/js/admin-settings/index.jsx
@@ -23,6 +23,14 @@ function AdminSettings() {
const { apple_news_automation: ruleList } = settings;
const { fields } = AppleNewsAutomationConfig;
+ if (!ruleList) {
+ return null;
+ }
+
+ const LINK_SECTIONS = 'links.sections';
+ const allFieldsButSections = Object.keys(fields).filter((field) => field !== LINK_SECTIONS);
+ const sectionsOnly = [LINK_SECTIONS];
+
/**
* Helper function for pushing to in-memory settings inside useSiteOptions.
* @param {array} updatedRules - The new array of rules.
@@ -33,12 +41,13 @@ function AdminSettings() {
/**
* Adds a new empty rule to the end of the list.
+ * @param {string} field - The field to add a rule for.
*/
- const addRule = () => {
+ const addRule = (field = '') => {
updateSettings([
...(ruleList ?? []),
{
- field: '',
+ field,
taxonomy: '',
term_id: 0,
value: '',
@@ -75,11 +84,103 @@ function AdminSettings() {
updateSettings(updatedRules);
};
+ /**
+ * Generates a rule component.
+ * @param {object} item - The rule object.
+ * @param {number} index - The index of the rule.
+ * @return {React.JSX.Element}
+ */
+ const generateRule = (item, index) => {
+ const hideFieldTypes = item.field === LINK_SECTIONS ? allFieldsButSections : sectionsOnly;
+ const hideColumns = item.field === LINK_SECTIONS ? ['field'] : [];
+
+ return (
+ updateSettings(deleteAtIndex(ruleList, index))}
+ onDragEnd={(e) => {
+ const targetRow = document
+ .elementFromPoint(e.clientX, e.clientY)
+ .closest('.apple-news-automation-row');
+ // Checking for the parent element ensures that the row is in the same table.
+ if (targetRow && targetRow.parentElement === e.currentTarget.parentElement) {
+ reorderRule(
+ Number(e.currentTarget.dataset.index),
+ Number(targetRow.dataset.index),
+ );
+ }
+ }}
+ onUpdate={(key, value) => updateRule(index, key, value)}
+ taxonomy={item.taxonomy}
+ termId={item.term_id}
+ value={item.value}
+ index={index}
+ hideFieldTypes={hideFieldTypes}
+ hideColumns={hideColumns}
+ />
+ );
+ };
+
+ // Split the rows into sections and non-sections.
+ const { sectionAutomationRows, additionalAutomationRows } = ruleList.reduce(
+ (acc, item, index) => {
+ const ruleComponent = generateRule(item, index);
+
+ if (item.field === LINK_SECTIONS) {
+ acc.sectionAutomationRows.push(ruleComponent);
+ } else {
+ acc.additionalAutomationRows.push(ruleComponent);
+ }
+
+ return acc;
+ },
+ { sectionAutomationRows: [], additionalAutomationRows: [] },
+ );
+
return (
{__('Apple News Automation', 'apple-news')}
{__('Configure automation rules below to automatically apply certain settings based on the taxonomy terms applied to each post.', 'apple-news')}
-
{__('For more information on how automation works, visit our wiki.', 'apple-news')}
+
+ {__('For more information on how automation works, visit our wiki.', 'apple-news')}
+
+
{__('Section Mapping Automation', 'apple-news')}
+
+
+
+ {__('Taxonomy', 'apple-news')}
+ {__('Term', 'apple-news')}
+ {__('Section', 'apple-news')}
+ {__('Delete?', 'apple-news')}
+
+
+
+ {sectionAutomationRows}
+
+
+
+
+ addRule(LINK_SECTIONS)}
+ >
+ {__('Add Rule', 'apple-news')}
+
+ {' '}
+
+ {__('Save Settings', 'apple-news')}
+
+
+
+
+
{__('Additional Automation', 'apple-news')}
@@ -91,32 +192,7 @@ function AdminSettings() {
- {!loading && ruleList ? (
- ruleList.map((item, index) => (
- updateSettings(deleteAtIndex(ruleList, index))}
- onDragEnd={(e) => {
- const targetRow = document
- .elementFromPoint(e.clientX, e.clientY)
- .closest('.apple-news-automation-row');
- if (targetRow) {
- reorderRule(
- index,
- Array.from(targetRow.parentElement.querySelectorAll('tr'))
- .indexOf(targetRow),
- );
- }
- }}
- onUpdate={(key, value) => updateRule(index, key, value)}
- taxonomy={item.taxonomy}
- termId={item.term_id}
- value={item.value}
- />
- ))
- ) : null}
+ {additionalAutomationRows}
diff --git a/assets/js/admin-settings/rule.jsx b/assets/js/admin-settings/rule.jsx
index 7ff51878f..e06b0a54d 100644
--- a/assets/js/admin-settings/rule.jsx
+++ b/assets/js/admin-settings/rule.jsx
@@ -21,6 +21,9 @@ function Rule({
taxonomy,
termId,
value,
+ index,
+ hideFieldTypes,
+ hideColumns,
}) {
const {
fields,
@@ -43,132 +46,178 @@ function Rule({
fieldType = 'string';
}
+ /**
+ * Get field type options.
+ */
+ const getFieldTypes = () => {
+ // Filter out field types that should be hidden.
+ const filteredFieldsObject = Object.keys(fields).reduce((acc, fieldSlug) => {
+ if (!hideFieldTypes.includes(fieldSlug)) {
+ acc[fieldSlug] = fields[fieldSlug];
+ }
+ return acc;
+ }, {});
+
+ return [
+ { value: '', label: __('Select Field', 'apple-news') },
+ ...Object.keys(filteredFieldsObject).map((fieldSlug) => ({
+ label: fields[fieldSlug].label,
+ value: fieldSlug,
+ })),
+ ];
+ };
+
return (
-
- onUpdate('taxonomy', next)}
- options={[
- { value: '', label: __('Select Taxonomy', 'apple-news') },
- ...Object.keys(taxonomies).map((tax) => ({ value: tax, label: tax })),
- ]}
- value={taxonomy}
- />
-
-
- onUpdate('term_id', next)}
- taxonomy={taxonomy}
- termId={termId}
- />
-
-
- onUpdate('field', next)}
- options={[
- { value: '', label: __('Select Field', 'apple-news') },
- ...Object.keys(fields).map((fieldSlug) => ({
- label: fields[fieldSlug].label,
- value: fieldSlug,
- })),
- ]}
- value={field}
- />
-
-
- {fieldType === 'contentGenerationType' ? (
- onUpdate('value', next)}
- options={[
- { value: '', label: __('None', 'apple-news') },
- { value: 'AI', label: __('AI', 'apple-news') },
- ]}
- value={value}
- />
- ) : null}
- {fieldType === 'sections' ? (
- onUpdate('value', next)}
- options={[
- { value: '', label: __('Select Section', 'apple-news') },
- ...sections.map((sect) => ({ value: sect.id, label: sect.name })),
- ]}
- value={value}
- />
- ) : null}
- {fieldType === 'boolean-select' ? (
- onUpdate('value', next)}
- options={[
- { value: '', label: __('Channel Default', 'apple-news') },
- { value: 'true', label: __('True', 'apple-news') },
- { value: 'false', label: __('False', 'apple-news') },
- ]}
- value={value}
- />
- ) : null}
- {fieldType === 'boolean' ? (
- onUpdate('value', next.toString())}
- />
- ) : null}
- {fieldType === 'string' ? (
- onUpdate('value', next)}
- value={value}
- />
- ) : null}
- {fieldType === 'themes' ? (
- onUpdate('value', next)}
- options={[
- { value: '', label: __('Select Theme', 'apple-news') },
- ...themes.map((name) => ({ value: name, label: name })),
- ]}
- value={value}
- />
- ) : null}
-
-
-
- {__('Delete Rule', 'apple-news')}
-
-
+ {
+ hideColumns.includes('taxonomy')
+ ? null
+ : (
+
+ onUpdate('taxonomy', next)}
+ options={[
+ { value: '', label: __('Select Taxonomy', 'apple-news') },
+ ...Object.keys(taxonomies).map((tax) => ({ value: tax, label: tax })),
+ ]}
+ value={taxonomy}
+ />
+
+ )
+ }
+ {
+ hideColumns.includes('term') ? null : (
+
+ onUpdate('term_id', next)}
+ taxonomy={taxonomy}
+ termId={termId}
+ />
+
+ )
+ }
+ {
+ hideColumns.includes('field') ? null : (
+
+ onUpdate('field', next)}
+ options={getFieldTypes()}
+ value={field}
+ />
+
+ )
+ }
+ {
+ hideColumns.includes('value') ? null : (
+
+ {fieldType === 'contentGenerationType' ? (
+ onUpdate('value', next)}
+ options={[
+ { value: '', label: __('None', 'apple-news') },
+ { value: 'AI', label: __('AI', 'apple-news') },
+ ]}
+ value={value}
+ />
+ ) : null}
+ {fieldType === 'sections' ? (
+ onUpdate('value', next)}
+ options={[
+ { value: '', label: __('Select Section', 'apple-news') },
+ ...sections.map((sect) => ({ value: sect.id, label: sect.name })),
+ ]}
+ value={value}
+ />
+ ) : null}
+ {fieldType === 'boolean-select' ? (
+ onUpdate('value', next)}
+ options={[
+ { value: '', label: __('Channel Default', 'apple-news') },
+ { value: 'true', label: __('True', 'apple-news') },
+ { value: 'false', label: __('False', 'apple-news') },
+ ]}
+ value={value}
+ />
+ ) : null}
+ {fieldType === 'boolean' ? (
+ onUpdate('value', next.toString())}
+ />
+ ) : null}
+ {fieldType === 'string' ? (
+ onUpdate('value', next)}
+ value={value}
+ />
+ ) : null}
+ {fieldType === 'themes' ? (
+ onUpdate('value', next)}
+ options={[
+ { value: '', label: __('Select Theme', 'apple-news') },
+ ...themes.map((name) => ({ value: name, label: name })),
+ ]}
+ value={value}
+ />
+ ) : null}
+
+ )
+ }
+ {
+ hideColumns.includes('delete') ? null : (
+
+
+ {__('Delete Rule', 'apple-news')}
+
+
+ )
+ }
);
}
+Rule.defaultProps = {
+ hideColumns: [],
+ hideFieldTypes: [],
+};
+
Rule.propTypes = {
busy: PropTypes.bool.isRequired,
field: PropTypes.string.isRequired,
+ hideColumns: PropTypes.arrayOf(PropTypes.string),
+ hideFieldTypes: PropTypes.arrayOf(PropTypes.string),
+ index: PropTypes.number.isRequired,
onDelete: PropTypes.func.isRequired,
onDragEnd: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
diff --git a/assets/js/bulk-export.js b/assets/js/bulk-export.js
index 85752ffc3..c9d71e169 100644
--- a/assets/js/bulk-export.js
+++ b/assets/js/bulk-export.js
@@ -1,10 +1,13 @@
(function ( $, window, undefined ) {
'use strict';
- var started = false;
+ var started = false,
+ searchParams = new URLSearchParams( window.location.search ),
+ $submitButton = $( '.bulk-export-submit' );
function done() {
- $( '.bulk-export-submit' ).text( 'Done' );
+ $submitButton.text( 'Done' );
+ $submitButton.attr( 'disabled', 'disabled' );
}
function pushItem( item, next, nonce ) {
@@ -12,14 +15,14 @@
var $status = $item.find( '.bulk-export-list-item-status' );
var id = +$item.data( 'post-id' ); // fetch the post-id and cast to integer
- $status.removeClass( 'pending' ).addClass( 'in-progress' ).text( 'Publishing...' );
+ $status.removeClass( 'pending' ).addClass( 'in-progress' ).text( 'In Progress…' );
// Send a GET request to ajaxurl, which is WordPress endpoint for AJAX
// requests. Expects JSON as response.
$.getJSON(
ajaxurl,
{
- action: 'push_post',
+ action: searchParams.get( 'action' ),
id: id,
_ajax_nonce: nonce
},
@@ -27,7 +30,7 @@
if ( res.success ) {
$status.removeClass( 'in-progress' ).addClass( 'success' ).text( 'Success' );
} else {
- $status.removeClass( 'in-progress' ).addClass( 'failed' ).text( res.error );
+ $status.removeClass( 'in-progress' ).addClass( 'failed' ).text( res.data );
}
next();
},
@@ -56,7 +59,7 @@
next();
}
- $('.bulk-export-submit').click(function (e) {
+ $submitButton.click( function ( e ) {
e.preventDefault();
if ( started ) {
@@ -65,6 +68,6 @@
started = true;
bulkPush();
- });
+ } );
})( jQuery, window );
diff --git a/assets/js/cover-image.js b/assets/js/cover-image.js
deleted file mode 100644
index 5eb14d23f..000000000
--- a/assets/js/cover-image.js
+++ /dev/null
@@ -1,60 +0,0 @@
-(function ( $, window, undefined ) {
- 'use strict';
-
- // Set up add and remove image functionality.
- $( '.apple-news-coverimage-image-container' ).each( function () {
- var $this = $( this ),
- $addImgButton = $this.find( '.apple-news-coverimage-add' ),
- $delImgButton = $this.find( '.apple-news-coverimage-remove' ),
- $imgContainer = $this.find( '.apple-news-coverimage-image' ),
- $imgIdInput = $this.find( '.apple-news-coverimage-id' ),
- frame;
-
- // Set up handler for remove image functionality.
- $delImgButton.on( 'click', function() {
- $imgContainer.empty();
- $addImgButton.removeClass( 'hidden' );
- $delImgButton.addClass( 'hidden' );
- $imgIdInput.val( '' );
- } );
-
- // Set up handler for add image functionality.
- $addImgButton.on( 'click', function () {
-
- // Open frame, if it already exists.
- if ( frame ) {
- frame.open();
- return;
- }
-
- // Set configuration for media frame.
- frame = wp.media( { multiple: false } );
-
- // Set up handler for image selection.
- frame.on( 'select', function () {
-
- // Get information about the attachment.
- var attachment = frame.state().get( 'selection' ).first().toJSON(),
- imgUrl = attachment.url;
-
- // Set image URL to medium size, if available.
- if ( attachment.sizes.medium && attachment.sizes.medium.url ) {
- imgUrl = attachment.sizes.medium.url;
- }
-
- // Clear current values.
- $imgContainer.empty();
- $imgIdInput.val( '' );
-
- // Add the image and ID, swap visibility of add and remove buttons.
- $imgContainer.append( '
' ); // phpcs:ignore WordPressVIPMinimum.JS.StringConcat.Found, WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
- $imgIdInput.val( attachment.id );
- $addImgButton.addClass( 'hidden' );
- $delImgButton.removeClass( 'hidden' );
- } );
-
- // Open the media frame.
- frame.open();
- } );
- } );
-})( jQuery, window );
diff --git a/assets/js/cover-media.js b/assets/js/cover-media.js
new file mode 100644
index 000000000..50ad4be65
--- /dev/null
+++ b/assets/js/cover-media.js
@@ -0,0 +1,154 @@
+(function ( $, window, undefined ) {
+ 'use strict';
+
+ // Set up show/hide functionality for cover media provider containers.
+ (function () {
+ var $provider = $( '[name="apple_news_cover_media_provider"]' ),
+ $containers = $( '.apple-news-cover-media-provider-container' ),
+ showSelectedContainer = function () {
+ $containers.hide();
+ $containers.filter( '[data-provider="' + $provider.filter( ':checked' ).val() + '"]' ).show();
+ };
+
+ showSelectedContainer();
+ $provider.on( 'change', showSelectedContainer );
+ })();
+
+ // Set up add and remove image functionality.
+ $( '.apple-news-coverimage-image-container' ).each( function () {
+ var $this = $( this ),
+ $addImgButton = $this.find( '.apple-news-coverimage-add' ),
+ $delImgButton = $this.find( '.apple-news-coverimage-remove' ),
+ $imgContainer = $this.find( '.apple-news-coverimage-image' ),
+ $imgIdInput = $this.find( '.apple-news-coverimage-id' ),
+ frame;
+
+ // Set up handler for remove image functionality.
+ $delImgButton.on( 'click', function() {
+ $imgContainer.empty();
+ $addImgButton.removeClass( 'hidden' );
+ $delImgButton.addClass( 'hidden' );
+ $imgIdInput.val( '' );
+ } );
+
+ // Set up handler for add image functionality.
+ $addImgButton.on( 'click', function () {
+
+ // Open frame, if it already exists.
+ if ( frame ) {
+ frame.open();
+ return;
+ }
+
+ // Set configuration for media frame.
+ frame = wp.media( { multiple: false } );
+
+ // Set up handler for image selection.
+ frame.on( 'select', function () {
+
+ // Get information about the attachment.
+ var attachment = frame.state().get( 'selection' ).first().toJSON(),
+ imgUrl = attachment.url;
+
+ // Set image URL to medium size, if available.
+ if ( attachment.sizes.medium && attachment.sizes.medium.url ) {
+ imgUrl = attachment.sizes.medium.url;
+ }
+
+ // Clear current values.
+ $imgContainer.empty();
+ $imgIdInput.val( '' );
+
+ // Add the image and ID, swap visibility of add and remove buttons.
+ $imgContainer.append( '
' ); // phpcs:ignore WordPressVIPMinimum.JS.StringConcat.Found, WordPressVIPMinimum.JS.HTMLExecutingFunctions.append
+ $imgIdInput.val( attachment.id );
+ $addImgButton.addClass( 'hidden' );
+ $delImgButton.removeClass( 'hidden' );
+ } );
+
+ // Open the media frame.
+ frame.open();
+ } );
+ } );
+
+ // Set up add and remove video functionality.
+ $( '[data-provider="video_id"]' ).each( function () {
+ var $this = $( this ),
+ $addVideoButton = $this.find( '.apple-news-cover-video-id-add' ),
+ $delVideoButton = $this.find( '.apple-news-cover-video-id-remove' ),
+ $previewContainer = $this.find( '.apple-news-cover-media-video' ),
+ $videoIdInput = $this.find( '[name="apple_news_cover_video_id"]' ),
+ frame;
+
+ // Set up handler for remove functionality.
+ $delVideoButton.on( 'click', function() {
+ $previewContainer.empty();
+ $addVideoButton.removeClass( 'hidden' );
+ $delVideoButton.addClass( 'hidden' );
+ $videoIdInput.val( '' );
+ } );
+
+ // Set up handler for add functionality.
+ $addVideoButton.on( 'click', function () {
+ // Open frame, if it already exists.
+ if ( frame ) {
+ frame.open();
+ return;
+ }
+
+ // Set configuration for media frame.
+ frame = wp.media( {
+ multiple: false,
+ library: {
+ type: 'video/mp4'
+ },
+ } );
+
+ // Set up handler for image selection.
+ frame.on( 'select', function () {
+ // Get information about the attachment.
+ var attachment = frame.state().get( 'selection' ).first().toJSON(),
+ videoUrl = attachment.url;
+
+ // Clear current values.
+ $previewContainer.empty();
+ $videoIdInput.val( '' );
+
+ // Add the preview and ID, swap visibility of add and remove buttons.
+ $previewContainer.append( $( '
' ).attr( 'src', videoUrl ) );
+ $videoIdInput.val( attachment.id );
+ $addVideoButton.addClass( 'hidden' );
+ $delVideoButton.removeClass( 'hidden' );
+ } );
+
+ // Open the media frame.
+ frame.open();
+ } );
+ } );
+
+ // Set up URL validation for video URLs.
+ (function () {
+ var $videoInputs = $( '[name="apple_news_cover_video_url"], [name="apple_news_cover_embedwebvideo_url"]' ),
+ validateVideoUrl = function () {
+ var input = $(this),
+ $container = input.closest('.apple-news-cover-media-provider-container'),
+ $notice = $container.find('.notice'),
+ options;
+
+ options = {
+ path: '/apple-news/v1/is-valid-cover-media',
+ data: {
+ url: input.val(),
+ type: $container.data('provider'),
+ },
+ };
+
+ wp.apiRequest(options).done(function (response) {
+ $notice.toggle(!response.isValidCoverMedia);
+ });
+ };
+
+ $videoInputs.each( validateVideoUrl );
+ $videoInputs.on( 'input', validateVideoUrl );
+ })();
+})( jQuery, window );
diff --git a/assets/js/pluginsidebar/panels/cover-image.jsx b/assets/js/pluginsidebar/panels/cover-image.jsx
deleted file mode 100644
index 57a2767e3..000000000
--- a/assets/js/pluginsidebar/panels/cover-image.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { ImagePicker } from '@alleyinteractive/block-editor-tools';
-import { BaseControl, PanelBody, TextareaControl } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-function CoverImage({
- coverImageCaption,
- coverImageId,
- onChangeCoverImageCaption,
- onChangeCoverImageId,
-}) {
- return (
-
-
- onChangeCoverImageId(0)}
- onUpdate={({ id }) => onChangeCoverImageId(id)}
- value={coverImageId}
- />
-
-
-
- );
-}
-
-CoverImage.propTypes = {
- coverImageCaption: PropTypes.string.isRequired,
- coverImageId: PropTypes.number.isRequired,
- onChangeCoverImageCaption: PropTypes.func.isRequired,
- onChangeCoverImageId: PropTypes.func.isRequired,
-};
-
-export default CoverImage;
diff --git a/assets/js/pluginsidebar/panels/cover-media.jsx b/assets/js/pluginsidebar/panels/cover-media.jsx
new file mode 100644
index 000000000..de31aa5d4
--- /dev/null
+++ b/assets/js/pluginsidebar/panels/cover-media.jsx
@@ -0,0 +1,177 @@
+import apiFetch from '@wordpress/api-fetch';
+import { ImagePicker, MediaPicker } from '@alleyinteractive/block-editor-tools';
+import {
+ BaseControl,
+ PanelBody,
+ RadioControl,
+ TextareaControl,
+ TextControl,
+ Notice,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { addQueryArgs } from '@wordpress/url';
+import PropTypes from 'prop-types';
+import React, { useState, useEffect } from 'react';
+
+function CoverMedia({
+ coverMediaProvider,
+ coverImageCaption,
+ coverImageId,
+ coverVideoId,
+ coverVideoUrl,
+ coverEmbedWebVideoUrl,
+ onChangeCoverMediaProvider,
+ onChangeCoverImageCaption,
+ onChangeCoverImageId,
+ onChangeCoverVideoId,
+ onChangeCoverVideoUrl,
+ onChangeCoverEmbedWebVideoUrl,
+}) {
+ const [isValidCoverMedia, setIsValidCoverMedia] = useState(true);
+
+ useEffect(() => {
+ const endpoint = '/apple-news/v1/is-valid-cover-media';
+ const type = coverMediaProvider;
+ let url = '';
+
+ if (coverMediaProvider === 'video_url') {
+ url = coverVideoUrl;
+ }
+
+ if (coverMediaProvider === 'embedwebvideo') {
+ url = coverEmbedWebVideoUrl;
+ }
+
+ if (url) {
+ const apiPath = addQueryArgs(endpoint, { type, url });
+ const apiReq = apiFetch({ path: apiPath });
+
+ apiReq.then((response) => {
+ setIsValidCoverMedia(response.isValidCoverMedia);
+ });
+ }
+ }, [coverMediaProvider, coverVideoUrl, coverEmbedWebVideoUrl]);
+
+ return (
+
+
+
+ {coverMediaProvider === 'image' ? (
+ <>
+
+ onChangeCoverImageId(0)}
+ onUpdate={({ id }) => onChangeCoverImageId(id)}
+ value={coverImageId}
+ />
+
+
+
+ >
+ ) : null}
+
+ {coverMediaProvider === 'video_id' ? (
+ onChangeCoverVideoId(0)}
+ onUpdate={({ id }) => onChangeCoverVideoId(id)}
+ value={coverVideoId}
+ preview={({ src }) => ( // eslint-disable-line react/no-unstable-nested-components
+ // eslint-disable-line jsx-a11y/media-has-caption
+ )}
+ />
+ ) : null}
+
+ {coverMediaProvider === 'video_url' ? (
+ <>
+
+
+ {!isValidCoverMedia ? (
+
+ {__('This URL is not supported. Only MP4 video URLs are supported.', 'apple-news')}
+
+ ) : null}
+ >
+ ) : null}
+
+ {coverMediaProvider === 'embedwebvideo' ? (
+ <>
+
+
+ {!isValidCoverMedia ? (
+
+ {__('This URL is not supported. Only videos from YouTube, Vimeo, or Dailymotion are supported.', 'apple-news')}
+
+ ) : null}
+ >
+ ) : null}
+
+ );
+}
+
+CoverMedia.propTypes = {
+ coverMediaProvider: PropTypes.string.isRequired,
+ coverImageCaption: PropTypes.string.isRequired,
+ coverImageId: PropTypes.number.isRequired,
+ coverVideoId: PropTypes.number.isRequired,
+ coverVideoUrl: PropTypes.string.isRequired,
+ coverEmbedWebVideoUrl: PropTypes.string.isRequired,
+ onChangeCoverMediaProvider: PropTypes.func.isRequired,
+ onChangeCoverImageCaption: PropTypes.func.isRequired,
+ onChangeCoverImageId: PropTypes.func.isRequired,
+ onChangeCoverVideoId: PropTypes.func.isRequired,
+ onChangeCoverVideoUrl: PropTypes.func.isRequired,
+ onChangeCoverEmbedWebVideoUrl: PropTypes.func.isRequired,
+};
+
+export default CoverMedia;
diff --git a/assets/js/pluginsidebar/sidebar.jsx b/assets/js/pluginsidebar/sidebar.jsx
index db68a458e..3b8042106 100644
--- a/assets/js/pluginsidebar/sidebar.jsx
+++ b/assets/js/pluginsidebar/sidebar.jsx
@@ -10,7 +10,7 @@ import DOMPurify from 'dompurify';
import React, { useCallback, useEffect, useState } from 'react';
// Panels.
-import CoverImage from './panels/cover-image';
+import CoverMedia from './panels/cover-media';
import MaturityRating from './panels/maturity-rating';
import Metadata from './panels/metadata';
import PublishControls from './panels/publish-controls';
@@ -81,8 +81,12 @@ function Sidebar() {
}] = usePostMeta();
// Getters and setters for individual postmeta values.
+ const [coverMediaProvider, setCoverMediaProvider] = usePostMetaValue('apple_news_cover_media_provider');
const [coverImageId, setCoverImageId] = usePostMetaValue('apple_news_coverimage');
const [coverImageCaption, setCoverImageCaption] = usePostMetaValue('apple_news_coverimage_caption');
+ const [coverVideoId, setCoverVideoId] = usePostMetaValue('apple_news_cover_video_id');
+ const [coverVideoUrl, setCoverVideoUrl] = usePostMetaValue('apple_news_cover_video_url');
+ const [coverEmbedWebVideoUrl, setCoverEmbedWebVideoUrl] = usePostMetaValue('apple_news_cover_embedwebvideo_url');
const [isHidden, setIsHidden] = usePostMetaValue('apple_news_is_hidden');
const [isPaid, setIsPaid] = usePostMetaValue('apple_news_is_paid');
const [isPreview, setIsPreview] = usePostMetaValue('apple_news_is_preview');
@@ -263,11 +267,19 @@ function Sidebar() {
pullquotePosition={pullquotePosition}
pullquoteText={pullquoteText}
/>
-
{publishState !== 'N/A' ? (
{
data: finalSettings,
});
setSettings(response || {});
- await success(__('Settings Saved', 'bassmaster-plugin'));
+ await success(__('Settings Saved', 'apple-news'));
} catch ({ message }) {
await error(message);
} finally {
diff --git a/assets/themes/classic.json b/assets/themes/classic.json
index 29ea13b64..c3c5c0812 100644
--- a/assets/themes/classic.json
+++ b/assets/themes/classic.json
@@ -44,6 +44,7 @@
"caption_line_height": 19,
"caption_size": 14,
"caption_tracking": -1,
+ "cover_caption": true,
"cite_color": "#333333",
"cite_font": "Helvetica-Bold",
"cite_line_height": 19,
@@ -269,6 +270,48 @@
"pullquote_size": 27,
"pullquote_tracking": -2,
"pullquote_transform": "none",
+ "recipe_background_color": "#d3d3d3",
+ "recipe_body_background_color": "#d3d3d3",
+ "recipe_body_color": "#000000",
+ "recipe_body_font": "Georgia",
+ "recipe_body_line_height": 23,
+ "recipe_body_link_color": "#5c5c5c",
+ "recipe_body_size": 17,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "#d3d3d3",
+ "recipe_caption_color": "#000000",
+ "recipe_caption_font": "Georgia",
+ "recipe_caption_line_height": 23,
+ "recipe_caption_link_color": "#5c5c5c",
+ "recipe_caption_size": 17,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "#d3d3d3",
+ "recipe_details_color": "#000000",
+ "recipe_details_font": "Georgia",
+ "recipe_details_line_height": 23,
+ "recipe_details_link_color": "#5c5c5c",
+ "recipe_details_size": 17,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#000000",
+ "recipe_header2_font": "Helvetica",
+ "recipe_header2_line_height": 32,
+ "recipe_header2_size": 23,
+ "recipe_header2_tracking": -2,
+ "recipe_header3_color": "#000000",
+ "recipe_header3_font": "Helvetica",
+ "recipe_header3_line_height": 29,
+ "recipe_header3_size": 21,
+ "recipe_header3_tracking": -2,
+ "recipe_header4_color": "#000000",
+ "recipe_header4_font": "Helvetica",
+ "recipe_header4_line_height": 27,
+ "recipe_header4_size": 19,
+ "recipe_header4_tracking": -2,
+ "recipe_title_color": "#000000",
+ "recipe_title_font": "Helvetica",
+ "recipe_title_line_height": 35,
+ "recipe_title_size": 25,
+ "recipe_title_tracking": -2,
"slug_color": "#000000",
"slug_font": "Helvetica-Bold",
"slug_line_height": 24,
diff --git a/assets/themes/colorful.json b/assets/themes/colorful.json
index eacb84e9b..0aeae0ad6 100644
--- a/assets/themes/colorful.json
+++ b/assets/themes/colorful.json
@@ -44,6 +44,7 @@
"caption_line_height": 22,
"caption_size": 14,
"caption_tracking": -3,
+ "cover_caption": true,
"cite_color": "#3045ca",
"cite_font": "Georgia",
"cite_line_height": 22,
@@ -273,6 +274,48 @@
"pullquote_size": 36,
"pullquote_tracking": -5,
"pullquote_transform": "none",
+ "recipe_background_color": "#ffff00",
+ "recipe_body_background_color": "#ffff00",
+ "recipe_body_color": "#3045ca",
+ "recipe_body_font": "Optima-Regular",
+ "recipe_body_line_height": 22,
+ "recipe_body_link_color": "#3045ca",
+ "recipe_body_size": 17,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "#ffff00",
+ "recipe_caption_color": "#3045ca",
+ "recipe_caption_font": "Optima-Regular",
+ "recipe_caption_line_height": 22,
+ "recipe_caption_link_color": "#3045ca",
+ "recipe_caption_size": 17,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "#ffff00",
+ "recipe_details_color": "#3045ca",
+ "recipe_details_font": "Optima-Regular",
+ "recipe_details_line_height": 22,
+ "recipe_details_link_color": "#3045ca",
+ "recipe_details_size": 17,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#3045ca",
+ "recipe_header2_font": "Georgia",
+ "recipe_header2_line_height": 28,
+ "recipe_header2_size": 28,
+ "recipe_header2_tracking": -3,
+ "recipe_header3_color": "#3045ca",
+ "recipe_header3_font": "Georgia",
+ "recipe_header3_line_height": 26,
+ "recipe_header3_size": 26,
+ "recipe_header3_tracking": -3,
+ "recipe_header4_color": "#3045ca",
+ "recipe_header4_font": "Georgia",
+ "recipe_header4_line_height": 24,
+ "recipe_header4_size": 24,
+ "recipe_header4_tracking": -3,
+ "recipe_title_color": "#3045ca",
+ "recipe_title_font": "Georgia",
+ "recipe_title_line_height": 36,
+ "recipe_title_size": 36,
+ "recipe_title_tracking": -3,
"slug_color": "#000000",
"slug_font": "Optima-Regular",
"slug_line_height": 26,
diff --git a/assets/themes/dark.json b/assets/themes/dark.json
index 3e7ac2390..f2cdbc8c0 100644
--- a/assets/themes/dark.json
+++ b/assets/themes/dark.json
@@ -44,6 +44,7 @@
"caption_line_height": 19,
"caption_size": 14,
"caption_tracking": -1,
+ "cover_caption": true,
"cite_color": "#999999",
"cite_font": "Helvetica-Light",
"cite_line_height": 19,
@@ -289,6 +290,48 @@
"pullquote_size": 29,
"pullquote_tracking": -2,
"pullquote_transform": "none",
+ "recipe_background_color": "",
+ "recipe_body_background_color": "",
+ "recipe_body_color": "#e2e2e2",
+ "recipe_body_font": "Georgia",
+ "recipe_body_line_height": 23,
+ "recipe_body_link_color": "#ffe890",
+ "recipe_body_size": 17,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "",
+ "recipe_caption_color": "#e2e2e2",
+ "recipe_caption_font": "Georgia",
+ "recipe_caption_line_height": 23,
+ "recipe_caption_link_color": "#ffe890",
+ "recipe_caption_size": 17,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "",
+ "recipe_details_color": "#e2e2e2",
+ "recipe_details_font": "Georgia",
+ "recipe_details_line_height": 23,
+ "recipe_details_link_color": "#ffe890",
+ "recipe_details_size": 17,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#ffffff",
+ "recipe_header2_font": "HelveticaNeue-Thin",
+ "recipe_header2_line_height": 31,
+ "recipe_header2_size": 27,
+ "recipe_header2_tracking": 0,
+ "recipe_header3_color": "#ffffff",
+ "recipe_header3_font": "HelveticaNeue-Thin",
+ "recipe_header3_line_height": 28,
+ "recipe_header3_size": 25,
+ "recipe_header3_tracking": 0,
+ "recipe_header4_color": "#ffffff",
+ "recipe_header4_font": "HelveticaNeue-Thin",
+ "recipe_header4_line_height": 26,
+ "recipe_header4_size": 23,
+ "recipe_header4_tracking": 0,
+ "recipe_title_color": "#ffffff",
+ "recipe_title_font": "HelveticaNeue-Thin",
+ "recipe_title_line_height": 33,
+ "recipe_title_size": 29,
+ "recipe_title_tracking": 0,
"slug_color": "#e2e2e2",
"slug_font": "Helvetica-Bold",
"slug_line_height": 26,
diff --git a/assets/themes/default.json b/assets/themes/default.json
index 11319f700..06801f3fd 100644
--- a/assets/themes/default.json
+++ b/assets/themes/default.json
@@ -44,6 +44,7 @@
"caption_line_height": 24,
"caption_size": 16,
"caption_tracking": 0,
+ "cover_caption": true,
"cite_color": "#4f4f4f",
"cite_font": "AvenirNext-Italic",
"cite_line_height": 24,
@@ -120,6 +121,48 @@
"pullquote_size": 48,
"pullquote_tracking": 0,
"pullquote_transform": "uppercase",
+ "recipe_background_color": "#e1e1e1",
+ "recipe_body_background_color": "#e1e1e1",
+ "recipe_body_color": "#4f4f4f",
+ "recipe_body_font": "AvenirNext-Regular",
+ "recipe_body_line_height": 24,
+ "recipe_body_link_color": "#428bca",
+ "recipe_body_size": 18,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "#e1e1e1",
+ "recipe_caption_color": "#4f4f4f",
+ "recipe_caption_font": "AvenirNext-Regular",
+ "recipe_caption_line_height": 24,
+ "recipe_caption_link_color": "#428bca",
+ "recipe_caption_size": 18,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "#e1e1e1",
+ "recipe_details_color": "#4f4f4f",
+ "recipe_details_font": "AvenirNext-Regular",
+ "recipe_details_line_height": 24,
+ "recipe_details_link_color": "#428bca",
+ "recipe_details_size": 18,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#333333",
+ "recipe_header2_font": "AvenirNext-Bold",
+ "recipe_header2_line_height": 28,
+ "recipe_header2_size": 24,
+ "recipe_header2_tracking": 0,
+ "recipe_header3_color": "#333333",
+ "recipe_header3_font": "AvenirNext-Bold",
+ "recipe_header3_line_height": 26,
+ "recipe_header3_size": 21,
+ "recipe_header3_tracking": 0,
+ "recipe_header4_color": "#333333",
+ "recipe_header4_font": "AvenirNext-Bold",
+ "recipe_header4_line_height": 24,
+ "recipe_header4_size": 18,
+ "recipe_header4_tracking": 0,
+ "recipe_title_color": "#333333",
+ "recipe_title_font": "AvenirNext-Bold",
+ "recipe_title_line_height": 36,
+ "recipe_title_size": 32,
+ "recipe_title_tracking": 0,
"slug_color": "#4f4f4f",
"slug_font": "AvenirNext-Medium",
"slug_line_height": 26,
diff --git a/assets/themes/modern.json b/assets/themes/modern.json
index 133ce0baf..343c8e8a4 100644
--- a/assets/themes/modern.json
+++ b/assets/themes/modern.json
@@ -44,6 +44,7 @@
"caption_line_height": 17,
"caption_size": 14,
"caption_tracking": 0,
+ "cover_caption": true,
"cite_color": "#000000",
"cite_font": "AvenirNext-DemiBold",
"cite_line_height": 17,
@@ -308,6 +309,48 @@
"pullquote_size": 36,
"pullquote_tracking": -5,
"pullquote_transform": "none",
+ "recipe_background_color": "#e6f7ff",
+ "recipe_body_background_color": "#e6f7ff",
+ "recipe_body_color": "#30bcff",
+ "recipe_body_font": "Optima-Regular",
+ "recipe_body_line_height": 22,
+ "recipe_body_link_color": "#30bcff",
+ "recipe_body_size": 17,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "#e6f7ff",
+ "recipe_caption_color": "#30bcff",
+ "recipe_caption_font": "Optima-Regular",
+ "recipe_caption_line_height": 22,
+ "recipe_caption_link_color": "#30bcff",
+ "recipe_caption_size": 17,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "#e6f7ff",
+ "recipe_details_color": "#30bcff",
+ "recipe_details_font": "Optima-Regular",
+ "recipe_details_line_height": 22,
+ "recipe_details_link_color": "#30bcff",
+ "recipe_details_size": 17,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#000000",
+ "recipe_header2_font": "AvenirNext-Bold",
+ "recipe_header2_line_height": 46,
+ "recipe_header2_size": 48,
+ "recipe_header2_tracking": -4,
+ "recipe_header3_color": "#000000",
+ "recipe_header3_font": "AvenirNext-Bold",
+ "recipe_header3_line_height": 34,
+ "recipe_header3_size": 36,
+ "recipe_header3_tracking": -4,
+ "recipe_header4_color": "#000000",
+ "recipe_header4_font": "AvenirNext-Bold",
+ "recipe_header4_line_height": 22,
+ "recipe_header4_size": 24,
+ "recipe_header4_tracking": -4,
+ "recipe_title_color": "#000000",
+ "recipe_title_font": "AvenirNext-Bold",
+ "recipe_title_line_height": 58,
+ "recipe_title_size": 60,
+ "recipe_title_tracking": -4,
"slug_color": "#000000",
"slug_font": "AvenirNext-Regular",
"slug_line_height": 26,
diff --git a/assets/themes/pastel.json b/assets/themes/pastel.json
index 2ce65b3ea..8d4930122 100644
--- a/assets/themes/pastel.json
+++ b/assets/themes/pastel.json
@@ -44,6 +44,7 @@
"caption_line_height": 19,
"caption_size": 14,
"caption_tracking": -1,
+ "cover_caption": true,
"cite_color": "#333333",
"cite_font": "AvenirNext-Regular",
"cite_line_height": 19,
@@ -277,6 +278,48 @@
"pullquote_size": 22,
"pullquote_tracking": 5,
"pullquote_transform": "uppercase",
+ "recipe_background_color": "#ffebe0",
+ "recipe_body_background_color": "#ffebe0",
+ "recipe_body_color": "#000000",
+ "recipe_body_font": "Georgia",
+ "recipe_body_line_height": 23,
+ "recipe_body_link_color": "#000000",
+ "recipe_body_size": 17,
+ "recipe_body_tracking": 0,
+ "recipe_caption_background_color": "#ffebe0",
+ "recipe_caption_color": "#000000",
+ "recipe_caption_font": "Georgia",
+ "recipe_caption_line_height": 23,
+ "recipe_caption_link_color": "#000000",
+ "recipe_caption_size": 17,
+ "recipe_caption_tracking": 0,
+ "recipe_details_background_color": "#ffebe0",
+ "recipe_details_color": "#000000",
+ "recipe_details_font": "Georgia",
+ "recipe_details_line_height": 23,
+ "recipe_details_link_color": "#000000",
+ "recipe_details_size": 17,
+ "recipe_details_tracking": 0,
+ "recipe_header2_color": "#000000",
+ "recipe_header2_font": "AvenirNext-Italic",
+ "recipe_header2_line_height": 31,
+ "recipe_header2_size": 23,
+ "recipe_header2_tracking": 12,
+ "recipe_header3_color": "#000000",
+ "recipe_header3_font": "AvenirNext-Italic",
+ "recipe_header3_line_height": 29,
+ "recipe_header3_size": 21,
+ "recipe_header3_tracking": 12,
+ "recipe_header4_color": "#000000",
+ "recipe_header4_font": "AvenirNext-Italic",
+ "recipe_header4_line_height": 26,
+ "recipe_header4_size": 19,
+ "recipe_header4_tracking": 12,
+ "recipe_title_color": "#000000",
+ "recipe_title_font": "AvenirNext-Italic",
+ "recipe_title_line_height": 34,
+ "recipe_title_size": 25,
+ "recipe_title_tracking": 12,
"slug_color": "#000000",
"slug_font": "AvenirNext-Bold",
"slug_line_height": 26,
diff --git a/includes/REST/apple-news-is-valid-cover-media.php b/includes/REST/apple-news-is-valid-cover-media.php
new file mode 100644
index 000000000..aecbeca1d
--- /dev/null
+++ b/includes/REST/apple-news-is-valid-cover-media.php
@@ -0,0 +1,60 @@
+ WP_REST_Server::READABLE,
+ 'callback' => function ( $request ): WP_REST_Response {
+ $type = $request['type'];
+ $url = $request['url'];
+
+ // Assume URLs are OK but check for a few known cases where they are not.
+ $valid = true;
+
+ if ( 'video_url' === $type ) {
+ $check = wp_check_filetype( $url );
+ $valid = isset( $check['ext'] ) && 'mp4' === $check['ext'];
+ }
+
+ if ( 'embedwebvideo' === $type ) {
+ $valid = preg_match( Embed_Web_Video::VIMEO_MATCH, $url )
+ || preg_match( Embed_Web_Video::YOUTUBE_MATCH, $url )
+ || preg_match( Embed_Web_Video::DAILYMOTION_MATCH, $url );
+ }
+
+ return rest_ensure_response( [ 'isValidCoverMedia' => $valid ] );
+ },
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'type' => [
+ 'type' => 'string',
+ 'required' => true,
+ ],
+ 'url' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'format' => 'uri',
+ ],
+ ],
+ ],
+ );
+ },
+);
diff --git a/includes/apple-exporter/builders/class-components.php b/includes/apple-exporter/builders/class-components.php
index c26a4c603..d42b5d250 100644
--- a/includes/apple-exporter/builders/class-components.php
+++ b/includes/apple-exporter/builders/class-components.php
@@ -72,6 +72,9 @@ protected function build() {
// Remove any identifiers that are duplicated.
$components = $this->remove_duplicate_identifiers( $components );
+ // Remove any components that duplicate the cover media.
+ $components = $this->remove_cover_from_components( $components );
+
return $components;
}
@@ -220,8 +223,14 @@ private function add_thumbnail_if_needed( &$components ) {
}
}
+ $cover_config = $this->content_cover();
+
+ // If configured cover is not an image, don't try to replace it with an image from the post.
+ if ( isset( $cover_config['provider'] ) && 'image' !== $cover_config['provider'] ) {
+ return;
+ }
+
// If the normalized URL for the first image is different than the URL for the featured image, use the featured image.
- $cover_config = $this->content_cover();
$cover_url = $this->get_image_full_size_url( isset( $cover_config['url'] ) ? $cover_config['url'] : $cover_config );
$normalized_url = $this->get_image_full_size_url( $original_url );
if ( ! empty( $cover_url ) && $normalized_url !== $cover_url ) {
@@ -928,4 +937,39 @@ private function split_into_components() {
return $components;
}
+
+ /**
+ * Remove any components that duplicate the cover media.
+ *
+ * @param array $components The array of components to remove the cover from.
+ * @return array The updated array of components.
+ */
+ private function remove_cover_from_components( $components ) {
+ if ( 'yes' !== $this->get_setting( 'deduplicate_cover_media' ) ) {
+ return $components;
+ }
+
+ $cover = $this->content_cover();
+
+ if ( empty( $cover['url'] ) ) {
+ return $components;
+ }
+
+ foreach ( $components as $i => $component ) {
+ // Special case: Don't remove the cover from the header itself.
+ if ( isset( $component['role'] ) && 'header' === $component['role'] ) {
+ continue;
+ }
+
+ if ( isset( $component['URL'] ) && $component['URL'] === $cover['url'] ) {
+ unset( $components[ $i ] );
+ }
+
+ if ( isset( $component['components'] ) && is_array( $component['components'] ) ) {
+ $components[ $i ]['components'] = $this->remove_cover_from_components( $component['components'] );
+ }
+ }
+
+ return array_values( $components );
+ }
}
diff --git a/includes/apple-exporter/builders/class-metadata.php b/includes/apple-exporter/builders/class-metadata.php
index 64a47d4ca..f8a6a3ad0 100644
--- a/includes/apple-exporter/builders/class-metadata.php
+++ b/includes/apple-exporter/builders/class-metadata.php
@@ -40,9 +40,17 @@ protected function build() {
// If the content has a cover, use it as thumb.
$content_cover = $this->content_cover();
if ( ! empty( $content_cover ) ) {
- $meta['thumbnailURL'] = $this->maybe_bundle_source(
- isset( $content_cover['url'] ) ? $content_cover['url'] : $content_cover
- );
+ $thumbnail_url = isset( $content_cover['url'] ) ? $content_cover['url'] : $content_cover;
+
+ // If the cover is not an image, try to get the post thumbnail.
+ if ( isset( $content_cover['provider'] ) && 'image' !== $content_cover['provider'] ) {
+ $thumbnail_id = get_post_thumbnail_id( $this->content_id() );
+ $thumbnail_url = (string) wp_get_attachment_url( $thumbnail_id );
+ }
+
+ if ( $thumbnail_url ) {
+ $meta['thumbnailURL'] = $this->maybe_bundle_source( $thumbnail_url );
+ }
}
// Add authors.
diff --git a/includes/apple-exporter/class-component-factory.php b/includes/apple-exporter/class-component-factory.php
index 91eb783f0..ac26a842e 100644
--- a/includes/apple-exporter/class-component-factory.php
+++ b/includes/apple-exporter/class-component-factory.php
@@ -121,6 +121,7 @@ public static function initialize(
self::register_component( 'author', '\\Apple_Exporter\\Components\\Author' );
self::register_component( 'date', '\\Apple_Exporter\\Components\\Date' );
self::register_component( 'slug', '\\Apple_Exporter\\Components\\Slug' );
+ self::register_component( 'recipe', '\\Apple_Exporter\\Components\\Recipe' );
self::register_component( 'end-of-article', '\\Apple_Exporter\\Components\\End_Of_Article' );
self::register_component( 'in-article', '\\Apple_Exporter\\Components\\In_Article' );
diff --git a/includes/apple-exporter/class-settings.php b/includes/apple-exporter/class-settings.php
index fefb22204..b767069dc 100644
--- a/includes/apple-exporter/class-settings.php
+++ b/includes/apple-exporter/class-settings.php
@@ -43,10 +43,13 @@ class Settings {
'apple_news_admin_email' => '',
'apple_news_enable_debugging' => 'no',
'component_alerts' => 'none',
+ 'excluded_selectors' => '',
'full_bleed_images' => 'no',
+ 'deduplicate_cover_media' => 'no',
'html_support' => 'yes',
'in_article_position' => 3,
'post_types' => [ 'post' ],
+ 'recipe_component_use_schema' => 'yes',
'show_metabox' => 'yes',
'use_remote_images' => 'yes',
];
diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php
index b66250123..4384ec455 100644
--- a/includes/apple-exporter/class-theme.php
+++ b/includes/apple-exporter/class-theme.php
@@ -556,970 +556,1283 @@ private static function sort_registry( $registry ) {
*/
private static function initialize_options() {
self::$options = [
- 'aside_alignment' => [
+ 'aside_alignment' => [
'default' => 'right',
'label' => __( 'Aside component alignment', 'apple-news' ),
'options' => [ 'left', 'right' ],
'type' => 'select',
],
- 'aside_background_color' => [
+ 'aside_background_color' => [
'default' => '#e1e1e1',
'label' => __( 'Aside background color', 'apple-news' ),
'type' => 'color',
],
- 'aside_background_color_dark' => [
+ 'aside_background_color_dark' => [
'default' => '',
'label' => __( 'Aside background color', 'apple-news' ),
'type' => 'color',
],
- 'aside_border_color' => [
+ 'aside_border_color' => [
'default' => '#4f4f4f',
'label' => __( 'Aside border color', 'apple-news' ),
'type' => 'color',
],
- 'aside_border_color_dark' => [
+ 'aside_border_color_dark' => [
'default' => '',
'label' => __( 'Aside border color', 'apple-news' ),
'type' => 'color',
],
- 'aside_border_style' => [
+ 'aside_border_style' => [
'default' => 'solid',
'label' => __( 'Aside border style', 'apple-news' ),
'options' => [ 'solid', 'dashed', 'dotted', 'none' ],
'type' => 'select',
],
- 'aside_border_width' => [
+ 'aside_border_width' => [
'default' => 3,
'label' => __( 'Aside border width', 'apple-news' ),
'type' => 'integer',
],
- 'aside_padding' => [
+ 'aside_padding' => [
'default' => 20,
'label' => __( 'Aside padding', 'apple-news' ),
'type' => 'integer',
],
- 'author_color' => [
+ 'author_color' => [
'default' => '#7c7c7c',
'label' => __( 'Author font color', 'apple-news' ),
'type' => 'color',
],
- 'author_color_dark' => [
+ 'author_color_dark' => [
'default' => '',
'label' => __( 'Author font color', 'apple-news' ),
'type' => 'color',
],
- 'author_font' => [
+ 'author_font' => [
'default' => 'AvenirNext-Medium',
'label' => __( 'Author font face', 'apple-news' ),
'type' => 'font',
],
- 'author_format' => [
+ 'author_format' => [
'default' => 'By #author#',
'description' => __( 'Set the byline format. #author# denotes the location of the author name. The default format is "By #author#. Note that byline format updates only preview on save.', 'apple-news' ),
'label' => __( 'Author format', 'apple-news' ),
'type' => 'text',
],
- 'author_line_height' => [
+ 'author_line_height' => [
'default' => 24.0,
'label' => __( 'Author line height', 'apple-news' ),
'type' => 'float',
],
- 'author_link_color' => [
+ 'author_link_color' => [
'default' => '#7c7c7c',
'label' => __( 'Author URL font color', 'apple-news' ),
'type' => 'color',
],
- 'author_link_color_dark' => [
+ 'author_link_color_dark' => [
'default' => '',
'label' => __( 'Author URL font color ', 'apple-news' ),
'type' => 'color',
],
- 'author_links' => [
+ 'author_links' => [
'default' => 'no',
'label' => __( 'Hyperlink author names?', 'apple-news' ),
'options' => [ 'yes', 'no' ],
'type' => 'select',
],
- 'author_size' => [
+ 'author_size' => [
'default' => 13,
'label' => __( 'Author font size', 'apple-news' ),
'type' => 'integer',
],
- 'author_tracking' => [
+ 'author_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Author tracking', 'apple-news' ),
'type' => 'integer',
],
- 'blockquote_background_color' => [
+ 'blockquote_background_color' => [
'default' => '#e1e1e1',
'label' => __( 'Blockquote background color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_background_color_dark' => [
+ 'blockquote_background_color_dark' => [
'default' => '',
'label' => __( 'Blockquote background color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_border_color' => [
+ 'blockquote_border_color' => [
'default' => '#4f4f4f',
'label' => __( 'Blockquote border color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_border_color_dark' => [
+ 'blockquote_border_color_dark' => [
'default' => '',
'label' => __( 'Blockquote border color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_border_style' => [
+ 'blockquote_border_style' => [
'default' => 'solid',
'label' => __( 'Blockquote border style', 'apple-news' ),
'options' => [ 'solid', 'dashed', 'dotted', 'none' ],
'type' => 'select',
],
- 'blockquote_border_width' => [
+ 'blockquote_border_width' => [
'default' => 3,
'label' => __( 'Blockquote border width', 'apple-news' ),
'type' => 'integer',
],
- 'blockquote_color' => [
+ 'blockquote_color' => [
'default' => '#4f4f4f',
'label' => __( 'Blockquote color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_color_dark' => [
+ 'blockquote_color_dark' => [
'default' => '',
'label' => __( 'Blockquote color', 'apple-news' ),
'type' => 'color',
],
- 'blockquote_font' => [
+ 'blockquote_font' => [
'default' => 'AvenirNext-Regular',
'label' => __( 'Blockquote font face', 'apple-news' ),
'type' => 'font',
],
- 'blockquote_line_height' => [
+ 'blockquote_line_height' => [
'default' => 24.0,
'label' => __( 'Blockquote line height', 'apple-news' ),
'type' => 'float',
],
- 'blockquote_size' => [
+ 'blockquote_size' => [
'default' => 18,
'label' => __( 'Blockquote font size', 'apple-news' ),
'type' => 'integer',
],
- 'blockquote_tracking' => [
+ 'blockquote_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Blockquote tracking', 'apple-news' ),
'type' => 'integer',
],
- 'body_background_color' => [
+ 'body_background_color' => [
'default' => '#fafafa',
'label' => __( 'Body background color', 'apple-news' ),
'type' => 'color',
],
- 'body_background_color_dark' => [
+ 'body_background_color_dark' => [
'default' => '',
'label' => __( 'Body background color', 'apple-news' ),
'type' => 'color',
],
- 'body_color' => [
+ 'body_color' => [
'default' => '#4f4f4f',
'label' => __( 'Body font color', 'apple-news' ),
'type' => 'color',
],
- 'body_color_dark' => [
+ 'body_color_dark' => [
'default' => '',
'label' => __( 'Body font color', 'apple-news' ),
'type' => 'color',
],
- 'body_font' => [
+ 'body_font' => [
'default' => 'AvenirNext-Regular',
'label' => __( 'Body font face', 'apple-news' ),
'type' => 'font',
],
- 'body_line_height' => [
+ 'body_line_height' => [
'default' => 24.0,
'label' => __( 'Body line height', 'apple-news' ),
'type' => 'float',
],
- 'body_link_color' => [
+ 'body_link_color' => [
'default' => '#428bca',
'label' => __( 'Body font hyperlink color', 'apple-news' ),
'type' => 'color',
],
- 'body_link_color_dark' => [
+ 'body_link_color_dark' => [
'default' => '',
'label' => __( 'Body font hyperlink color', 'apple-news' ),
'type' => 'color',
],
- 'body_orientation' => [
+ 'body_orientation' => [
'default' => 'left',
'description' => __( 'Controls margins on larger screens. Left orientation includes one column of margin on the right, right orientation includes one column of margin on the left, and center orientation includes one column of margin on either side.', 'apple-news' ),
'label' => __( 'Body orientation', 'apple-news' ),
'options' => [ 'left', 'center', 'right' ],
'type' => 'select',
],
- 'body_size' => [
+ 'body_size' => [
'default' => 18,
'label' => __( 'Body font size', 'apple-news' ),
'type' => 'integer',
],
- 'body_tracking' => [
+ 'body_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Body tracking', 'apple-news' ),
'type' => 'integer',
],
- 'button_background_color' => [
+ 'button_background_color' => [
'default' => '#ffffff',
'label' => __( 'Button background color', 'apple-news' ),
'type' => 'color',
],
- 'button_border_color' => [
+ 'button_border_color' => [
'default' => '#000000',
'label' => __( 'Button border color', 'apple-news' ),
'type' => 'color',
],
- 'button_border_radius' => [
+ 'button_border_radius' => [
'default' => 18,
'label' => __( 'Button border radius', 'apple-news' ),
'type' => 'integer',
],
- 'button_border_width' => [
+ 'button_border_width' => [
'default' => 1,
'label' => __( 'Button border width', 'apple-news' ),
'type' => 'integer',
],
- 'button_horizontal_alignment' => [
+ 'button_horizontal_alignment' => [
'default' => 'center',
'label' => __( 'Button alignment', 'apple-news' ),
'options' => [ 'left', 'center', 'right' ],
'type' => 'select',
],
- 'button_text_color' => [
+ 'button_text_color' => [
'default' => '#000000',
'label' => __( 'Button text color', 'apple-news' ),
'type' => 'color',
],
- 'button_font_face' => [
+ 'button_font_face' => [
'default' => 'HelveticaNeue-Medium',
'label' => __( 'Button font face', 'apple-news' ),
'type' => 'font',
],
- 'button_font_size' => [
+ 'button_font_size' => [
'default' => 15,
'label' => __( 'Button font size', 'apple-news' ),
'type' => 'integer',
],
- 'byline_color' => [
+ 'byline_color' => [
'default' => '#7c7c7c',
'label' => __( 'Combined Date and Author font color', 'apple-news' ),
'type' => 'color',
],
- 'byline_color_dark' => [
+ 'byline_color_dark' => [
'default' => '',
'label' => __( 'Combined Date and Author font color', 'apple-news' ),
'type' => 'color',
],
- 'byline_font' => [
+ 'byline_font' => [
'default' => 'AvenirNext-Medium',
'label' => __( 'Combined Date and Author font face', 'apple-news' ),
'type' => 'font',
],
- 'byline_format' => [
+ 'byline_format' => [
'default' => 'By #author# | #M j, Y | g:i A#',
'description' => __( 'Set the byline format. Two tokens can be present, #author# to denote the location of the author name and a PHP date format string also encapsulated by #. The default format is "by #author# | #M j, Y | g:i A#". Note that byline format updates only preview on save.', 'apple-news' ),
'label' => __( 'Combined Date and Author format', 'apple-news' ),
'type' => 'text',
],
- 'byline_line_height' => [
+ 'byline_line_height' => [
'default' => 24.0,
'label' => __( 'Combined Date and Author line height', 'apple-news' ),
'type' => 'float',
],
- 'byline_size' => [
+ 'byline_size' => [
'default' => 13,
'label' => __( 'Combined Date and Author font size', 'apple-news' ),
'type' => 'integer',
],
- 'byline_tracking' => [
+ 'byline_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Combined Date and Author tracking', 'apple-news' ),
'type' => 'integer',
],
- 'caption_color' => [
+ 'caption_color' => [
'default' => '#4f4f4f',
'label' => __( 'Caption font color', 'apple-news' ),
'type' => 'color',
],
- 'caption_color_dark' => [
+ 'caption_color_dark' => [
'default' => '',
'label' => __( 'Caption font color', 'apple-news' ),
'type' => 'color',
],
- 'caption_font' => [
+ 'caption_font' => [
'default' => 'AvenirNext-Italic',
'label' => __( 'Caption font face', 'apple-news' ),
'type' => 'font',
],
- 'caption_line_height' => [
+ 'caption_line_height' => [
'default' => 24.0,
'label' => __( 'Caption line height', 'apple-news' ),
'type' => 'float',
],
- 'caption_margin_bottom' => [
+ 'caption_margin_bottom' => [
'default' => 25,
'label' => __( 'Margin below the caption', 'apple-news' ),
'type' => 'integer',
],
- 'caption_size' => [
+ 'caption_size' => [
'default' => 16,
'label' => __( 'Caption font size', 'apple-news' ),
'type' => 'integer',
],
- 'caption_tracking' => [
+ 'caption_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Caption tracking', 'apple-news' ),
'type' => 'integer',
],
- 'cover_caption' => [
- 'default' => false,
+ 'cover_caption' => [
+ 'default' => true,
'label' => __( 'Enable caption on the Cover component', 'apple-news' ),
'type' => 'boolean',
],
- 'dark_mode_colors_heading' => [
+ 'dark_mode_colors_heading' => [
'label' => __( 'Dark Mode Colors', 'apple-news' ),
'description' => __( 'Colors specific to Apple News Dark Mode', 'apple-news' ),
'type' => 'group_heading',
],
- 'dropcap_background_color' => [
+ 'dropcap_background_color' => [
'default' => '',
'label' => __( 'Drop cap background color', 'apple-news' ),
'type' => 'color',
],
- 'dropcap_background_color_dark' => [
+ 'dropcap_background_color_dark' => [
'default' => '',
'label' => __( 'Drop cap background color', 'apple-news' ),
'type' => 'color',
],
- 'dropcap_color' => [
+ 'dropcap_color' => [
'default' => '#4f4f4f',
'label' => __( 'Drop cap font color', 'apple-news' ),
'type' => 'color',
],
- 'dropcap_color_dark' => [
+ 'dropcap_color_dark' => [
'default' => '',
'label' => __( 'Drop cap font color', 'apple-news' ),
'type' => 'color',
],
- 'dropcap_font' => [
+ 'dropcap_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Dropcap font face', 'apple-news' ),
'type' => 'font',
],
- 'dropcap_minimum' => [
+ 'dropcap_minimum' => [
'default' => 100,
'label' => __( 'Minimum number of characters for dropcap to take effect.', 'apple-news' ),
'type' => 'integer',
],
- 'dropcap_minimum_opt_out' => [
+ 'dropcap_minimum_opt_out' => [
'default' => 'no',
'label' => __( 'Opt out of conditional dropcap behavior.', 'apple-news' ),
'options' => [ 'yes', 'no' ],
'type' => 'select',
],
- 'dropcap_number_of_characters' => [
+ 'dropcap_number_of_characters' => [
'default' => 1,
'label' => __( 'Drop cap number of characters', 'apple-news' ),
'type' => 'integer',
],
- 'dropcap_number_of_lines' => [
+ 'dropcap_number_of_lines' => [
'default' => 4,
'description' => __( 'Must be an integer between 2 and 10. Actual number of lines occupied will vary based on device size.', 'apple-news' ),
'label' => __( 'Drop cap number of lines', 'apple-news' ),
'type' => 'integer',
],
- 'dropcap_number_of_raised_lines' => [
+ 'dropcap_number_of_raised_lines' => [
'default' => 0,
'label' => __( 'Drop cap number of raised lines', 'apple-news' ),
'type' => 'integer',
],
- 'dropcap_padding' => [
+ 'dropcap_padding' => [
'default' => 5,
'label' => __( 'Drop cap padding', 'apple-news' ),
'type' => 'integer',
],
- 'gallery_type' => [
+ 'gallery_type' => [
'default' => 'gallery',
'label' => __( 'Gallery type', 'apple-news' ),
'options' => [ 'gallery', 'mosaic' ],
'type' => 'select',
],
- 'header1_color' => [
+ 'header1_color' => [
'default' => '#333333',
'label' => __( 'Header 1 font color', 'apple-news' ),
'type' => 'color',
],
- 'header1_color_dark' => [
+ 'header1_color_dark' => [
'default' => '',
'label' => __( 'Header 1 font color', 'apple-news' ),
'type' => 'color',
],
- 'header1_font' => [
+ 'header1_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 1 font face', 'apple-news' ),
'type' => 'font',
],
- 'header1_line_height' => [
+ 'header1_line_height' => [
'default' => 52.0,
'label' => __( 'Header 1 line height', 'apple-news' ),
'type' => 'float',
],
- 'header1_size' => [
+ 'header1_size' => [
'default' => 48,
'label' => __( 'Header 1 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header1_tracking' => [
+ 'header1_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 1 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'header2_color' => [
+ 'header2_color' => [
'default' => '#333333',
'label' => __( 'Header 2 font color', 'apple-news' ),
'type' => 'color',
],
- 'header2_color_dark' => [
+ 'header2_color_dark' => [
'default' => '',
'label' => __( 'Header 2 font color', 'apple-news' ),
'type' => 'color',
],
- 'header2_font' => [
+ 'header2_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 2 font face', 'apple-news' ),
'type' => 'font',
],
- 'header2_line_height' => [
+ 'header2_line_height' => [
'default' => 36.0,
'label' => __( 'Header 2 line height', 'apple-news' ),
'type' => 'float',
],
- 'header2_size' => [
+ 'header2_size' => [
'default' => 32,
'label' => __( 'Header 2 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header2_tracking' => [
+ 'header2_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 2 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'header3_color' => [
+ 'header3_color' => [
'default' => '#333333',
'label' => __( 'Header 3 font color', 'apple-news' ),
'type' => 'color',
],
- 'header3_color_dark' => [
+ 'header3_color_dark' => [
'default' => '',
'label' => __( 'Header 3 font color', 'apple-news' ),
'type' => 'color',
],
- 'header3_font' => [
+ 'header3_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 3 font face', 'apple-news' ),
'type' => 'font',
],
- 'header3_line_height' => [
+ 'header3_line_height' => [
'default' => 28.0,
'label' => __( 'Header 3 line height', 'apple-news' ),
'type' => 'float',
],
- 'header3_size' => [
+ 'header3_size' => [
'default' => 24,
'label' => __( 'Header 3 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header3_tracking' => [
+ 'header3_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 3 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'header4_color' => [
+ 'header4_color' => [
'default' => '#333333',
'label' => __( 'Header 4 font color', 'apple-news' ),
'type' => 'color',
],
- 'header4_color_dark' => [
+ 'header4_color_dark' => [
'default' => '',
'label' => __( 'Header 4 font color', 'apple-news' ),
'type' => 'color',
],
- 'header4_font' => [
+ 'header4_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 4 font face', 'apple-news' ),
'type' => 'font',
],
- 'header4_line_height' => [
+ 'header4_line_height' => [
'default' => 26.0,
'label' => __( 'Header 4 line height', 'apple-news' ),
'type' => 'float',
],
- 'header4_size' => [
+ 'header4_size' => [
'default' => 21,
'label' => __( 'Header 4 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header4_tracking' => [
+ 'header4_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 4 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'header5_color' => [
+ 'header5_color' => [
'default' => '#333333',
'label' => __( 'Header 5 font color', 'apple-news' ),
'type' => 'color',
],
- 'header5_color_dark' => [
+ 'header5_color_dark' => [
'default' => '',
'label' => __( 'Header 5 font color', 'apple-news' ),
'type' => 'color',
],
- 'header5_font' => [
+ 'header5_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 5 font face', 'apple-news' ),
'type' => 'font',
],
- 'header5_line_height' => [
+ 'header5_line_height' => [
'default' => 24.0,
'label' => __( 'Header 5 line height', 'apple-news' ),
'type' => 'float',
],
- 'header5_size' => [
+ 'header5_size' => [
'default' => 18,
'label' => __( 'Header 5 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header5_tracking' => [
+ 'header5_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 5 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'header6_color' => [
+ 'header6_color' => [
'default' => '#333333',
'label' => __( 'Header 6 font color', 'apple-news' ),
'type' => 'color',
],
- 'header6_color_dark' => [
+ 'header6_color_dark' => [
'default' => '',
'label' => __( 'Header 6 font color', 'apple-news' ),
'type' => 'color',
],
- 'header6_font' => [
+ 'header6_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Header 6 font face', 'apple-news' ),
'type' => 'font',
],
- 'header6_line_height' => [
+ 'header6_line_height' => [
'default' => 22.0,
'label' => __( 'Header 6 line height', 'apple-news' ),
'type' => 'float',
],
- 'header6_size' => [
+ 'header6_size' => [
'default' => 16,
'label' => __( 'Header 6 font size', 'apple-news' ),
'type' => 'integer',
],
- 'header6_tracking' => [
+ 'header6_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Header 6 tracking', 'apple-news' ),
'type' => 'integer',
],
- 'initial_dropcap' => [
+ 'initial_dropcap' => [
'default' => 'yes',
'label' => __( 'Use initial drop cap', 'apple-news' ),
'options' => [ 'yes', 'no' ],
'type' => 'select',
],
- 'json_templates' => [
+ 'json_templates' => [
'default' => [],
'hidden' => true,
'type' => 'array',
],
- 'layout_columns' => [
+ 'layout_columns' => [
'default' => 0,
'label' => __( 'Layout columns', 'apple-news' ),
'type' => 'integer',
],
- 'layout_columns_override' => [
+ 'layout_columns_override' => [
'default' => 'no',
'label' => __( 'Override computed value with user configured value set above?', 'apple-news' ),
'options' => [ 'yes', 'no' ],
'type' => 'select',
],
- 'layout_gutter' => [
+ 'layout_gutter' => [
'default' => 20,
'label' => __( 'Layout gutter', 'apple-news' ),
'type' => 'integer',
],
- 'layout_margin' => [
+ 'layout_margin' => [
'default' => 100,
'label' => __( 'Layout margin', 'apple-news' ),
'type' => 'integer',
],
- 'layout_width' => [
+ 'layout_width' => [
'default' => 1024,
'label' => __( 'Layout width', 'apple-news' ),
'type' => 'integer',
],
- 'meta_component_order' => [
+ 'meta_component_order' => [
'default' => [ 'cover', 'slug', 'title', 'author', 'date' ],
'all_options' => [ 'cover', 'title', 'slug', 'byline', 'author', 'date', 'intro' ],
'callback' => [ get_called_class(), 'render_meta_component_order' ],
'type' => 'array',
],
- 'cite_color' => [
+ 'cite_color' => [
'default' => '#4f4f4f',
'label' => __( 'Citation font color', 'apple-news' ),
'type' => 'color',
],
- 'cite_color_dark' => [
+ 'cite_color_dark' => [
'default' => '',
'label' => __( 'Citation font color', 'apple-news' ),
'type' => 'color',
],
- 'cite_font' => [
+ 'cite_font' => [
'default' => 'AvenirNext-Italic',
'label' => __( 'Citation font face', 'apple-news' ),
'type' => 'font',
],
- 'cite_line_height' => [
+ 'cite_line_height' => [
'default' => 24.0,
'label' => __( 'Citation line height', 'apple-news' ),
'type' => 'float',
],
- 'cite_size' => [
+ 'cite_size' => [
'default' => 16,
'label' => __( 'Citation font size', 'apple-news' ),
'type' => 'integer',
],
- 'cite_tracking' => [
+ 'cite_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Citation tracking', 'apple-news' ),
'type' => 'integer',
],
- 'monospaced_color' => [
+ 'monospaced_color' => [
'default' => '#4f4f4f',
'label' => __( 'Monospaced font color', 'apple-news' ),
'type' => 'color',
],
- 'monospaced_color_dark' => [
+ 'monospaced_color_dark' => [
'default' => '',
'label' => __( 'Monospaced font color', 'apple-news' ),
'type' => 'color',
],
- 'monospaced_font' => [
+ 'monospaced_font' => [
'default' => 'Menlo-Regular',
'label' => __( 'Monospaced font face', 'apple-news' ),
'type' => 'font',
],
- 'monospaced_line_height' => [
+ 'monospaced_line_height' => [
'default' => 20.0,
'label' => __( 'Monospaced line height', 'apple-news' ),
'type' => 'float',
],
- 'monospaced_size' => [
+ 'monospaced_size' => [
'default' => 16,
'label' => __( 'Monospaced font size', 'apple-news' ),
'type' => 'integer',
],
- 'monospaced_tracking' => [
+ 'monospaced_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Monospaced tracking', 'apple-news' ),
'type' => 'integer',
],
- 'date_color' => [
+ 'date_color' => [
'default' => '#7c7c7c',
'label' => __( 'Date font color', 'apple-news' ),
'type' => 'color',
],
- 'date_color_dark' => [
+ 'date_color_dark' => [
'default' => '',
'label' => __( 'Date font color', 'apple-news' ),
'type' => 'color',
],
- 'date_font' => [
+ 'date_font' => [
'default' => 'AvenirNext-Medium',
'label' => __( 'Date font face', 'apple-news' ),
'type' => 'font',
],
- 'date_format' => [
+ 'date_format' => [
'default' => '#M j, Y | g:i A#',
'description' => __( 'Set the date format. PHP date format string is encapsulated by #. The default format is "#M j, Y | g:i A#". Note that date format updates only preview on save.', 'apple-news' ),
'label' => __( 'Date format', 'apple-news' ),
'type' => 'text',
],
- 'date_line_height' => [
+ 'date_line_height' => [
'default' => 24.0,
'label' => __( 'Date line height', 'apple-news' ),
'type' => 'float',
],
- 'date_size' => [
+ 'date_size' => [
'default' => 13,
'label' => __( 'Date font size', 'apple-news' ),
'type' => 'integer',
],
- 'date_tracking' => [
+ 'date_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Date tracking', 'apple-news' ),
'type' => 'integer',
],
- 'pullquote_border_color' => [
+ 'pullquote_border_color' => [
'default' => '#53585f',
'label' => __( 'Pull quote border color', 'apple-news' ),
'type' => 'color',
],
- 'pullquote_border_color_dark' => [
+ 'pullquote_border_color_dark' => [
'default' => '',
'label' => __( 'Pull quote border color', 'apple-news' ),
'type' => 'color',
],
- 'pullquote_border_style' => [
+ 'pullquote_border_style' => [
'default' => 'solid',
'label' => __( 'Pull quote border style', 'apple-news' ),
'options' => [ 'solid', 'dashed', 'dotted', 'none' ],
'type' => 'select',
],
- 'pullquote_border_width' => [
+ 'pullquote_border_width' => [
'default' => 3,
'label' => __( 'Pull quote border width', 'apple-news' ),
'type' => 'integer',
],
- 'pullquote_color' => [
+ 'pullquote_color' => [
'default' => '#53585f',
'label' => __( 'Pull quote color', 'apple-news' ),
'type' => 'color',
],
- 'pullquote_color_dark' => [
+ 'pullquote_color_dark' => [
'default' => '',
'label' => __( 'Pull quote color', 'apple-news' ),
'type' => 'color',
],
- 'pullquote_font' => [
+ 'pullquote_font' => [
'default' => 'AvenirNext-Bold',
'label' => __( 'Pullquote font face', 'apple-news' ),
'type' => 'font',
],
- 'pullquote_hanging_punctuation' => [
+ 'pullquote_hanging_punctuation' => [
'default' => 'no',
'description' => __( 'If set to "yes," adds smart quotes (if not already present) and sets the hanging punctuation option to true.', 'apple-news' ),
'label' => __( 'Pullquote hanging punctuation', 'apple-news' ),
'options' => [ 'no', 'yes' ],
'type' => 'select',
],
- 'pullquote_line_height' => [
+ 'pullquote_line_height' => [
'default' => 48.0,
'label' => __( 'Pull quote line height', 'apple-news' ),
'type' => 'float',
],
- 'pullquote_size' => [
+ 'pullquote_size' => [
'default' => 48,
'label' => __( 'Pull quote font size', 'apple-news' ),
'type' => 'integer',
],
- 'pullquote_tracking' => [
+ 'pullquote_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Pullquote tracking', 'apple-news' ),
'type' => 'integer',
],
- 'pullquote_transform' => [
+ 'pullquote_transform' => [
'default' => 'uppercase',
'label' => __( 'Pull quote transformation', 'apple-news' ),
'options' => [ 'none', 'uppercase' ],
'type' => 'select',
],
- 'screenshot_url' => [
+ 'recipe_background_color' => [
+ 'default' => '#e1e1e1',
+ 'label' => __( 'Recipe background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_background_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_background_color' => [
+ 'default' => '#e1e1e1',
+ 'label' => __( 'Recipe body background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_background_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe body background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_color' => [
+ 'default' => '#4f4f4f',
+ 'label' => __( 'Recipe body font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe body font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_font' => [
+ 'default' => 'AvenirNext-Regular',
+ 'label' => __( 'Recipe body font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_body_heading' => [
+ 'label' => __( 'Recipe Body', 'apple-news' ),
+ 'description' => __( 'Recipe instructions body text.', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_body_line_height' => [
+ 'default' => 24.0,
+ 'label' => __( 'Recipe body line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_body_link_color' => [
+ 'default' => '#428bca',
+ 'label' => __( 'Recipe body font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_link_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe body font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_body_size' => [
+ 'default' => 18,
+ 'label' => __( 'Recipe body font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_body_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe body tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_caption_background_color' => [
+ 'default' => '#e1e1e1',
+ 'label' => __( 'Recipe caption background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_background_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe caption background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_color' => [
+ 'default' => '#4f4f4f',
+ 'label' => __( 'Recipe caption font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe caption font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_font' => [
+ 'default' => 'AvenirNext-Regular',
+ 'label' => __( 'Recipe caption font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_caption_heading' => [
+ 'label' => __( 'Recipe Image Captions', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_caption_line_height' => [
+ 'default' => 24.0,
+ 'label' => __( 'Recipe caption line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_caption_link_color' => [
+ 'default' => '#428bca',
+ 'label' => __( 'Recipe caption font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_link_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe caption font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_caption_size' => [
+ 'default' => 18,
+ 'label' => __( 'Recipe caption font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_caption_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe caption tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_details_background_color' => [
+ 'default' => '#e1e1e1',
+ 'label' => __( 'Recipe details background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_background_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe details background color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_color' => [
+ 'default' => '#4f4f4f',
+ 'label' => __( 'Recipe details font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe details font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_font' => [
+ 'default' => 'AvenirNext-Regular',
+ 'label' => __( 'Recipe details font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_details_heading' => [
+ 'label' => __( 'Recipe Details', 'apple-news' ),
+ 'description' => __( 'Recipe yield, prep time, cook time, etc.', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_details_line_height' => [
+ 'default' => 24.0,
+ 'label' => __( 'Recipe details line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_details_link_color' => [
+ 'default' => '#428bca',
+ 'label' => __( 'Recipe details font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_link_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe details font hyperlink color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_details_size' => [
+ 'default' => 18,
+ 'label' => __( 'Recipe details font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_details_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe details tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header2_color' => [
+ 'default' => '#333333',
+ 'label' => __( 'Recipe header 2 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header2_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe header 2 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header2_font' => [
+ 'default' => 'AvenirNext-Bold',
+ 'label' => __( 'Recipe header 2 font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_header2_heading' => [
+ 'label' => __( 'Recipe Headings', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_header2_line_height' => [
+ 'default' => 28.0,
+ 'label' => __( 'Recipe header 2 line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_header2_size' => [
+ 'default' => 24,
+ 'label' => __( 'Recipe header 2 font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header2_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe header 2 tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header3_color' => [
+ 'default' => '#333333',
+ 'label' => __( 'Recipe header 3 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header3_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe header 3 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header3_font' => [
+ 'default' => 'AvenirNext-Bold',
+ 'label' => __( 'Recipe header 3 font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_header3_line_height' => [
+ 'default' => 26.0,
+ 'label' => __( 'Recipe header 3 line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_header3_size' => [
+ 'default' => 21,
+ 'label' => __( 'Recipe header 3 font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header3_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe header 3 tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header4_color' => [
+ 'default' => '#333333',
+ 'label' => __( 'Recipe header 4 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header4_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe header 4 font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_header4_font' => [
+ 'default' => 'AvenirNext-Bold',
+ 'label' => __( 'Recipe header 4 font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_header4_line_height' => [
+ 'default' => 24.0,
+ 'label' => __( 'Recipe header 4 line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_header4_size' => [
+ 'default' => 18,
+ 'label' => __( 'Recipe header 4 font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_header4_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe header 4 tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_headers_heading' => [
+ 'label' => __( 'Recipe Headings', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_title_color' => [
+ 'default' => '#333333',
+ 'label' => __( 'Recipe title font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_title_color_dark' => [
+ 'default' => '',
+ 'label' => __( 'Recipe title font color', 'apple-news' ),
+ 'type' => 'color',
+ ],
+ 'recipe_title_font' => [
+ 'default' => 'AvenirNext-Bold',
+ 'label' => __( 'Recipe title font face', 'apple-news' ),
+ 'type' => 'font',
+ ],
+ 'recipe_title_heading' => [
+ 'label' => __( 'Recipe Title', 'apple-news' ),
+ 'type' => 'group_heading',
+ ],
+ 'recipe_title_line_height' => [
+ 'default' => 36.0,
+ 'label' => __( 'Recipe title line height', 'apple-news' ),
+ 'type' => 'float',
+ ],
+ 'recipe_title_size' => [
+ 'default' => 32,
+ 'label' => __( 'Recipe title font size', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'recipe_title_tracking' => [
+ 'default' => 0,
+ 'description' => __( '(Percentage of font size)', 'apple-news' ),
+ 'label' => __( 'Recipe title tracking', 'apple-news' ),
+ 'type' => 'integer',
+ ],
+ 'screenshot_url' => [
'default' => '',
'description' => __( 'An optional URL to a screenshot of this theme. Should be a 1200x900 PNG.', 'apple-news' ),
'label' => __( 'Screenshot URL', 'apple-news' ),
'type' => 'text',
],
- 'slug_color' => [
+ 'slug_color' => [
'default' => '#4f4f4f',
'label' => __( 'Slug font color', 'apple-news' ),
'type' => 'color',
],
- 'slug_color_dark' => [
+ 'slug_color_dark' => [
'default' => '',
'label' => __( 'Slug font color', 'apple-news' ),
'type' => 'color',
],
- 'slug_font' => [
+ 'slug_font' => [
'default' => 'AvenirNext-Medium',
'label' => __( 'Slug font', 'apple-news' ),
'type' => 'font',
],
- 'slug_line_height' => [
+ 'slug_line_height' => [
'default' => 26.0,
'label' => __( 'Slug line height', 'apple-news' ),
'type' => 'float',
],
- 'slug_size' => [
+ 'slug_size' => [
'default' => 20,
'label' => __( 'Slug font size', 'apple-news' ),
'type' => 'integer',
],
- 'slug_tracking' => [
+ 'slug_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Slug tracking', 'apple-news' ),
'type' => 'integer',
],
- 'table_body_background_color' => [
+ 'table_body_background_color' => [
'default' => '#fafafa',
'label' => __( 'Table body background color', 'apple-news' ),
'type' => 'color',
],
- 'table_body_background_color_dark' => [
+ 'table_body_background_color_dark' => [
'default' => '',
'label' => __( 'Table body background color', 'apple-news' ),
'type' => 'color',
],
- 'table_body_color' => [
+ 'table_body_color' => [
'default' => '#4f4f4f',
'label' => __( 'Table body font color', 'apple-news' ),
'type' => 'color',
],
- 'table_body_color_dark' => [
+ 'table_body_color_dark' => [
'default' => '',
'label' => __( 'Table body font color', 'apple-news' ),
'type' => 'color',
],
- 'table_body_font' => [
+ 'table_body_font' => [
'default' => 'AvenirNext-Regular',
'label' => __( 'Table body font face', 'apple-news' ),
'type' => 'font',
],
- 'table_body_horizontal_alignment' => [
+ 'table_body_horizontal_alignment' => [
'default' => 'left',
'label' => __( 'Table body horizontal alignment', 'apple-news' ),
'options' => [ 'left', 'center', 'right' ],
'type' => 'select',
],
- 'table_body_line_height' => [
+ 'table_body_line_height' => [
'default' => 24.0,
'label' => __( 'Table body line height', 'apple-news' ),
'type' => 'float',
],
- 'table_body_padding' => [
+ 'table_body_padding' => [
'default' => 10.0,
'label' => __( 'Table body padding', 'apple-news' ),
'type' => 'float',
],
- 'table_body_size' => [
+ 'table_body_size' => [
'default' => 17,
'label' => __( 'Table body font size', 'apple-news' ),
'type' => 'integer',
],
- 'table_body_tracking' => [
+ 'table_body_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Table body tracking', 'apple-news' ),
'type' => 'integer',
],
- 'table_body_vertical_alignment' => [
+ 'table_body_vertical_alignment' => [
'default' => 'center',
'label' => __( 'Table body vertical alignment', 'apple-news' ),
'options' => [ 'top', 'center', 'bottom' ],
'type' => 'select',
],
- 'table_border_color' => [
+ 'table_border_color' => [
'default' => '#333333',
'label' => __( 'Table border color', 'apple-news' ),
'type' => 'color',
],
- 'table_border_color_dark' => [
+ 'table_border_color_dark' => [
'default' => '',
'label' => __( 'Table border color', 'apple-news' ),
'type' => 'color',
],
- 'table_border_style' => [
+ 'table_border_style' => [
'default' => 'solid',
'label' => __( 'Table border style', 'apple-news' ),
'options' => [ 'solid', 'dashed', 'dotted', 'none' ],
'type' => 'select',
],
- 'table_border_width' => [
+ 'table_border_width' => [
'default' => 1.0,
'label' => __( 'Table border width', 'apple-news' ),
'type' => 'float',
],
- 'table_header_background_color' => [
+ 'table_header_background_color' => [
'default' => '#e1e1e1',
'label' => __( 'Table header background color', 'apple-news' ),
'type' => 'color',
],
- 'table_header_background_color_dark' => [
+ 'table_header_background_color_dark' => [
'default' => '',
'label' => __( 'Table header background color', 'apple-news' ),
'type' => 'color',
],
- 'table_header_color' => [
+ 'table_header_color' => [
'default' => '#4f4f4f',
'label' => __( 'Table header font color', 'apple-news' ),
'type' => 'color',
],
- 'table_header_color_dark' => [
+ 'table_header_color_dark' => [
'default' => '',
'label' => __( 'Table header font color', 'apple-news' ),
'type' => 'color',
],
- 'table_header_font' => [
+ 'table_header_font' => [
'default' => 'AvenirNext-Regular',
'label' => __( 'Table header font face', 'apple-news' ),
'type' => 'font',
],
- 'table_header_horizontal_alignment' => [
+ 'table_header_horizontal_alignment' => [
'default' => 'left',
'label' => __( 'Table header horizontal alignment', 'apple-news' ),
'options' => [ 'left', 'center', 'right' ],
'type' => 'select',
],
- 'table_header_line_height' => [
+ 'table_header_line_height' => [
'default' => 24.0,
'label' => __( 'Table header line height', 'apple-news' ),
'type' => 'float',
],
- 'table_header_padding' => [
+ 'table_header_padding' => [
'default' => 10.0,
'label' => __( 'Table header padding', 'apple-news' ),
'type' => 'float',
],
- 'table_header_size' => [
+ 'table_header_size' => [
'default' => 17,
'label' => __( 'Table header font size', 'apple-news' ),
'type' => 'integer',
],
- 'table_header_tracking' => [
+ 'table_header_tracking' => [
'default' => 0,
'description' => __( '(Percentage of font size)', 'apple-news' ),
'label' => __( 'Table header tracking', 'apple-news' ),
'type' => 'integer',
],
- 'table_header_vertical_alignment' => [
+ 'table_header_vertical_alignment' => [
'default' => 'center',
'label' => __( 'Table header vertical alignment', 'apple-news' ),
'options' => [ 'top', 'center', 'bottom' ],
@@ -2533,6 +2846,73 @@ private function initialize_groups() {
'aside_border_color_dark',
],
],
+ 'recipe' => [
+ 'label' => __( 'Recipe', 'apple-news' ),
+ 'settings' => [
+ 'recipe_background_color',
+ 'recipe_title_heading',
+ 'recipe_title_font',
+ 'recipe_title_color',
+ 'recipe_title_size',
+ 'recipe_title_line_height',
+ 'recipe_title_tracking',
+ 'recipe_body_heading',
+ 'recipe_body_font',
+ 'recipe_body_size',
+ 'recipe_body_line_height',
+ 'recipe_body_tracking',
+ 'recipe_body_color',
+ 'recipe_body_link_color',
+ 'recipe_body_background_color',
+ 'recipe_headers_heading',
+ 'recipe_header2_font',
+ 'recipe_header2_color',
+ 'recipe_header2_size',
+ 'recipe_header2_line_height',
+ 'recipe_header2_tracking',
+ 'recipe_header3_font',
+ 'recipe_header3_color',
+ 'recipe_header3_size',
+ 'recipe_header3_line_height',
+ 'recipe_header3_tracking',
+ 'recipe_header4_font',
+ 'recipe_header4_color',
+ 'recipe_header4_size',
+ 'recipe_header4_line_height',
+ 'recipe_header4_tracking',
+ 'recipe_details_heading',
+ 'recipe_details_font',
+ 'recipe_details_size',
+ 'recipe_details_line_height',
+ 'recipe_details_tracking',
+ 'recipe_details_color',
+ 'recipe_details_link_color',
+ 'recipe_details_background_color',
+ 'recipe_caption_heading',
+ 'recipe_caption_font',
+ 'recipe_caption_size',
+ 'recipe_caption_line_height',
+ 'recipe_caption_tracking',
+ 'recipe_caption_color',
+ 'recipe_caption_link_color',
+ 'recipe_caption_background_color',
+ 'dark_mode_colors_heading',
+ 'recipe_background_color_dark',
+ 'recipe_title_color_dark',
+ 'recipe_body_color_dark',
+ 'recipe_body_link_color_dark',
+ 'recipe_body_background_color_dark',
+ 'recipe_header2_color_dark',
+ 'recipe_header3_color_dark',
+ 'recipe_header4_color_dark',
+ 'recipe_details_color_dark',
+ 'recipe_details_link_color_dark',
+ 'recipe_details_background_color_dark',
+ 'recipe_caption_color_dark',
+ 'recipe_caption_link_color_dark',
+ 'recipe_caption_background_color_dark',
+ ],
+ ],
'component_order' => [
'label' => __( 'Component Order', 'apple-news' ),
'settings' => [ 'meta_component_order' ],
diff --git a/includes/apple-exporter/components/class-cover.php b/includes/apple-exporter/components/class-cover.php
index 18fc0091c..7f90b4871 100644
--- a/includes/apple-exporter/components/class-cover.php
+++ b/includes/apple-exporter/components/class-cover.php
@@ -146,44 +146,65 @@ public function register_specs() {
/**
* Build the component.
*
- * @param array|string $options {
- * The options for the component. If a string is provided, assume it is a URL.
+ * @param array|string $html {
+ * The options for the component. If a string is provided, assume it is an image URL.
*
- * @type string $caption The caption for the image.
- * @type string $url The URL to the featured image.
+ * @type string $caption The caption for the media.
+ * @type string $url The URL to the media.
+ * @type string $provider The provider of the media. Can be the name of a supported provider, e.g. 'youtube'. Can
+ * be a media type, e.g. 'video' or 'image'. Can be blank, implying 'image'.
* }
* @access protected
*/
- protected function build( $options ) {
-
+ protected function build( $html ) {
$theme = Theme::get_used();
// Handle case where options is a URL.
- if ( ! is_array( $options ) ) {
- $options = [
- 'url' => $options,
+ if ( ! is_array( $html ) ) {
+ $html = [
+ 'url' => $html,
];
}
+ if ( empty( $html['provider'] ) ) {
+ $html['provider'] = 'image';
+ }
+
+ $url = match ( $html['provider'] ) {
+ 'video_url', 'embedwebvideo' => $html['url'],
+ default => $this->maybe_bundle_source( $html['url'] ),
+ };
+
// If we can't get a valid URL, bail.
- $url = $this->maybe_bundle_source( $options['url'] );
$check = trim( $url );
if ( empty( $check ) ) {
return;
}
- // Use postmeta to determine if component role should be registered as 'image' or 'photo'.
- $use_image = get_post_meta( $this->workspace->content_id, 'apple_news_use_image_component', true );
- $role = $use_image ? 'image' : 'photo';
+ switch ( $html['provider'] ) {
+ case 'video_url':
+ case 'video_id':
+ $role = 'video';
+ break;
+ case 'embedwebvideo':
+ $role = 'embedwebvideo';
+ break;
+ default:
+ // Use postmeta to determine if component role should be registered as 'image' or 'photo'.
+ $use_image = get_post_meta( $this->workspace->content_id, 'apple_news_use_image_component', true );
+ $role = $use_image ? 'image' : 'photo';
+ break;
+ }
// Fork for caption vs. not.
- if ( ! empty( $options['caption'] )
+ if (
+ ! empty( $html['caption'] )
&& true === $theme->get_value( 'cover_caption' )
) {
$this->register_json(
'jsonWithCaption',
[
- '#caption#' => $options['caption'],
+ '#caption#' => $html['caption'],
'#role#' => $role,
'#url#' => $url,
'#caption_tracking#' => intval( $theme->get_value( 'caption_tracking' ) ) / 100,
diff --git a/includes/apple-exporter/components/class-embed-web-video.php b/includes/apple-exporter/components/class-embed-web-video.php
index f75e9e7b8..9c5b06eea 100644
--- a/includes/apple-exporter/components/class-embed-web-video.php
+++ b/includes/apple-exporter/components/class-embed-web-video.php
@@ -28,6 +28,11 @@ class Embed_Web_Video extends Component {
*/
const YOUTUBE_MATCH = '#^https?://(?:www\.)?(?:youtube\.com/((watch\?v=)|(embed/))([\w\-]+)|youtu\.be/([\w\-]+))[^ ]*$#';
+ /**
+ * Regex pattern for a Dailymotion video.
+ */
+ const DAILYMOTION_MATCH = '#^https?:\/\/(?:(?:(?:www\.)?dailymotion\.com\/video\/(\w+))|(?:geo\.dailymotion\.com\/player\.html\?video=\w+)|(?:dai\.ly\/(\w+)))$#';
+
/**
* Look for node matches for this component.
*
diff --git a/includes/apple-exporter/components/class-recipe.php b/includes/apple-exporter/components/class-recipe.php
new file mode 100644
index 000000000..aa7f76538
--- /dev/null
+++ b/includes/apple-exporter/components/class-recipe.php
@@ -0,0 +1,1206 @@
+ '1.14',
+ 'preferredColorScheme' => 'dark',
+ ];
+
+ $component_style = [
+ 'backgroundColor' => '#recipe_background_color#',
+ ];
+ if ( $theme->get_value( 'recipe_background_color_dark' ) ) {
+ $component_style['conditional'] = [
+ 'backgroundColor' => '#recipe_background_color_dark#',
+ 'conditions' => $dark_mode_conditions,
+ ];
+ }
+
+ $caption_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_caption_font#',
+ 'fontSize' => '#recipe_caption_size#',
+ 'tracking' => '#recipe_caption_tracking#',
+ 'lineHeight' => '#recipe_caption_line_height#',
+ 'textColor' => '#recipe_caption_color#',
+ 'linkStyle' => [
+ 'textColor' => '#recipe_caption_link_color#',
+ ],
+ ];
+ $caption_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_caption_color_dark' ) ) {
+ $caption_textstyle_conditional['textColor'] = '#recipe_caption_color_dark#';
+ }
+ if ( $theme->get_value( 'recipe_caption_link_color_dark' ) ) {
+ $caption_textstyle_conditional['linkStyle'] = [
+ 'textColor' => '#recipe_caption_link_color_dark#',
+ ];
+ }
+ if ( $caption_textstyle_conditional ) {
+ $caption_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $caption_textstyle['conditional'] = $caption_textstyle_conditional;
+ }
+
+ $title_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_title_font#',
+ 'fontSize' => '#recipe_title_size#',
+ 'tracking' => '#recipe_title_tracking#',
+ 'lineHeight' => '#recipe_title_line_height#',
+ 'textColor' => '#recipe_title_color#',
+ ];
+ $title_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_title_color_dark' ) ) {
+ $title_textstyle_conditional['textColor'] = '#recipe_title_color_dark#';
+ }
+ if ( $title_textstyle_conditional ) {
+ $title_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $title_textstyle['conditional'] = $title_textstyle_conditional;
+ }
+
+ $body_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_body_font#',
+ 'fontSize' => '#recipe_body_size#',
+ 'tracking' => '#recipe_body_tracking#',
+ 'lineHeight' => '#recipe_body_line_height#',
+ 'textColor' => '#recipe_body_color#',
+ 'backgroundColor' => '#recipe_body_background_color#',
+ 'linkStyle' => [
+ 'textColor' => '#recipe_body_link_color#',
+ ],
+ ];
+ $body_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_body_color_dark' ) ) {
+ $body_textstyle_conditional['textColor'] = '#recipe_body_color_dark#';
+ }
+ if ( $theme->get_value( 'recipe_body_background_color_dark' ) ) {
+ $body_textstyle_conditional['backgroundColor'] = '#recipe_body_background_color_dark#';
+ }
+ if ( $theme->get_value( 'recipe_body_link_color_dark' ) ) {
+ $body_textstyle_conditional['linkStyle'] = [
+ 'textColor' => '#recipe_body_link_color_dark#',
+ ];
+ }
+ if ( $body_textstyle_conditional ) {
+ $body_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $body_textstyle['conditional'] = $body_textstyle_conditional;
+ }
+
+ $header2_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_header2_font#',
+ 'fontSize' => '#recipe_header2_size#',
+ 'tracking' => '#recipe_header2_tracking#',
+ 'lineHeight' => '#recipe_header2_line_height#',
+ 'textColor' => '#recipe_header2_color#',
+ ];
+ $header2_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_header2_color_dark' ) ) {
+ $header2_textstyle_conditional['textColor'] = '#recipe_header2_color_dark#';
+ }
+ if ( $header2_textstyle_conditional ) {
+ $header2_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $header2_textstyle['conditional'] = $header2_textstyle_conditional;
+ }
+
+ $header3_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_header3_font#',
+ 'fontSize' => '#recipe_header3_size#',
+ 'tracking' => '#recipe_header3_tracking#',
+ 'lineHeight' => '#recipe_header3_line_height#',
+ 'textColor' => '#recipe_header3_color#',
+ ];
+ $header3_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_header3_color_dark' ) ) {
+ $header3_textstyle_conditional['textColor'] = '#recipe_header3_color_dark#';
+ }
+ if ( $header3_textstyle_conditional ) {
+ $header3_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $header3_textstyle['conditional'] = $header3_textstyle_conditional;
+ }
+
+ $header4_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_header4_font#',
+ 'fontSize' => '#recipe_header4_size#',
+ 'tracking' => '#recipe_header4_tracking#',
+ 'lineHeight' => '#recipe_header4_line_height#',
+ 'textColor' => '#recipe_header4_color#',
+ ];
+ $header4_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_header4_color_dark' ) ) {
+ $header4_textstyle_conditional['textColor'] = '#recipe_header4_color_dark#';
+ }
+ if ( $header4_textstyle_conditional ) {
+ $header4_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $header4_textstyle['conditional'] = $header4_textstyle_conditional;
+ }
+
+ $details_textstyle = [
+ 'textAlignment' => 'left',
+ 'fontName' => '#recipe_details_font#',
+ 'fontSize' => '#recipe_details_size#',
+ 'tracking' => '#recipe_details_tracking#',
+ 'lineHeight' => '#recipe_details_line_height#',
+ 'textColor' => '#recipe_details_color#',
+ 'linkStyle' => [
+ 'textColor' => '#recipe_details_link_color#',
+ ],
+ ];
+ $details_textstyle_conditional = [];
+ if ( $theme->get_value( 'recipe_details_color_dark' ) ) {
+ $details_textstyle_conditional['textColor'] = '#recipe_details_color_dark#';
+ }
+ if ( $theme->get_value( 'recipe_details_link_color_dark' ) ) {
+ $details_textstyle_conditional['linkStyle'] = [
+ 'textColor' => '#recipe_details_link_color_dark#',
+ ];
+ }
+ if ( $details_textstyle_conditional ) {
+ $details_textstyle_conditional['conditions'] = $dark_mode_conditions;
+ $details_textstyle['conditional'] = $details_textstyle_conditional;
+ }
+
+ $this->register_spec(
+ 'json',
+ __( 'JSON', 'apple-news' ),
+ [
+ 'role' => 'recipe',
+ 'URL' => '#url#',
+ 'layout' => [
+ 'margin' => [
+ 'top' => 5,
+ 'bottom' => 5,
+ ],
+ ],
+ 'style' => $component_style,
+ 'components' => [
+ [
+ 'role' => 'photo',
+ 'layout' => [
+ 'margin' => [
+ 'top' => 0,
+ 'bottom' => 12,
+ ],
+ ],
+ 'URL' => '#recipe_photo_url#',
+ 'caption' => [
+ 'text' => '#recipe_photo_caption#',
+ 'format' => 'html',
+ 'textStyle' => $caption_textstyle,
+ ],
+ ],
+ [
+ 'role' => 'container',
+ 'layout' => [
+ 'padding' => [
+ 'left' => 12,
+ 'right' => 12,
+ ],
+ ],
+ 'components' => [
+ [
+ 'role' => 'title',
+ 'layout' => [
+ 'margin' => [
+ 'top' => 8,
+ 'bottom' => 12,
+ ],
+ ],
+ 'textStyle' => $title_textstyle,
+ 'text' => '#recipe_title#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'heading2',
+ 'layout' => 'recipe-header2-layout',
+ 'textStyle' => 'recipe-header2-style',
+ 'text' => __( 'Recipe Information', 'apple-news' ),
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'container',
+ 'layout' => [
+ 'columnSpan' => 4,
+ 'columnStart' => 0,
+ 'margin' => [
+ 'bottom' => 10,
+ 'top' => 24,
+ ],
+ 'padding' => [
+ 'left' => 0,
+ 'right' => 0,
+ 'top' => 10,
+ 'bottom' => 0,
+ ],
+ ],
+ 'contentDisplay' => [
+ 'type' => 'collection',
+ 'gutter' => '20',
+ 'rowSpacing' => '10',
+ 'alignment' => 'left',
+ 'minimumWidth' => 200,
+ ],
+ 'components' => [
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-details-layout',
+ 'textStyle' => 'recipe-details-style',
+ 'text' => '#recipe_yield#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-details-layout',
+ 'textStyle' => 'recipe-details-style',
+ 'text' => '#recipe_prep_time#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-details-layout',
+ 'textStyle' => 'recipe-details-style',
+ 'text' => '#recipe_cook_time#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-details-layout',
+ 'textStyle' => 'recipe-details-style',
+ 'text' => '#recipe_total_time#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-details-layout',
+ 'textStyle' => 'recipe-details-style',
+ 'text' => '#recipe_calories_per_serving#',
+ 'format' => 'html',
+ ],
+ ],
+ ],
+ [
+ 'role' => 'divider',
+ 'style' => [
+ 'border' => [
+ 'all' => [
+ 'width' => 1,
+ 'style' => 'solid',
+ 'color' => '#000',
+ ],
+ 'left' => false,
+ 'right' => false,
+ 'top' => false,
+ ],
+ 'layout' => [
+ 'margin' => [
+ 'top' => 10,
+ 'bottom' => 30,
+ ],
+ ],
+ ],
+ ],
+ [
+ 'role' => 'section',
+ 'components' => [
+ [
+ 'role' => 'container',
+ 'components' => [
+ [
+ 'role' => 'container',
+ 'layout' => [
+ 'margin' => [
+ 'bottom' => 20,
+ 'top' => 20,
+ ],
+ ],
+ 'components' => [
+ [
+ 'role' => 'heading2',
+ 'layout' => 'recipe-header2-layout',
+ 'textStyle' => 'recipe-header2-style',
+ 'text' => __( 'Ingredients', 'apple-news' ),
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-body-layout',
+ 'textStyle' => 'recipe-body-style',
+ 'text' => '#recipe_ingredients#',
+ 'format' => 'html',
+ ],
+ ],
+ ],
+ [
+ 'role' => 'container',
+ 'components' => [
+ [
+ 'role' => 'heading2',
+ 'layout' => 'recipe-header2-layout',
+ 'textStyle' => 'recipe-header2-style',
+ 'text' => __( 'Directions', 'apple-news' ),
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'container',
+ 'components' => '#recipe_instructions#',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ );
+
+ $this->register_spec(
+ 'recipe-body-layout',
+ __( 'Recipe Body Layout', 'apple-news' ),
+ [
+ 'margin' => [
+ 'bottom' => 10,
+ ],
+ ],
+ );
+ $this->register_spec(
+ 'recipe-body-style',
+ __( 'Recipe Body Text Style', 'apple-news' ),
+ $body_textstyle,
+ );
+
+ $this->register_spec(
+ 'recipe-header2-layout',
+ __( 'Recipe Header 2 Layout', 'apple-news' ),
+ [
+ 'margin' => [
+ 'top' => 6,
+ 'bottom' => 6,
+ ],
+ ],
+ );
+ $this->register_spec(
+ 'recipe-header2-style',
+ __( 'Recipe Header 2 Text Style', 'apple-news' ),
+ $header2_textstyle,
+ );
+
+ $this->register_spec(
+ 'recipe-header3-layout',
+ __( 'Recipe Header 3 Layout', 'apple-news' ),
+ [
+ 'margin' => [
+ 'top' => 6,
+ 'bottom' => 6,
+ ],
+ ],
+ );
+ $this->register_spec(
+ 'recipe-header3-style',
+ __( 'Recipe Header 3 Text Style', 'apple-news' ),
+ $header3_textstyle,
+ );
+
+ $this->register_spec(
+ 'recipe-header4-layout',
+ __( 'Recipe Header 4 Layout', 'apple-news' ),
+ [
+ 'margin' => [
+ 'top' => 6,
+ 'bottom' => 6,
+ ],
+ ],
+ );
+ $this->register_spec(
+ 'recipe-header4-style',
+ __( 'Recipe Header 4 Text Style', 'apple-news' ),
+ $header4_textstyle,
+ );
+
+ $this->register_spec(
+ 'recipe-details-layout',
+ __( 'Recipe Details Layout', 'apple-news' ),
+ [
+ 'margin' => [
+ 'bottom' => 10,
+ ],
+ ],
+ );
+ $this->register_spec(
+ 'recipe-details-style',
+ __( 'Recipe Details Text Style', 'apple-news' ),
+ $details_textstyle,
+ );
+
+ $this->register_spec(
+ 'recipe-section-json',
+ __( 'JSON for a Section of Recipe Instructions', 'apple-news' ),
+ [
+ 'role' => 'container',
+ 'components' => [
+ [
+ 'role' => 'heading3',
+ 'layout' => 'recipe-header3-layout',
+ 'textStyle' => 'recipe-header3-style',
+ 'text' => '#recipe_section_name#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'container',
+ 'components' => '#components#',
+ ],
+ ],
+ ],
+ );
+
+ $this->register_spec(
+ 'recipe-section-step-json',
+ __( 'JSON for a Step Within a Section of Recipe Instructions', 'apple-news' ),
+ [
+ 'role' => 'container',
+ 'components' => [
+ [
+ 'role' => 'heading4',
+ 'layout' => 'recipe-header4-layout',
+ 'textStyle' => 'recipe-header4-style',
+ 'text' => '#recipe_step_name#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'photo',
+ 'layout' => [
+ 'margin' => [
+ 'top' => 6,
+ 'bottom' => 12,
+ ],
+ ],
+ 'URL' => '#recipe_step_photo_url#',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-body-layout',
+ 'textStyle' => 'recipe-body-style',
+ 'text' => '#recipe_step_text#',
+ 'format' => 'html',
+ ],
+ ],
+ ],
+ );
+
+ $this->register_spec(
+ 'recipe-step-json',
+ __( 'JSON for a Standalone Step in Recipe Instructions', 'apple-news' ),
+ [
+ 'role' => 'container',
+ 'components' => [
+ [
+ 'role' => 'heading3',
+ 'layout' => 'recipe-header3-layout',
+ 'textStyle' => 'recipe-header3-style',
+ 'text' => '#recipe_step_name#',
+ 'format' => 'html',
+ ],
+ [
+ 'role' => 'photo',
+ 'layout' => [
+ 'margin' => [
+ 'top' => 6,
+ 'bottom' => 12,
+ ],
+ ],
+ 'URL' => '#recipe_step_photo_url#',
+ ],
+ [
+ 'role' => 'body',
+ 'layout' => 'recipe-body-layout',
+ 'textStyle' => 'recipe-body-style',
+ 'text' => '#recipe_step_text#',
+ 'format' => 'html',
+ ],
+ ],
+ ],
+ );
+
+ $this->register_spec(
+ 'wrapper-only-json',
+ __( 'JSON for a Wrapper Only', 'apple-news' ),
+ [
+ 'role' => 'recipe',
+ 'URL' => '#url#',
+ 'components' => '#components#',
+ ],
+ );
+ }
+
+ /**
+ * Build the component.
+ *
+ * @param string $html The HTML to parse into text for processing.
+ */
+ protected function build( $html ) {
+ $theme = \Apple_Exporter\Theme::get_used();
+ $schema = $this->matching_schema( $html );
+
+ if ( ! $schema ) {
+ return;
+ }
+
+ if ( $this->get_setting( 'recipe_component_use_schema' ) === 'no' ) {
+ $export = new Exporter_Content( $this->workspace->content_id, '', $html );
+ $components = [];
+
+ foreach ( $export->nodes() as $node ) {
+ // $node is the original recipe element. We want the HTML within that element, otherwise we'd match the recipe node again.
+ if ( $node->hasChildNodes() ) {
+ foreach ( $node->childNodes as $child ) {
+ $components = array_merge( $components, Component_Factory::get_components_from_node( $child, $this ) );
+ }
+ }
+ }
+
+ $this->register_json(
+ 'wrapper-only-json',
+ [
+ '#url#' => $this->value_for_token( '#url#', $schema ),
+ '#components#' => array_map( fn ( Component $component ) => $component->to_array(), $components ),
+ ],
+ );
+
+ return;
+ }
+
+ // Set up values for the tokens.
+ $recipe_photo_url = $this->value_for_token( '#recipe_photo_url#', $schema );
+ if ( $recipe_photo_url ) {
+ $recipe_photo_url = $this->maybe_bundle_source( $recipe_photo_url );
+ }
+
+ $recipe_instructions = $this->value_for_token( '#recipe_instructions#', $schema );
+ if ( is_array( $recipe_instructions ) ) {
+ $recipe_instructions = $this->without_objects_containing_tokens( $recipe_instructions, $this->get_spec( 'recipe-step-json' ) );
+ }
+
+ $this->register_json(
+ 'json',
+ [
+ '#url#' => $this->value_for_token( '#url#', $schema ),
+ '#recipe_photo_url#' => $recipe_photo_url,
+ '#recipe_photo_caption#' => $this->value_for_token( '#recipe_photo_caption#', $schema ),
+ '#recipe_title#' => $this->value_for_token( '#recipe_title#', $schema ),
+ '#recipe_yield#' => $this->value_for_token( '#recipe_yield#', $schema ),
+ '#recipe_prep_time#' => $this->value_for_token( '#recipe_prep_time#', $schema ),
+ '#recipe_cook_time#' => $this->value_for_token( '#recipe_cook_time#', $schema ),
+ '#recipe_total_time#' => $this->value_for_token( '#recipe_total_time#', $schema ),
+ '#recipe_calories_per_serving#' => $this->value_for_token( '#recipe_calories_per_serving#', $schema ),
+ '#recipe_ingredients#' => $this->value_for_token( '#recipe_ingredients#', $schema ),
+ '#recipe_instructions#' => $recipe_instructions,
+ ],
+ );
+
+ $this->register_layout(
+ 'recipe-body-layout',
+ 'recipe-body-layout',
+ );
+ $this->register_style(
+ 'recipe-body-style',
+ 'recipe-body-style',
+ [
+ '#recipe_body_font#' => $theme->get_value( 'recipe_body_font' ),
+ '#recipe_body_size#' => (int) $theme->get_value( 'recipe_body_size' ),
+ '#recipe_body_line_height#' => (int) $theme->get_value( 'recipe_body_line_height' ),
+ '#recipe_body_tracking#' => (int) $theme->get_value( 'recipe_body_tracking' ) / 100,
+ '#recipe_body_color#' => $theme->get_value( 'recipe_body_color' ),
+ '#recipe_body_color_dark#' => $theme->get_value( 'recipe_body_color_dark' ),
+ '#recipe_body_link_color#' => $theme->get_value( 'recipe_body_link_color' ),
+ '#recipe_body_link_color_dark#' => $theme->get_value( 'recipe_body_link_color_dark' ),
+ '#recipe_body_background_color#' => $theme->get_value( 'recipe_body_background_color' ),
+ '#recipe_body_background_color_dark#' => $theme->get_value( 'recipe_body_background_color_dark' ),
+ ],
+ 'textStyle',
+ );
+
+ $this->register_layout(
+ 'recipe-header2-layout',
+ 'recipe-header2-layout',
+ );
+ $this->register_style(
+ 'recipe-header2-style',
+ 'recipe-header2-style',
+ [
+ '#recipe_header2_font#' => $theme->get_value( 'recipe_header2_font' ),
+ '#recipe_header2_size#' => (int) $theme->get_value( 'recipe_header2_size' ),
+ '#recipe_header2_line_height#' => (int) $theme->get_value( 'recipe_header2_line_height' ),
+ '#recipe_header2_tracking#' => (int) $theme->get_value( 'recipe_header2_tracking' ) / 100,
+ '#recipe_header2_color#' => $theme->get_value( 'recipe_header2_color' ),
+ '#recipe_header2_color_dark#' => $theme->get_value( 'recipe_header2_color_dark' ),
+ ],
+ 'textStyle',
+ );
+
+ $this->register_layout(
+ 'recipe-header3-layout',
+ 'recipe-header3-layout',
+ );
+ $this->register_style(
+ 'recipe-header3-style',
+ 'recipe-header3-style',
+ [
+ '#recipe_header3_font#' => $theme->get_value( 'recipe_header3_font' ),
+ '#recipe_header3_size#' => (int) $theme->get_value( 'recipe_header3_size' ),
+ '#recipe_header3_line_height#' => (int) $theme->get_value( 'recipe_header3_line_height' ),
+ '#recipe_header3_tracking#' => (int) $theme->get_value( 'recipe_header3_tracking' ) / 100,
+ '#recipe_header3_color#' => $theme->get_value( 'recipe_header3_color' ),
+ '#recipe_header3_color_dark#' => $theme->get_value( 'recipe_header3_color_dark' ),
+ ],
+ 'textStyle',
+ );
+
+ $this->register_layout(
+ 'recipe-header4-layout',
+ 'recipe-header4-layout',
+ );
+ $this->register_style(
+ 'recipe-header4-style',
+ 'recipe-header4-style',
+ [
+ '#recipe_header4_font#' => $theme->get_value( 'recipe_header4_font' ),
+ '#recipe_header4_size#' => (int) $theme->get_value( 'recipe_header4_size' ),
+ '#recipe_header4_line_height#' => (int) $theme->get_value( 'recipe_header4_line_height' ),
+ '#recipe_header4_tracking#' => (int) $theme->get_value( 'recipe_header4_tracking' ) / 100,
+ '#recipe_header4_color#' => $theme->get_value( 'recipe_header4_color' ),
+ '#recipe_header4_color_dark#' => $theme->get_value( 'recipe_header4_color_dark' ),
+ ],
+ 'textStyle',
+ );
+
+ $this->register_layout(
+ 'recipe-details-layout',
+ 'recipe-details-layout',
+ );
+ $this->register_style(
+ 'recipe-details-style',
+ 'recipe-details-style',
+ [
+ '#recipe_details_font#' => $theme->get_value( 'recipe_details_font' ),
+ '#recipe_details_size#' => (int) $theme->get_value( 'recipe_details_size' ),
+ '#recipe_details_line_height#' => (int) $theme->get_value( 'recipe_details_line_height' ),
+ '#recipe_details_tracking#' => (int) $theme->get_value( 'recipe_details_tracking' ) / 100,
+ '#recipe_details_color#' => $theme->get_value( 'recipe_details_color' ),
+ '#recipe_details_color_dark#' => $theme->get_value( 'recipe_details_color_dark' ),
+ '#recipe_details_link_color#' => $theme->get_value( 'recipe_details_link_color' ),
+ '#recipe_details_link_color_dark#' => $theme->get_value( 'recipe_details_link_color_dark' ),
+ '#recipe_details_background_color#' => $theme->get_value( 'recipe_details_background_color' ),
+ '#recipe_details_background_color_dark#' => $theme->get_value( 'recipe_details_background_color_dark' ),
+ ],
+ 'textStyle',
+ );
+ }
+
+ /**
+ * Set the JSON for the component.
+ *
+ * @param string $spec_name The spec to use for defining the JSON.
+ * @param array $values Values to substitute for placeholders in the spec.
+ */
+ protected function register_json( $spec_name, $values = [] ) {
+ if ( 'json' === $spec_name ) {
+ $values = $this->prepare_tokens( $values );
+ }
+
+ // Go through normal token replacement.
+ parent::register_json( $spec_name, $values );
+
+ if ( 'json' === $spec_name ) {
+ if ( is_array( $this->json ) ) {
+ $spec = $this->get_spec( $spec_name );
+
+ if ( $spec instanceof Component_Spec ) {
+ // Update the JSON to remove objects with recipe tokens that weren't replaced.
+ $this->json = $this->without_objects_containing_tokens( $this->json, $spec );
+ }
+ }
+ }
+ }
+
+ /**
+ * Find JSON-LD Recipe items for the given recipe HTML.
+ *
+ * @param string $recipe The recipe HTML found in the matching node for this component instance.
+ * @return array|null The matching schema, or null if none found.
+ */
+ private function matching_schema( $recipe ): ?array {
+ // First, check the post content for a JSON-LD Recipe.
+ $post = get_post( $this->workspace->content_id );
+
+ /** This filter is documented in admin/apple-actions/index/class-export.php */
+ $post_content = apply_filters( 'apple_news_exporter_content_pre', $post->post_content, $post->ID );
+
+ /** This filter is documented in wp-includes/post-template.php */
+ $post_content = apply_filters( 'the_content', $post_content ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
+
+ $items = self::recipe_items( $post_content, 'body' );
+
+ foreach ( $items as $item ) {
+ if ( self::is_recipe_item_for_recipe_html( $item, $recipe ) ) {
+ return $item;
+ }
+ }
+
+ // Second, try to find schema in the HEAD of the post on the frontend.
+ // Could check BODY as well, but the post content is already being scanned. That might be enough.
+
+ /**
+ * Filters the URL for an article that will be fetched and searched for JSON-LD Recipe items.
+ *
+ * @param string $permalink The URL to fetch.
+ * @param int $content_id The ID of the content being processed.
+ */
+ $permalink = apply_filters( 'apple_news_recipe_schema_permalink', get_permalink( $this->workspace->content_id ), $this->workspace->content_id );
+
+ if ( $permalink ) {
+ $resp = wp_remote_get( $permalink ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
+ $code = wp_remote_retrieve_response_code( $resp );
+ $html = ( $code >= 200 && $code < 300 ) ? wp_remote_retrieve_body( $resp ) : '';
+
+ $items = self::recipe_items( $html, 'head' );
+
+ foreach ( $items as $item ) {
+ if ( self::is_recipe_item_for_recipe_html( $item, $recipe ) ) {
+ return $item;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * All JSON-LD Recipe items in the given HTML.
+ *
+ * @param string $document The document to search for recipe items.
+ * @param string $tag_name The tag name to search for recipe items.
+ * @return array The recipe items found in the document.
+ */
+ private static function recipe_items( string $document, string $tag_name ): array {
+ $out = [];
+
+ // Do some naive checks before the intensive processing to see if the HTML contains JSON-LD and a Recipe.
+ if ( str_contains( $document, 'application/ld+json' ) && str_contains( $document, '"Recipe"' ) ) {
+ $dom = new \DOMDocument();
+ libxml_use_internal_errors( true );
+ $dom->loadHTML( '' . $document );
+ libxml_clear_errors();
+
+ $body = $dom->getElementsByTagName( $tag_name )->item( 0 );
+ $nodes = $body ? $body->childNodes : new DOMNodeList();
+ $out = self::recipe_items_in_nodes( [], $nodes );
+ }
+
+ return $out;
+ }
+
+ /**
+ * Recursively search for JSON-LD Recipe items in the given nodes.
+ *
+ * @param array $carry The array to accumulate recipe items.
+ * @param DOMNodeList $nodes The nodes to search for recipe items.
+ * @return array The recipe items found in the nodes.
+ */
+ private static function recipe_items_in_nodes( array $carry, DOMNodeList $nodes ): array {
+ foreach ( $nodes as $node ) {
+ if ( 'script' === $node->nodeName && 'application/ld+json' === $node->getAttribute( 'type' ) ) {
+ try {
+ $json = json_decode( $node->textContent, true, 512, JSON_THROW_ON_ERROR ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
+
+ if ( isset( $json['@type'] ) && 'Recipe' === $json['@type'] ) {
+ $carry[] = $json;
+ }
+ } catch ( \JsonException $e ) {
+ // Do nothing.
+ unset( $e );
+ }
+ }
+
+ if ( $node->hasChildNodes() ) {
+ $carry = self::recipe_items_in_nodes( $carry, $node->childNodes );
+ }
+ }
+
+ return $carry;
+ }
+
+ /**
+ * Best-guess if the given schema item is for the given recipe HTML.
+ *
+ * @param array $schema The schema item to check.
+ * @param string $html The recipe HTML to check.
+ * @return bool True if the schema item is for the recipe HTML, false otherwise.
+ */
+ private static function is_recipe_item_for_recipe_html( array $schema, string $html ) {
+ return (
+ isset( $schema['name'] )
+ && is_string( $schema['name'] )
+ && strlen( $schema['name'] ) > 0
+ && str_contains( $html, $schema['name'] )
+ );
+ }
+
+ /**
+ * Value for the given Apple News theme token in the given JSON-LD Recipe item.
+ *
+ * @param string $token The token to replace.
+ * @param array $schema The item to search.
+ * @return string|array|null
+ */
+ private function value_for_token( string $token, array $schema ) {
+ switch ( $token ) {
+ case '#url#':
+ return $schema['url'] ?? get_permalink( $this->workspace->content_id );
+
+ case '#recipe_photo_url#':
+ // The `image` property appears to be predominant in actual uses, e.g. the example at https://schema.org/Recipe. Can be an ImageObject or URL.
+ $url = $schema['image']['contentUrl'] ?? $schema['image']['url'] ?? $schema['image'][0] ?? $schema['image'] ?? null;
+
+ return is_string( $url ) ? $url : null;
+
+ case '#recipe_photo_caption#':
+ // Available when the `image` is an ImageObject. Can be MediaObject or Text, but MediaObject use case ("closed caption, subtitles etc.") doesn't apply here.
+ return $schema['image']['caption'] ?? null;
+
+ case '#recipe_title#':
+ // Text type.
+ return $schema['name'] ?? null;
+
+ case '#recipe_yield#':
+ // Can be QuantitativeValue or Text. It isn't clear from the examples how the properties of QuantitativeValue would be compiled into a final value, so not yet implementing.
+ if ( ! isset( $schema['recipeYield'] ) || ! is_string( $schema['recipeYield'] ) ) {
+ return null;
+ }
+
+ return sprintf(
+ '%s %s
',
+ esc_html__( 'Yields:', 'apple-news' ),
+ esc_html( $schema['recipeYield'] )
+ );
+
+ case '#recipe_prep_time#':
+ // ISO 8601 duration format.
+ if ( ! isset( $schema['prepTime'] ) || ! is_string( $schema['prepTime'] ) ) {
+ return null;
+ }
+
+ try {
+ $di = new \DateInterval( $schema['prepTime'] );
+ } catch ( \Exception $e ) {
+ return null;
+ }
+
+ return sprintf(
+ '%s %s
',
+ esc_html__( 'Prep Time:', 'apple-news' ),
+ esc_html( $this->duration( $di ) )
+ );
+
+ case '#recipe_cook_time#':
+ // ISO 8601 duration format.
+ if ( ! isset( $schema['cookTime'] ) || ! is_string( $schema['cookTime'] ) ) {
+ return null;
+ }
+
+ try {
+ $di = new \DateInterval( $schema['cookTime'] );
+ } catch ( \Exception $e ) {
+ return null;
+ }
+
+ return sprintf(
+ '%s %s
',
+ esc_html__( 'Cook Time:', 'apple-news' ),
+ esc_html( $this->duration( $di ) )
+ );
+
+ case '#recipe_total_time#':
+ // ISO 8601 duration format.
+ if ( ! isset( $schema['totalTime'] ) || ! is_string( $schema['totalTime'] ) ) {
+ return null;
+ }
+
+ try {
+ $di = new \DateInterval( $schema['totalTime'] );
+ } catch ( \Exception $e ) {
+ return null;
+ }
+
+ return sprintf(
+ '%s %s
',
+ esc_html__( 'Total Time:', 'apple-news' ),
+ esc_html( $this->duration( $di ) )
+ );
+
+ case '#recipe_calories_per_serving#':
+ // NutritionInformation type.
+ if ( ! isset( $schema['nutrition']['calories'] ) || ! is_string( $schema['nutrition']['calories'] ) ) {
+ return null;
+ }
+
+ return sprintf(
+ '%s %s
',
+ esc_html__( 'Calories/Serving:', 'apple-news' ),
+ // `calories` is of type Energy, which is of the form ' '.
+ esc_html( preg_replace( '/\D/', '', $schema['nutrition']['calories'] ) )
+ );
+
+ case '#recipe_ingredients#':
+ // Of type Text. All examples show an array of strings.
+ if ( ! isset( $schema['recipeIngredient'] ) ) {
+ return null;
+ }
+
+ $out = '';
+ $out .= is_array( $schema['recipeIngredient'] )
+ ? implode( '', array_map( fn ( $ingredient ) => '' . esc_html( $ingredient ) . ' ', $schema['recipeIngredient'] ) )
+ : '' . esc_html( $schema['recipeIngredient'] ) . ' ';
+ $out .= ' ';
+
+ return $out;
+
+ case '#recipe_instructions#':
+ if ( ! isset( $schema['recipeInstructions'] ) ) {
+ return null;
+ }
+
+ $step_spec = $this->get_spec( 'recipe-step-json' );
+
+ if ( ! $step_spec ) {
+ return null;
+ }
+
+ // CreativeWork or ItemList or Text.
+ $instructions = $schema['recipeInstructions'];
+
+ if ( is_string( $instructions ) ) {
+ $instructions = [ $instructions ];
+ }
+
+ if ( wp_is_numeric_array( $instructions ) && count( $instructions ) > 0 ) {
+ if ( is_string( $instructions[0] ) ) {
+ return array_reduce(
+ $instructions,
+ function ( $carry, $value ) use ( $step_spec ) {
+ $carry[] = $step_spec->substitute_values(
+ $this->prepare_tokens(
+ [
+ '#recipe_step_name#' => null,
+ '#recipe_step_photo_url#' => null,
+ '#recipe_step_text#' => $value,
+ ],
+ ),
+ (int) $this->workspace->content_id,
+ );
+
+ return $carry;
+ },
+ [],
+ );
+ }
+
+ if ( is_array( $instructions[0] ) ) {
+ return $this->how_to_steps( [], $instructions, $step_spec );
+ }
+ }
+
+ return null;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Duration in readable format for recipe component.
+ *
+ * @param \DateInterval $di The duration to format.
+ * @return string The formatted duration.
+ */
+ private function duration( \DateInterval $di ) {
+ $from = new \DateTimeImmutable();
+ $diff = $from->add( $di )->getTimestamp() - $from->getTimestamp();
+
+ $minutes = $diff % HOUR_IN_SECONDS;
+ $hours = $diff - $minutes;
+
+ $out = [];
+
+ if ( $hours > 0 ) {
+ /* translators: %d: number of hours */
+ $out[] = sprintf( __( '%d hr', 'apple-news' ), $hours / HOUR_IN_SECONDS );
+ }
+
+ if ( $minutes > 0 ) {
+ /* translators: %d: number of minutes */
+ $out[] = sprintf( __( '%d mins', 'apple-news' ), (int) round( $minutes / MINUTE_IN_SECONDS ) );
+ }
+
+ return implode( ' ', $out );
+ }
+
+ /**
+ * Recursively process HowToStep objects, possibly in HowToSection objects.
+ *
+ * @param array $carry The array to accumulate the steps.
+ * @param mixed $schema The schema to process.
+ * @param Component_Spec $step_spec The spec to use for the steps.
+ * @return array The steps in the schema.
+ */
+ private function how_to_steps( array $carry, mixed $schema, Component_Spec $step_spec ) {
+ foreach ( $schema as $item ) {
+ if ( isset( $item['@type'] ) ) {
+ if ( 'HowToSection' === $item['@type'] ) {
+ // Steps in the section are contained here.
+ if ( isset( $item['itemListElement'] ) && is_array( $item['itemListElement'] ) ) {
+ $section_spec = $this->get_spec( 'recipe-section-json' );
+ $section_step_spec = $this->get_spec( 'recipe-section-step-json' );
+
+ if ( $section_spec instanceof Component_Spec && $section_step_spec instanceof Component_Spec ) {
+ $carry[] = $section_spec->substitute_values(
+ $this->prepare_tokens(
+ [
+ '#recipe_section_name#' => $item['name'] ?? null,
+ '#components#' => $this->how_to_steps( [], $item['itemListElement'], $section_step_spec ),
+ ],
+ ),
+ (int) $this->workspace->content_id
+ );
+ }
+ }
+ }
+
+ if ( 'HowToStep' === $item['@type'] ) {
+ $name = $item['name'] ?? null;
+ $text = $item['text'] ?? null;
+ $image = $item['image']['contentUrl'] ?? $item['image']['url'] ?? $item['image'][0] ?? $item['image'] ?? null;
+
+ /*
+ * We've seen examples of recipe plugins that generate JSON with identical strings for
+ * name and text in some scenarios. In that case, use just body text and not a heading.
+ */
+ if ( $name === $text ) {
+ $name = null;
+ }
+
+ $carry[] = $step_spec->substitute_values(
+ $this->prepare_tokens(
+ [
+ '#recipe_step_name#' => $name,
+ '#recipe_step_photo_url#' => is_string( $image ) && $image ? $this->maybe_bundle_source( $image ) : null,
+ '#recipe_step_text#' => $text,
+ ],
+ ),
+ (int) $this->workspace->content_id,
+ );
+ }
+ }
+ }
+
+ return $carry;
+ }
+
+ /**
+ * Mark recipe-specific tokens that had no value so that they can be detected after value substitution and removed if unused.
+ *
+ * @param array $tokens The tokens to prepare.
+ * @return array The prepared tokens.
+ */
+ private function prepare_tokens( array $tokens ) {
+ foreach ( $tokens as $key => $value ) {
+ if ( 0 === strpos( $key, '#recipe_' ) && null === $value ) {
+ $tokens[ $key ] = $key;
+ }
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Remove objects still containing tokens from the given JSON.
+ *
+ * @param array $json The JSON to process.
+ * @param Component_Spec $spec The spec to use for checking tokens.
+ * @return array|null The JSON with objects containing tokens removed, or null if the entire JSON should be removed.
+ */
+ private function without_objects_containing_tokens( array $json, Component_Spec $spec ) {
+ $is_numeric = wp_is_numeric_array( $json );
+
+ foreach ( $json as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $check = $this->without_objects_containing_tokens( $value, $spec );
+
+ if ( null === $check ) {
+ unset( $json[ $key ] );
+ } else {
+ $json[ $key ] = $check;
+ }
+ }
+
+ if ( is_string( $value ) && $spec->is_token( $value ) ) {
+ return null;
+ }
+ }
+
+ if ( $is_numeric ) {
+ $json = array_values( $json );
+ }
+
+ return $json;
+ }
+}
diff --git a/includes/apple-push-api/request/class-request.php b/includes/apple-push-api/request/class-request.php
index ee44a57db..cf529209c 100644
--- a/includes/apple-push-api/request/class-request.php
+++ b/includes/apple-push-api/request/class-request.php
@@ -371,6 +371,10 @@ private function request( $verb, $url, $data = [] ) {
$args['timeout'] = 30; // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
}
+ if ( 'DELETE' === $verb ) {
+ $args['timeout'] = 5; // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
+ }
+
/**
* Allow filtering of the default arguments for the request.
*
@@ -411,8 +415,8 @@ private function request( $verb, $url, $data = [] ) {
}
}
- // NULL is a valid response for DELETE.
- if ( 'DELETE' === $verb && is_null( $response ) ) {
+ // Successful DELETE requests have no response body.
+ if ( 'DELETE' === $verb && 204 === wp_remote_retrieve_response_code( $response ) ) {
return null;
}
diff --git a/includes/class-apple-news.php b/includes/class-apple-news.php
index 7ff604585..7036bf480 100644
--- a/includes/class-apple-news.php
+++ b/includes/class-apple-news.php
@@ -50,7 +50,7 @@ class Apple_News {
* @var string
* @access public
*/
- public static string $version = '2.6.3';
+ public static string $version = '2.7.0';
/**
* Link to support for the plugin on WordPress.org.
@@ -378,8 +378,8 @@ public function __construct() {
);
add_action(
- 'init',
- [ $this, 'action_init' ]
+ 'admin_init',
+ [ $this, 'action_admin_init' ]
);
add_filter(
@@ -423,7 +423,7 @@ public function action_admin_enqueue_scripts( $hook ) {
// Enqueue the script for cover images in the classic editor.
wp_enqueue_script(
$this->plugin_slug . '_cover_image_js',
- plugin_dir_url( __FILE__ ) . '../assets/js/cover-image.js',
+ plugin_dir_url( __FILE__ ) . '../assets/js/cover-media.js',
[ 'jquery' ],
self::$version,
true
@@ -476,9 +476,11 @@ public function action_enqueue_block_editor_assets(): void {
/**
* Action hook callback for init.
*
- * @since 1.3.0
+ * @since 2.7.0
+ * @since 2.6.2 as action_init()
+ * @since 1.3.0 as action_plugins_loaded()
*/
- public function action_init(): void {
+ public function action_admin_init(): void {
// Determine if the database version and code version are the same.
$current_version = get_option( 'apple_news_version', '' );
@@ -509,6 +511,11 @@ public function action_init(): void {
if ( version_compare( $current_version, '2.5.0', '<' ) ) {
$this->upgrade_to_2_5_0();
}
+
+ // Handle upgrade to version 2.7.0.
+ if ( version_compare( $current_version, '2.7.0', '<' ) ) {
+ $this->upgrade_to_2_7_0();
+ }
}
// Ensure the default themes are created.
@@ -1151,6 +1158,87 @@ public function upgrade_to_2_5_0(): void {
}
}
+ /**
+ * Upgrades settings and data formats to be compatible with version 2.7.0.
+ */
+ public function upgrade_to_2_7_0(): void {
+ $registry = Theme::get_registry();
+ foreach ( $registry as $theme_name ) {
+ $theme_object = Admin_Apple_Themes::get_theme_by_name( $theme_name );
+
+ // Set defaults from blockquote settings.
+ $theme_object->set_value( 'recipe_background_color', $theme_object->get_value( 'blockquote_background_color' ) );
+ $theme_object->set_value( 'recipe_body_background_color', $theme_object->get_value( 'blockquote_background_color' ) );
+ $theme_object->set_value( 'recipe_caption_background_color', $theme_object->get_value( 'blockquote_background_color' ) );
+ $theme_object->set_value( 'recipe_details_background_color', $theme_object->get_value( 'blockquote_background_color' ) );
+ $theme_object->set_value( 'recipe_background_color_dark', $theme_object->get_value( 'blockquote_background_color_dark' ) );
+ $theme_object->set_value( 'recipe_body_background_color_dark', $theme_object->get_value( 'blockquote_background_color_dark' ) );
+ $theme_object->set_value( 'recipe_caption_background_color_dark', $theme_object->get_value( 'blockquote_background_color_dark' ) );
+ $theme_object->set_value( 'recipe_details_background_color_dark', $theme_object->get_value( 'blockquote_background_color_dark' ) );
+ $theme_object->set_value( 'recipe_body_color', $theme_object->get_value( 'blockquote_color' ) );
+ $theme_object->set_value( 'recipe_caption_color', $theme_object->get_value( 'blockquote_color' ) );
+ $theme_object->set_value( 'recipe_details_color', $theme_object->get_value( 'blockquote_color' ) );
+ $theme_object->set_value( 'recipe_body_color_dark', $theme_object->get_value( 'blockquote_color_dark' ) );
+ $theme_object->set_value( 'recipe_caption_color_dark', $theme_object->get_value( 'blockquote_color_dark' ) );
+ $theme_object->set_value( 'recipe_details_color_dark', $theme_object->get_value( 'blockquote_color_dark' ) );
+
+ // Set defaults from body settings.
+ $theme_object->set_value( 'recipe_body_font', $theme_object->get_value( 'body_font' ) );
+ $theme_object->set_value( 'recipe_caption_font', $theme_object->get_value( 'body_font' ) );
+ $theme_object->set_value( 'recipe_details_font', $theme_object->get_value( 'body_font' ) );
+ $theme_object->set_value( 'recipe_body_line_height', $theme_object->get_value( 'body_line_height' ) );
+ $theme_object->set_value( 'recipe_caption_line_height', $theme_object->get_value( 'body_line_height' ) );
+ $theme_object->set_value( 'recipe_details_line_height', $theme_object->get_value( 'body_line_height' ) );
+ $theme_object->set_value( 'recipe_body_link_color', $theme_object->get_value( 'body_link_color' ) );
+ $theme_object->set_value( 'recipe_caption_link_color', $theme_object->get_value( 'body_link_color' ) );
+ $theme_object->set_value( 'recipe_details_link_color', $theme_object->get_value( 'body_link_color' ) );
+ $theme_object->set_value( 'recipe_body_link_color_dark', $theme_object->get_value( 'body_link_color_dark' ) );
+ $theme_object->set_value( 'recipe_caption_link_color_dark', $theme_object->get_value( 'body_link_color_dark' ) );
+ $theme_object->set_value( 'recipe_details_link_color_dark', $theme_object->get_value( 'body_link_color_dark' ) );
+ $theme_object->set_value( 'recipe_body_size', $theme_object->get_value( 'body_size' ) );
+ $theme_object->set_value( 'recipe_caption_size', $theme_object->get_value( 'body_size' ) );
+ $theme_object->set_value( 'recipe_details_size', $theme_object->get_value( 'body_size' ) );
+ $theme_object->set_value( 'recipe_body_tracking', $theme_object->get_value( 'body_tracking' ) );
+ $theme_object->set_value( 'recipe_caption_tracking', $theme_object->get_value( 'body_tracking' ) );
+ $theme_object->set_value( 'recipe_details_tracking', $theme_object->get_value( 'body_tracking' ) );
+
+ // Set defaults from heading2 settings.
+ $theme_object->set_value( 'recipe_title_color', $theme_object->get_value( 'header2_color' ) );
+ $theme_object->set_value( 'recipe_title_color_dark', $theme_object->get_value( 'header2_color_dark' ) );
+ $theme_object->set_value( 'recipe_title_font', $theme_object->get_value( 'header2_font' ) );
+ $theme_object->set_value( 'recipe_title_line_height', $theme_object->get_value( 'header2_line_height' ) );
+ $theme_object->set_value( 'recipe_title_size', $theme_object->get_value( 'header2_size' ) );
+ $theme_object->set_value( 'recipe_title_tracking', $theme_object->get_value( 'header2_tracking' ) );
+
+ // Set defaults from heading3 settings.
+ $theme_object->set_value( 'recipe_header2_color', $theme_object->get_value( 'header3_color' ) );
+ $theme_object->set_value( 'recipe_header2_color_dark', $theme_object->get_value( 'header3_color_dark' ) );
+ $theme_object->set_value( 'recipe_header2_font', $theme_object->get_value( 'header3_font' ) );
+ $theme_object->set_value( 'recipe_header2_line_height', $theme_object->get_value( 'header3_line_height' ) );
+ $theme_object->set_value( 'recipe_header2_size', $theme_object->get_value( 'header3_size' ) );
+ $theme_object->set_value( 'recipe_header2_tracking', $theme_object->get_value( 'header3_tracking' ) );
+
+ // Set defaults from heading4 settings.
+ $theme_object->set_value( 'recipe_header3_color', $theme_object->get_value( 'header4_color' ) );
+ $theme_object->set_value( 'recipe_header3_color_dark', $theme_object->get_value( 'header4_color_dark' ) );
+ $theme_object->set_value( 'recipe_header3_font', $theme_object->get_value( 'header4_font' ) );
+ $theme_object->set_value( 'recipe_header3_line_height', $theme_object->get_value( 'header4_line_height' ) );
+ $theme_object->set_value( 'recipe_header3_size', $theme_object->get_value( 'header4_size' ) );
+ $theme_object->set_value( 'recipe_header3_tracking', $theme_object->get_value( 'header4_tracking' ) );
+
+ // Set defaults from heading5 settings.
+ $theme_object->set_value( 'recipe_header4_color', $theme_object->get_value( 'header5_color' ) );
+ $theme_object->set_value( 'recipe_header4_color_dark', $theme_object->get_value( 'header5_color_dark' ) );
+ $theme_object->set_value( 'recipe_header4_font', $theme_object->get_value( 'header5_font' ) );
+ $theme_object->set_value( 'recipe_header4_line_height', $theme_object->get_value( 'header5_line_height' ) );
+ $theme_object->set_value( 'recipe_header4_size', $theme_object->get_value( 'header5_size' ) );
+ $theme_object->set_value( 'recipe_header4_tracking', $theme_object->get_value( 'header5_tracking' ) );
+
+ // Save our changes.
+ $theme_object->save();
+ }
+ }
+
/**
* Load example themes into the theme list.
*
diff --git a/package-lock.json b/package-lock.json
index 1d851ad5c..f461d7f7a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "publish-to-apple-news",
- "version": "2.4.9",
+ "version": "2.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "publish-to-apple-news",
- "version": "2.4.9",
+ "version": "2.5.1",
"hasInstallScript": true,
"license": "GPLv3",
"dependencies": {
@@ -19,6 +19,7 @@
"@wordpress/i18n": "^4.51.1",
"@wordpress/plugins": "^6.19.6",
"@wordpress/scripts": "^27.2.5",
+ "@wordpress/url": "^4.12.0",
"dompurify": "^3.0.8",
"prop-types": "^15.8.1",
"react": "^18.2.0",
@@ -1806,9 +1807,9 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"node_modules/@babel/runtime": {
- "version": "7.24.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
- "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
+ "version": "7.25.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz",
+ "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -4870,6 +4871,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/api-fetch/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/autop": {
"version": "3.56.0",
"resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.56.0.tgz",
@@ -5122,6 +5135,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/block-editor/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/block-editor/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -5319,6 +5344,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/block-library/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/block-library/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -5737,6 +5774,18 @@
"react-dom": "^18.0.0"
}
},
+ "node_modules/@wordpress/core-commands/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/core-data": {
"version": "6.33.0",
"resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-6.33.0.tgz",
@@ -5843,6 +5892,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/core-data/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/data": {
"version": "9.21.1",
"resolved": "https://registry.npmjs.org/@wordpress/data/-/data-9.21.1.tgz",
@@ -5956,6 +6017,18 @@
"@playwright/test": ">=1"
}
},
+ "node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/edit-post": {
"version": "7.28.10",
"resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-7.28.10.tgz",
@@ -6029,6 +6102,18 @@
"react-dom": "^18.0.0"
}
},
+ "node_modules/@wordpress/edit-post/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/editor": {
"version": "13.33.0",
"resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-13.33.0.tgz",
@@ -6236,6 +6321,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/editor/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/editor/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -6788,6 +6885,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/media-utils/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/notices": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-4.24.0.tgz",
@@ -7002,6 +7111,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/patterns/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/patterns/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -7410,6 +7531,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/reusable-blocks/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/reusable-blocks/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -7518,6 +7651,18 @@
"react": "^18.0.0"
}
},
+ "node_modules/@wordpress/router/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/scripts": {
"version": "27.2.5",
"resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.2.5.tgz",
@@ -7816,6 +7961,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/server-side-render/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/server-side-render/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -7884,6 +8041,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/sync/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/token-list": {
"version": "2.56.0",
"resolved": "https://registry.npmjs.org/@wordpress/token-list/-/token-list-2.56.0.tgz",
@@ -7908,15 +8077,16 @@
}
},
"node_modules/@wordpress/url": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.57.0.tgz",
- "integrity": "sha512-W3F0KVEaMoRENya7GGUPXrZGYnhAg3fuLSLpNcf1skSrM5rUVMNdeRlZj+jln1O/+qjboJnC+y+IzOlQRwlS6A==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-4.12.0.tgz",
+ "integrity": "sha512-pnWf/cWbblj4cp27aW2R6DWOomct0nzIUM1F7rJgWIosUXCeFTKTxSJ2gL79pbZXmq22e7fRdgn7JIrUzlBMAw==",
"dependencies": {
- "@babel/runtime": "^7.16.0",
+ "@babel/runtime": "7.25.7",
"remove-accents": "^0.5.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18.12.0",
+ "npm": ">=8.19.2"
}
},
"node_modules/@wordpress/viewport": {
@@ -8139,6 +8309,18 @@
"node": ">=12"
}
},
+ "node_modules/@wordpress/widgets/node_modules/@wordpress/url": {
+ "version": "3.59.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.59.0.tgz",
+ "integrity": "sha512-GxvoMjYCav0w4CiX0i0h3qflrE/9rhLIZg5aPCQjbrBdwTxYR3Exfw0IJYcmVaTKXQOUU8fOxlDxULsbLmKe9w==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "remove-accents": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@wordpress/widgets/node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
diff --git a/package.json b/package.json
index c82bf173b..b09e59c95 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "publish-to-apple-news",
- "version": "2.6.3",
+ "version": "2.7.0",
"license": "GPLv3",
"main": "index.php",
"engines": {
@@ -40,6 +40,7 @@
"@wordpress/i18n": "^4.51.1",
"@wordpress/plugins": "^6.19.6",
"@wordpress/scripts": "^27.2.5",
+ "@wordpress/url": "^4.12.0",
"dompurify": "^3.0.8",
"prop-types": "^15.8.1",
"react": "^18.2.0",
diff --git a/readme.txt b/readme.txt
index 354164c96..1f001d21c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,7 +5,7 @@ Tags: publish, apple, news, iOS
Requires at least: 6.3
Tested up to: 6.7
Requires PHP: 8.0
-Stable tag: 2.6.3
+Stable tag: 2.7.0
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl.html
@@ -45,13 +45,27 @@ Please visit our [wiki](https://github.com/alleyinteractive/apple-news/wiki) for
== Changelog ==
+= 2.7.0 =
+
+* Enhancement: Add support for the Apple News Recipe component. Please [visit our wiki for detailed documentation](https://github.com/alleyinteractive/apple-news/wiki/configuration#recipe-component).
+* Enhancement: Add "Cover Media" options, expanding on existing "Cover Image" feature, allowing YouTube or other videos to display in the Cover component.
+* Enhancement: Add setting that allows elements in post content matching CSS classes or ID selectors to be excluded from the content sent to Apple News.
+* Enhancement: Allow article deletions with Bulk Actions.
+* Enhancement: Move section mapping to its own table in the Automation settings for easier management.
+* Enhancement: Enable caption on the Cover component by default.
+
+= 2.6.3 =
+
+* Bugfix: Fixed an issue where the deletion of a post's autosave could cause the Apple News article for that post to be deleted.
+
= 2.6.2 =
* Enhancement: i18n - Remove `load_plugin_textdomain`. Since WordPress 4.6, plugins no longer need to load their own textdomain.
* Enhancement: i18n - Moved some plugin initialization code to the `init` action hook.
+* Enhancement: Add license to plugin header.
* Bugfix: i18n - Fixed the `_load_textdomain_just_in_time` error with WordPress 6.7.
+* Bugfix: Update bulk action text to clarify that the "Publish" action can also be used to update published articles.
* Bugfix: Update plugin name in plugin header.
-* Enhancement: Add license to plugin header.
= 2.6.1 =
diff --git a/tests/admin/apple-actions/index/test-class-export.php b/tests/admin/apple-actions/index/test-class-export.php
index 7ffa9ac69..64a124020 100644
--- a/tests/admin/apple-actions/index/test-class-export.php
+++ b/tests/admin/apple-actions/index/test-class-export.php
@@ -259,6 +259,34 @@ public function test_remove_entities() {
);
}
+ /**
+ * Tests the ability to remove selectors from the content.
+ */
+ public function test_remove_selectors() {
+ $post_id = $this->factory->post->create(
+ [
+ 'post_content' => <<First paragraph
+Second paragraph
+Third paragraph
+Fourth paragraph
+Fifth paragraph
+Sixth paragraph
+HTML,
+ ]
+ );
+
+ $this->settings->excluded_selectors = '.foo, #foo';
+
+ $export = new Export( $this->settings, $post_id );
+ $exporter = $export->fetch_exporter();
+ $content = $exporter->get_content()->content();
+
+ $this->assertStringNotContainsString( 'First paragraph', $content );
+ $this->assertStringNotContainsString( 'Third paragraph', $content );
+ $this->assertStringNotContainsString( 'Fifth paragraph', $content );
+ }
+
/**
* Tests the behavior of the apple_news_is_exporting() function.
*/
diff --git a/tests/apple-exporter/builders/test-class-components.php b/tests/apple-exporter/builders/test-class-components.php
index e63f11bf2..c1761c266 100644
--- a/tests/apple-exporter/builders/test-class-components.php
+++ b/tests/apple-exporter/builders/test-class-components.php
@@ -346,4 +346,51 @@ public function test_meta_component_ordering( $order, $expected, $components ) {
}
}
}
+
+ /**
+ * Tests that when a URL is set as the cover media, it doesn't appear again in the body.
+ */
+ public function test_remove_cover_from_body_components() {
+ $video_url = 'https://www.example.com/example.mp4';
+
+ $post_id = self::factory()->post->create(
+ [
+ 'post_content' => <<
+At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti.
+
+
+
+
+
+HTML,
+ ],
+ );
+
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'video_url' );
+ update_post_meta( $post_id, 'apple_news_cover_video_url', $video_url );
+
+ $count_of_video_url_in_body = function () use ( $post_id, $video_url ) {
+ $count = 0;
+ $json = $this->get_json_for_post( $post_id );
+
+ foreach ( $json['components'] as $component ) {
+ if ( 'container' === $component['role'] ) {
+ foreach ( $component['components'] as $subcomponent ) {
+ if ( isset( $subcomponent['URL'] ) && $video_url === $subcomponent['URL'] ) {
+ $count++;
+ }
+ }
+ }
+ }
+
+ return $count;
+ };
+
+ $this->settings->deduplicate_cover_media = 'no';
+ $this->assertSame( 1, $count_of_video_url_in_body() );
+
+ $this->settings->deduplicate_cover_media = 'yes';
+ $this->assertSame( 0, $count_of_video_url_in_body() );
+ }
}
diff --git a/tests/apple-exporter/components/test-class-cover.php b/tests/apple-exporter/components/test-class-cover.php
index 4972b3700..5cd8a6590 100644
--- a/tests/apple-exporter/components/test-class-cover.php
+++ b/tests/apple-exporter/components/test-class-cover.php
@@ -135,6 +135,76 @@ public function test_relative_url() {
$this->assertEquals( $image_url, $json_2['components'][0]['components'][0]['URL'] );
}
+ /**
+ * Tests that the cover component is populated with video or embed sources.
+ */
+ public function test_other_cover_media_providers() {
+ // Needed to load `wp_read_video_metadata()`.
+ require_once ABSPATH . 'wp-admin/includes/media.php';
+
+ $post_id = self::factory()->post->create();
+
+ // MP4 video URLs.
+ $video_url = 'https://www.example.com/example.mp4';
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'video_url' );
+ update_post_meta( $post_id, 'apple_news_cover_video_url', $video_url );
+ $json = $this->get_json_for_post( $post_id );
+ $this->assertSame( 'video', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( $video_url, $json['components'][0]['components'][0]['URL'] );
+
+ // Uploaded videos.
+ $video_id = self::factory()->attachment->with_image( dirname( __DIR__, 2 ) . '/data/test-video.mp4', $post_id )->create();
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'video_id' );
+ update_post_meta( $post_id, 'apple_news_cover_video_id', $video_id );
+ $json = $this->get_json_for_post( $post_id );
+ $this->assertSame( 'video', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( wp_get_attachment_url( $video_id ), $json['components'][0]['components'][0]['URL'] );
+
+ // YouTube videos.
+ $video_url = 'https://www.youtube.com/watch?v=example';
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'embedwebvideo' );
+ update_post_meta( $post_id, 'apple_news_cover_embedwebvideo_url', $video_url );
+ $json = $this->get_json_for_post( $post_id );
+ $this->assertSame( 'embedwebvideo', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( 'https://www.youtube.com/embed/example', $json['components'][0]['components'][0]['URL'] );
+
+ // Vimeo videos.
+ $video_url = 'https://vimeo.com/123456789';
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'embedwebvideo' );
+ update_post_meta( $post_id, 'apple_news_cover_embedwebvideo_url', $video_url );
+ $json = $this->get_json_for_post( $post_id );
+ $this->assertSame( 'embedwebvideo', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( 'https://player.vimeo.com/video/123456789', $json['components'][0]['components'][0]['URL'] );
+
+ // Dailymotion videos.
+ $video_url = 'https://www.dailymotion.com/video/example';
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'embedwebvideo' );
+ update_post_meta( $post_id, 'apple_news_cover_embedwebvideo_url', $video_url );
+ $json = $this->get_json_for_post( $post_id );
+ $this->assertSame( 'embedwebvideo', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( 'https://geo.dailymotion.com/player.html?video=example', $json['components'][0]['components'][0]['URL'] );
+ }
+
+ /**
+ * Tests that the cover component falls back to an image if the saved cover media is invalid.
+ */
+ public function test_invalid_cover_media_falls_back_to_image() {
+ $post_id = self::factory()->post->create();
+
+ $thumbnail_id = $this->get_new_attachment( 0 );
+ set_post_thumbnail( $post_id, $thumbnail_id );
+
+ // Invalid video URL.
+ update_post_meta( $post_id, 'apple_news_cover_media_provider', 'embedwebvideo' );
+ update_post_meta( $post_id, 'apple_news_cover_embedwebvideo_url', 'https://www.example.com/example' );
+
+ $json = $this->get_json_for_post( $post_id );
+
+ // Ensure that the cover component falls back to the image.
+ $this->assertSame( 'photo', $json['components'][0]['components'][0]['role'] );
+ $this->assertSame( wp_get_attachment_url( $thumbnail_id ), $json['components'][0]['components'][0]['URL'] );
+ }
+
/**
* Tests the render method for the component.
*/
diff --git a/tests/apple-exporter/components/test-class-recipe.php b/tests/apple-exporter/components/test-class-recipe.php
new file mode 100644
index 000000000..29099349b
--- /dev/null
+++ b/tests/apple-exporter/components/test-class-recipe.php
@@ -0,0 +1,298 @@
+ 'test-recipe-class',
+ ]
+ )
+ );
+
+ // To be able to save SCRIPT tags.
+ $user = $this->acting_as( 'administrator' );
+ grant_super_admin( $user->ID );
+ kses_init();
+ }
+
+ /**
+ * Test that recipe tokens with no replacement value from the JSON schema are removed.
+ */
+ public function test_remove_objects_with_empty_tokens(): void {
+ $post_content = <<
+ Chocolate Cake
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ // There should be a photo component, but there shouldn't be a caption.
+ $this->assertTrue( isset( $json['components'][3]['components'][0]['role'] ) );
+ $this->assertSame( 'photo', $json['components'][3]['components'][0]['role'] );
+ $this->assertFalse( isset( $json['components'][3]['components'][0]['caption'] ) );
+ }
+
+ /**
+ * Test that recipe instructions are expanded from a list of strings.
+ */
+ public function test_recipe_instructions_list_of_strings(): void {
+ // These instructions via https://www.delicious.com.au/recipes/huevos-rancheros-beans/7WDKzQe3.
+ $post_content = <<
+
Chocolate Cake
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ $this->assertStringStartsWith(
+ 'Preheat oven to 180',
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][0]['text']
+ );
+ }
+
+ /**
+ * Test that recipe instructions are expanded from a list of HowToStep objects.
+ */
+ public function test_recipe_instructions_steps(): void {
+ // These instructions via https://chatelaine.com/recipe/desserts/easy-chocolate-cake/.
+ $post_content = <<
+ Chocolate Cake
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ $this->assertStringStartsWith(
+ 'Step 1',
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][0]['text']
+ );
+ $this->assertStringStartsWith(
+ 'PREHEAT oven to 350F',
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][1]['text']
+ );
+ }
+
+ /**
+ * Test that recipe instructions are expanded from a tree of HowToSection and HowToStep objects.
+ */
+ public function test_recipe_instructions_sections_and_steps(): void {
+ // These instructions via https://www.ricardocuisine.com/recettes/3808-chop-suey-au-poulet.
+ $post_content = <<
+ Chocolate Cake
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ $this->assertSame(
+ 'Chop suey au poulet',
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][0]['text']
+ );
+ $this->assertStringStartsWith(
+ 'Dans un bol',
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][1]['components'][0]['components'][0]['text']
+ );
+ }
+
+ /**
+ * Test that recipe instructions don't include a heading and body text with the same content.
+ */
+ public function test_recipe_step_with_duplicate_text(): void {
+ $post_content = <<
+ Chocolate Cake
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ $this->assertCount(
+ 1,
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components']
+ );
+ $this->assertSame(
+ 'body', // Not heading.
+ $json['components'][3]['components'][0]['components'][4]['components'][0]['components'][1]['components'][1]['components'][0]['components'][0]['role']
+ );
+ }
+
+ /**
+ * Test that recipe HTML is processed normally when the 'use schema' setting is 'no'.
+ */
+ public function test_recipe_component_with_component_use_schema_no(): void {
+ $this->settings->__set( 'recipe_component_use_schema', 'no' );
+
+ // These instructions via https://www.ricardocuisine.com/recettes/3808-chop-suey-au-poulet.
+ $post_content = <<
+
+
+
+
+HTML;
+
+ $post_id = self::factory()->post->create( [ 'post_content' => $post_content ] );
+ $json = $this->get_json_for_post( $post_id );
+
+ // This should be a table because the raw HTML found in the matching recipe element should be used.
+ $this->assertSame(
+ 'htmltable',
+ $json['components'][3]['components'][0]['role']
+ );
+ }
+}
diff --git a/tests/data/test-video.mp4 b/tests/data/test-video.mp4
new file mode 100644
index 000000000..2872b527b
Binary files /dev/null and b/tests/data/test-video.mp4 differ
diff --git a/tests/test-class-apple-news.php b/tests/test-class-apple-news.php
index d76127b34..4294dfb9a 100644
--- a/tests/test-class-apple-news.php
+++ b/tests/test-class-apple-news.php
@@ -352,13 +352,193 @@ public function test_upgrade_to_2_5_0(): void {
$this->assertEquals( 5, $theme->get_value( 'aside_border_width' ), 'Expected the custom blockquote border width to be applied to the aside border width as part of the upgrade.' );
}
+ /**
+ * Tests an upgrade from a version prior to 2.7.0 to version 2.7.0.
+ */
+ public function test_upgrade_to_2_7_0(): void {
+ // Load the default theme and get its data.
+ $this->load_example_theme( 'default' );
+ $theme_data = get_option( Theme::theme_key( 'Default' ) );
+
+ // Remove all keys for the new recipe settings.
+ unset( $theme_data['recipe_background_color'] );
+ unset( $theme_data['recipe_background_color_dark'] );
+ unset( $theme_data['recipe_body_background_color'] );
+ unset( $theme_data['recipe_body_background_color_dark'] );
+ unset( $theme_data['recipe_body_color'] );
+ unset( $theme_data['recipe_body_color_dark'] );
+ unset( $theme_data['recipe_body_font'] );
+ unset( $theme_data['recipe_body_line_height'] );
+ unset( $theme_data['recipe_body_link_color'] );
+ unset( $theme_data['recipe_body_link_color_dark'] );
+ unset( $theme_data['recipe_body_size'] );
+ unset( $theme_data['recipe_body_tracking'] );
+ unset( $theme_data['recipe_caption_background_color'] );
+ unset( $theme_data['recipe_caption_background_color_dark'] );
+ unset( $theme_data['recipe_caption_color'] );
+ unset( $theme_data['recipe_caption_color_dark'] );
+ unset( $theme_data['recipe_caption_font'] );
+ unset( $theme_data['recipe_caption_line_height'] );
+ unset( $theme_data['recipe_caption_link_color'] );
+ unset( $theme_data['recipe_caption_link_color_dark'] );
+ unset( $theme_data['recipe_caption_size'] );
+ unset( $theme_data['recipe_caption_tracking'] );
+ unset( $theme_data['recipe_details_background_color'] );
+ unset( $theme_data['recipe_details_background_color_dark'] );
+ unset( $theme_data['recipe_details_color'] );
+ unset( $theme_data['recipe_details_color_dark'] );
+ unset( $theme_data['recipe_details_font'] );
+ unset( $theme_data['recipe_details_line_height'] );
+ unset( $theme_data['recipe_details_link_color'] );
+ unset( $theme_data['recipe_details_link_color_dark'] );
+ unset( $theme_data['recipe_details_size'] );
+ unset( $theme_data['recipe_details_tracking'] );
+ unset( $theme_data['recipe_header2_color'] );
+ unset( $theme_data['recipe_header2_color_dark'] );
+ unset( $theme_data['recipe_header2_font'] );
+ unset( $theme_data['recipe_header2_line_height'] );
+ unset( $theme_data['recipe_header2_size'] );
+ unset( $theme_data['recipe_header2_tracking'] );
+ unset( $theme_data['recipe_header3_color'] );
+ unset( $theme_data['recipe_header3_color_dark'] );
+ unset( $theme_data['recipe_header3_font'] );
+ unset( $theme_data['recipe_header3_line_height'] );
+ unset( $theme_data['recipe_header3_size'] );
+ unset( $theme_data['recipe_header3_tracking'] );
+ unset( $theme_data['recipe_header4_color'] );
+ unset( $theme_data['recipe_header4_color_dark'] );
+ unset( $theme_data['recipe_header4_font'] );
+ unset( $theme_data['recipe_header4_line_height'] );
+ unset( $theme_data['recipe_header4_size'] );
+ unset( $theme_data['recipe_header4_tracking'] );
+ unset( $theme_data['recipe_title_color'] );
+ unset( $theme_data['recipe_title_color_dark'] );
+ unset( $theme_data['recipe_title_font'] );
+ unset( $theme_data['recipe_title_line_height'] );
+ unset( $theme_data['recipe_title_size'] );
+ unset( $theme_data['recipe_title_tracking'] );
+
+ // Set different values for the source keys for the recipe settings so we can ensure they are migrated properly.
+ $theme_data['blockquote_background_color'] = '#abc123';
+ $theme_data['blockquote_background_color_dark'] = '#bcd234';
+ $theme_data['blockquote_color'] = '#cde345';
+ $theme_data['blockquote_color_dark'] = '#def456';
+ $theme_data['body_font'] = 'Papyrus';
+ $theme_data['body_line_height'] = 123.0;
+ $theme_data['body_link_color'] = '#aaa111';
+ $theme_data['body_link_color_dark'] = '#bbb222';
+ $theme_data['body_size'] = 24;
+ $theme_data['body_tracking'] = 5;
+ $theme_data['header2_color'] = '#222222';
+ $theme_data['header2_color_dark'] = '#222ddd';
+ $theme_data['header2_font'] = 'Zapfino';
+ $theme_data['header2_line_height'] = 48.0;
+ $theme_data['header2_size'] = 48;
+ $theme_data['header2_tracking'] = 15;
+ $theme_data['header3_color'] = '#333333';
+ $theme_data['header3_color_dark'] = '#333ddd';
+ $theme_data['header3_font'] = 'Copperplate';
+ $theme_data['header3_line_height'] = 36.0;
+ $theme_data['header3_size'] = 36;
+ $theme_data['header3_tracking'] = 10;
+ $theme_data['header4_color'] = '#444444';
+ $theme_data['header4_color_dark'] = '#444ddd';
+ $theme_data['header4_font'] = 'Baskerville';
+ $theme_data['header4_line_height'] = 34.0;
+ $theme_data['header4_size'] = 34;
+ $theme_data['header4_tracking'] = 9;
+ $theme_data['header5_color'] = '#555555';
+ $theme_data['header5_color_dark'] = '#555ddd';
+ $theme_data['header5_font'] = 'Verdana';
+ $theme_data['header5_line_height'] = 35.0;
+ $theme_data['header5_size'] = 35;
+ $theme_data['header5_tracking'] = 8;
+
+ // Save the updated theme data to prep for the upgrade test.
+ update_option( Theme::theme_key( 'Default' ), $theme_data );
+
+ // Run the upgrade.
+ $apple_news = new Apple_News();
+ $apple_news->upgrade_to_2_7_0();
+ $theme = Theme::get_used();
+ $theme->load();
+
+ // Check settings that are migrated from blockquote settings.
+ $this->assertEquals( '#abc123', $theme->get_value( 'recipe_background_color' ), 'Expected the blockquote background color to be applied to the recipe background color as part of the upgrade.' );
+ $this->assertEquals( '#abc123', $theme->get_value( 'recipe_body_background_color' ), 'Expected the blockquote background color to be applied to the recipe body background color as part of the upgrade.' );
+ $this->assertEquals( '#abc123', $theme->get_value( 'recipe_caption_background_color' ), 'Expected the blockquote background color to be applied to the recipe caption background color as part of the upgrade.' );
+ $this->assertEquals( '#abc123', $theme->get_value( 'recipe_details_background_color' ), 'Expected the blockquote background color to be applied to the recipe details background color as part of the upgrade.' );
+ $this->assertEquals( '#bcd234', $theme->get_value( 'recipe_background_color_dark' ), 'Expected the blockquote dark background color to be applied to the recipe dark background color as part of the upgrade.' );
+ $this->assertEquals( '#bcd234', $theme->get_value( 'recipe_body_background_color_dark' ), 'Expected the blockquote dark background color to be applied to the recipe body dark background color as part of the upgrade.' );
+ $this->assertEquals( '#bcd234', $theme->get_value( 'recipe_caption_background_color_dark' ), 'Expected the blockquote dark background color to be applied to the recipe caption dark background color as part of the upgrade.' );
+ $this->assertEquals( '#bcd234', $theme->get_value( 'recipe_details_background_color_dark' ), 'Expected the blockquote dark background color to be applied to the recipe details dark background color as part of the upgrade.' );
+ $this->assertEquals( '#cde345', $theme->get_value( 'recipe_body_color' ), 'Expected the blockquote color to be applied to the recipe body color as part of the upgrade.' );
+ $this->assertEquals( '#cde345', $theme->get_value( 'recipe_caption_color' ), 'Expected the blockquote color to be applied to the recipe caption color as part of the upgrade.' );
+ $this->assertEquals( '#cde345', $theme->get_value( 'recipe_details_color' ), 'Expected the blockquote color to be applied to the recipe details color as part of the upgrade.' );
+ $this->assertEquals( '#def456', $theme->get_value( 'recipe_body_color_dark' ), 'Expected the blockquote dark color to be applied to the recipe body dark color as part of the upgrade.' );
+ $this->assertEquals( '#def456', $theme->get_value( 'recipe_caption_color_dark' ), 'Expected the blockquote dark color to be applied to the recipe caption dark color as part of the upgrade.' );
+ $this->assertEquals( '#def456', $theme->get_value( 'recipe_details_color_dark' ), 'Expected the blockquote dark color to be applied to the recipe details dark color as part of the upgrade.' );
+
+ // Check settings that are migrated from body settings.
+ $this->assertEquals( 'Papyrus', $theme->get_value( 'recipe_body_font' ), 'Expected the body font to be applied to the recipe body font as part of the upgrade.' );
+ $this->assertEquals( 'Papyrus', $theme->get_value( 'recipe_caption_font' ), 'Expected the body font to be applied to the recipe caption font as part of the upgrade.' );
+ $this->assertEquals( 'Papyrus', $theme->get_value( 'recipe_details_font' ), 'Expected the body font to be applied to the recipe details font as part of the upgrade.' );
+ $this->assertEquals( 123.0, $theme->get_value( 'recipe_body_line_height' ), 'Expected the body line height to be applied to the recipe body line height as part of the upgrade.' );
+ $this->assertEquals( 123.0, $theme->get_value( 'recipe_caption_line_height' ), 'Expected the body line height to be applied to the recipe caption line height as part of the upgrade.' );
+ $this->assertEquals( 123.0, $theme->get_value( 'recipe_details_line_height' ), 'Expected the body line height to be applied to the recipe details line height as part of the upgrade.' );
+ $this->assertEquals( '#aaa111', $theme->get_value( 'recipe_body_link_color' ), 'Expected the body link color to be applied to the recipe body link color as part of the upgrade.' );
+ $this->assertEquals( '#aaa111', $theme->get_value( 'recipe_caption_link_color' ), 'Expected the body link color to be applied to the recipe caption link color as part of the upgrade.' );
+ $this->assertEquals( '#aaa111', $theme->get_value( 'recipe_details_link_color' ), 'Expected the body link color to be applied to the recipe details link color as part of the upgrade.' );
+ $this->assertEquals( '#bbb222', $theme->get_value( 'recipe_body_link_color_dark' ), 'Expected the body dark link color to be applied to the recipe body dark link color as part of the upgrade.' );
+ $this->assertEquals( '#bbb222', $theme->get_value( 'recipe_caption_link_color_dark' ), 'Expected the body dark link color to be applied to the recipe caption dark link color as part of the upgrade.' );
+ $this->assertEquals( '#bbb222', $theme->get_value( 'recipe_details_link_color_dark' ), 'Expected the body dark link color to be applied to the recipe details dark link color as part of the upgrade.' );
+ $this->assertEquals( 24, $theme->get_value( 'recipe_body_size' ), 'Expected the body size to be applied to the recipe body size as part of the upgrade.' );
+ $this->assertEquals( 24, $theme->get_value( 'recipe_caption_size' ), 'Expected the body size to be applied to the recipe caption size as part of the upgrade.' );
+ $this->assertEquals( 24, $theme->get_value( 'recipe_details_size' ), 'Expected the body size to be applied to the recipe details size as part of the upgrade.' );
+ $this->assertEquals( 5, $theme->get_value( 'recipe_body_tracking' ), 'Expected the body tracking to be applied to the recipe body tracking as part of the upgrade.' );
+ $this->assertEquals( 5, $theme->get_value( 'recipe_caption_tracking' ), 'Expected the body tracking to be applied to the recipe caption tracking as part of the upgrade.' );
+ $this->assertEquals( 5, $theme->get_value( 'recipe_details_tracking' ), 'Expected the body tracking to be applied to the recipe details tracking as part of the upgrade.' );
+
+ // Check settings that are migrated from h2 settings.
+ $this->assertEquals( '#222222', $theme->get_value( 'recipe_title_color' ), 'Expected the h2 color to be applied to the recipe title color as part of the upgrade.' );
+ $this->assertEquals( '#222ddd', $theme->get_value( 'recipe_title_color_dark' ), 'Expected the h2 dark color to be applied to the recipe title dark color as part of the upgrade.' );
+ $this->assertEquals( 'Zapfino', $theme->get_value( 'recipe_title_font' ), 'Expected the h2 font to be applied to the recipe title font as part of the upgrade.' );
+ $this->assertEquals( 48.0, $theme->get_value( 'recipe_title_line_height' ), 'Expected the h2 line height to be applied to the recipe title line height as part of the upgrade.' );
+ $this->assertEquals( 48, $theme->get_value( 'recipe_title_size' ), 'Expected the h2 size to be applied to the recipe title size as part of the upgrade.' );
+ $this->assertEquals( 15, $theme->get_value( 'recipe_title_tracking' ), 'Expected the h2 tracking to be applied to the recipe title tracking as part of the upgrade.' );
+
+ // Check settings that are migrated from h3 settings.
+ $this->assertEquals( '#333333', $theme->get_value( 'recipe_header2_color' ), 'Expected the h3 color to be applied to the recipe header2 color as part of the upgrade.' );
+ $this->assertEquals( '#333ddd', $theme->get_value( 'recipe_header2_color_dark' ), 'Expected the h3 dark color to be applied to the recipe header2 dark color as part of the upgrade.' );
+ $this->assertEquals( 'Copperplate', $theme->get_value( 'recipe_header2_font' ), 'Expected the h3 font to be applied to the recipe header2 font as part of the upgrade.' );
+ $this->assertEquals( 36.0, $theme->get_value( 'recipe_header2_line_height' ), 'Expected the h3 line height to be applied to the recipe header2 line height as part of the upgrade.' );
+ $this->assertEquals( 36, $theme->get_value( 'recipe_header2_size' ), 'Expected the h3 size to be applied to the recipe header2 size as part of the upgrade.' );
+ $this->assertEquals( 10, $theme->get_value( 'recipe_header2_tracking' ), 'Expected the h3 tracking to be applied to the recipe header2 tracking as part of the upgrade.' );
+
+ // Check settings that are migrated from h4 settings.
+ $this->assertEquals( '#444444', $theme->get_value( 'recipe_header3_color' ), 'Expected the h4 color to be applied to the recipe header3 color as part of the upgrade.' );
+ $this->assertEquals( '#444ddd', $theme->get_value( 'recipe_header3_color_dark' ), 'Expected the h4 dark color to be applied to the recipe header3 dark color as part of the upgrade.' );
+ $this->assertEquals( 'Baskerville', $theme->get_value( 'recipe_header3_font' ), 'Expected the h4 font to be applied to the recipe header3 font as part of the upgrade.' );
+ $this->assertEquals( 34.0, $theme->get_value( 'recipe_header3_line_height' ), 'Expected the h4 line height to be applied to the recipe header3 line height as part of the upgrade.' );
+ $this->assertEquals( 34, $theme->get_value( 'recipe_header3_size' ), 'Expected the h4 size to be applied to the recipe header3 size as part of the upgrade.' );
+ $this->assertEquals( 9, $theme->get_value( 'recipe_header3_tracking' ), 'Expected the h4 tracking to be applied to the recipe header3 tracking as part of the upgrade.' );
+
+ // Check settings that are migrated from h5 settings.
+ $this->assertEquals( '#555555', $theme->get_value( 'recipe_header4_color' ), 'Expected the h5 color to be applied to the recipe header4 color as part of the upgrade.' );
+ $this->assertEquals( '#555ddd', $theme->get_value( 'recipe_header4_color_dark' ), 'Expected the h5 dark color to be applied to the recipe header4 dark color as part of the upgrade.' );
+ $this->assertEquals( 'Verdana', $theme->get_value( 'recipe_header4_font' ), 'Expected the h5 font to be applied to the recipe header4 font as part of the upgrade.' );
+ $this->assertEquals( 35.0, $theme->get_value( 'recipe_header4_line_height' ), 'Expected the h5 line height to be applied to the recipe header4 line height as part of the upgrade.' );
+ $this->assertEquals( 35, $theme->get_value( 'recipe_header4_size' ), 'Expected the h5 size to be applied to the recipe header4 size as part of the upgrade.' );
+ $this->assertEquals( 8, $theme->get_value( 'recipe_header4_tracking' ), 'Expected the h5 tracking to be applied to the recipe header4 tracking as part of the upgrade.' );
+ }
+
/**
* Ensures that the version in Apple_News matches the reported plugin version.
*
* @see Apple_News::$version
*/
public function test_version() {
- $plugin_data = apple_news_get_plugin_data( translate: false );
+ $plugin_data = apple_news_get_plugin_data();
$this->assertEquals( Apple_News::$version, $plugin_data['Version'] );
}
}