diff --git a/phpstan.neon b/phpstan.neon index 979b34ac5a..2b087e5511 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,8 +14,8 @@ parameters: - vendor/php-stubs/wordpress-tests-stubs/wordpress-tests-stubs.php type_coverage: return_type: 87.6 - param_type: 79.1 - property_type: 0 # We can't use typed properties until PHP 7.4 becomes the plugin's minimum version. https://php.watch/versions/7.4/typed-properties + param_type: 79 + property_type: 1.4 constant_type: 0 # We can't use typed constants until PHP 8.3 becomes the plugin's minimum version. https://php.watch/versions/8.3/typed-constants print_suggestions: false typeAliases: diff --git a/src/UI/class-settings-page.php b/src/UI/class-settings-page.php index 315e5ac06f..e0bc14a4b5 100644 --- a/src/UI/class-settings-page.php +++ b/src/UI/class-settings-page.php @@ -62,6 +62,7 @@ * custom_taxonomy_section?: string, * cats_as_tags?: bool|string, * content_helper: Parsely_Settings_Options_Content_Helper, + * headline_testing?: Parsely_Options_Headline_Testing, * lowercase_tags?: bool, * force_https_canonicals?: bool, * disable_autotrack?: bool|string, @@ -81,6 +82,7 @@ * } * * @phpstan-import-type Parsely_Options from Parsely + * @phpstan-import-type Parsely_Options_Headline_Testing from Parsely */ final class Settings_Page { /** @@ -262,6 +264,7 @@ public function initialize_settings(): void { $this->initialize_basic_section(); $this->initialize_content_helper_section(); + $this->initialize_headline_testing_section(); $this->initialize_recrawl_section(); $this->initialize_advanced_section(); } @@ -430,7 +433,7 @@ private function initialize_basic_section(): void { } /** - * Registers the Content Intelligence section and its settings. + * Registers section and settings for Content Intelligence section. * * @since 3.16.0 */ @@ -528,6 +531,138 @@ private function initialize_content_helper_section(): void { ); } + /** + * Registers section and settings for Headline Testing section. + * + * @since 3.21.0 + */ + private function initialize_headline_testing_section(): void { + $section_key = 'headline-testing-section'; + + add_settings_section( + $section_key, + __( 'Headline Testing', 'wp-parsely' ), + function (): void { + echo '

' . esc_html__( 'Configure Parse.ly Headline Testing to automatically test different headline variations and optimize for engagement.', 'wp-parsely' ) . '

'; + echo '

' . esc_html__( 'Learn more about Headline Testing', 'wp-parsely' ) . '

