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 (
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' ? ( +