From 06ed5f26eee0358d9cf486a47aa2dcfce1fd046b Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 5 Mar 2024 08:44:05 -0500 Subject: [PATCH 001/125] WIP: APPLE-163 From ac7344910194660c078d63b09123324527ff969c Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:03:43 -0500 Subject: [PATCH 002/125] APPLE-163 Add prepend to title automation feature --- admin/class-automation.php | 25 +++++++++++++++++++++++++ assets/js/admin-settings/rule.jsx | 19 +++++++++++++++---- tests/admin/test-class-automation.php | 27 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/admin/class-automation.php b/admin/class-automation.php index 77b7df58..fbe8d020 100644 --- a/admin/class-automation.php +++ b/admin/class-automation.php @@ -66,6 +66,7 @@ public static function init(): void { add_filter( 'apple_news_active_theme', [ __CLASS__, 'filter__apple_news_active_theme' ], 0, 2 ); add_filter( 'apple_news_article_metadata', [ __CLASS__, 'filter__apple_news_article_metadata' ], 0, 2 ); add_filter( 'apple_news_exporter_slug', [ __CLASS__, 'filter__apple_news_exporter_slug' ], 0, 2 ); + add_filter( 'apple_news_exporter_title', [ __CLASS__, 'filter__apple_news_exporter_title' ], 0, 2 ); } /** @@ -168,6 +169,25 @@ public static function filter__apple_news_exporter_slug( $slug, $post_id ) { return $slug; } + /** + * Applies automation rules affecting the post title. + * + * @param string $title The title the plugin wants to use. + * @param int $post_id The post ID associated with the title. + * + * @return string The filtered title value. + */ + public static function filter__apple_news_exporter_title( $title, $post_id ) { + $rules = self::get_automation_for_post( $post_id ); + foreach ( $rules as $rule ) { + if ( 'title.prepend' === ( $rule['field'] ?? '' ) ) { + $title = sprintf( '%s %s', $rule['value'] ?? '', $title ); + } + } + + return $title; + } + /** * Given a post ID, returns an array of matching automation rules. * @@ -245,6 +265,11 @@ public static function get_fields(): array { 'type' => 'string', 'label' => __( 'Theme', 'apple-news' ), ], + 'title.prepend' => [ + 'location' => 'component', + 'type' => 'string', + 'label' => __( 'Title: Prepend Text', 'apple-news' ), + ], ]; } diff --git a/assets/js/admin-settings/rule.jsx b/assets/js/admin-settings/rule.jsx index c07a40ae..08a35d1d 100644 --- a/assets/js/admin-settings/rule.jsx +++ b/assets/js/admin-settings/rule.jsx @@ -28,6 +28,16 @@ function Rule({ taxonomies, themes, } = AppleNewsAutomationConfig; + let fieldType = ''; + if (field === 'links.sections') { + fieldType = 'sections'; + } else if (field === 'theme') { + fieldType = 'themes'; + } else if (fields[field]?.type === 'boolean') { + fieldType = 'boolean'; + } else if (fields[field]?.type === 'string') { + fieldType = 'string'; + } return ( - {fields[field]?.label === 'Section' ? ( + {fieldType === 'sections' ? ( ) : null} - {fields[field]?.type === 'boolean' ? ( + {fieldType === 'boolean' ? ( onUpdate('value', next.toString())} /> ) : null} - {fields[field]?.label === 'Slug' ? ( + {fieldType === 'string' ? ( ) : null} - {fields[field]?.label === 'Theme' ? ( + {fieldType === 'themes' ? ( get_json_for_post( $post_id ); $this->assertEquals( '#000000', $json['componentTextStyles']['default-title']['textColor'] ); } + + /** + * Tests the ability to prepend arbitrary text to the title of an article before it is published. + */ + public function test_title_automation() { + $post_id = self::factory()->post->create( [ 'post_title' => 'Lorem Ipsum Dolor Sit Amet' ] ); + + // Create an automation routine for the title component. + $result = wp_insert_term( 'Opinion', 'category' ); + $term_id = $result['term_id']; + update_option( + 'apple_news_automation', + [ + [ + 'field' => 'title.prepend', + 'taxonomy' => 'category', + 'term_id' => $term_id, + 'value' => 'Opinion:', + ], + ] + ); + + // Set the taxonomy term to trigger the automation routine and ensure the title value is set properly. + wp_set_post_terms( $post_id, [ $term_id ], 'category' ); + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 'Opinion: Lorem Ipsum Dolor Sit Amet', $json['title'] ); + } } From b06cf4a922fbf9a574ee4e7df52d9b9f5a148d86 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 5 Mar 2024 15:24:29 -0500 Subject: [PATCH 003/125] WIP: APPLE-164 From 2a414faa439d2c73b5cabd839d3a1370cf0d6124 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 5 Mar 2024 15:47:42 -0500 Subject: [PATCH 004/125] APPLE-163 Add contentGenerationType automation feature --- admin/class-automation.php | 49 ++++++++++++++++++++++----- assets/js/admin-settings/rule.jsx | 16 ++++++++- tests/admin/test-class-automation.php | 27 +++++++++++++++ 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/admin/class-automation.php b/admin/class-automation.php index fbe8d020..283e3d0d 100644 --- a/admin/class-automation.php +++ b/admin/class-automation.php @@ -67,6 +67,7 @@ public static function init(): void { add_filter( 'apple_news_article_metadata', [ __CLASS__, 'filter__apple_news_article_metadata' ], 0, 2 ); add_filter( 'apple_news_exporter_slug', [ __CLASS__, 'filter__apple_news_exporter_slug' ], 0, 2 ); add_filter( 'apple_news_exporter_title', [ __CLASS__, 'filter__apple_news_exporter_title' ], 0, 2 ); + add_filter( 'apple_news_metadata', [ __CLASS__, 'filter__apple_news_metadata' ], 0, 2 ); } /** @@ -188,6 +189,33 @@ public static function filter__apple_news_exporter_title( $title, $post_id ) { return $title; } + /** + * Adds or sets metadata based on automation rules. + * + * @param array $meta Apple News metadata for a post. + * @param int $post_id The ID of the post. + * + * @return array The modified array of metadata. + */ + public static function filter__apple_news_metadata( $meta, $post_id ) { + // Trim down the list of matched rules to only those affecting metadata. + $metadata_rules = array_values( + array_filter( + self::get_automation_for_post( $post_id ), + function ( $rule ) { + return 'metadata' === self::get_fields()[ $rule['field'] ]['location'] ?? ''; + } + ) + ); + + // Loop through each matched rule and apply the value to metadata. + foreach ( $metadata_rules as $rule ) { + $meta[ $rule['field'] ] = $rule['value']; + } + + return $meta; + } + /** * Given a post ID, returns an array of matching automation rules. * @@ -230,42 +258,47 @@ public static function get_automation_rules(): array { */ public static function get_fields(): array { return [ - 'isHidden' => [ + 'contentGenerationType' => [ + 'location' => 'metadata', + 'type' => 'string', + 'label' => __( 'contentGenerationType', 'apple-news' ), + ], + 'isHidden' => [ 'location' => 'article_metadata', 'type' => 'boolean', 'label' => __( 'isHidden', 'apple-news' ), ], - 'isPaid' => [ + 'isPaid' => [ 'location' => 'article_metadata', 'type' => 'boolean', 'label' => __( 'isPaid', 'apple-news' ), ], - 'isPreview' => [ + 'isPreview' => [ 'location' => 'article_metadata', 'type' => 'boolean', 'label' => __( 'isPreview', 'apple-news' ), ], - 'isSponsored' => [ + 'isSponsored' => [ 'location' => 'article_metadata', 'type' => 'boolean', 'label' => __( 'isSponsored', 'apple-news' ), ], - 'links.sections' => [ + 'links.sections' => [ 'location' => 'article_metadata', 'type' => 'string', 'label' => __( 'Section', 'apple-news' ), ], - 'slug.#text#' => [ + 'slug.#text#' => [ 'location' => 'component', 'type' => 'string', 'label' => __( 'Slug', 'apple-news' ), ], - 'theme' => [ + 'theme' => [ 'location' => 'exporter', 'type' => 'string', 'label' => __( 'Theme', 'apple-news' ), ], - 'title.prepend' => [ + 'title.prepend' => [ 'location' => 'component', 'type' => 'string', 'label' => __( 'Title: Prepend Text', 'apple-news' ), diff --git a/assets/js/admin-settings/rule.jsx b/assets/js/admin-settings/rule.jsx index 08a35d1d..8d948bbd 100644 --- a/assets/js/admin-settings/rule.jsx +++ b/assets/js/admin-settings/rule.jsx @@ -29,7 +29,9 @@ function Rule({ themes, } = AppleNewsAutomationConfig; let fieldType = ''; - if (field === 'links.sections') { + if (field === 'contentGenerationType') { + fieldType = 'contentGenerationType'; + } else if (field === 'links.sections') { fieldType = 'sections'; } else if (field === 'theme') { fieldType = 'themes'; @@ -82,6 +84,18 @@ function Rule({ /> + {fieldType === 'contentGenerationType' ? ( + onUpdate('value', next)} + options={[ + { value: '', label: __('None', 'apple-news') }, + { value: 'AI', label: __('AI', 'apple-news') }, + ]} + value={value} + /> + ) : null} {fieldType === 'sections' ? ( assertEquals( 'Test Slug Value', $json['components'][0]['text'] ); } + /** + * Tests the functionality of using Automation to set the value of the contentGenerationType metadata. + */ + public function test_content_generation_type() { + $post_id = self::factory()->post->create(); + + // Create an automation routine for the slug component. + $result = wp_insert_term( 'AI Generated', 'category' ); + $term_id = $result['term_id']; + update_option( + 'apple_news_automation', + [ + [ + 'field' => 'contentGenerationType', + 'taxonomy' => 'category', + 'term_id' => $term_id, + 'value' => 'AI', + ], + ] + ); + + // Set the taxonomy term to trigger the automation routine and ensure the contentGenerationType is properly set. + wp_set_post_terms( $post_id, [ $term_id ], 'category' ); + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 'AI', $json['metadata']['contentGenerationType'] ); + } + /** * Ensures that named metadata is properly set via an automation process. * From ef7824bddc6ebe2f68fe974f06853cd54d407e55 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:03:37 -0500 Subject: [PATCH 005/125] APPLE-163 Remove i18n for labels that are field keys and shouldn't be translated --- admin/class-automation.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/admin/class-automation.php b/admin/class-automation.php index 283e3d0d..7595ac3f 100644 --- a/admin/class-automation.php +++ b/admin/class-automation.php @@ -261,27 +261,27 @@ public static function get_fields(): array { 'contentGenerationType' => [ 'location' => 'metadata', 'type' => 'string', - 'label' => __( 'contentGenerationType', 'apple-news' ), + 'label' => 'contentGenerationType', ], 'isHidden' => [ 'location' => 'article_metadata', 'type' => 'boolean', - 'label' => __( 'isHidden', 'apple-news' ), + 'label' => 'isHidden', ], 'isPaid' => [ 'location' => 'article_metadata', 'type' => 'boolean', - 'label' => __( 'isPaid', 'apple-news' ), + 'label' => 'isPaid', ], 'isPreview' => [ 'location' => 'article_metadata', 'type' => 'boolean', - 'label' => __( 'isPreview', 'apple-news' ), + 'label' => 'isPreview', ], 'isSponsored' => [ 'location' => 'article_metadata', 'type' => 'boolean', - 'label' => __( 'isSponsored', 'apple-news' ), + 'label' => 'isSponsored', ], 'links.sections' => [ 'location' => 'article_metadata', From c1ebadae62ea6c595740d233e4492129900064e5 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 8 Mar 2024 09:48:13 -0500 Subject: [PATCH 006/125] APPLE-165 Make custom fonts from channel available to the theme edit screen --- admin/apple-actions/index/class-channel.php | 28 +++++++++++++++++++++ admin/class-admin-apple-themes.php | 12 ++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 admin/apple-actions/index/class-channel.php diff --git a/admin/apple-actions/index/class-channel.php b/admin/apple-actions/index/class-channel.php new file mode 100644 index 00000000..0c07521f --- /dev/null +++ b/admin/apple-actions/index/class-channel.php @@ -0,0 +1,28 @@ +get_api()->get_channel( $this->get_setting( 'api_channel' ) ); + } +} diff --git a/admin/class-admin-apple-themes.php b/admin/class-admin-apple-themes.php index 7b8ca6a6..035a2e35 100644 --- a/admin/class-admin-apple-themes.php +++ b/admin/class-admin-apple-themes.php @@ -353,6 +353,15 @@ public function register_assets( $hook ) { return; } + // Get information about custom fonts from the channel. + require_once __DIR__ . '/apple-actions/index/class-channel.php'; + $admin_settings = new Admin_Apple_Settings(); + $channel_api = new \Apple_Actions\Index\Channel( $admin_settings->fetch_settings() ); + $channel = $channel_api->perform(); + $custom_fonts = ! empty( $channel->data->fonts ) && is_array( $channel->data->fonts ) + ? wp_list_pluck( $channel->data->fonts, 'name' ) + : []; + wp_enqueue_style( 'apple-news-themes-css', plugin_dir_url( __FILE__ ) . '../assets/css/themes.css', @@ -419,7 +428,8 @@ public function register_assets( $hook ) { 'apple-news-theme-edit-js', 'appleNewsThemeEdit', [ - 'fontNotice' => __( 'Font preview is only available on macOS', 'apple-news' ), + 'customFonts' => $custom_fonts, + 'fontNotice' => __( 'Font preview is only available on macOS', 'apple-news' ), ] ); } From bf473f67acee10aceeeb69ec20d5766e5594ed36 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 8 Mar 2024 10:24:37 -0500 Subject: [PATCH 007/125] WIP: APPLE-165 From f997550c78902ab32aaacb22617c10aa5bfa6120 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 8 Mar 2024 16:19:31 -0500 Subject: [PATCH 008/125] add custom fonts to get_fonts method --- includes/apple-exporter/class-theme.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index 9c776f1a..256b8e68 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -361,6 +361,18 @@ public static function get_active_theme_name() { * @return array The list of iOS fonts. */ public static function get_fonts() { + // Get custom fonts from this channel. + require_once plugin_dir_path( __DIR__ ) . '../admin/apple-actions/index/class-channel.php'; + $admin_settings = new \Admin_Apple_Settings(); + $channel_api = new \Apple_Actions\Index\Channel( $admin_settings->fetch_settings() ); + $channel = $channel_api->perform(); + $custom_fonts = ! empty( $channel->data->fonts ) && is_array( $channel->data->fonts ) + ? wp_list_pluck( $channel->data->fonts, 'name' ) + : []; + + $all_fonts = array_unique( array_merge( self::$fonts, $custom_fonts ) ); + sort( $all_fonts ); + /** * Allows the font list to be filtered, so that any custom * fonts that have been approved by Apple and added to your @@ -375,7 +387,7 @@ public static function get_fonts() { * * @param array $fonts An array of TrueType font names. */ - return apply_filters( 'apple_news_fonts_list', self::$fonts ); + return apply_filters( 'apple_news_fonts_list', $all_fonts ); } /** From 8a8d630eacab9da72db8ea21bd233987ed8c235e Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 8 Mar 2024 17:11:42 -0500 Subject: [PATCH 009/125] update font notice logic --- assets/js/theme-edit.js | 54 +++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index c364894c..8573e3d0 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -23,21 +23,45 @@ } function appleNewsSelectInit() { - // Only show fonts on Macs since they're system fonts - if ( appleNewsSupportsMacFeatures() ) { - $( '.select2.standard' ).select2(); - $( '.select2.font' ).select2({ - templateResult: appleNewsFontSelectTemplate, - templateSelection: appleNewsFontSelectTemplate - }); - } else { - $( '.select2' ).select2(); - $( 'span.select2' ).after( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after - $( '
' ) - .addClass( 'font-notice' ) - .text( appleNewsThemeEdit.fontNotice ) - ) - } + $( '.select2.standard' ).select2(); + $( '.select2.font' ).select2({ + templateResult: appleNewsFontSelectTemplate, + templateSelection: appleNewsFontSelectTemplate + }).on('select2:select', async function (e) { + var selectedFont = e.params.data.text; + var isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); + + // Remove any warnings. + $( 'span.select2' ).next( '.font-notice' ).remove(); + + // Check if font is installed. + var localFontInstalled = false; + if ( 'queryLocalFonts' in window ) { + try { + var availableFonts = await window.queryLocalFonts(); + localFontInstalled = availableFonts.some((font) => font.postscriptName === selectedFont); + } catch (err) { + console.error(err.name, err.message); + } + } + + if ( !localFontInstalled ) { + // Display notice if local font is not installed or cannot be determined. + if ( isCustomFont ) { + $( 'span.select2' ).after( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + $( '
' ) + .addClass( 'font-notice' ) + .text( appleNewsThemeEdit.customFontNotice ) + ); + } else if ( !appleNewsSupportsMacFeatures() ) { + $( 'span.select2' ).after( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + $( '
' ) + .addClass( 'font-notice' ) + .text( appleNewsThemeEdit.fontNotice ) + ); + } + } + }); } function appleNewsThemeEditBorderInit() { From c74aa7f52328be6ceeb26c7062562feda46215c4 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Mon, 11 Mar 2024 11:03:35 -0400 Subject: [PATCH 010/125] cleanup local font check --- admin/class-admin-apple-themes.php | 5 +-- assets/js/preview.js | 19 +++++++++++ assets/js/theme-edit.js | 52 ++++++++++++++---------------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/admin/class-admin-apple-themes.php b/admin/class-admin-apple-themes.php index 035a2e35..7d376817 100644 --- a/admin/class-admin-apple-themes.php +++ b/admin/class-admin-apple-themes.php @@ -428,8 +428,9 @@ public function register_assets( $hook ) { 'apple-news-theme-edit-js', 'appleNewsThemeEdit', [ - 'customFonts' => $custom_fonts, - 'fontNotice' => __( 'Font preview is only available on macOS', 'apple-news' ), + 'customFonts' => $custom_fonts, + 'customFontNotice' => __( 'Font preview may not be available', 'apple-news' ), + 'fontNotice' => __( 'Font preview is only available on macOS', 'apple-news' ), ] ); } diff --git a/assets/js/preview.js b/assets/js/preview.js index ecab201d..d04229a4 100644 --- a/assets/js/preview.js +++ b/assets/js/preview.js @@ -412,3 +412,22 @@ function appleNewsSupportsMacFeatures() { return false; } } + +/** + * Checks if a local font is installed. + * + * @async + * @param {string} selectedFont The PostScript name of the font to check. + * @returns {Promise} A promise that resolves to true if it can be + * determined that the font is installed locally, + * false otherwise. + */ +async function appleNewsLocalFontInstalled( selectedFont ) { + let localFonts = []; + if ( 'queryLocalFonts' in window ) { + localFonts = await window.queryLocalFonts({ + postscriptNames: [selectedFont], + }); + } + return localFonts.length !== 0; +} diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index 8573e3d0..0287004d 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -28,38 +28,34 @@ templateResult: appleNewsFontSelectTemplate, templateSelection: appleNewsFontSelectTemplate }).on('select2:select', async function (e) { + // Check if font preview is available. var selectedFont = e.params.data.text; var isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); - - // Remove any warnings. - $( 'span.select2' ).next( '.font-notice' ).remove(); - - // Check if font is installed. - var localFontInstalled = false; - if ( 'queryLocalFonts' in window ) { - try { - var availableFonts = await window.queryLocalFonts(); - localFontInstalled = availableFonts.some((font) => font.postscriptName === selectedFont); - } catch (err) { - console.error(err.name, err.message); - } + var localFontInstalled = await appleNewsLocalFontInstalled( selectedFont ); + var $fontNotice = $( 'span.select2' ).next( '.font-notice' ); + let noticeText = ''; + + if ( localFontInstalled ) { + // Local font is installed. Remove any warnings. + noticeText = ''; + } else if ( isCustomFont ) { + // Local font is not installed or cannot be determined. + noticeText = appleNewsThemeEdit.customFontNotice; + } else if ( ! appleNewsSupportsMacFeatures() ) { + // MacOS system font. + noticeText = appleNewsThemeEdit.fontNotice; } - if ( !localFontInstalled ) { - // Display notice if local font is not installed or cannot be determined. - if ( isCustomFont ) { - $( 'span.select2' ).after( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after - $( '
' ) - .addClass( 'font-notice' ) - .text( appleNewsThemeEdit.customFontNotice ) - ); - } else if ( !appleNewsSupportsMacFeatures() ) { - $( 'span.select2' ).after( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after - $( '
' ) - .addClass( 'font-notice' ) - .text( appleNewsThemeEdit.fontNotice ) - ); - } + if ( $fontNotice.length > 0 ) { + // Update existing notice. + $fontNotice.text( noticeText ); + } else if ( noticeText ) { + // Append new notice if it doesn't exist. + $( 'span.select2' ).after( + $('
') + .addClass( 'font-notice' ) + .text( noticeText ) + ); } }); } From 77fd71cef97b10b5f98099ed043d36a1cac7d330 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Mon, 11 Mar 2024 11:07:05 -0400 Subject: [PATCH 011/125] move function definition --- assets/js/preview.js | 19 ------------------- assets/js/theme-edit.js | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/assets/js/preview.js b/assets/js/preview.js index d04229a4..ecab201d 100644 --- a/assets/js/preview.js +++ b/assets/js/preview.js @@ -412,22 +412,3 @@ function appleNewsSupportsMacFeatures() { return false; } } - -/** - * Checks if a local font is installed. - * - * @async - * @param {string} selectedFont The PostScript name of the font to check. - * @returns {Promise} A promise that resolves to true if it can be - * determined that the font is installed locally, - * false otherwise. - */ -async function appleNewsLocalFontInstalled( selectedFont ) { - let localFonts = []; - if ( 'queryLocalFonts' in window ) { - localFonts = await window.queryLocalFonts({ - postscriptNames: [selectedFont], - }); - } - return localFonts.length !== 0; -} diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index 0287004d..dfb3630e 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -138,3 +138,22 @@ } }( jQuery ) ); + +/** + * Checks if a local font is installed. + * + * @async + * @param {string} selectedFont The PostScript name of the font to check. + * @returns {Promise} A promise that resolves to true if it can be + * determined that the font is installed locally, + * false otherwise. + */ +async function appleNewsLocalFontInstalled( selectedFont ) { + let localFonts = []; + if ( 'queryLocalFonts' in window ) { + localFonts = await window.queryLocalFonts({ + postscriptNames: [selectedFont], + }); + } + return localFonts.length !== 0; +} From cac54ef91c14ac8e2b3a363888d31496941db337 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Mon, 11 Mar 2024 11:23:08 -0400 Subject: [PATCH 012/125] testing phpunit --- includes/apple-exporter/class-theme.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index 256b8e68..b0a77f76 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -361,6 +361,7 @@ public static function get_active_theme_name() { * @return array The list of iOS fonts. */ public static function get_fonts() { + wp_die( 'get_fonts invoked' ); // Get custom fonts from this channel. require_once plugin_dir_path( __DIR__ ) . '../admin/apple-actions/index/class-channel.php'; $admin_settings = new \Admin_Apple_Settings(); From d5287b5de47c627d0df6f778474fef673a0ec976 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:52:51 -0400 Subject: [PATCH 013/125] APPLE-165 Fix unit tests making requests for channel data --- admin/apple-actions/index/class-channel.php | 12 ++++++++++-- includes/apple-exporter/class-theme.php | 1 - tests/bootstrap.php | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/admin/apple-actions/index/class-channel.php b/admin/apple-actions/index/class-channel.php index 0c07521f..0d1ac09c 100644 --- a/admin/apple-actions/index/class-channel.php +++ b/admin/apple-actions/index/class-channel.php @@ -20,9 +20,17 @@ class Channel extends API_Action { /** * Get the channel data from Apple News. * - * @return object An object containing the response from the API. + * @return object|null An object containing the response from the API or null on failure. */ public function perform() { - return $this->get_api()->get_channel( $this->get_setting( 'api_channel' ) ); + $channel = get_transient( 'apple_news_channel' ); + if ( false === $channel ) { + if ( $this->is_api_configuration_valid() ) { + $channel = $this->get_api()->get_channel( $this->get_setting( 'api_channel' ) ); + set_transient( 'apple_news_channel', $channel, 300 ); + } + } + + return ! empty( $channel ) ? $channel : null; } } diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index b0a77f76..256b8e68 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -361,7 +361,6 @@ public static function get_active_theme_name() { * @return array The list of iOS fonts. */ public static function get_fonts() { - wp_die( 'get_fonts invoked' ); // Get custom fonts from this channel. require_once plugin_dir_path( __DIR__ ) . '../admin/apple-actions/index/class-channel.php'; $admin_settings = new \Admin_Apple_Settings(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 550bbbae..e4638a0e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -85,6 +85,27 @@ function ( $protocols ) { } ); + // Pre-populate the channel transient to prevent Apple News from making a request to the API for channel data. + $channel_api_response = << Date: Mon, 11 Mar 2024 15:12:43 -0400 Subject: [PATCH 014/125] change var to const --- assets/js/theme-edit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index dfb3630e..e099117d 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -29,10 +29,10 @@ templateSelection: appleNewsFontSelectTemplate }).on('select2:select', async function (e) { // Check if font preview is available. - var selectedFont = e.params.data.text; - var isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); - var localFontInstalled = await appleNewsLocalFontInstalled( selectedFont ); - var $fontNotice = $( 'span.select2' ).next( '.font-notice' ); + const selectedFont = e.params.data.text; + const isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); + const localFontInstalled = await appleNewsLocalFontInstalled( selectedFont ); + const $fontNotice = $( 'span.select2' ).next( '.font-notice' ); let noticeText = ''; if ( localFontInstalled ) { From d4b30204aad8af65e1a6d313aa81c39fe15de617 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Mon, 11 Mar 2024 17:39:46 -0400 Subject: [PATCH 015/125] change back to var --- assets/js/theme-edit.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index e099117d..46508921 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -29,11 +29,11 @@ templateSelection: appleNewsFontSelectTemplate }).on('select2:select', async function (e) { // Check if font preview is available. - const selectedFont = e.params.data.text; - const isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); - const localFontInstalled = await appleNewsLocalFontInstalled( selectedFont ); - const $fontNotice = $( 'span.select2' ).next( '.font-notice' ); - let noticeText = ''; + var selectedFont = e.params.data.text; + var isCustomFont = appleNewsThemeEdit.customFonts.includes(selectedFont); + var localFontInstalled = await appleNewsLocalFontInstalled( selectedFont ); + var $fontNotice = $( 'span.select2' ).next( '.font-notice' ); + var noticeText = ''; if ( localFontInstalled ) { // Local font is installed. Remove any warnings. @@ -149,7 +149,7 @@ * false otherwise. */ async function appleNewsLocalFontInstalled( selectedFont ) { - let localFonts = []; + var localFonts = []; if ( 'queryLocalFonts' in window ) { localFonts = await window.queryLocalFonts({ postscriptNames: [selectedFont], From c4cb0be7045c21fa4d1eb26b4568722a531d591a Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:27:09 -0400 Subject: [PATCH 016/125] WIP: APPLE-166 From 2445e7597231cc07f6df044d354de0781c9ffd68 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:03:54 -0400 Subject: [PATCH 017/125] APPLE-166 Add failing test for new in article module --- .../components/test-class-in-article.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/apple-exporter/components/test-class-in-article.php diff --git a/tests/apple-exporter/components/test-class-in-article.php b/tests/apple-exporter/components/test-class-in-article.php new file mode 100644 index 00000000..347b81f4 --- /dev/null +++ b/tests/apple-exporter/components/test-class-in-article.php @@ -0,0 +1,79 @@ +post->create( + [ + 'post_content' => << +

Paragraph 1

+ + + +

Paragraph 2

+ + + +

Paragraph 3

+ + + +

Paragraph 4

+ + + +

Paragraph 5

+ +HTML, + ] + ); + + // Ensure that an in-article component does not exist if it isn't configured. + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 8, count( $json['components'] ) ); + $this->assertEquals( '

Paragraph 1

', $json['components'][3]['text'] ); + $this->assertEquals( '

Paragraph 5

', $json['components'][7]['text'] ); + + // Configure an in-article component in theme settings. + $this->set_theme_settings( + [ + 'json_templates' => [ + 'in_article' => [ + 'json' => [ + 'role' => 'heading', + 'text' => 'In Article Module Test', + 'format' => 'html', + 'textStyle' => 'default-heading-1', + 'layout' => 'heading-layout', + ], + 'layout' => [], + 'position' => 3, + ], + ], + ] + ); + + // Fetch the JSON for the article again and confirm that the in-article module is inserted at the proper position. + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 9, count( $json['components'] ) ); + $this->assertEquals( '

Paragraph 1

', $json['components'][3]['text'] ); + $this->assertEquals( 'In Article Module Test', $json['components'][6]['text'] ); + $this->assertEquals( '

Paragraph 5

', $json['components'][8]['text'] ); + } +} From 4885a5842c559d9f61d20909d59d565efdcc53a5 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:19:14 -0400 Subject: [PATCH 018/125] APPLE-166 Add basic use case of inserting module after 3rd block --- .../builders/class-components.php | 12 ++++ .../class-component-factory.php | 1 + .../components/class-in-article.php | 62 +++++++++++++++++++ .../components/test-class-in-article.php | 9 +-- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 includes/apple-exporter/components/class-in-article.php diff --git a/includes/apple-exporter/builders/class-components.php b/includes/apple-exporter/builders/class-components.php index 103909ad..c457ad29 100644 --- a/includes/apple-exporter/builders/class-components.php +++ b/includes/apple-exporter/builders/class-components.php @@ -905,6 +905,18 @@ private function split_into_components() { $theme = Theme::get_used(); $json_templates = $theme->get_value( 'json_templates' ); + // Insert in-article module at the proper position, if set. + if ( ! empty( $json_templates['in_article']['json'] ) ) { + // TODO: Fetch this value from settings. + $position = 3; + $components = array_merge( + array_slice( $components, 0, $position ), + [ Component_Factory::get_component( 'in-article', '' ) ], + array_slice( $components, $position ) + ); + } + + // Insert end of article module, if set. if ( ! empty( $json_templates['end_of_article']['json'] ) ) { $components[] = Component_Factory::get_component( 'end-of-article', '' ); } diff --git a/includes/apple-exporter/class-component-factory.php b/includes/apple-exporter/class-component-factory.php index ec07e2b6..43276cdd 100644 --- a/includes/apple-exporter/class-component-factory.php +++ b/includes/apple-exporter/class-component-factory.php @@ -118,6 +118,7 @@ public static function initialize( self::register_component( 'slug', '\\Apple_Exporter\\Components\\Slug' ); self::register_component( 'advertisement', '\\Apple_Exporter\\Components\\Advertisement' ); self::register_component( 'end-of-article', '\\Apple_Exporter\\Components\\End_Of_Article' ); + self::register_component( 'in-article', '\\Apple_Exporter\\Components\\In_Article' ); /** * Allows you to add custom component classes to the plugin. diff --git a/includes/apple-exporter/components/class-in-article.php b/includes/apple-exporter/components/class-in-article.php new file mode 100644 index 00000000..4346a37e --- /dev/null +++ b/includes/apple-exporter/components/class-in-article.php @@ -0,0 +1,62 @@ +register_spec( + 'json', + __( 'JSON', 'apple-news' ), + [] + ); + $this->register_spec( + 'layout', + __( 'Layout', 'apple-news' ), + [] + ); + } + + /** + * Build the component. + * + * @param string $html The HTML to parse into text for processing. + */ + protected function build( $html ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $this->register_json( + 'json', + [] + ); + + $this->set_layout(); + } + + /** + * Set the layout for the component. + * + * @access private + */ + private function set_layout() { + $this->register_full_width_layout( + 'in-article-layout', + 'layout', + [], + 'layout' + ); + } +} diff --git a/tests/apple-exporter/components/test-class-in-article.php b/tests/apple-exporter/components/test-class-in-article.php index 347b81f4..af9db4c8 100644 --- a/tests/apple-exporter/components/test-class-in-article.php +++ b/tests/apple-exporter/components/test-class-in-article.php @@ -55,25 +55,26 @@ public function test_in_article_insertion() { [ 'json_templates' => [ 'in_article' => [ - 'json' => [ + 'json' => [ 'role' => 'heading', 'text' => 'In Article Module Test', 'format' => 'html', 'textStyle' => 'default-heading-1', 'layout' => 'heading-layout', ], - 'layout' => [], - 'position' => 3, + 'layout' => [], ], ], ] ); - // Fetch the JSON for the article again and confirm that the in-article module is inserted at the proper position. + // Fetch the JSON for the article again and confirm that the in-article module is inserted at the default position (after the third item). $json = $this->get_json_for_post( $post_id ); $this->assertEquals( 9, count( $json['components'] ) ); $this->assertEquals( '

Paragraph 1

', $json['components'][3]['text'] ); $this->assertEquals( 'In Article Module Test', $json['components'][6]['text'] ); $this->assertEquals( '

Paragraph 5

', $json['components'][8]['text'] ); + + // TODO: Test additional position values. } } From 4245a19458cfb05ca2f1a7b988da53515ac65aa4 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:39:53 -0400 Subject: [PATCH 019/125] APPLE-166 Add support for different positions for in article module --- ...lass-admin-apple-settings-section-advanced.php | 15 ++++++++++----- includes/REST/apple-news-get-settings.php | 7 +++++-- .../apple-exporter/builders/class-components.php | 12 ++++++++++-- includes/apple-exporter/class-settings.php | 1 + .../components/test-class-in-article.php | 15 ++++++++++++++- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/admin/settings/class-admin-apple-settings-section-advanced.php b/admin/settings/class-admin-apple-settings-section-advanced.php index c2c44d96..a7d5867f 100644 --- a/admin/settings/class-admin-apple-settings-section-advanced.php +++ b/admin/settings/class-admin-apple-settings-section-advanced.php @@ -32,22 +32,22 @@ 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' => [ + 'html_support' => [ 'label' => __( 'Enable HTML support?', 'apple-news' ), 'type' => [ 'yes', 'no' ], 'description' => sprintf( @@ -57,6 +57,11 @@ public function __construct( $page ) { '' ), ], + 'in_article_position' => [ + 'label' => __( 'Position of In Article Module', 'apple-news' ), + 'type' => 'integer', + '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' ), + ], ]; // Add the groups. @@ -71,7 +76,7 @@ public function __construct( $page ) { ], 'format' => [ 'label' => __( 'Format Settings', 'apple-news' ), - 'settings' => [ 'html_support' ], + 'settings' => [ 'html_support', 'in_article_position' ], ], ]; diff --git a/includes/REST/apple-news-get-settings.php b/includes/REST/apple-news-get-settings.php index bd9c2e36..46a33def 100644 --- a/includes/REST/apple-news-get-settings.php +++ b/includes/REST/apple-news-get-settings.php @@ -7,6 +7,7 @@ namespace Apple_News\REST; +use Apple_Exporter\Settings; use Apple_News\Admin\Automation; /** @@ -24,8 +25,9 @@ function get_settings_response( $data ) { // phpcs:ignore Generic.CodeAnalysis.U } // Compile non-sensitive plugin settings into a JS-friendly format and return. - $admin_settings = new \Admin_Apple_Settings(); - $settings = $admin_settings->fetch_settings(); + $admin_settings = new \Admin_Apple_Settings(); + $settings = $admin_settings->fetch_settings(); + $default_settings = ( new Settings() )->all(); return [ 'adminUrl' => esc_url_raw( admin_url( 'admin.php?page=apple-news-options' ) ), 'automaticAssignment' => ! empty( Automation::get_automation_rules() ), @@ -35,6 +37,7 @@ function get_settings_response( $data ) { // phpcs:ignore Generic.CodeAnalysis.U 'apiAutosyncUpdate' => 'yes' === $settings->api_autosync_update, 'fullBleedImages' => 'yes' === $settings->full_bleed_images, 'htmlSupport' => 'yes' === $settings->html_support, + 'inArticlePosition' => is_int( $settings->in_article_position ) ? $settings->in_article_position : $default_settings['in_article_position'], 'postTypes' => ! empty( $settings->post_types ) && is_array( $settings->post_types ) ? array_map( 'sanitize_text_field', $settings->post_types ) : [], 'showMetabox' => 'yes' === $settings->show_metabox, 'useRemoteImages' => 'yes' === $settings->use_remote_images, diff --git a/includes/apple-exporter/builders/class-components.php b/includes/apple-exporter/builders/class-components.php index c457ad29..68880309 100644 --- a/includes/apple-exporter/builders/class-components.php +++ b/includes/apple-exporter/builders/class-components.php @@ -14,6 +14,7 @@ use Apple_Exporter\Component_Factory; use Apple_Exporter\Components\Component; use Apple_Exporter\Components\Image; +use Apple_Exporter\Settings; use Apple_Exporter\Theme; use Apple_Exporter\Workspace; use Apple_News; @@ -907,8 +908,15 @@ private function split_into_components() { // Insert in-article module at the proper position, if set. if ( ! empty( $json_templates['in_article']['json'] ) ) { - // TODO: Fetch this value from settings. - $position = 3; + $position = $this->get_setting( 'in_article_position' ); + if ( is_int( $position ) ) { + if ( $position < 0 ) { + $position = 0; + } + } else { + $default_settings = ( new Settings() )->all(); + $position = $default_settings['in_article_position']; + } $components = array_merge( array_slice( $components, 0, $position ), [ Component_Factory::get_component( 'in-article', '' ) ], diff --git a/includes/apple-exporter/class-settings.php b/includes/apple-exporter/class-settings.php index 56d567cf..fefb2220 100644 --- a/includes/apple-exporter/class-settings.php +++ b/includes/apple-exporter/class-settings.php @@ -45,6 +45,7 @@ class Settings { 'component_alerts' => 'none', 'full_bleed_images' => 'no', 'html_support' => 'yes', + 'in_article_position' => 3, 'post_types' => [ 'post' ], 'show_metabox' => 'yes', 'use_remote_images' => 'yes', diff --git a/tests/apple-exporter/components/test-class-in-article.php b/tests/apple-exporter/components/test-class-in-article.php index af9db4c8..4b04daae 100644 --- a/tests/apple-exporter/components/test-class-in-article.php +++ b/tests/apple-exporter/components/test-class-in-article.php @@ -75,6 +75,19 @@ public function test_in_article_insertion() { $this->assertEquals( 'In Article Module Test', $json['components'][6]['text'] ); $this->assertEquals( '

Paragraph 5

', $json['components'][8]['text'] ); - // TODO: Test additional position values. + // Test insertion at the beginning. + $this->settings->in_article_position = 0; + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 'In Article Module Test', $json['components'][3]['text'] ); + + // Test insertion at the end. + $this->settings->in_article_position = 5; + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 'In Article Module Test', $json['components'][8]['text'] ); + + // Test overflow insertion. + $this->settings->in_article_position = 99; + $json = $this->get_json_for_post( $post_id ); + $this->assertEquals( 'In Article Module Test', $json['components'][8]['text'] ); } } From 3db59b5867d8a96a64a356d5d0bb7649fd619c58 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:04:06 -0400 Subject: [PATCH 020/125] APPLE-166 Allow position of in article module to be configured on the settings screen --- ...-admin-apple-settings-section-advanced.php | 5 +- .../class-admin-apple-settings-section.php | 58 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/admin/settings/class-admin-apple-settings-section-advanced.php b/admin/settings/class-admin-apple-settings-section-advanced.php index a7d5867f..b4ad9e66 100644 --- a/admin/settings/class-admin-apple-settings-section-advanced.php +++ b/admin/settings/class-admin-apple-settings-section-advanced.php @@ -59,7 +59,10 @@ public function __construct( $page ) { ], 'in_article_position' => [ 'label' => __( 'Position of In Article Module', 'apple-news' ), - 'type' => 'integer', + 'type' => 'number', + 'min' => 0, + 'max' => 99, + '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' ), ], ]; diff --git a/admin/settings/class-admin-apple-settings-section.php b/admin/settings/class-admin-apple-settings-section.php index d3221583..aa101859 100644 --- a/admin/settings/class-admin-apple-settings-section.php +++ b/admin/settings/class-admin-apple-settings-section.php @@ -122,6 +122,8 @@ class Admin_Apple_Settings_Section extends Apple_News { 'name' => [], 'value' => [], 'placeholder' => [], + 'min' => [], + 'max' => [], 'step' => [], 'type' => [], 'required' => [], @@ -260,7 +262,7 @@ public function render_field( $args ) { $type = $this->get_type_for( $name ); - // If the field has it's own render callback, use that here. + // If the field has its own render callback, use that here. // This is because the options page doesn't actually use do_settings_section. if ( ! empty( $callback ) ) { return call_user_func( $callback, $type ); @@ -320,6 +322,8 @@ public function render_field( $args ) { $field = ''; } elseif ( 'textarea' === $type ) { $field = ''; + } elseif ( 'number' === $type ) { + $field = ''; } else { // If nothing else matches, it's a string. $field = ''; @@ -365,6 +369,18 @@ public function render_field( $args ) { esc_attr( $name ), esc_attr( $value ) ); + } elseif ( 'number' === $type ) { + return sprintf( + $field, + esc_attr( $name ), + esc_attr( $name ), + esc_attr( $value ), + 5, + $this->get_min_for( $name ), + $this->get_max_for( $name ), + $this->get_step_for( $name ), + esc_attr( $this->is_required( $name ) ) + ); } else { return sprintf( $field, @@ -374,7 +390,6 @@ public function render_field( $args ) { intval( $size ), esc_attr( $this->is_required( $name ) ) ); - } } @@ -404,6 +419,45 @@ protected function get_description_for( $name ) { return empty( $this->settings[ $name ]['description'] ) ? '' : $this->settings[ $name ]['description']; } + /** + * Get the maximum value for a field. + * + * @param string $name The name of the field for which to fetch a maximum value. + * + * @return int|float|string The maximum value, or an empty string if not set. + */ + protected function get_max_for( $name ) { + return isset( $this->settings[ $name ]['max'] ) && ( is_int( $this->settings[ $name ]['max'] ) || is_float( $this->settings[ $name ]['max'] ) ) + ? $this->settings[ $name ]['max'] + : ''; + } + + /** + * Get the minimum value for a field. + * + * @param string $name The name of the field for which to fetch a minimum value. + * + * @return int|float|string The minimum value, or an empty string if not set. + */ + protected function get_min_for( $name ) { + return isset( $this->settings[ $name ]['min'] ) && ( is_int( $this->settings[ $name ]['min'] ) || is_float( $this->settings[ $name ]['min'] ) ) + ? $this->settings[ $name ]['min'] + : ''; + } + + /** + * Get the stepping value for a field. + * + * @param string $name The name of the field for which to fetch a stepping value. + * + * @return int|float The stepping value, or a default value of 1 if not set. + */ + protected function get_step_for( $name ) { + return isset( $this->settings[ $name ]['step'] ) && ( is_int( $this->settings[ $name ]['step'] ) || is_float( $this->settings[ $name ]['step'] ) ) + ? $this->settings[ $name ]['step'] + : 1; + } + /** * Get the size for a field. * From c3033906371003587af7842da73f72dec8b8f5cd Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:20:52 -0400 Subject: [PATCH 021/125] APPLE-166 Fix bugs with setting and reading numeric values in settings --- admin/class-admin-apple-settings.php | 2 +- includes/apple-exporter/builders/class-components.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/admin/class-admin-apple-settings.php b/admin/class-admin-apple-settings.php index 8dbc5adb..7e6e76b1 100644 --- a/admin/class-admin-apple-settings.php +++ b/admin/class-admin-apple-settings.php @@ -225,7 +225,7 @@ public function fetch_settings() { // Merge settings in the option with defaults. foreach ( $settings->all() as $key => $value ) { - $wp_value = ( empty( $wp_settings[ $key ] ) ) + $wp_value = ( ! isset( $wp_settings[ $key ] ) ) ? $value : $wp_settings[ $key ]; $settings->$key = $wp_value; diff --git a/includes/apple-exporter/builders/class-components.php b/includes/apple-exporter/builders/class-components.php index 68880309..5b3bd6a5 100644 --- a/includes/apple-exporter/builders/class-components.php +++ b/includes/apple-exporter/builders/class-components.php @@ -909,7 +909,8 @@ private function split_into_components() { // Insert in-article module at the proper position, if set. if ( ! empty( $json_templates['in_article']['json'] ) ) { $position = $this->get_setting( 'in_article_position' ); - if ( is_int( $position ) ) { + if ( is_numeric( $position ) ) { + $position = (int) $position; if ( $position < 0 ) { $position = 0; } From d3d5626614ccd6b324909efac6c6bde837138252 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:21:25 -0400 Subject: [PATCH 022/125] Ready for review From ea31b42831362d3509d8badb2a22c97b77158b7d Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:34:22 -0400 Subject: [PATCH 023/125] APPLE-166 Add escaping for number fields, fix bug in REST API endpoint when getting value for in article position --- admin/settings/class-admin-apple-settings-section.php | 6 +++--- includes/REST/apple-news-get-settings.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/settings/class-admin-apple-settings-section.php b/admin/settings/class-admin-apple-settings-section.php index aa101859..4598d508 100644 --- a/admin/settings/class-admin-apple-settings-section.php +++ b/admin/settings/class-admin-apple-settings-section.php @@ -376,9 +376,9 @@ public function render_field( $args ) { esc_attr( $name ), esc_attr( $value ), 5, - $this->get_min_for( $name ), - $this->get_max_for( $name ), - $this->get_step_for( $name ), + esc_attr( $this->get_min_for( $name ) ), + esc_attr( $this->get_max_for( $name ) ), + esc_attr( $this->get_step_for( $name ) ), esc_attr( $this->is_required( $name ) ) ); } else { diff --git a/includes/REST/apple-news-get-settings.php b/includes/REST/apple-news-get-settings.php index 46a33def..fedaeff4 100644 --- a/includes/REST/apple-news-get-settings.php +++ b/includes/REST/apple-news-get-settings.php @@ -37,7 +37,7 @@ function get_settings_response( $data ) { // phpcs:ignore Generic.CodeAnalysis.U 'apiAutosyncUpdate' => 'yes' === $settings->api_autosync_update, 'fullBleedImages' => 'yes' === $settings->full_bleed_images, 'htmlSupport' => 'yes' === $settings->html_support, - 'inArticlePosition' => is_int( $settings->in_article_position ) ? $settings->in_article_position : $default_settings['in_article_position'], + 'inArticlePosition' => is_numeric( $settings->in_article_position ) ? (int) $settings->in_article_position : $default_settings['in_article_position'], 'postTypes' => ! empty( $settings->post_types ) && is_array( $settings->post_types ) ? array_map( 'sanitize_text_field', $settings->post_types ) : [], 'showMetabox' => 'yes' === $settings->show_metabox, 'useRemoteImages' => 'yes' === $settings->use_remote_images, From 4a0460c41c22424d5bf38b198bb34590b82775cd Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:33:58 -0400 Subject: [PATCH 024/125] APPLE-163 Only prepend to metadata titles via Automation --- admin/class-automation.php | 17 +++++++++-------- tests/admin/test-class-automation.php | 4 ++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/admin/class-automation.php b/admin/class-automation.php index 7595ac3f..6d330194 100644 --- a/admin/class-automation.php +++ b/admin/class-automation.php @@ -66,7 +66,7 @@ public static function init(): void { add_filter( 'apple_news_active_theme', [ __CLASS__, 'filter__apple_news_active_theme' ], 0, 2 ); add_filter( 'apple_news_article_metadata', [ __CLASS__, 'filter__apple_news_article_metadata' ], 0, 2 ); add_filter( 'apple_news_exporter_slug', [ __CLASS__, 'filter__apple_news_exporter_slug' ], 0, 2 ); - add_filter( 'apple_news_exporter_title', [ __CLASS__, 'filter__apple_news_exporter_title' ], 0, 2 ); + add_filter( 'apple_news_generate_json', [ __CLASS__, 'filter__apple_news_generate_json' ], 0, 2 ); add_filter( 'apple_news_metadata', [ __CLASS__, 'filter__apple_news_metadata' ], 0, 2 ); } @@ -171,22 +171,23 @@ public static function filter__apple_news_exporter_slug( $slug, $post_id ) { } /** - * Applies automation rules affecting the post title. + * Applies automation rules after the JSON has been generated. * - * @param string $title The title the plugin wants to use. - * @param int $post_id The post ID associated with the title. + * @param array $json Generated JSON for the article. + * @param int $post_id The post ID associated with the JSON. * - * @return string The filtered title value. + * @return array Filtered JSON for the article. */ - public static function filter__apple_news_exporter_title( $title, $post_id ) { + public static function filter__apple_news_generate_json( $json, $post_id ) { + $title = $json['title'] ?? ''; $rules = self::get_automation_for_post( $post_id ); foreach ( $rules as $rule ) { if ( 'title.prepend' === ( $rule['field'] ?? '' ) ) { - $title = sprintf( '%s %s', $rule['value'] ?? '', $title ); + $json['title'] = sprintf( '%s %s', $rule['value'] ?? '', $title ); } } - return $title; + return $json; } /** diff --git a/tests/admin/test-class-automation.php b/tests/admin/test-class-automation.php index acc2a8c4..d3fa2b42 100644 --- a/tests/admin/test-class-automation.php +++ b/tests/admin/test-class-automation.php @@ -380,5 +380,9 @@ public function test_title_automation() { wp_set_post_terms( $post_id, [ $term_id ], 'category' ); $json = $this->get_json_for_post( $post_id ); $this->assertEquals( 'Opinion: Lorem Ipsum Dolor Sit Amet', $json['title'] ); + + // Ensure that the title modification only applies to the metadata title and not the visible title in the component tree. + $this->assertEquals( 'title', $json['components'][0]['role'] ); + $this->assertEquals( 'Lorem Ipsum Dolor Sit Amet', $json['components'][0]['text'] ); } } From 90eeb2390045546bc508d2a072634712bae1331a Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:17:00 -0400 Subject: [PATCH 025/125] WIP: APPLE-167 From 24f5b21114c60aac87d7b9b934c9679c4ee3f619 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:42:43 -0400 Subject: [PATCH 026/125] APPLE-167 Change apple_news_is_* flags to support true, false, or channel default in push --- admin/apple-actions/index/class-push.php | 30 +++++------ admin/class-admin-apple-news.php | 14 ++---- .../apple-actions/index/test-class-push.php | 50 ++++++++++++------- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/admin/apple-actions/index/class-push.php b/admin/apple-actions/index/class-push.php index 09e066a7..d3f22c24 100644 --- a/admin/apple-actions/index/class-push.php +++ b/admin/apple-actions/index/class-push.php @@ -331,21 +331,21 @@ private function push( $user_id = null ) { $meta['data']['links'] = [ 'sections' => $this->sections ]; } - // Get the isPreview setting. - $is_paid = (bool) get_post_meta( $this->id, 'apple_news_is_paid', true ); - $meta['data']['isPaid'] = $is_paid; - - // Get the isPreview setting. - $is_preview = (bool) get_post_meta( $this->id, 'apple_news_is_preview', true ); - $meta['data']['isPreview'] = $is_preview; - - // Get the isHidden setting. - $is_hidden = (bool) get_post_meta( $this->id, 'apple_news_is_hidden', true ); - $meta['data']['isHidden'] = $is_hidden; - - // Get the isSponsored setting. - $is_sponsored = (bool) get_post_meta( $this->id, 'apple_news_is_sponsored', true ); - $meta['data']['isSponsored'] = $is_sponsored; + // Set boolean metadata. Don't set values at all if they are not set in postmeta, or are set to empty string. + $metadata_keys = [ + 'isHidden' => 'apple_news_is_hidden', + 'isPaid' => 'apple_news_is_paid', + 'isPreview' => 'apple_news_is_preview', + 'isSponsored' => 'apple_news_is_sponsored', + ]; + foreach ( $metadata_keys as $metadata_property => $meta_key ) { + $meta_value = get_post_meta( $this->id, $meta_key, true ); + if ( 'true' === $meta_value || '1' === $meta_value ) { + $meta['data'][ $metadata_property ] = true; + } elseif ( 'false' === $meta_value || '0' === $meta_value ) { + $meta['data'][ $metadata_property ] = false; + } + } // Get the maturity rating setting. $maturity_rating = get_post_meta( $this->id, 'apple_news_maturity_rating', true ); diff --git a/admin/class-admin-apple-news.php b/admin/class-admin-apple-news.php index fc39a0e4..defebb57 100644 --- a/admin/class-admin-apple-news.php +++ b/admin/class-admin-apple-news.php @@ -122,20 +122,16 @@ public function __construct() { 'default' => '', ], 'apple_news_is_hidden' => [ - 'default' => false, - 'type' => 'boolean', + 'default' => '', ], 'apple_news_is_paid' => [ - 'default' => false, - 'type' => 'boolean', + 'default' => '', ], 'apple_news_is_preview' => [ - 'default' => false, - 'type' => 'boolean', + 'default' => '', ], 'apple_news_is_sponsored' => [ - 'default' => false, - 'type' => 'boolean', + 'default' => '', ], 'apple_news_maturity_rating' => [ 'default' => '', @@ -192,7 +188,7 @@ function ( $key ) { } ) : $meta_keys; - } + } ); add_action( diff --git a/tests/admin/apple-actions/index/test-class-push.php b/tests/admin/apple-actions/index/test-class-push.php index a6738214..e25f3819 100644 --- a/tests/admin/apple-actions/index/test-class-push.php +++ b/tests/admin/apple-actions/index/test-class-push.php @@ -21,10 +21,26 @@ class Apple_News_Admin_Action_Index_Push_Test extends Apple_News_Testcase { */ public function data_metadata() { return [ - [ 'apple_news_is_hidden', true, false, false, false ], - [ 'apple_news_is_paid', false, true, false, false ], - [ 'apple_news_is_preview', false, false, true, false ], - [ 'apple_news_is_sponsored', false, false, false, true ], + [ 'apple_news_is_hidden', 'true', 'isHidden', true ], + [ 'apple_news_is_hidden', 'false', 'isHidden', false ], + [ 'apple_news_is_hidden', '1', 'isHidden', true ], + [ 'apple_news_is_hidden', '0', 'isHidden', false ], + [ 'apple_news_is_hidden', null, 'isHidden', null ], + [ 'apple_news_is_paid', 'true', 'isPaid', true ], + [ 'apple_news_is_paid', 'false', 'isPaid', false ], + [ 'apple_news_is_paid', '1', 'isPaid', true ], + [ 'apple_news_is_paid', '0', 'isPaid', false ], + [ 'apple_news_is_paid', null, 'isPaid', null ], + [ 'apple_news_is_preview', 'true', 'isPreview', true ], + [ 'apple_news_is_preview', 'false', 'isPreview', false ], + [ 'apple_news_is_preview', '1', 'isPreview', true ], + [ 'apple_news_is_preview', '0', 'isPreview', false ], + [ 'apple_news_is_preview', null, 'isPreview', null ], + [ 'apple_news_is_sponsored', 'true', 'isSponsored', true ], + [ 'apple_news_is_sponsored', 'false', 'isSponsored', false ], + [ 'apple_news_is_sponsored', '1', 'isSponsored', true ], + [ 'apple_news_is_sponsored', '0', 'isSponsored', false ], + [ 'apple_news_is_sponsored', null, 'isSponsored', null ], ]; } @@ -194,23 +210,23 @@ public function test_maturity_rating() { * * @dataProvider data_metadata * - * @param string $meta_key The meta key to set to true (e.g., apple_news_is_hidden). - * @param bool $is_hidden The expected value for isHidden in the request. - * @param bool $is_paid The expected value for isPaid in the request. - * @param bool $is_preview The expected value for isPreview in the request. - * @param bool $is_sponsored The expected value for isSponsored in the request. + * @param string $meta_key The meta key to set (e.g., apple_news_is_hidden). + * @param string|null $meta_value The meta value to set, or null if the meta key should not be set at all. + * @param string $property The metadata property to check. + * @param string|null $expected The expected value for the property, or null if it is expected to not be set. */ - public function test_metadata( $meta_key, $is_hidden, $is_paid, $is_preview, $is_sponsored ) { + public function test_metadata( $meta_key, $meta_value, $property, $expected ) { $post_id = self::factory()->post->create(); - add_post_meta( $post_id, $meta_key, true ); + if ( ! is_null( $meta_value ) ) { + add_post_meta( $post_id, $meta_key, $meta_value ); + } $request = $this->get_request_for_post( $post_id ); $metadata = $this->get_metadata_from_request( $request ); - - // Check the values for the four metadata keys against expected values. - $this->assertEquals( $is_hidden, $metadata['data']['isHidden'] ); - $this->assertEquals( $is_paid, $metadata['data']['isPaid'] ); - $this->assertEquals( $is_preview, $metadata['data']['isPreview'] ); - $this->assertEquals( $is_sponsored, $metadata['data']['isSponsored'] ); + if ( ! is_null( $expected ) ) { + $this->assertEquals( $expected, $metadata['data'][ $property ], sprintf( 'Expected property %s to be %s given meta key %s and meta value %s', $property, $expected, $meta_key, $meta_value ) ); + } else { + $this->assertArrayNotHasKey( $property, $metadata['data'] ?? [], sprintf( 'Expected property %s to not exist, but it does', $property ) ); + } } /** From 66d1a0763e70b558b97975d22edbbf441ea5e013 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:07:31 -0400 Subject: [PATCH 027/125] APPLE-167 Update metabox test to use new metadata format for flags --- .../test-class-admin-apple-meta-boxes.php | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/admin/test-class-admin-apple-meta-boxes.php b/tests/admin/test-class-admin-apple-meta-boxes.php index 99771e66..81475aa3 100644 --- a/tests/admin/test-class-admin-apple-meta-boxes.php +++ b/tests/admin/test-class-admin-apple-meta-boxes.php @@ -31,9 +31,10 @@ public function test_save_no_auto_sync() { /* phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */ $_POST['post_ID'] = $post_id; $_POST['apple_news_sections'] = [ 'https://news-api.apple.com/sections/1234567890' ]; - $_POST['apple_news_is_paid'] = 0; - $_POST['apple_news_is_preview'] = 0; - $_POST['apple_news_is_sponsored'] = 0; + $_POST['apple_news_is_hidden'] = ''; + $_POST['apple_news_is_paid'] = ''; + $_POST['apple_news_is_preview'] = ''; + $_POST['apple_news_is_sponsored'] = ''; $_POST['apple_news_maturity_rating'] = 'MATURE'; $_POST['apple_news_pullquote'] = 'test pullquote'; $_POST['apple_news_pullquote_position'] = 'middle'; @@ -51,9 +52,10 @@ public function test_save_no_auto_sync() { // Check the meta values. $this->assertEquals( [ 'https://news-api.apple.com/sections/1234567890' ], get_post_meta( $post_id, 'apple_news_sections', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_paid', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_preview', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_sponsored', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_hidden', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_paid', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_preview', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_sponsored', true ) ); $this->assertEquals( 'MATURE', get_post_meta( $post_id, 'apple_news_maturity_rating', true ) ); $this->assertEquals( 'test pullquote', get_post_meta( $post_id, 'apple_news_pullquote', true ) ); $this->assertEquals( 'middle', get_post_meta( $post_id, 'apple_news_pullquote_position', true ) ); @@ -74,9 +76,10 @@ public function test_save_with_auto_sync() { /* phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized */ $_POST['post_ID'] = $post_id; $_POST['apple_news_sections'] = [ 'https://news-api.apple.com/sections/1234567890' ]; - $_POST['apple_news_is_paid'] = 0; - $_POST['apple_news_is_preview'] = 0; - $_POST['apple_news_is_sponsored'] = 0; + $_POST['apple_news_is_hidden'] = ''; + $_POST['apple_news_is_paid'] = ''; + $_POST['apple_news_is_preview'] = ''; + $_POST['apple_news_is_sponsored'] = ''; $_POST['apple_news_maturity_rating'] = 'MATURE'; $_POST['apple_news_pullquote'] = 'test pullquote'; $_POST['apple_news_pullquote_position'] = 'middle'; @@ -94,9 +97,10 @@ public function test_save_with_auto_sync() { // Check the meta values. $this->assertEquals( [ 'https://news-api.apple.com/sections/1234567890' ], get_post_meta( $post_id, 'apple_news_sections', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_paid', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_preview', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_is_sponsored', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_hidden', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_paid', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_preview', true ) ); + $this->assertEquals( '', get_post_meta( $post_id, 'apple_news_is_sponsored', true ) ); $this->assertEquals( 'MATURE', get_post_meta( $post_id, 'apple_news_maturity_rating', true ) ); $this->assertEquals( 'test pullquote', get_post_meta( $post_id, 'apple_news_pullquote', true ) ); $this->assertEquals( 'middle', get_post_meta( $post_id, 'apple_news_pullquote_position', true ) ); From 29b154aaf84ab8e3a02efb04a14f5aa7a1a34d8d Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:17:18 -0400 Subject: [PATCH 028/125] APPLE-167 Update automation screen to treat is* flags as boolean selects, with true/false/empty options --- admin/class-automation.php | 8 ++++---- assets/js/admin-settings/rule.jsx | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/admin/class-automation.php b/admin/class-automation.php index 6d330194..9732d618 100644 --- a/admin/class-automation.php +++ b/admin/class-automation.php @@ -266,22 +266,22 @@ public static function get_fields(): array { ], 'isHidden' => [ 'location' => 'article_metadata', - 'type' => 'boolean', + 'type' => 'string', 'label' => 'isHidden', ], 'isPaid' => [ 'location' => 'article_metadata', - 'type' => 'boolean', + 'type' => 'string', 'label' => 'isPaid', ], 'isPreview' => [ 'location' => 'article_metadata', - 'type' => 'boolean', + 'type' => 'string', 'label' => 'isPreview', ], 'isSponsored' => [ 'location' => 'article_metadata', - 'type' => 'boolean', + 'type' => 'string', 'label' => 'isSponsored', ], 'links.sections' => [ diff --git a/assets/js/admin-settings/rule.jsx b/assets/js/admin-settings/rule.jsx index 8d948bbd..7ff51878 100644 --- a/assets/js/admin-settings/rule.jsx +++ b/assets/js/admin-settings/rule.jsx @@ -31,6 +31,8 @@ function Rule({ let fieldType = ''; if (field === 'contentGenerationType') { fieldType = 'contentGenerationType'; + } else if (['isHidden', 'isPaid', 'isPreview', 'isSponsored'].includes(field)) { + fieldType = 'boolean-select'; } else if (field === 'links.sections') { fieldType = 'sections'; } else if (field === 'theme') { @@ -108,6 +110,19 @@ function Rule({ 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' ? ( Date: Thu, 21 Mar 2024 16:14:47 -0400 Subject: [PATCH 029/125] APPLE-167 Update Gutenberg edit screen to convert is* fields to dropdowns --- assets/js/pluginsidebar/panels/metadata.jsx | 44 +++++++++++++++------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/assets/js/pluginsidebar/panels/metadata.jsx b/assets/js/pluginsidebar/panels/metadata.jsx index 6fdd7ab9..4549d9e2 100644 --- a/assets/js/pluginsidebar/panels/metadata.jsx +++ b/assets/js/pluginsidebar/panels/metadata.jsx @@ -37,29 +37,49 @@ function Metadata({ initialOpen={false} title={__('Metadata', 'apple-news')} > - - -
-
+ + can_be_anchor_target && empty( $this->uid ); } + /** + * Returns whether this component can be a parent (have subcomponents). + * + * @since 2.5.0 + * @return boolean + */ + public function can_be_parent() { + return $this->can_be_parent; + } + /** * Get the current UID. * From 340d42710b81f0f104968fe29ca8ce2952fddffd Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 29 May 2024 07:09:16 -0400 Subject: [PATCH 113/125] Refactor method of getting subcomponent specs --- admin/class-admin-apple-json.php | 39 +++++++++++---- .../apple-exporter/class-component-spec.php | 27 ++++++++-- .../components/class-component.php | 49 +++++-------------- 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/admin/class-admin-apple-json.php b/admin/class-admin-apple-json.php index 25736a79..229ce35b 100644 --- a/admin/class-admin-apple-json.php +++ b/admin/class-admin-apple-json.php @@ -210,7 +210,7 @@ public function page_json_render() { : ''; // If we have a component or subcomponent, get its specs. - $specs = $this->get_specs( $selected_subcomponent ?: $selected_component ); + $specs = $this->get_specs( $selected_component, $selected_subcomponent ); /* phpcs:enable */ @@ -282,8 +282,11 @@ private function reset_json() { return; } + // Get the selected subcomponent. + $subcomponent = $this->get_selected_subcomponent(); + // Get the specs for the component. - $specs = $this->get_specs( $component ); + $specs = $this->get_specs( $component, $subcomponent ); if ( empty( $specs ) ) { \Admin_Apple_Notice::error( sprintf( @@ -339,9 +342,12 @@ private function save_json() { return; } - // Get the specs for the component and theme. + // Get the selected subcomponent. + $subcomponent = $this->get_selected_subcomponent(); + + // Get the specs for the component or subcomponent. $theme = sanitize_text_field( wp_unslash( $_POST['apple_news_theme'] ) ); - $specs = $this->get_specs( $component, $theme ); + $specs = $this->get_specs( $component, $subcomponent ); if ( empty( $specs ) ) { \Admin_Apple_Notice::error( sprintf( @@ -390,24 +396,37 @@ private function save_json() { /** * Given a component slug, returns the associated component class. * - * @param string $component The component to get the class for. + * @param string $component The component to get the class for. + * @param ?string $subcomponent The subcomponent to get the class for. * * @return ?Component */ - private function get_component_class( $component ) { + private function get_component_class( $component, $subcomponent = null ) { $classname = $this->namespace . $component; - return class_exists( $classname ) ? new $classname() : null; + + // If we aren't requesting a subcomponent, just return the class. + if ( empty( $subcomponent ) ) { + return class_exists( $classname ) ? new $classname() : null; + } + + // If we are requesting a subcomponent, create its class and set it as the parent, then return. + $child_classname = $this->get_component_class( $subcomponent ); + + return class_exists( $classname ) && class_exists( $child_classname ) + ? new $child_classname( null, null, null, null, null, null, null, new $classname ) + : null; } /** * Loads the JSON specs that can be customized for the component * - * @param string $component The component to get specs for. + * @param string $component The component to get specs for. + * @param ?string $subcomponent The subcomponent to get specs for. * * @return array */ - private function get_specs( $component ) { - $component_class = $this->get_component_class( $component ); + private function get_specs( $component, $subcomponent = null ) { + $component_class = $this->get_component_class( $component, $subcomponent ); return $component_class ? $component_class->get_specs() : []; } diff --git a/includes/apple-exporter/class-component-spec.php b/includes/apple-exporter/class-component-spec.php index 70a53712..926043be 100644 --- a/includes/apple-exporter/class-component-spec.php +++ b/includes/apple-exporter/class-component-spec.php @@ -52,21 +52,31 @@ class Component_Spec { */ public $spec; + /** + * The parent component name, if any. + * + * @since 2.5.0 + * @var ?string + */ + public $parent; + /** * Initializes the object with the name, label and the spec. * - * @param string $component The component name. - * @param string $name The spec name. - * @param string $label The human-readable label for the spec. - * @param array $spec The spec definition. + * @param string $component The component name. + * @param string $name The spec name. + * @param string $label The human-readable label for the spec. + * @param array $spec The spec definition. + * @param ?string $parent The parent component name. * * @access public */ - public function __construct( $component, $name, $label, $spec ) { + public function __construct( $component, $name, $label, $spec, $parent = null ) { $this->component = $component; $this->name = $name; $this->label = $label; $this->spec = $spec; + $this->parent = $parent; } /** @@ -231,6 +241,8 @@ public function find_tokens( $spec, &$tokens ) { */ public function save( $spec, $theme_name = '' ) { + // TODO: Handle subcomponents. + // Check for empty JSON. $json = $this->spec; if ( empty( $spec ) ) { @@ -449,6 +461,11 @@ public function get_override( $theme_name = '' ) { return null; } + // Determine if there is a subcomponent override in the theme. + if ( ! empty( $json_templates[ $this->parent ]['subcomponents'][ $this->key_from_name( $this->component ) ][ $this->name ] ) ) { + return $json_templates[ $this->parent ]['subcomponents'][ $this->key_from_name( $this->component ) ][ $this->name ]; + } + // Determine if there is an override in the theme. $component = $this->key_from_name( $this->component ); if ( ! empty( $json_templates[ $component ][ $this->name ] ) ) { diff --git a/includes/apple-exporter/components/class-component.php b/includes/apple-exporter/components/class-component.php index 17ac10b3..fa4d7b6b 100644 --- a/includes/apple-exporter/components/class-component.php +++ b/includes/apple-exporter/components/class-component.php @@ -254,15 +254,6 @@ public function __construct( $component_styles = null, $parent = null ) { - // Register specs for this component. - $this->register_specs(); - - // If all params are null, then this was just used to get spec data. - // Exit. - if ( 0 === func_num_args() ) { - return; - } - $this->workspace = $workspace; $this->settings = $settings; $this->styles = $styles; @@ -272,6 +263,15 @@ public function __construct( $this->parent = $parent; $this->json = null; + // Register specs for this component. + $this->register_specs(); + + // If all params are null, then this was just used to get spec data. + // Exit. + if ( 0 === func_num_args() ) { + return; + } + // Negotiate parser. if ( empty( $parser ) ) { @@ -562,7 +562,7 @@ protected function set_setting( $name, $value ) { */ protected function register_spec( $name, $label, $spec ) { // Store as a multidimensional array with the label and spec, indexed by name. - $this->specs[ $name ] = new Component_Spec( $this->get_component_name(), $name, $label, $spec ); + $this->specs[ $name ] = new Component_Spec( $this->get_component_name(), $name, $label, $spec, $this->is_subcomponent() ? $this->parent->get_component_name() : null ); } /** @@ -598,34 +598,11 @@ protected function get_component_object_key( $name ) { * @since 1.2.4 */ protected function get_spec( $spec_name ) { - $subcomponent_spec = $this->get_subcomponent_spec( $spec_name ); - if ( ! empty( $subcomponent_spec ) ) { - return $subcomponent_spec; - } elseif ( isset( $this->specs[ $spec_name ] ) ) { - return $this->specs[ $spec_name ]; - } - - return null; - } - - /** - * Given a spec name, tries to find it in subcomponent spec definitions in the theme. - * - * @param string $spec_name The spec name to look up. - * - * @return ?Component_Spec Array containing subcomponent spec if found, null otherwise. - */ - protected function get_subcomponent_spec( $spec_name ) { - if ( $this->is_subcomponent() ) { - $theme = Theme::get_used(); - $json_templates = $theme->get_value( 'json_templates' ) ?: []; - $spec = $json_templates[ $this->parent->get_component_name() ]['subcomponents'][ $this->get_component_name() ][ $spec_name ] ?? null; - if ( ! empty( $spec ) ) { - return new Component_Spec( $this->get_component_name(), $this->get_component_object_key( $spec_name ), '', $spec ); - } + if ( ! isset( $this->specs[ $spec_name ] ) ) { + return null; } - return null; + return $this->specs[ $spec_name ]; } /** From 6c3523747fda7c6f0049fc11ac3bef05982e072b Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 29 May 2024 07:11:12 -0400 Subject: [PATCH 114/125] Clean up the diff a bit --- admin/class-admin-apple-json.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/admin/class-admin-apple-json.php b/admin/class-admin-apple-json.php index 229ce35b..89366e14 100644 --- a/admin/class-admin-apple-json.php +++ b/admin/class-admin-apple-json.php @@ -393,6 +393,19 @@ private function save_json() { } } + /** + * Loads the JSON specs that can be customized for the component + * + * @param string $component The component to get specs for. + * @param ?string $subcomponent The subcomponent to get specs for. + * + * @return array + */ + private function get_specs( $component, $subcomponent = null ) { + $component_class = $this->get_component_class( $component, $subcomponent ); + return $component_class ? $component_class->get_specs() : []; + } + /** * Given a component slug, returns the associated component class. * @@ -417,19 +430,6 @@ private function get_component_class( $component, $subcomponent = null ) { : null; } - /** - * Loads the JSON specs that can be customized for the component - * - * @param string $component The component to get specs for. - * @param ?string $subcomponent The subcomponent to get specs for. - * - * @return array - */ - private function get_specs( $component, $subcomponent = null ) { - $component_class = $this->get_component_class( $component, $subcomponent ); - return $component_class ? $component_class->get_specs() : []; - } - /** * Lists all components that can be customized * From 3dfd9755eb2584de62313b4ca7196089041ba61e Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 29 May 2024 07:18:55 -0400 Subject: [PATCH 115/125] Fix get_component_class --- admin/class-admin-apple-json.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/admin/class-admin-apple-json.php b/admin/class-admin-apple-json.php index 89366e14..3d5e571c 100644 --- a/admin/class-admin-apple-json.php +++ b/admin/class-admin-apple-json.php @@ -416,18 +416,17 @@ private function get_specs( $component, $subcomponent = null ) { */ private function get_component_class( $component, $subcomponent = null ) { $classname = $this->namespace . $component; - - // If we aren't requesting a subcomponent, just return the class. - if ( empty( $subcomponent ) ) { - return class_exists( $classname ) ? new $classname() : null; + if ( ! class_exists( $classname ) ) { + return null; } - // If we are requesting a subcomponent, create its class and set it as the parent, then return. - $child_classname = $this->get_component_class( $subcomponent ); + // Handle subcomponents. + if ( ! empty( $subcomponent ) ) { + $subcomponent_classname = $this->namespace . $subcomponent; + return class_exists( $subcomponent_classname ) ? new $subcomponent_classname( null, null, null, null, null, null, null, new $classname() ) : null; + } - return class_exists( $classname ) && class_exists( $child_classname ) - ? new $child_classname( null, null, null, null, null, null, null, new $classname ) - : null; + return new $classname(); } /** From 403bda077251f19193c1c20c5f4e0e7ab51554ae Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Thu, 30 May 2024 12:09:25 -0400 Subject: [PATCH 116/125] Handle JSON spec save/delete --- .../apple-exporter/class-component-spec.php | 29 ++++++++++++------- includes/apple-exporter/class-theme.php | 5 ++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/includes/apple-exporter/class-component-spec.php b/includes/apple-exporter/class-component-spec.php index 926043be..cb75331b 100644 --- a/includes/apple-exporter/class-component-spec.php +++ b/includes/apple-exporter/class-component-spec.php @@ -241,8 +241,6 @@ public function find_tokens( $spec, &$tokens ) { */ public function save( $spec, $theme_name = '' ) { - // TODO: Handle subcomponents. - // Check for empty JSON. $json = $this->spec; if ( empty( $spec ) ) { @@ -323,9 +321,15 @@ public function save( $spec, $theme_name = '' ) { $theme_settings['json_templates'] = []; } - // Try to load the custom JSON into the theme. + // Set the JSON template for this component spec. $component_key = $this->key_from_name( $this->component ); - $theme_settings['json_templates'][ $component_key ][ $this->name ] = $json; + if ( $this->parent ) { + $theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] = $json; + } else { + $theme_settings['json_templates'][ $component_key ][ $this->name ] = $json; + } + + // Try to load the custom JSON into the theme. if ( ! $theme->load( $theme_settings ) ) { Admin_Apple_Notice::error( sprintf( @@ -376,16 +380,21 @@ public function delete( $theme_name = '' ) { return false; } - // Determine if this spec override is defined in the theme. + // Remove the JSON template for this component spec or fail if it doesn't exist. $component_key = $this->key_from_name( $this->component ); $theme_settings = $theme->all_settings(); - if ( ! isset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ) ) { - return false; + if ( $this->parent ) { + if ( ! isset( $theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] ) ) { + return false; + } + unset( $theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] ); + } else { + if ( ! isset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ) ) { + return false; + } + unset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ); } - // Remove this spec from the theme. - unset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ); - // If there are no more overrides for this component, remove it. if ( empty( $theme_settings['json_templates'][ $component_key ] ) ) { unset( $theme_settings['json_templates'][ $component_key ] ); diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index a17f3cbe..94338696 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -2681,6 +2681,11 @@ private function validate_json_templates() { // Clean up root-level components list. $invalid_components = array_filter( $invalid_components ); } + + // Allow subcomponents through. + if ( isset( $invalid_components[ $component_key ]['subcomponents'] ) ) { + unset( $invalid_components[ $component_key ]['subcomponents'] ); + } } // If there are any invalid components, fail. From 861e530a031e4e083683e51dc5aa6d052cf92f15 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:12:19 -0500 Subject: [PATCH 117/125] WIP: issue-1132 From e3704825a6b8103e14615defa54dd2dd72564fee Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:15:49 -0500 Subject: [PATCH 118/125] Completely remove advertising settings --- README.md | 1 - assets/themes/classic.json | 3 - assets/themes/colorful.json | 3 - assets/themes/dark.json | 3 - assets/themes/default.json | 3 - assets/themes/modern.json | 3 - assets/themes/pastel.json | 3 - .../builders/class-advertising-settings.php | 69 --------- .../class-component-factory.php | 1 - includes/apple-exporter/class-exporter.php | 1 - includes/apple-exporter/class-theme.php | 26 ---- .../components/class-advertisement.php | 74 ---------- .../components/class-component.php | 3 +- readme.txt | 1 - tests/admin/test-class-admin-apple-themes.php | 31 ++-- .../test-class-advertising-settings.php | 137 ------------------ .../builders/test-class-components.php | 1 - .../components/test-class-advertisement.php | 60 -------- 18 files changed, 11 insertions(+), 412 deletions(-) delete mode 100644 includes/apple-exporter/builders/class-advertising-settings.php delete mode 100644 includes/apple-exporter/components/class-advertisement.php delete mode 100644 tests/apple-exporter/builders/test-class-advertising-settings.php delete mode 100644 tests/apple-exporter/components/test-class-advertisement.php diff --git a/README.md b/README.md index 2367642c..0aeec17b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ The Publish to Apple News plugin enables your WordPress content to be published - Control individual posts with options to publish, update, or delete. - Publish individual posts or in bulk. - Handles image galleries and popular embeds like YouTube and Vimeo that are supported by Apple News. -- Automatically adjust advertisement settings. Please visit our [wiki](https://github.com/alleyinteractive/apple-news/wiki) for detailed information on the follow items: diff --git a/assets/themes/classic.json b/assets/themes/classic.json index f562a3e6..29ea13b6 100644 --- a/assets/themes/classic.json +++ b/assets/themes/classic.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "#d3d3d3", "aside_border_color": "#000000", @@ -58,7 +56,6 @@ "dropcap_number_of_lines": 5, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#000000", "header1_font": "Baskerville", diff --git a/assets/themes/colorful.json b/assets/themes/colorful.json index 12ea9fd5..eacb84e9 100644 --- a/assets/themes/colorful.json +++ b/assets/themes/colorful.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "#ffff00", "aside_border_color": "#3045ca", @@ -64,7 +62,6 @@ "dropcap_number_of_lines": 5, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#000000", "header1_font": "HelveticaNeue-CondensedBlack", diff --git a/assets/themes/dark.json b/assets/themes/dark.json index 7ed7e40d..3e7ac239 100644 --- a/assets/themes/dark.json +++ b/assets/themes/dark.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "", "aside_border_color": "#ffe890", @@ -64,7 +62,6 @@ "dropcap_number_of_lines": 5, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#ffffff", "header1_font": "HelveticaNeue-CondensedBold", diff --git a/assets/themes/default.json b/assets/themes/default.json index 3e72d27c..11319f70 100644 --- a/assets/themes/default.json +++ b/assets/themes/default.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "#e1e1e1", "aside_border_color": "#4f4f4f", @@ -64,7 +62,6 @@ "dropcap_number_of_lines": 4, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#333333", "header1_font": "AvenirNext-Bold", diff --git a/assets/themes/modern.json b/assets/themes/modern.json index fb2d4c69..133ce0ba 100644 --- a/assets/themes/modern.json +++ b/assets/themes/modern.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "#e6f7ff", "aside_border_color": "#30bcff", @@ -64,7 +62,6 @@ "dropcap_number_of_lines": 4, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#000000", "header1_font": "AvenirNext-Bold", diff --git a/assets/themes/pastel.json b/assets/themes/pastel.json index e56cc644..2ce65b3e 100644 --- a/assets/themes/pastel.json +++ b/assets/themes/pastel.json @@ -1,6 +1,4 @@ { - "ad_frequency": 5, - "ad_margin": 15, "aside_alignment": "right", "aside_background_color": "#ffebe0", "aside_border_color": "#ffffff", @@ -64,7 +62,6 @@ "dropcap_number_of_lines": 5, "dropcap_number_of_raised_lines": 0, "dropcap_padding": 5, - "enable_advertisement": "yes", "gallery_type": "gallery", "header1_color": "#000000", "header1_font": "BodoniSvtyTwoOSITCTT-Book", diff --git a/includes/apple-exporter/builders/class-advertising-settings.php b/includes/apple-exporter/builders/class-advertising-settings.php deleted file mode 100644 index 30949806..00000000 --- a/includes/apple-exporter/builders/class-advertising-settings.php +++ /dev/null @@ -1,69 +0,0 @@ -get_value( 'enable_advertisement' ); - $ad_frequency = intval( $theme->get_value( 'ad_frequency' ) ); - - if ( 'yes' === $enable_advertisement && $ad_frequency > 0 ) { - - // Build basic advertisement configuration settings. - $advertising_settings = [ - 'bannerType' => 'any', - 'distanceFromMedia' => '10vh', - 'enabled' => true, - 'frequency' => $ad_frequency, - ]; - - // Add the ad margin, if defined. - $ad_margin = intval( $theme->get_value( 'ad_margin' ) ); - if ( ! empty( $ad_margin ) ) { - $advertising_settings['layout'] = [ - 'margin' => $ad_margin, - ]; - } - } - - /** - * Filters the advertisement settings. - * - * @since 0.4.0 - * - * @param array $advertising_settings The advertising settings to be modified. - */ - $advertising_settings = apply_filters( 'apple_news_advertising_settings', $advertising_settings, $this->content_id() ); - - // If there are no advertising settings, bail out. - if ( empty( $advertising_settings ) ) { - return []; - } - - // Wrap the advertising settings in the advertisement key. - return [ - 'advertisement' => $advertising_settings, - ]; - } -} diff --git a/includes/apple-exporter/class-component-factory.php b/includes/apple-exporter/class-component-factory.php index dee97d49..91eb783f 100644 --- a/includes/apple-exporter/class-component-factory.php +++ b/includes/apple-exporter/class-component-factory.php @@ -121,7 +121,6 @@ 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( 'advertisement', '\\Apple_Exporter\\Components\\Advertisement' ); 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-exporter.php b/includes/apple-exporter/class-exporter.php index 500d2581..47306d40 100644 --- a/includes/apple-exporter/class-exporter.php +++ b/includes/apple-exporter/class-exporter.php @@ -92,7 +92,6 @@ public function initialize_builders( $builders = null ) { $this->register_builder( 'textStyles', new Builders\Text_Styles( $this->content, $this->settings ) ); $this->register_builder( 'componentLayouts', new Builders\Component_Layouts( $this->content, $this->settings ) ); $this->register_builder( 'metadata', new Builders\Metadata( $this->content, $this->settings ) ); - $this->register_builder( 'autoplacement', new Builders\Advertising_Settings( $this->content, $this->settings ) ); } Component_Factory::initialize( diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index 94338696..cc8599bd 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -556,18 +556,6 @@ private static function sort_registry( $registry ) { */ private static function initialize_options() { self::$options = [ - 'ad_frequency' => [ - 'default' => 5, - 'description' => __( 'A number between 1 and 10 defining the frequency for automatically inserting dynamic advertisements into articles. For more information, see the Apple News Format Reference.', 'apple-news' ), - 'label' => __( 'Ad Frequency', 'apple-news' ), - 'type' => 'integer', - ], - 'ad_margin' => [ - 'default' => 15, - 'description' => __( 'The margin to use above and below inserted ads.', 'apple-news' ), - 'label' => __( 'Ad Margin', 'apple-news' ), - 'type' => 'integer', - ], 'aside_alignment' => [ 'default' => 'right', 'label' => __( 'Aside component alignment', 'apple-news' ), @@ -964,12 +952,6 @@ private static function initialize_options() { 'label' => __( 'Drop cap padding', 'apple-news' ), 'type' => 'integer', ], - 'enable_advertisement' => [ - 'default' => 'yes', - 'label' => __( 'Enable advertisements', 'apple-news' ), - 'options' => [ 'yes', 'no' ], - 'type' => 'select', - ], 'gallery_type' => [ 'default' => 'gallery', 'label' => __( 'Gallery type', 'apple-news' ), @@ -2536,14 +2518,6 @@ private function initialize_groups() { 'description' => __( 'Can either be a standard gallery, or mosaic.', 'apple-news' ), 'settings' => [ 'gallery_type' ], ], - 'advertisement' => [ - 'label' => __( 'Advertisement', 'apple-news' ), - 'settings' => [ - 'enable_advertisement', - 'ad_frequency', - 'ad_margin', - ], - ], 'aside' => [ 'label' => __( 'Aside', 'apple-news' ), 'description' => __( 'Content that is not directly related to the article.', 'apple-news' ), diff --git a/includes/apple-exporter/components/class-advertisement.php b/includes/apple-exporter/components/class-advertisement.php deleted file mode 100644 index 9b88d406..00000000 --- a/includes/apple-exporter/components/class-advertisement.php +++ /dev/null @@ -1,74 +0,0 @@ -register_spec( - 'json', - __( 'JSON', 'apple-news' ), - [ - 'role' => 'banner_advertisement', - 'bannerType' => 'standard', - ] - ); - - $this->register_spec( - 'layout', - __( 'Layout', 'apple-news' ), - [ - 'margin' => [ - 'top' => 25, - 'bottom' => 25, - ], - ] - ); - } - - /** - * Build the component. - * - * @param string $html The HTML to parse into text for processing. - * @access protected - */ - protected function build( $html ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $this->register_json( - 'json', - [] - ); - - $this->set_layout(); - } - - /** - * Set the layout for the component. - * - * @access private - */ - private function set_layout() { - $this->register_full_width_layout( - 'advertisement-layout', - 'layout', - [], - 'layout' - ); - } -} diff --git a/includes/apple-exporter/components/class-component.php b/includes/apple-exporter/components/class-component.php index fa4d7b6b..b7a300c8 100644 --- a/includes/apple-exporter/components/class-component.php +++ b/includes/apple-exporter/components/class-component.php @@ -44,8 +44,7 @@ abstract class Component { /** * Anchorable components are anchored to the previous element that appears in - * the position specified. If the previous element is an advertisement, - * attaches to the next instead of the previous element. + * the position specified. * * @since 0.6.0 * @var int diff --git a/readme.txt b/readme.txt index 395cf6aa..1d71d3f1 100644 --- a/readme.txt +++ b/readme.txt @@ -23,7 +23,6 @@ The 'Publish to Apple News' plugin enables WordPress sites with approved Apple N * Control individual posts with options to publish, update, or delete. * Publish individual posts or in bulk. * Handles image galleries and popular embeds like YouTube and Vimeo that are supported by Apple News. -* Automatically adjust advertisement settings. To enable content from your WordPress site to be published to your Apple News channel, you must obtain and enter Apple News API credentials from Apple. diff --git a/tests/admin/test-class-admin-apple-themes.php b/tests/admin/test-class-admin-apple-themes.php index 26d32cc7..05224b40 100644 --- a/tests/admin/test-class-admin-apple-themes.php +++ b/tests/admin/test-class-admin-apple-themes.php @@ -212,19 +212,10 @@ public function test_delete_theme() { public function test_import_theme() { // Setup. - $advertisement_json = [ - 'role' => 'banner_advertisement', - 'bannerType' => 'double_height', - ]; - $import_settings = [ - 'layout_margin' => 100, - 'layout_gutter' => 20, - 'json_templates' => [ - 'advertisement' => [ - 'json' => $advertisement_json, - ], - ], - 'theme_name' => 'Test Import Theme', + $import_settings = [ + 'layout_margin' => 100, + 'layout_gutter' => 20, + 'theme_name' => 'Test Import Theme', ]; // Test. @@ -235,10 +226,6 @@ public function test_import_theme() { $theme_settings = $theme->all_settings(); $this->assertEquals( 100, $theme_settings['layout_margin'] ); $this->assertEquals( 20, $theme_settings['layout_gutter'] ); - $this->assertEquals( - $advertisement_json, - $theme_settings['json_templates']['advertisement']['json'] - ); // Cleanup. $theme->delete(); @@ -336,14 +323,16 @@ public function test_json_save_custom_spec() { $this->create_default_theme(); $json = <<assertTrue( $theme->load() ); $theme_settings = $theme->all_settings(); $stored_json = wp_json_encode( - $theme_settings['json_templates']['advertisement']['json'], + $theme_settings['json_templates']['body']['json'], JSON_PRETTY_PRINT ); $this->assertEquals( $stored_json, $json ); diff --git a/tests/apple-exporter/builders/test-class-advertising-settings.php b/tests/apple-exporter/builders/test-class-advertising-settings.php deleted file mode 100644 index b07e564a..00000000 --- a/tests/apple-exporter/builders/test-class-advertising-settings.php +++ /dev/null @@ -1,137 +0,0 @@ -content, $this->content_settings ); - $this->assertEquals( - [ - 'advertisement' => [ - 'bannerType' => 'any', - 'distanceFromMedia' => '10vh', - 'enabled' => true, - 'frequency' => 5, - 'layout' => [ - 'margin' => 15, - ], - ], - ], - $builder->to_array() - ); - } - - /** - * Tests the behavior of the component when advertisements are disabled. - */ - public function test_no_ads() { - - // Setup. - $settings = $this->theme->all_settings(); - $settings['enable_advertisement'] = 'no'; - $this->theme->load( $settings ); - $this->assertTrue( $this->theme->save() ); - - // Test. - $builder = new Advertising_Settings( $this->content, $this->content_settings ); - $result = $builder->to_array(); - $this->assertEquals( 0, count( $result ) ); - } - - /** - * Tests the ability to customize ad frequency. - */ - public function test_custom_ad_frequency() { - - // Setup. - $settings = $this->theme->all_settings(); - $settings['ad_frequency'] = 10; - $this->theme->load( $settings ); - $this->assertTrue( $this->theme->save() ); - - // Test. - $builder = new Advertising_Settings( $this->content, $this->content_settings ); - $this->assertEquals( - [ - 'advertisement' => [ - 'bannerType' => 'any', - 'distanceFromMedia' => '10vh', - 'enabled' => true, - 'frequency' => 10, - 'layout' => [ - 'margin' => 15, - ], - ], - ], - $builder->to_array() - ); - } - - /** - * Tests the ability to customize the ad margin. - */ - public function test_custom_ad_margin() { - - // Setup. - $settings = $this->theme->all_settings(); - $settings['ad_margin'] = 20; - $this->theme->load( $settings ); - $this->assertTrue( $this->theme->save() ); - - // Test. - $builder = new Advertising_Settings( $this->content, $this->content_settings ); - $this->assertEquals( - [ - 'advertisement' => [ - 'bannerType' => 'any', - 'distanceFromMedia' => '10vh', - 'enabled' => true, - 'frequency' => 5, - 'layout' => [ - 'margin' => 20, - ], - ], - ], - $builder->to_array() - ); - } - - /** - * Tests the article-level automatic advertisement settings. - */ - public function test_autoplacement() { - $post_id = self::factory()->post->create(); - $json = $this->get_json_for_post( $post_id ); - $this->assertEquals( - [ - 'advertisement' => [ - 'bannerType' => 'any', - 'distanceFromMedia' => '10vh', - 'enabled' => true, - 'frequency' => 5, - 'layout' => [ - 'margin' => 15, - ], - ], - ], - $json['autoplacement'] - ); - } -} diff --git a/tests/apple-exporter/builders/test-class-components.php b/tests/apple-exporter/builders/test-class-components.php index 59e98e01..e63f11bf 100644 --- a/tests/apple-exporter/builders/test-class-components.php +++ b/tests/apple-exporter/builders/test-class-components.php @@ -321,7 +321,6 @@ public function test_image_bundling() { public function test_meta_component_ordering( $order, $expected, $components ) { $this->set_theme_settings( [ - 'enable_advertisement' => 'no', 'meta_component_order' => $order, ] ); diff --git a/tests/apple-exporter/components/test-class-advertisement.php b/tests/apple-exporter/components/test-class-advertisement.php deleted file mode 100644 index f7e08be7..00000000 --- a/tests/apple-exporter/components/test-class-advertisement.php +++ /dev/null @@ -1,60 +0,0 @@ -workspace, - $this->settings, - $this->styles, - $this->layouts - ); - $json = $component->to_array(); - - $this->assertEquals( 'banner_advertisement', $json['role'] ); - $this->assertEquals( 'standard', $json['bannerType'] ); - } - - /** - * Tests the behavior of the apple_news_advertisement_json filter. - */ - public function test_filter() { - $component = new Advertisement( - null, - $this->workspace, - $this->settings, - $this->styles, - $this->layouts - ); - - add_filter( - 'apple_news_advertisement_json', - function ( $json ) { - $json['bannerType'] = 'double_height'; - return $json; - } - ); - - $json = $component->to_array(); - $this->assertEquals( 'double_height', $json['bannerType'] ); - } -} From 294072d3a177e972d460ee3788c5cd0f5e5a0907 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:25:10 -0500 Subject: [PATCH 119/125] Add note about advertising settings to changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 1d71d3f1..d2b02dae 100644 --- a/readme.txt +++ b/readme.txt @@ -47,6 +47,7 @@ Please visit our [wiki](https://github.com/alleyinteractive/apple-news/wiki) for = 2.5.0 = +* Breaking Change: Removed support for per-article advertising settings, which have been deprecated by Apple. Advertising settings can now only be set at the channel level. * Enhancement: Added support for an aside component. To use, specify the class for the container that includes the aside content in the plugin settings. * Enhancement: Added support for the Footnotes block. * Enhancement: Added a new In Article module, similar to the End of Article module, but which is configurable to appear within article content instead. From 4d42f22d0aaf5faeae971ce78546d06b4276207e Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:27:15 -0500 Subject: [PATCH 120/125] Fix bug with accessing settings on components --- includes/apple-exporter/components/class-component.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/apple-exporter/components/class-component.php b/includes/apple-exporter/components/class-component.php index b7a300c8..fa4b6b69 100644 --- a/includes/apple-exporter/components/class-component.php +++ b/includes/apple-exporter/components/class-component.php @@ -275,7 +275,7 @@ public function __construct( if ( empty( $parser ) ) { // Load format from settings. - $format = ( 'yes' === $this->settings->html_support ) + $format = ( 'yes' === $this->settings?->html_support ) ? 'html' : 'markdown'; From 6b0b159dd231bdce50635bcd49d7acb7421d340c Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 21 Jun 2024 11:23:19 -0400 Subject: [PATCH 121/125] WIP: APPLE-185 From 41fef687378d8690b0eccf3d17fab91ae2d53de1 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 21 Jun 2024 17:32:54 -0400 Subject: [PATCH 122/125] fix issue with aside borders set to none --- assets/js/theme-edit.js | 8 ++ includes/apple-exporter/class-theme.php | 2 +- .../apple-exporter/components/class-aside.php | 83 ++++++++++++++----- 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/assets/js/theme-edit.js b/assets/js/theme-edit.js index 46508921..7fc2b95a 100644 --- a/assets/js/theme-edit.js +++ b/assets/js/theme-edit.js @@ -76,6 +76,14 @@ $( '#pullquote_border_color, #pullquote_border_width' ).parent().show().next( 'br' ).show(); } } ).change(); + + $( '#aside_border_style' ).on( 'change', function () { + if ( 'none' === $( this ).val() ) { + $( '#aside_border_color, #aside_border_width' ).parent().hide().next( 'br' ).hide(); + } else { + $( '#aside_border_color, #aside_border_width' ).parent().show().next( 'br' ).show(); + } + } ).change(); } function appleNewsThemeEditSortInit( activeSelector, activeKey, inactiveSelector, inactiveKey, connectWith ) { diff --git a/includes/apple-exporter/class-theme.php b/includes/apple-exporter/class-theme.php index cc8599bd..b6625012 100644 --- a/includes/apple-exporter/class-theme.php +++ b/includes/apple-exporter/class-theme.php @@ -2524,8 +2524,8 @@ private function initialize_groups() { 'settings' => [ 'aside_alignment', 'aside_background_color', - 'aside_border_color', 'aside_border_style', + 'aside_border_color', 'aside_border_width', 'aside_padding', 'dark_mode_colors_heading', diff --git a/includes/apple-exporter/components/class-aside.php b/includes/apple-exporter/components/class-aside.php index 7c36b85b..666b101b 100644 --- a/includes/apple-exporter/components/class-aside.php +++ b/includes/apple-exporter/components/class-aside.php @@ -84,29 +84,59 @@ public function register_specs() { ], ); - $aside_conditional_style = []; - if ( ! empty( $theme->get_value( 'aside_background_color_dark' ) ) || ! empty( $theme->get_value( 'aside_border_color_dark' ) ) ) { - $aside_conditional_style = [ - 'conditional' => [ - 'backgroundColor' => '#aside_background_color_dark#', - 'border' => [ - 'all' => [ - 'width' => '#blockquote_border_width#', - 'style' => '#blockquote_border_style#', - 'color' => '#aside_border_color_dark#', - ], - ], - 'conditions' => [ - 'minSpecVersion' => '1.14', - 'preferredColorScheme' => 'dark', + $aside_conditional_background_style = []; + if ( ! empty( $theme->get_value( 'aside_background_color_dark' ) ) ) { + $aside_conditional_background_style = [ + 'backgroundColor' => '#aside_background_color_dark#', + ]; + } + + $aside_conditional_border_style = []; + if ( ! empty( $theme->get_value( 'aside_border_color_dark' ) ) ) { + $aside_conditional_border_style = [ + 'border' => [ + 'all' => [ + 'width' => '#aside_border_width#', + 'style' => '#aside_border_style#', + 'color' => '#aside_border_color_dark#', ], ], ]; } + $aside_conditional_conditions = [ + 'conditions' => [ + 'minSpecVersion' => '1.14', + 'preferredColorScheme' => 'dark', + ], + ]; + + $aside_conditional_style_with_borders = ( ! empty( $aside_conditional_background_style ) || ! empty( $aside_conditional_border_style ) ) + ? [ + 'conditional' => [ + array_merge( + $aside_conditional_background_style, + $aside_conditional_border_style, + $aside_conditional_conditions, + ), + ] + ] + : []; + + $aside_conditional_style_without_borders = ( ! empty( $aside_conditional_background_style ) || ! empty( $aside_conditional_border_style ) ) + ? [ + 'conditional' => [ + array_merge( + $aside_conditional_background_style, + $aside_conditional_conditions, + ), + ] + ] + : []; + $this->register_spec( - 'default-aside', - __( 'Aside Style', 'apple-news' ), + 'aside-with-border-json', + __( 'Aside With Border JSON', 'apple-news' ), array_merge( [ 'backgroundColor' => '#aside_background_color#', @@ -118,7 +148,18 @@ public function register_specs() { ], ], ], - $aside_conditional_style + $aside_conditional_style_with_borders + ) + ); + + $this->register_spec( + 'aside-without-border-json', + __( 'Aside Without Border JSON', 'apple-news' ), + array_merge( + [ + 'backgroundColor' => '#aside_background_color#', + ], + $aside_conditional_style_without_borders ) ); @@ -178,9 +219,13 @@ protected function build( $html ) { ], ); + $component_style = ( 'none' !== $theme->get_value( 'aside_border_style' ) ) + ? 'aside-with-border-json' + : 'aside-without-border-json'; + $this->register_component_style( 'default-aside', - 'default-aside', + $component_style, ); $alignment = $theme->get_value( 'aside_alignment' ); From a0465fd573be04440b53738705bc1733770cbbad Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 21 Jun 2024 17:37:59 -0400 Subject: [PATCH 123/125] cleanup --- includes/apple-exporter/components/class-aside.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/apple-exporter/components/class-aside.php b/includes/apple-exporter/components/class-aside.php index 666b101b..9d952b80 100644 --- a/includes/apple-exporter/components/class-aside.php +++ b/includes/apple-exporter/components/class-aside.php @@ -119,7 +119,7 @@ public function register_specs() { $aside_conditional_border_style, $aside_conditional_conditions, ), - ] + ], ] : []; @@ -130,7 +130,7 @@ public function register_specs() { $aside_conditional_background_style, $aside_conditional_conditions, ), - ] + ], ] : []; From bd875e74b6e595d55efd906988ed9612446e8fa5 Mon Sep 17 00:00:00 2001 From: Kaitlin Bolling Date: Fri, 21 Jun 2024 17:38:05 -0400 Subject: [PATCH 124/125] Ready for review From 537ce01d6f5f3b75dbcff84195de6fcc825316b7 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:36:30 -0400 Subject: [PATCH 125/125] Fix bug with images within asides --- includes/apple-exporter/components/class-image.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/apple-exporter/components/class-image.php b/includes/apple-exporter/components/class-image.php index 8696d683..0204f38d 100644 --- a/includes/apple-exporter/components/class-image.php +++ b/includes/apple-exporter/components/class-image.php @@ -295,7 +295,7 @@ private function register_anchor_layout( $values ) { $this->register_layout( $layout_name, $layout_name ); - $values['#layout#'] = $layout_name; + $values['#layout#'] = $this->get_component_object_key( $layout_name ); return $values; } @@ -331,7 +331,7 @@ private function register_non_anchor_layout( $values ) { // Register the layout. $this->register_full_width_layout( $layout_name, $spec_name, $layout_values ); - $values['#layout#'] = $layout_name; + $values['#layout#'] = $this->get_component_object_key( $layout_name ); return $values; }