'; + }, + Parsely::MENU_SLUG + ); + + // Enable Headline Testing. + $field_id = 'headline_testing[enabled]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'yes_text' => __( 'Enabled', 'wp-parsely' ), + 'add_fieldset' => true, + 'legend' => __( 'Headline Testing', 'wp-parsely' ), + 'help_text' => __( 'Enable Parse.ly Headline Testing to automatically test different headline variations.', 'wp-parsely' ), + ); + add_settings_field( + $field_id, + __( 'Headline Testing', 'wp-parsely' ), + array( $this, 'print_checkbox_tag' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + + // Installation Method. + $field_id = 'headline_testing[installation_method]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'help_text' => __( 'Choose how you want to install the Headline Testing script. One-line snippet is recommended for most sites.', 'wp-parsely' ), + 'radio_options' => array( + 'one_line' => __( 'One-line Snippet (Recommended)', 'wp-parsely' ), + 'advanced' => __( 'Advanced Installation', 'wp-parsely' ), + ), + ); + add_settings_field( + $field_id, + __( 'Installation Method', 'wp-parsely' ), + array( $this, 'print_radio_tags' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + + // Enable Flicker Control (Advanced only). + $field_id = 'headline_testing[enable_flicker_control]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'yes_text' => __( 'Enabled', 'wp-parsely' ), + 'help_text' => __( 'Hide page body for up to 500ms to prevent flickering when headlines are replaced. Only available with Advanced installation.', 'wp-parsely' ), + ); + add_settings_field( + $field_id, + __( 'Enable Flicker Control', 'wp-parsely' ), + array( $this, 'print_checkbox_tag' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + + // Enable Live Updates. + $field_id = 'headline_testing[enable_live_updates]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'yes_text' => __( 'Enabled', 'wp-parsely' ), + 'help_text' => __( 'Watch for new content and automatically update headlines for newly added anchors.', 'wp-parsely' ), + ); + add_settings_field( + $field_id, + __( 'Enable Live Updates', 'wp-parsely' ), + array( $this, 'print_checkbox_tag' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + + // Live Update Timeout. + $field_id = 'headline_testing[live_update_timeout]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'help_text' => __( 'How long to watch for new content (in milliseconds). Default: 30000 (30 seconds).', 'wp-parsely' ), + 'optional_args' => array( + 'type' => 'number', + 'placeholder' => '30000', + 'min' => '1000', + 'max' => '60000', + 'step' => '1000', + ), + ); + add_settings_field( + $field_id, + __( 'Live Update Timeout (ms)', 'wp-parsely' ), + array( $this, 'print_text_tag' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + + // Allow After Content Load. + $field_id = 'headline_testing[allow_after_content_load]'; + $field_args = array( + 'option_key' => $field_id, + 'label_for' => $field_id, + 'yes_text' => __( 'Enabled', 'wp-parsely' ), + 'help_text' => __( 'Allow headline swapping even after the main content has loaded. May cause flickering. Highly recommended if you are loading your script asynchronously.', 'wp-parsely' ), + ); + add_settings_field( + $field_id, + __( 'Allow After Content Load', 'wp-parsely' ), + array( $this, 'print_checkbox_tag' ), + Parsely::MENU_SLUG, + $section_key, + $field_args + ); + } + /** * Registers section and settings for Recrawl section. * @@ -836,25 +971,25 @@ private function print_description_text( $args ): void { * @param Setting_Arguments $args The arguments for text tag. */ public function print_text_tag( $args ): void { - $options = $this->parsely->get_options(); - $name = $args['option_key']; - /** - * Variable. - * - * @var string - */ - $value = $options[ $name ] ?? ''; + $options = $this->parsely->get_options(); + $name = $args['option_key']; + $raw_value = $this->get_option_value( $name, $options ); + $value_as_string = is_scalar( $raw_value ) ? (string) $raw_value : ''; $optional_args = $args['optional_args'] ?? array(); $id = esc_attr( $name ); - $name = Parsely::OPTIONS_KEY . "[$id]"; + $html_name = $this->get_html_name_attribute( $name ); $is_obfuscated_value = $optional_args['is_obfuscated_value'] ?? false; - $value = $is_obfuscated_value ? $this->get_obfuscated_value( $value ) : esc_attr( $value ); - $accepted_args = array( 'placeholder', 'required', 'disabled' ); + $value = $is_obfuscated_value ? $this->get_obfuscated_value( $value_as_string ) : esc_attr( $value_as_string ); $type = $optional_args['type'] ?? 'text'; + $accepted_args = array( 'placeholder', 'required', 'disabled' ); + + if ( 'number' === $type ) { + $accepted_args = array_merge( $accepted_args, array( 'min', 'max', 'step' ) ); + } $is_managed = key_exists( $id, $this->parsely->managed_options ); echo '' : '>'; - printf( "get_html_name_attribute( $name ); $yes_text = $args['yes_text'] ?? ''; - - // Get option value. - if ( false === strpos( $name, '[' ) ) { - $value = $options[ $name ]; - } else { - $value = Parsely::get_nested_option_value( $name, $options ); - } + $value = $this->get_option_value( $name, $options ); // Fieldset start. if ( $has_fieldset ) { @@ -1039,9 +1164,11 @@ public function print_select_tag( $args ): void { * @param Setting_Arguments $args The arguments for the radio buttons. */ public function print_radio_tags( $args ): void { + $options = $this->parsely->get_options(); $name = $args['option_key']; $id = esc_attr( $name ); - $selected = $this->parsely->get_options()[ $name ]; + $selected = $this->get_option_value( $name, $options ); + $html_name = $this->get_html_name_attribute( $name ); $title = $args['title'] ?? ''; $radio_options = $args['radio_options'] ?? array(); @@ -1059,7 +1186,7 @@ public function print_radio_tags( $args ): void {