From 6954862b3fd8de1c55fb0afcd04474aace504bfe Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 6 Nov 2023 18:00:22 +0530 Subject: [PATCH 001/127] init --- .../Classifai/Features/ContentResizing.php | 104 +++++ .../Classifai/Features/ExcerptGeneration.php | 136 ++++++ includes/Classifai/Features/Feature.php | 372 ++++++++++++++++ .../Classifai/Features/TitleGeneration.php | 113 +++++ .../Providers/Azure/ComputerVision.php | 2 + .../Providers/Azure/TextToSpeech.php | 2 + .../Classifai/Providers/OpenAI/ChatGPT.php | 420 ++++++++---------- includes/Classifai/Providers/OpenAI/DallE.php | 2 + includes/Classifai/Providers/Provider.php | 10 - .../Classifai/Services/LanguageProcessing.php | 8 +- includes/Classifai/Services/Service.php | 27 +- .../Classifai/Services/ServicesManager.php | 10 + 12 files changed, 949 insertions(+), 257 deletions(-) create mode 100644 includes/Classifai/Features/ContentResizing.php create mode 100644 includes/Classifai/Features/ExcerptGeneration.php create mode 100644 includes/Classifai/Features/Feature.php create mode 100644 includes/Classifai/Features/TitleGeneration.php diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php new file mode 100644 index 000000000..323515d60 --- /dev/null +++ b/includes/Classifai/Features/ContentResizing.php @@ -0,0 +1,104 @@ + __( 'OpenAI ChatGPT', 'classifai' ), + ] + ); + } + + public function setup_fields_sections() { + $default_settings = $this->get_default_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable content resizing', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $default_settings['status'], + 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $default_settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $default_settings['provider'], + ] + ); + + $this->add_provider_settings_fields(); + } + + public function get_default_settings() { + return [ + 'status' => '0', + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'roles' => $this->roles, + 'suggestion_count' => absint( apply_filters( 'excerpt_length', 55 ) ), + ]; + } + + public function sanitize_settings( $settings ) { + $new_settings = $this->get_settings(); + + if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { + $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); + } else { + $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); + } + + if ( isset( $settings['provider'] ) ) { + $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); + } else { + $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + } + + return $new_settings; + } +} diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php new file mode 100644 index 000000000..3c7995ed6 --- /dev/null +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -0,0 +1,136 @@ + __( 'OpenAI ChatGPT', 'classifai' ), + ] + ); + } + + public function setup_fields_sections() { + $default_settings = $this->get_default_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + array( $this, 'render_section' ), + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable excerpt generation', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $default_settings['status'], + 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $default_settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), + ] + ); + + add_settings_field( + 'length', + esc_html__( 'Excerpt length', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'length', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $default_settings['length'], + 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $default_settings['provider'], + ] + ); + + $this->add_provider_settings_fields(); + } + + public function get_default_settings() { + return [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), + ]; + } + + public function render_section() { + return; + } + + public function sanitize_settings( $settings ) { + $new_settings = $this->get_settings(); + + if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { + $new_settings['status'] = 'no'; + } else { + $new_settings['status'] = '1'; + } + + if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { + $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); + } else { + $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); + } + + if ( isset( $settings['provider'] ) ) { + $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); + } else { + $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + } + + if ( isset( $settings['length'] ) && is_numeric( $settings['length'] ) && (int) $settings['length'] >= 0 ) { + $new_settings['length'] = absint( $settings['length'] ); + } else { + $new_settings['length'] = 55; + } + + return $new_settings; + } +} diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php new file mode 100644 index 000000000..86a5032fd --- /dev/null +++ b/includes/Classifai/Features/Feature.php @@ -0,0 +1,372 @@ +get_default_settings(); + $this->roles = get_editable_roles() ?? []; + $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); + $this->provider_instances = $provider_instances; + + /** + * Filter the allowed WordPress roles for ChatGTP + * + * @since 2.3.0 + * @hook classifai_chatgpt_allowed_roles + * + * @param {array} $roles Array of arrays containing role information. + * @param {array} $default_settings Default setting values. + * + * @return {array} Roles array. + */ + $this->roles = apply_filters( 'classifai_chatgpt_allowed_roles', $this->roles, $default_settings ); + + add_action( 'admin_init', [ $this, 'register_setting' ] ); + add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); + } + + public function register_setting() { + register_setting( + $this->get_option_name(), + $this->get_option_name(), + [ + 'sanitize_callback' => [ $this, 'sanitize_settings' ], + ] + ); + } + + /** + * Set up the fields for each section. + */ + abstract public function setup_fields_sections(); + + abstract public function get_default_settings() ; + + abstract public function get_providers(); + + abstract public function sanitize_settings( $settings ); + + public function add_provider_settings_fields() { + do_action( 'classifai_' . static::ID . '_add_provider_settings_fields', $this ); + } + + public function get_option_name() { + return 'classifai_' . static::ID; + } + + public function get_settings( $index = false ) { + $defaults = $this->get_default_settings(); + $settings = get_option( $this->get_option_name(), [] ); + $settings = wp_parse_args( $settings, $defaults ); + + if ( $index && isset( $settings[ $index ] ) ) { + return $settings[ $index ]; + } + + return $settings; + } + + protected function get_data_attribute( $args ) { + $data_attr = $args['data_attr'] ?? []; + $data_attr_str = ''; + + foreach ( $data_attr as $attr_key => $attr_value ) { + if ( is_scalar( $attr_value ) ) { + $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( $attr_value ) . '"'; + } else { + $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( wp_json_encode( $attr_value ) ) . '"'; + } + } + + return $data_attr_str; + } + + /** + * Generic text input field callback + * + * @param array $args The args passed to add_settings_field. + */ + public function render_input( $args ) { + $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; + $setting_index = $this->get_settings( $option_index ); + $type = $args['input_type'] ?? 'text'; + $value = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; + + // Check for a default value + $value = ( empty( $value ) && isset( $args['default_value'] ) ) ? $args['default_value'] : $value; + $attrs = ''; + $class = ''; + + switch ( $type ) { + case 'text': + case 'password': + $attrs = ' value="' . esc_attr( $value ) . '"'; + $class = 'regular-text'; + break; + case 'number': + $attrs = ' value="' . esc_attr( $value ) . '"'; + + if ( isset( $args['max'] ) && is_numeric( $args['max'] ) ) { + $attrs .= ' max="' . esc_attr( (float) $args['max'] ) . '"'; + } + + if ( isset( $args['min'] ) && is_numeric( $args['min'] ) ) { + $attrs .= ' min="' . esc_attr( (float) $args['min'] ) . '"'; + } + + if ( isset( $args['step'] ) && is_numeric( $args['step'] ) ) { + $attrs .= ' step="' . esc_attr( (float) $args['step'] ) . '"'; + } + + $class = 'small-text'; + break; + case 'checkbox': + $attrs = ' value="1"' . checked( '1', $value, false ); + break; + } + + ?> + get_data_attribute( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + /> + ' . wp_kses_post( $args['description'] ) . ''; + } + } + + /** + * Generic prompt repeater field callback + * + * @since 2.4.0 + * + * @param array $args The args passed to add_settings_field. + */ + public function render_prompt_repeater_field( array $args ): void { + $option_index = $args['option_index'] ?? false; + $setting_index = $this->get_settings( $option_index ); + $value = $setting_index[ $args['label_for'] ] ?? ''; + $class = $args['class'] ?? 'large-text'; + $placeholder = $args['placeholder'] ?? ''; + $field_name_prefix = sprintf( + '%1$s%2$s[%3$s]', + $this->get_option_name(), + $option_index ? "[$option_index]" : '', + $args['label_for'] + ); + + $value = ( empty( $value ) && isset( $args['default_value'] ) ) ? $args['default_value'] : $value; + + $prompt_count = count( $value ); + $field_index = 0; + ?> + + + + +
+ " + value="" + class="js-setting-field__default"> + + + + + +
+ + + + + + ' . wp_kses_post( $args['description'] ) . ''; + } + } + + /** + * Renders a select menu + * + * @param array $args The args passed to add_settings_field. + */ + public function render_select( $args ) { + $setting_index = $this->get_settings(); + $saved = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; + $data_attr = isset( $args['data_attr'] ) ?: []; + + // Check for a default value + $saved = ( empty( $saved ) && isset( $args['default_value'] ) ) ? $args['default_value'] : $saved; + $options = isset( $args['options'] ) ? $args['options'] : []; + ?> + + + + ' . wp_kses_post( $args['description'] ) . ''; + } + } + + /** + * Render a group of checkboxes. + * + * @param array $args The args passed to add_settings_field + */ + public function render_checkbox_group( array $args = array() ) { + $setting_index = $this->get_settings(); + + // Iterate through all of our options. + foreach ( $args['options'] as $option_value => $option_label ) { + $value = ''; + $default_key = array_search( $option_value, $args['default_values'], true ); + + // Get saved value, if any. + if ( isset( $setting_index[ $args['label_for'] ] ) ) { + $value = $setting_index[ $args['label_for'] ][ $option_value ] ?? ''; + } + + // If no saved value, check if we have a default value. + if ( empty( $value ) && '0' !== $value && isset( $args['default_values'][ $default_key ] ) ) { + $value = $args['default_values'][ $default_key ]; + } + + // Render checkbox. + printf( + '

+ +

', + esc_attr( $this->get_option_name() ), + esc_attr( $args['label_for'] ), + esc_attr( $option_value ), + checked( $value, $option_value, false ), + esc_html( $option_label ) + ); + } + + // Render description, if any. + if ( ! empty( $args['description'] ) ) { + printf( + '%s', + esc_html( $args['description'] ) + ); + } + } + + /** + * Renders the checkbox group for 'Generate descriptive text' setting. + * + * @param array $args The args passed to add_settings_field. + */ + public function render_auto_caption_fields( $args ) { + $setting_index = $this->get_settings(); + + $default_value = ''; + + if ( isset( $setting_index['enable_image_captions'] ) ) { + if ( ! is_array( $setting_index['enable_image_captions'] ) ) { + if ( '1' === $setting_index['enable_image_captions'] ) { + $default_value = 'alt'; + } elseif ( 'no' === $setting_index['enable_image_captions'] ) { + $default_value = ''; + } + } + } + + $checkbox_options = array( + 'alt' => esc_html__( 'Alt text', 'classifai' ), + 'caption' => esc_html__( 'Image caption', 'classifai' ), + 'description' => esc_html__( 'Image description', 'classifai' ), + ); + + foreach ( $checkbox_options as $option_value => $option_label ) { + if ( isset( $setting_index['enable_image_captions'] ) ) { + if ( ! is_array( $setting_index['enable_image_captions'] ) ) { + $default_value = '1' === $setting_index['enable_image_captions'] ? 'alt' : ''; + } else { + $default_value = $setting_index['enable_image_captions'][ $option_value ]; + } + } + + printf( + '

+ +

', + esc_attr( $this->get_option_name() ), + esc_attr( $args['label_for'] ), + esc_attr( $option_value ), + checked( $default_value, $option_value, false ), + esc_html( $option_label ) + ); + } + + // Render description, if any. + if ( ! empty( $args['description'] ) ) { + printf( + '%s', + esc_html( $args['description'] ) + ); + } + } +} diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php new file mode 100644 index 000000000..ae62b730a --- /dev/null +++ b/includes/Classifai/Features/TitleGeneration.php @@ -0,0 +1,113 @@ + __( 'OpenAI ChatGPT', 'classifai' ), + ] + ); + } + + public function setup_fields_sections() { + $default_settings = $this->get_default_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + array( $this, 'render_section' ), + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable title generation', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $default_settings['status'], + 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $default_settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $default_settings['provider'], + ] + ); + + $this->add_provider_settings_fields(); + } + + public function get_default_settings() { + return [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + ]; + } + + public function render_section() { + return; + } + + public function sanitize_settings( $settings ) { + $new_settings = $this->get_settings(); + + if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { + $new_settings['status'] = 'no'; + } else { + $new_settings['status'] = '1'; + } + + if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { + $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); + } else { + $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); + } + + if ( isset( $settings['provider'] ) ) { + $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); + } else { + $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + } + + return $new_settings; + } +} diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 3ea2f275e..e441083ae 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -18,6 +18,8 @@ class ComputerVision extends Provider { + const ID = 'ms_computer_vision'; + /** * @var string URL fragment to the analyze API endpoint */ diff --git a/includes/Classifai/Providers/Azure/TextToSpeech.php b/includes/Classifai/Providers/Azure/TextToSpeech.php index e4a2906de..ba68c9f5d 100644 --- a/includes/Classifai/Providers/Azure/TextToSpeech.php +++ b/includes/Classifai/Providers/Azure/TextToSpeech.php @@ -16,6 +16,8 @@ class TextToSpeech extends Provider { + const ID = 'ms_azure_text_to_speech'; + /** * Name of the feature that is displayed to the end user. * diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 18a007033..ae1ec4b69 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -14,6 +14,8 @@ class ChatGPT extends Provider { use \Classifai\Providers\OpenAI\OpenAI; + const ID = 'openai_chatgpt'; + /** * OpenAI ChatGPT URL * @@ -86,6 +88,183 @@ public function __construct( $service ) { 'enable_resize_content' => __( 'Content resizing', 'classifai' ), ), ); + + add_action( 'classifai_feature_title_generation_add_provider_settings_fields', [ $this, 'add_feature_title_generation_fields' ] ); + add_action( 'classifai_feature_excerpt_generation_add_provider_settings_fields', [ $this, 'add_feature_excerpt_generation_fields' ] ); + add_action( 'classifai_feature_content_resizing_add_provider_settings_fields', [ $this, 'add_feature_content_resizing_fields' ] ); + } + + /** + * Adds provider fields for the title generation feature. + */ + public function add_feature_title_generation_fields( $feature_instance ) { + $default_settings = $this->get_default_settings(); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $feature_instance, 'render_input' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $default_settings['api_key'], + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + + add_settings_field( + 'number_titles', + esc_html__( 'Number of titles', 'classifai' ), + [ $feature_instance, 'render_select' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'number_titles', + 'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ), + 'default_value' => $default_settings['number_titles'], + 'description' => __( 'Number of titles that will be generated in one request.', 'classifai' ), + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + + // Custom prompt for generating titles. + add_settings_field( + 'generate_title_prompt', + esc_html__( 'Prompt', 'classifai' ), + [ $feature_instance, 'render_prompt_repeater_field' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'generate_title_prompt', + 'placeholder' => $this->generate_title_prompt, + 'default_value' => $default_settings['generate_title_prompt'], + 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + } + + /** + * Adds provider fields for the excerpt generation feature. + */ + public function add_feature_excerpt_generation_fields( $feature_instance ) { + $default_settings = $this->get_default_settings(); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $feature_instance, 'render_input' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $default_settings['api_key'], + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + + // Custom prompt for excerpt generation. + add_settings_field( + 'generate_excerpt_prompt', + esc_html__( 'Prompt', 'classifai' ), + [ $feature_instance, 'render_prompt_repeater_field' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'generate_excerpt_prompt', + 'placeholder' => $this->generate_excerpt_prompt, + 'default_value' => $default_settings['generate_excerpt_prompt'], + 'description' => __( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + } + + /** + * Adds provider fields for the content resizing feature. + */ + public function add_feature_content_resizing_fields( $feature_instance ) { + $default_settings = $this->get_default_settings(); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $feature_instance, 'render_input' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $default_settings['api_key'], + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + + add_settings_field( + 'suggestion_count', + esc_html__( 'Number of suggestions', 'classifai' ), + [ $feature_instance, 'render_input' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'suggestion_count', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $default_settings['suggestion_count'], + 'description' => __( 'Number of suggestions that will be generated in one request.', 'classifai' ), + ] + ); + + add_settings_field( + 'shrink_content_prompt', + esc_html__( 'Condense text prompt', 'classifai' ), + [ $feature_instance, 'render_prompt_repeater_field' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'shrink_content_prompt', + 'placeholder' => $this->shrink_content_prompt, + 'default_value' => $default_settings['shrink_content_prompt'], + 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); + + // Custom prompt for growing content. + add_settings_field( + 'grow_content_prompt', + esc_html__( 'Expand text prompt', 'classifai' ), + [ $feature_instance, 'render_prompt_repeater_field' ], + $feature_instance->get_option_name(), + $feature_instance->get_option_name() . '_section', + [ + 'label_for' => 'grow_content_prompt', + 'placeholder' => $this->grow_content_prompt, + 'default_value' => $default_settings['grow_content_prompt'], + 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), + 'data_attr' => [ + 'provider-scope' => [ static::ID ] + ], + ] + ); } /** @@ -351,244 +530,7 @@ public function register_generated_titles_template() { /** * Setup fields */ - public function setup_fields_sections() { - $default_settings = $this->get_default_settings(); - - // Add API fields. - $this->setup_api_fields( $default_settings['api_key'] ); - - // Add excerpt fields. - add_settings_section( - $this->get_option_name() . '_excerpt', - esc_html__( 'Excerpt settings', 'classifai' ), - '', - $this->get_option_name() - ); - - add_settings_field( - 'enable-excerpt', - esc_html__( 'Generate excerpt', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_excerpt', - [ - 'label_for' => 'enable_excerpt', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_excerpt'], - 'description' => __( 'A button will be added to the excerpt panel that can be used to generate an excerpt.', 'classifai' ), - ] - ); - - $roles = get_editable_roles() ?? []; - $roles = array_combine( array_keys( $roles ), array_column( $roles, 'name' ) ); - - /** - * Filter the allowed WordPress roles for ChatGTP - * - * @since 2.3.0 - * @hook classifai_chatgpt_allowed_roles - * - * @param {array} $roles Array of arrays containing role information. - * @param {array} $default_settings Default setting values. - * - * @return {array} Roles array. - */ - $roles = apply_filters( 'classifai_chatgpt_allowed_roles', $roles, $default_settings ); - - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_excerpt', - [ - 'label_for' => 'roles', - 'options' => $roles, - 'default_values' => $default_settings['roles'], - 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), - ] - ); - - add_settings_field( - 'length', - esc_html__( 'Excerpt length', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_excerpt', - [ - 'label_for' => 'length', - 'input_type' => 'number', - 'min' => 1, - 'step' => 1, - 'default_value' => $default_settings['length'], - 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), - ] - ); - - // Custom prompt for excerpt generation. - add_settings_field( - 'generate_excerpt_prompt', - esc_html__( 'Prompt', 'classifai' ), - [ $this, 'render_prompt_repeater_field' ], - $this->get_option_name(), - $this->get_option_name() . '_excerpt', - [ - 'label_for' => 'generate_excerpt_prompt', - 'placeholder' => $this->generate_excerpt_prompt, - 'default_value' => $default_settings['generate_excerpt_prompt'], - 'description' => __( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), - ] - ); - - // Add title fields. - add_settings_section( - $this->get_option_name() . '_title', - esc_html__( 'Title settings', 'classifai' ), - '', - $this->get_option_name() - ); - - add_settings_field( - 'enable-titles', - esc_html__( 'Generate titles', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_title', - [ - 'label_for' => 'enable_titles', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_titles'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), - ] - ); - - add_settings_field( - 'title-roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_title', - [ - 'label_for' => 'title_roles', - 'options' => $roles, - 'default_values' => $default_settings['title_roles'], - 'description' => __( 'Choose which roles are allowed to generate titles.', 'classifai' ), - ] - ); - - add_settings_field( - 'number-titles', - esc_html__( 'Number of titles', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_title', - [ - 'label_for' => 'number_titles', - 'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ), - 'default_value' => $default_settings['number_titles'], - 'description' => __( 'Number of titles that will be generated in one request.', 'classifai' ), - ] - ); - - // Custom prompt for generating titles. - add_settings_field( - 'generate_title_prompt', - esc_html__( 'Prompt', 'classifai' ), - [ $this, 'render_prompt_repeater_field' ], - $this->get_option_name(), - $this->get_option_name() . '_title', - [ - 'label_for' => 'generate_title_prompt', - 'placeholder' => $this->generate_title_prompt, - 'default_value' => $default_settings['generate_title_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); - - // Add content resizing fields. - add_settings_section( - $this->get_option_name() . '_resize_content_settings', - esc_html__( 'Content resizing settings', 'classifai' ), - '', - $this->get_option_name() - ); - - add_settings_field( - 'enable-resize-content', - esc_html__( 'Enable content resizing', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_resize_content_settings', - [ - 'label_for' => 'enable_resize_content', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_resize_content'], - 'description' => __( '"Condense this text" and "Expand this text" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ), - ] - ); - - $content_resize_roles = $roles; - - unset( $content_resize_roles['contributor'], $content_resize_roles['subscriber'] ); - - add_settings_field( - 'resize-content-roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_resize_content_settings', - [ - 'label_for' => 'resize_content_roles', - 'options' => $content_resize_roles, - 'default_values' => $default_settings['resize_content_roles'], - 'description' => __( 'Choose which roles are allowed to resize content.', 'classifai' ), - ] - ); - - add_settings_field( - 'number-resize-content', - esc_html__( 'Number of suggestions', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_resize_content_settings', - [ - 'label_for' => 'number_resize_content', - 'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ), - 'default_value' => $default_settings['number_resize_content'], - 'description' => __( 'Number of suggestions that will be generated in one request.', 'classifai' ), - ] - ); - - // Custom prompt for shrinking content. - add_settings_field( - 'shrink_content_prompt', - esc_html__( 'Condense text prompt', 'classifai' ), - [ $this, 'render_prompt_repeater_field' ], - $this->get_option_name(), - $this->get_option_name() . '_resize_content_settings', - [ - 'label_for' => 'shrink_content_prompt', - 'placeholder' => $this->shrink_content_prompt, - 'default_value' => $default_settings['shrink_content_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); - - // Custom prompt for growing content. - add_settings_field( - 'grow_content_prompt', - esc_html__( 'Expand text prompt', 'classifai' ), - [ $this, 'render_prompt_repeater_field' ], - $this->get_option_name(), - $this->get_option_name() . '_resize_content_settings', - [ - 'label_for' => 'grow_content_prompt', - 'placeholder' => $this->grow_content_prompt, - 'default_value' => $default_settings['grow_content_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); - } + public function setup_fields_sections() {} /** * Sanitization for the options being saved. @@ -730,7 +672,7 @@ public function get_default_settings() { ), 'enable_resize_content' => false, 'resize_content_roles' => array_keys( $editable_roles ), - 'number_resize_content' => 1, + 'suggestion_count' => 1, 'shrink_content_prompt' => array( array( 'title' => esc_html__( 'Default', 'classifai' ), diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index ffc279cf9..32f23bd09 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -14,6 +14,8 @@ class DallE extends Provider { use \Classifai\Providers\OpenAI\OpenAI; + const ID = 'openai_dalle'; + /** * OpenAI DALLĀ·E URL * diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index acfdf6a2b..d1e7474bf 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -132,19 +132,9 @@ abstract public function reset_settings(); * Initialization routine */ public function register_admin() { - add_action( 'admin_init', [ $this, 'register_settings' ] ); add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); } - /** - * Register the settings and sanitization callback method. - * - * It's very important that the option group matches the page slug. - */ - public function register_settings() { - register_setting( $this->get_option_name(), $this->get_option_name(), [ $this, 'sanitize_settings' ] ); - } - /** * Helper to get the settings and allow for settings default values. * diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 90b097078..85a56b5a3 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -21,11 +21,11 @@ public function __construct() { __( 'Language Processing', 'classifai' ), 'language_processing', [ - 'Classifai\Providers\Watson\NLU', + // 'Classifai\Providers\Watson\NLU', 'Classifai\Providers\OpenAI\ChatGPT', - 'Classifai\Providers\OpenAI\Embeddings', - 'Classifai\Providers\OpenAI\Whisper', - 'Classifai\Providers\Azure\TextToSpeech', + // 'Classifai\Providers\OpenAI\Embeddings', + // 'Classifai\Providers\OpenAI\Whisper', + // 'Classifai\Providers\Azure\TextToSpeech', ] ); } diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 460bcc089..ad576786f 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -30,6 +30,10 @@ abstract class Service { */ public $provider_classes; + public $features = []; + + public $feature_classes = []; + /** * Service constructor. * @@ -68,6 +72,16 @@ public function init() { $this->register_providers(); } + $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); + + if ( ! empty( $this->features ) && is_array( $this->features ) ) { + foreach ( $this->features as $feature ) { + if ( class_exists( $feature ) ) { + $this->feature_classes[] = new $feature( $this->provider_classes ); + } + } + } + add_filter( 'classifai_debug_information', [ $this, 'add_service_debug_information' ] ); } @@ -116,6 +130,8 @@ public function render_settings_page() { ), admin_url( 'tools.php' ) ); + $active_feature = $this->feature_classes ? $this->feature_classes[0]::ID : ''; + $active_feature = isset( $_GET['feature'] ) ? sanitize_text_field( wp_unslash( $_GET['feature'] ) ) : $active_feature; ?>
@@ -143,8 +159,11 @@ public function render_settings_page() {
diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index 033e81150..707f429dc 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -42,6 +42,8 @@ public function __construct( $services = [] ) { * Register the actions required for the settings page. */ public function register() { + add_filter( 'language_processing_features', [ $this, 'language_processing_features' ] ); + foreach ( $this->services as $key => $service ) { if ( class_exists( $service ) ) { $this->service_classes[ $key ] = new $service(); @@ -57,6 +59,14 @@ public function register() { add_filter( 'classifai_debug_information', [ $this, 'add_debug_information' ], 1 ); } + public function language_processing_features() { + return [ + '\Classifai\Features\TitleGeneration', + '\Classifai\Features\ExcerptGeneration', + '\Classifai\Features\ContentResizing', + ]; + } + /** * Get general ClassifAI settings * From 1e856879a41d747949e64c390db5cf599f165f76 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 6 Nov 2023 23:29:45 +0530 Subject: [PATCH 002/127] build apis for common provider fields --- .../Classifai/Features/ContentResizing.php | 76 ++++- .../Classifai/Features/ExcerptGeneration.php | 41 ++- includes/Classifai/Features/Feature.php | 20 +- .../Classifai/Features/TitleGeneration.php | 66 +++- includes/Classifai/Plugin.php | 2 +- .../Providers/Azure/Personalizer.php | 2 + .../Classifai/Providers/OpenAI/ChatGPT.php | 319 +++++------------- includes/Classifai/Services/Service.php | 8 +- 8 files changed, 262 insertions(+), 272 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 323515d60..df53154b0 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -2,6 +2,8 @@ namespace Classifai\Features; +use \Classifai\Providers\OpenAI\ChatGPT; + class ContentResizing extends Feature { const ID = 'feature_content_resizing'; @@ -16,7 +18,7 @@ public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ - \Classifai\Providers\OpenAI\ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), ] ); } @@ -33,7 +35,7 @@ public function setup_fields_sections() { add_settings_field( 'status', - esc_html__( 'Enable content resizing', 'classifai' ), + esc_html__( 'Enable title generation', 'classifai' ), [ $this, 'render_input' ], $this->get_option_name(), $this->get_option_name() . '_section', @@ -72,20 +74,75 @@ public function setup_fields_sections() { ] ); - $this->add_provider_settings_fields(); + $chat_gpt = new ChatGPT( null ); + $chat_gpt->add_api_key_field( $this ); + $chat_gpt->add_number_of_responses_field( + $this, + [ + 'id' => 'number_of_suggestions', + 'label' => esc_html__( 'Number of suggestions', 'classifai' ), + 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ) + ] + ); + $chat_gpt->add_prompt_field( + $this, + [ + 'id' => 'condense_text_prompt', + 'label' => esc_html__( 'Condense text prompt', 'classifai' ), + 'prompt_placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), + 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + ] + ); + $chat_gpt->add_prompt_field( + $this, + [ + 'id' => 'expand_text_prompt', + 'label' => esc_html__( 'Expand text prompt'), + 'prompt_placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), + 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + ] + ); } public function get_default_settings() { return [ 'status' => '0', - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, 'roles' => $this->roles, - 'suggestion_count' => absint( apply_filters( 'excerpt_length', 55 ) ), + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + ChatGPT::ID => [ + 'api_key' => '', + 'number_of_suggestions' => 1, + 'condense_text_prompt' => array( + array( + 'title' => esc_html__( 'Default', 'classifai' ), + 'prompt' => '', + 'default' => 1, + ), + ), + 'expand_text_prompt' => array( + array( + 'title' => esc_html__( 'Default', 'classifai' ), + 'prompt' => '', + 'default' => 1, + ), + ), + ], ]; } + public function render_section() { + return; + } + public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); + $chat_gpt = new ChatGPT( null ); + + if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { + $new_settings['status'] = 'no'; + } else { + $new_settings['status'] = '1'; + } if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); @@ -96,7 +153,14 @@ public function sanitize_settings( $settings ) { if ( isset( $settings['provider'] ) ) { $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); } else { - $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + $new_settings['provider'] = ChatGPT::ID; + } + + if ( isset( $settings[ ChatGPT::ID ] ) ) { + $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $new_settings[ ChatGPT::ID ]['number_of_suggestions'] = $chat_gpt->sanitize_number_of_responses_field( 'number_of_suggestions', $settings ); + $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $chat_gpt->sanitize_prompts( 'condense_text_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $chat_gpt->sanitize_prompts( 'expand_text_prompt', $settings ); } return $new_settings; diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 3c7995ed6..2f2573079 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -2,6 +2,8 @@ namespace Classifai\Features; +use \Classifai\Providers\OpenAI\ChatGPT; + class ExcerptGeneration extends Feature { const ID = 'feature_excerpt_generation'; @@ -16,7 +18,7 @@ public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ - \Classifai\Providers\OpenAI\ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), ] ); } @@ -27,7 +29,7 @@ public function setup_fields_sections() { add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), - array( $this, 'render_section' ), + '__return_empty_string', $this->get_option_name() ); @@ -88,24 +90,40 @@ public function setup_fields_sections() { ] ); - $this->add_provider_settings_fields(); + $chat_gpt = new ChatGPT( null ); + $chat_gpt->add_api_key_field( $this ); + $chat_gpt->add_prompt_field( + $this, + [ + 'id' => 'generate_excerpt_prompt', + 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ) + ] + ); } public function get_default_settings() { return [ 'status' => '0', 'roles' => $this->roles, - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + ChatGPT::ID => [ + 'api_key' => '', + 'generate_excerpt_prompt' => array( + array( + 'title' => esc_html__( 'Default', 'classifai' ), + 'prompt' => '', + 'default' => 1, + ), + ) + ], ]; } - public function render_section() { - return; - } - public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); + $chat_gpt = new ChatGPT( null ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; @@ -122,7 +140,7 @@ public function sanitize_settings( $settings ) { if ( isset( $settings['provider'] ) ) { $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); } else { - $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + $new_settings['provider'] = ChatGPT::ID; } if ( isset( $settings['length'] ) && is_numeric( $settings['length'] ) && (int) $settings['length'] >= 0 ) { @@ -131,6 +149,11 @@ public function sanitize_settings( $settings ) { $new_settings['length'] = 55; } + if ( isset( $settings[ ChatGPT::ID ] ) ) { + $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $chat_gpt->sanitize_prompts( 'generate_excerpt_prompt', $settings ); + } + return $new_settings; } } diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 86a5032fd..1ae0f19aa 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -7,13 +7,10 @@ abstract class Feature { public $roles = []; - public $provider_instances; - public function __construct( $provider_instances = [] ) { - $default_settings = $this->get_default_settings(); - $this->roles = get_editable_roles() ?? []; - $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); - $this->provider_instances = $provider_instances; + $default_settings = $this->get_default_settings(); + $this->roles = get_editable_roles() ?? []; + $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); /** * Filter the allowed WordPress roles for ChatGTP @@ -53,10 +50,6 @@ abstract public function get_providers(); abstract public function sanitize_settings( $settings ); - public function add_provider_settings_fields() { - do_action( 'classifai_' . static::ID . '_add_provider_settings_fields', $this ); - } - public function get_option_name() { return 'classifai_' . static::ID; } @@ -88,6 +81,13 @@ protected function get_data_attribute( $args ) { return $data_attr_str; } + /** + * Resets settings for the provider. + */ + public function reset_settings() { + update_option( $this->get_option_name(), $this->get_default_settings() ); + } + /** * Generic text input field callback * diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index ae62b730a..b22dc984f 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -2,6 +2,8 @@ namespace Classifai\Features; +use \Classifai\Providers\OpenAI\ChatGPT; + class TitleGeneration extends Feature { const ID = 'feature_title_generation'; @@ -16,7 +18,7 @@ public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ - \Classifai\Providers\OpenAI\ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), ] ); } @@ -27,7 +29,7 @@ public function setup_fields_sections() { add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), - array( $this, 'render_section' ), + '__return_empty_string', $this->get_option_name() ); @@ -59,6 +61,22 @@ public function setup_fields_sections() { ] ); + add_settings_field( + 'length', + esc_html__( 'Excerpt length', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'length', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $default_settings['length'], + 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), + ] + ); + add_settings_field( 'provider', esc_html__( 'Select a provider', 'classifai' ), @@ -72,23 +90,49 @@ public function setup_fields_sections() { ] ); - $this->add_provider_settings_fields(); + $chat_gpt = new ChatGPT( null ); + $chat_gpt->add_api_key_field( $this ); + $chat_gpt->add_number_of_responses_field( + $this, + [ + 'id' => 'number_of_titles', + 'label' => esc_html__( 'Number of titles', 'classifai' ), + 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ) + ] + ); + $chat_gpt->add_prompt_field( + $this, + [ + 'id' => 'generate_title_prompt', + 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + ] + ); } public function get_default_settings() { return [ 'status' => '0', 'roles' => $this->roles, + 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + ChatGPT::ID => [ + 'api_key' => '', + 'number_of_titles' => 1, + 'generate_title_prompt' => array( + array( + 'title' => esc_html__( 'Default', 'classifai' ), + 'prompt' => '', + 'default' => 1, + ), + ) + ], ]; } - public function render_section() { - return; - } - public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); + $chat_gpt = new ChatGPT( null ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; @@ -105,7 +149,13 @@ public function sanitize_settings( $settings ) { if ( isset( $settings['provider'] ) ) { $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); } else { - $new_settings['provider'] = \Classifai\Providers\OpenAI\ChatGPT::ID; + $new_settings['provider'] = ChatGPT::ID; + } + + if ( isset( $settings[ ChatGPT::ID ] ) ) { + $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $new_settings[ ChatGPT::ID ]['number_of_titles'] = $chat_gpt->sanitize_number_of_responses_field( 'number_of_titles', $settings ); + $new_settings[ ChatGPT::ID ]['generate_title_prompt'] = $chat_gpt->sanitize_prompts( 'generate_title_prompt', $settings ); } return $new_settings; diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 255b29db9..502c3c3d4 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -107,7 +107,7 @@ public function init_services() { 'classifai_services', [ 'language_processing' => 'Classifai\Services\LanguageProcessing', - 'image_processing' => 'Classifai\Services\ImageProcessing', + // 'image_processing' => 'Classifai\Services\ImageProcessing', 'personalizer' => 'Classifai\Services\Personalizer', ] ); diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 3a6775917..0f826d4ea 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -12,6 +12,8 @@ class Personalizer extends Provider { + const ID = 'ms_azure_personalizer'; + /** * @var string URL fragment to the Rank API endpoint */ diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index ae1ec4b69..cc730534c 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -88,75 +88,37 @@ public function __construct( $service ) { 'enable_resize_content' => __( 'Content resizing', 'classifai' ), ), ); - - add_action( 'classifai_feature_title_generation_add_provider_settings_fields', [ $this, 'add_feature_title_generation_fields' ] ); - add_action( 'classifai_feature_excerpt_generation_add_provider_settings_fields', [ $this, 'add_feature_excerpt_generation_fields' ] ); - add_action( 'classifai_feature_content_resizing_add_provider_settings_fields', [ $this, 'add_feature_content_resizing_fields' ] ); } /** - * Adds provider fields for the title generation feature. + * Adds a prompt repeater field. */ - public function add_feature_title_generation_fields( $feature_instance ) { - $default_settings = $this->get_default_settings(); - - add_settings_field( - 'api_key', - esc_html__( 'API Key', 'classifai' ), - [ $feature_instance, 'render_input' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'api_key', - 'input_type' => 'password', - 'default_value' => $default_settings['api_key'], - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); - - add_settings_field( - 'number_titles', - esc_html__( 'Number of titles', 'classifai' ), - [ $feature_instance, 'render_select' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'number_titles', - 'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ), - 'default_value' => $default_settings['number_titles'], - 'description' => __( 'Number of titles that will be generated in one request.', 'classifai' ), - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); + public function add_prompt_field( $feature_instance, $args = [] ) { + $default_settings = $feature_instance->get_default_settings(); + $default_settings = $default_settings[ static::ID ]; - // Custom prompt for generating titles. add_settings_field( - 'generate_title_prompt', - esc_html__( 'Prompt', 'classifai' ), + $args['id'], + $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), [ $feature_instance, 'render_prompt_repeater_field' ], $feature_instance->get_option_name(), $feature_instance->get_option_name() . '_section', [ - 'label_for' => 'generate_title_prompt', - 'placeholder' => $this->generate_title_prompt, - 'default_value' => $default_settings['generate_title_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], + 'option_index' => static::ID, + 'label_for' => $args['id'], + 'placeholder' => $args['prompt_placeholder'], + 'default_value' => $default_settings[ $args['id'] ], + 'description' => $args['description'], ] ); } /** - * Adds provider fields for the excerpt generation feature. + * Adds an api key field. */ - public function add_feature_excerpt_generation_fields( $feature_instance ) { - $default_settings = $this->get_default_settings(); + public function add_api_key_field( $feature_instance ) { + $default_settings = $feature_instance->get_default_settings(); + $default_settings = $default_settings[ static::ID ]; add_settings_field( 'api_key', @@ -165,6 +127,7 @@ public function add_feature_excerpt_generation_fields( $feature_instance ) { $feature_instance->get_option_name(), $feature_instance->get_option_name() . '_section', [ + 'option_index' => static::ID, 'label_for' => 'api_key', 'input_type' => 'password', 'default_value' => $default_settings['api_key'], @@ -173,98 +136,53 @@ public function add_feature_excerpt_generation_fields( $feature_instance ) { ], ] ); - - // Custom prompt for excerpt generation. - add_settings_field( - 'generate_excerpt_prompt', - esc_html__( 'Prompt', 'classifai' ), - [ $feature_instance, 'render_prompt_repeater_field' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'generate_excerpt_prompt', - 'placeholder' => $this->generate_excerpt_prompt, - 'default_value' => $default_settings['generate_excerpt_prompt'], - 'description' => __( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); } /** - * Adds provider fields for the content resizing feature. + * Adds number of responses number field. */ - public function add_feature_content_resizing_fields( $feature_instance ) { - $default_settings = $this->get_default_settings(); + public function add_number_of_responses_field( $feature_instance, $args = [] ) { + $default_settings = $feature_instance->get_default_settings(); + $default_settings = $default_settings[ static::ID ]; add_settings_field( - 'api_key', - esc_html__( 'API Key', 'classifai' ), + $args['id'], + $args['label'], [ $feature_instance, 'render_input' ], $feature_instance->get_option_name(), $feature_instance->get_option_name() . '_section', [ - 'label_for' => 'api_key', - 'input_type' => 'password', - 'default_value' => $default_settings['api_key'], - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); - - add_settings_field( - 'suggestion_count', - esc_html__( 'Number of suggestions', 'classifai' ), - [ $feature_instance, 'render_input' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'suggestion_count', + 'option_index' => static::ID, + 'label_for' => $args['id'], 'input_type' => 'number', 'min' => 1, 'step' => 1, - 'default_value' => $default_settings['suggestion_count'], - 'description' => __( 'Number of suggestions that will be generated in one request.', 'classifai' ), + 'default_value' => $default_settings[ $args['id'] ], + 'description' => $args['description'], ] ); + } - add_settings_field( - 'shrink_content_prompt', - esc_html__( 'Condense text prompt', 'classifai' ), - [ $feature_instance, 'render_prompt_repeater_field' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'shrink_content_prompt', - 'placeholder' => $this->shrink_content_prompt, - 'default_value' => $default_settings['shrink_content_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); + /** + * Sanitisation callback for api key. + */ + public function sanitize_api_key( $settings ) { + if ( isset( $settings[ ChatGPT::ID ]['api_key'] ) ) { + return sanitize_text_field( $settings[ ChatGPT::ID ]['api_key'] ); + } - // Custom prompt for growing content. - add_settings_field( - 'grow_content_prompt', - esc_html__( 'Expand text prompt', 'classifai' ), - [ $feature_instance, 'render_prompt_repeater_field' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', - [ - 'label_for' => 'grow_content_prompt', - 'placeholder' => $this->grow_content_prompt, - 'default_value' => $default_settings['grow_content_prompt'], - 'description' => __( 'Enter a custom prompt, if desired.', 'classifai' ), - 'data_attr' => [ - 'provider-scope' => [ static::ID ] - ], - ] - ); + return ''; + } + + /** + * Sanitisation callback for number of responses. + */ + public function sanitize_number_of_responses_field( $key, $settings ) { + if ( isset( $settings[ ChatGPT::ID ][ $key ] ) ) { + return absint( $settings[ ChatGPT::ID ][ $key ] ); + } + + return 1; } /** @@ -546,93 +464,13 @@ public function sanitize_settings( $settings ) { $this->sanitize_api_key_settings( $new_settings, $settings ) ); - if ( empty( $settings['enable_excerpt'] ) || 1 !== (int) $settings['enable_excerpt'] ) { - $new_settings['enable_excerpt'] = 'no'; - } else { - $new_settings['enable_excerpt'] = '1'; - } - - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } - - if ( isset( $settings['length'] ) && is_numeric( $settings['length'] ) && (int) $settings['length'] >= 0 ) { - $new_settings['length'] = absint( $settings['length'] ); - } else { - $new_settings['length'] = 55; - } - - if ( isset( $settings['generate_excerpt_prompt'] ) && is_array( $settings['generate_excerpt_prompt'] ) ) { - $new_settings['generate_excerpt_prompt'] = $this->sanitize_prompts( $settings['generate_excerpt_prompt'] ); - } else { - $new_settings['generate_excerpt_prompt'] = array(); - } - - if ( empty( $settings['enable_titles'] ) || 1 !== (int) $settings['enable_titles'] ) { - $new_settings['enable_titles'] = 'no'; - } else { - $new_settings['enable_titles'] = '1'; - } - - if ( isset( $settings['title_roles'] ) && is_array( $settings['title_roles'] ) ) { - $new_settings['title_roles'] = array_map( 'sanitize_text_field', $settings['title_roles'] ); - } else { - $new_settings['title_roles'] = array_keys( get_editable_roles() ?? [] ); - } - - if ( isset( $settings['number_titles'] ) && is_numeric( $settings['number_titles'] ) && (int) $settings['number_titles'] >= 1 && (int) $settings['number_titles'] <= 10 ) { - $new_settings['number_titles'] = absint( $settings['number_titles'] ); - } else { - $new_settings['number_titles'] = 1; - } - - if ( isset( $settings['generate_title_prompt'] ) && is_array( $settings['generate_title_prompt'] ) ) { - $new_settings['generate_title_prompt'] = $this->sanitize_prompts( $settings['generate_title_prompt'] ); - } else { - $new_settings['generate_title_prompt'] = array(); - } - - if ( empty( $settings['enable_resize_content'] ) || 1 !== (int) $settings['enable_resize_content'] ) { - $new_settings['enable_resize_content'] = 'no'; - } else { - $new_settings['enable_resize_content'] = '1'; - } - - if ( isset( $settings['resize_content_roles'] ) && is_array( $settings['resize_content_roles'] ) ) { - $new_settings['resize_content_roles'] = array_map( 'sanitize_text_field', $settings['resize_content_roles'] ); - } else { - $new_settings['resize_content_roles'] = array_keys( get_editable_roles() ?? [] ); - } - - if ( isset( $settings['number_resize_content'] ) && is_numeric( $settings['number_resize_content'] ) && (int) $settings['number_resize_content'] >= 1 && (int) $settings['number_resize_content'] <= 10 ) { - $new_settings['number_resize_content'] = absint( $settings['number_resize_content'] ); - } else { - $new_settings['number_resize_content'] = 1; - } - - if ( isset( $settings['shrink_content_prompt'] ) && is_array( $settings['shrink_content_prompt'] ) ) { - $new_settings['shrink_content_prompt'] = $this->sanitize_prompts( $settings['shrink_content_prompt'] ); - } else { - $new_settings['shrink_content_prompt'] = array(); - } - - if ( isset( $settings['grow_content_prompt'] ) && is_array( $settings['grow_content_prompt'] ) ) { - $new_settings['grow_content_prompt'] = $this->sanitize_prompts( $settings['grow_content_prompt'] ); - } else { - $new_settings['grow_content_prompt'] = array(); - } - return $new_settings; } /** * Resets settings for the provider. */ - public function reset_settings() { - update_option( $this->get_option_name(), $this->get_default_settings() ); - } + public function reset_settings() {} /** * Default settings for ChatGPT @@ -1140,41 +978,48 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use * * @return array Sanitized prompt data. */ - public function sanitize_prompts( array $prompts ): array { - // Remove any prompts that don't have a title and prompt. - $prompts = array_filter( - $prompts, - function ( $prompt ) { - return ! empty( $prompt['title'] ) && ! empty( $prompt['prompt'] ); - } - ); + public function sanitize_prompts( $prompt_key = '', array $settings ): array { + if ( isset( $settings[ ChatGPT::ID ][ $prompt_key ] ) && is_array( $settings[ ChatGPT::ID ][ $prompt_key ] ) ) { - // Sanitize the prompts and make sure only one prompt is marked as default. - $has_default = false; + $prompts = $settings[ ChatGPT::ID ][ $prompt_key ]; - $prompts = array_map( - function ( $prompt ) use ( &$has_default ) { - $default = $prompt['default'] && ! $has_default; - - if ( $default ) { - $has_default = true; + // Remove any prompts that don't have a title and prompt. + $prompts = array_filter( + $prompts, + function ( $prompt ) { + return ! empty( $prompt['title'] ) && ! empty( $prompt['prompt'] ); } + ); - return array( - 'title' => sanitize_text_field( $prompt['title'] ), - 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), - 'default' => absint( $default ), - ); - }, - $prompts - ); + // Sanitize the prompts and make sure only one prompt is marked as default. + $has_default = false; + + $prompts = array_map( + function ( $prompt ) use ( &$has_default ) { + $default = $prompt['default'] && ! $has_default; + + if ( $default ) { + $has_default = true; + } + + return array( + 'title' => sanitize_text_field( $prompt['title'] ), + 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), + 'default' => absint( $default ), + ); + }, + $prompts + ); + + // If there is no default, use the first prompt. + if ( false === $has_default && ! empty( $prompts ) ) { + $prompts[0]['default'] = 1; + } - // If there is no default, use the first prompt. - if ( false === $has_default && ! empty( $prompts ) ) { - $prompts[0]['default'] = 1; + return $prompts; } - return $prompts; + return array(); } /** diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index ad576786f..6c52f8793 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -74,10 +74,16 @@ public function init() { $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); + $provider_classes_with_id = []; + + foreach ( $this->provider_classes as $provider_class ) { + $provider_classes_with_id[ $provider_class::ID ] = $provider_class; + } + if ( ! empty( $this->features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $this->feature_classes[] = new $feature( $this->provider_classes ); + $this->feature_classes[] = new $feature( $provider_classes_with_id ); } } } From d28783f98051b2299f2a78512fe8672529f931de Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 6 Nov 2023 23:50:28 +0530 Subject: [PATCH 003/127] migrate repeater fields --- includes/Classifai/Features/ContentResizing.php | 8 ++++---- includes/Classifai/Features/ExcerptGeneration.php | 4 ++-- includes/Classifai/Features/TitleGeneration.php | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index df53154b0..eb2d82e86 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -114,16 +114,16 @@ public function get_default_settings() { 'number_of_suggestions' => 1, 'condense_text_prompt' => array( array( - 'title' => esc_html__( 'Default', 'classifai' ), + 'title' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), 'prompt' => '', - 'default' => 1, + 'original' => 1, ), ), 'expand_text_prompt' => array( array( - 'title' => esc_html__( 'Default', 'classifai' ), + 'title' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), 'prompt' => '', - 'default' => 1, + 'original' => 1, ), ), ], diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 2f2573079..742d17374 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -113,8 +113,8 @@ public function get_default_settings() { 'generate_excerpt_prompt' => array( array( 'title' => esc_html__( 'Default', 'classifai' ), - 'prompt' => '', - 'default' => 1, + 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'original' => 1, ), ) ], diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index b22dc984f..53bafb61a 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -104,7 +104,7 @@ public function setup_fields_sections() { $this, [ 'id' => 'generate_title_prompt', - 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'prompt_placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) ] ); @@ -121,9 +121,9 @@ public function get_default_settings() { 'number_of_titles' => 1, 'generate_title_prompt' => array( array( - 'title' => esc_html__( 'Default', 'classifai' ), - 'prompt' => '', - 'default' => 1, + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), + 'original' => 1, ), ) ], From dd065e8d257920f5b826cb3d55c8af58189bb440 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 6 Nov 2023 23:53:32 +0530 Subject: [PATCH 004/127] fix repeater field name --- includes/Classifai/Features/Feature.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index fe8a9e1fe..01bfa8f70 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -160,7 +160,7 @@ public function render_prompt_repeater_field( array $args ): void { $class = $args['class'] ?? 'large-text'; $placeholder = $args['placeholder'] ?? ''; $field_name_prefix = sprintf( - 'classifai_%1$s%2$s[%3$s]', + '%1$s%2$s[%3$s]', $this->get_option_name(), $option_index ? "[$option_index]" : '', $args['label_for'] From 97c2961976eb02941bab87913a5fc69820434a4d Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 6 Nov 2023 23:57:23 +0530 Subject: [PATCH 005/127] remove redundant fields from Provider.php --- includes/Classifai/Providers/Provider.php | 300 ---------------------- 1 file changed, 300 deletions(-) diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index e8080c048..4d7d181d4 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -163,306 +163,6 @@ public function get_default_settings() { return []; } - /** - * Generic text input field callback - * - * @param array $args The args passed to add_settings_field. - */ - public function render_input( $args ) { - $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; - $setting_index = $this->get_settings( $option_index ); - $type = $args['input_type'] ?? 'text'; - $value = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; - - // Check for a default value - $value = ( empty( $value ) && isset( $args['default_value'] ) ) ? $args['default_value'] : $value; - $attrs = ''; - $class = ''; - - switch ( $type ) { - case 'text': - case 'password': - $attrs = ' value="' . esc_attr( $value ) . '"'; - $class = 'regular-text'; - break; - case 'number': - $attrs = ' value="' . esc_attr( $value ) . '"'; - - if ( isset( $args['max'] ) && is_numeric( $args['max'] ) ) { - $attrs .= ' max="' . esc_attr( (float) $args['max'] ) . '"'; - } - - if ( isset( $args['min'] ) && is_numeric( $args['min'] ) ) { - $attrs .= ' min="' . esc_attr( (float) $args['min'] ) . '"'; - } - - if ( isset( $args['step'] ) && is_numeric( $args['step'] ) ) { - $attrs .= ' step="' . esc_attr( (float) $args['step'] ) . '"'; - } - - $class = 'small-text'; - break; - case 'checkbox': - $attrs = ' value="1"' . checked( '1', $value, false ); - break; - } - ?> - /> - ' . wp_kses_post( $args['description'] ) . ''; - } - } - - /** - * Generic prompt repeater field callback - * - * @since 2.4.0 - * - * @param array $args The args passed to add_settings_field. - */ - public function render_prompt_repeater_field( array $args ): void { - $option_index = $args['option_index'] ?? false; - $setting_index = $this->get_settings( $option_index ); - $prompts = $setting_index[ $args['label_for'] ] ?? ''; - $class = $args['class'] ?? 'large-text'; - $placeholder = $args['placeholder'] ?? ''; - $field_name_prefix = sprintf( - 'classifai_%1$s%2$s[%3$s]', - $this->option_name, - $option_index ? "[$option_index]" : '', - $args['label_for'] - ); - - $prompts = empty( $prompts ) && isset( $args['default_value'] ) ? $args['default_value'] : $prompts; - - $prompt_count = count( $prompts ); - $field_index = 0; - ?> - - - - -
- -

- ; %2$s with ; %3$s with prompt. */ - esc_html__( '%1$sClassifAI default prompt%2$s: %3$s', 'classifai' ), - '', - '', - esc_html( $placeholder ) - ); - ?> -

- - - " - value="" - class="js-setting-field__default"> - " - value=""> - - - - - -
- - - - - - ' . wp_kses_post( $args['description'] ) . ''; - } - } - - /** - * Renders a select menu - * - * @param array $args The args passed to add_settings_field. - */ - public function render_select( $args ) { - $setting_index = $this->get_settings(); - $saved = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; - - // Check for a default value - $saved = ( empty( $saved ) && isset( $args['default_value'] ) ) ? $args['default_value'] : $saved; - $options = isset( $args['options'] ) ? $args['options'] : []; - ?> - - - - ' . wp_kses_post( $args['description'] ) . ''; - } - } - - /** - * Render a group of checkboxes. - * - * @param array $args The args passed to add_settings_field - */ - public function render_checkbox_group( array $args = array() ) { - $setting_index = $this->get_settings(); - - // Iterate through all of our options. - foreach ( $args['options'] as $option_value => $option_label ) { - $value = ''; - $default_key = array_search( $option_value, $args['default_values'], true ); - - // Get saved value, if any. - if ( isset( $setting_index[ $args['label_for'] ] ) ) { - $value = $setting_index[ $args['label_for'] ][ $option_value ] ?? ''; - } - - // If no saved value, check if we have a default value. - if ( empty( $value ) && '0' !== $value && isset( $args['default_values'][ $default_key ] ) ) { - $value = $args['default_values'][ $default_key ]; - } - - // Render checkbox. - printf( - '

- -

', - esc_attr( $this->option_name ), - esc_attr( $args['label_for'] ), - esc_attr( $option_value ), - checked( $value, $option_value, false ), - esc_html( $option_label ) - ); - } - - // Render description, if any. - if ( ! empty( $args['description'] ) ) { - printf( - '%s', - esc_html( $args['description'] ) - ); - } - } - - /** - * Renders the checkbox group for 'Generate descriptive text' setting. - * - * @param array $args The args passed to add_settings_field. - */ - public function render_auto_caption_fields( $args ) { - $setting_index = $this->get_settings(); - - $default_value = ''; - - if ( isset( $setting_index['enable_image_captions'] ) ) { - if ( ! is_array( $setting_index['enable_image_captions'] ) ) { - if ( '1' === $setting_index['enable_image_captions'] ) { - $default_value = 'alt'; - } elseif ( 'no' === $setting_index['enable_image_captions'] ) { - $default_value = ''; - } - } - } - - $checkbox_options = array( - 'alt' => esc_html__( 'Alt text', 'classifai' ), - 'caption' => esc_html__( 'Image caption', 'classifai' ), - 'description' => esc_html__( 'Image description', 'classifai' ), - ); - - foreach ( $checkbox_options as $option_value => $option_label ) { - if ( isset( $setting_index['enable_image_captions'] ) ) { - if ( ! is_array( $setting_index['enable_image_captions'] ) ) { - $default_value = '1' === $setting_index['enable_image_captions'] ? 'alt' : ''; - } else { - $default_value = $setting_index['enable_image_captions'][ $option_value ]; - } - } - - printf( - '

- -

', - esc_attr( $this->option_name ), - esc_attr( $args['label_for'] ), - esc_attr( $option_value ), - checked( $default_value, $option_value, false ), - esc_html( $option_label ) - ); - } - - // Render description, if any. - if ( ! empty( $args['description'] ) ) { - printf( - '%s', - esc_html( $args['description'] ) - ); - } - } - /** * Set up the fields for each section. */ From 2b468db911ab22c481e6f404b54bde80eb182ffe Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 01:05:15 +0530 Subject: [PATCH 006/127] try generate titles with existing classes --- includes/Classifai/Features/Feature.php | 6 +++++- includes/Classifai/Helpers.php | 4 ++-- .../Classifai/Providers/OpenAI/ChatGPT.php | 12 ++++++----- .../Classifai/Services/LanguageProcessing.php | 21 +++++++++++-------- includes/Classifai/Services/Service.php | 8 +------ 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 01bfa8f70..be0c1a4a1 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -7,7 +7,7 @@ abstract class Feature { public $roles = []; - public function __construct( $provider_instances = [] ) { + public function __construct() { $default_settings = $this->get_default_settings(); $this->roles = get_editable_roles() ?? []; $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); @@ -50,6 +50,10 @@ abstract public function get_providers(); abstract public function sanitize_settings( $settings ); + public function is_feature_enabled() { + + } + public function get_option_name() { return 'classifai_' . static::ID; } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 47f47e3f1..ab68e7011 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -701,11 +701,11 @@ function clean_input( string $key = '', bool $is_get = false, string $sanitize_c * @param string $service_name Service name to look for. * @return Provider|WP_Error */ -function find_provider_class( array $provider_classes = [], string $service_name = '' ) { +function find_provider_class( array $provider_classes = [], string $provider_id = '' ) { $provider = ''; foreach ( $provider_classes as $provider_class ) { - if ( $service_name === $provider_class->provider_service_name ) { + if ( $provider_id === $provider_class::ID ) { $provider = $provider_class; } } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index a1e6f9c2b..5c246a297 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -5,6 +5,7 @@ namespace Classifai\Providers\OpenAI; +use Classifai\Features\TitleGeneration; use Classifai\Providers\Provider; use Classifai\Watson\Normalizer; use WP_Error; @@ -708,24 +709,25 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to generate titles.', 'classifai' ) ); } - $settings = $this->get_settings(); + $feature = new TitleGeneration(); + $settings = $feature->get_settings(); $args = wp_parse_args( array_filter( $args ), [ - 'num' => $settings['number_titles'] ?? 1, + 'num' => $settings[ static::ID ]['number_titles'] ?? 1, 'content' => '', ] ); // These checks happen in the REST permission_callback, // but we run them again here in case this method is called directly. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) || ! $this->is_feature_enabled( 'enable_titles' ) ) { + if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ! $this->is_feature_enabled( 'enable_titles' ) ) { return new WP_Error( 'not_enabled', esc_html__( 'Title generation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } - $request = new APIRequest( $settings['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); - $prompt = esc_textarea( $this->get_default_prompt( $settings['generate_title_prompt'] ) ?? $this->generate_title_prompt ); + $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_title_prompt'] ) ?? $this->generate_title_prompt ); /** * Filter the prompt we will send to ChatGPT. diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 85a56b5a3..7bade1aa8 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -6,6 +6,8 @@ namespace Classifai\Services; use Classifai\Admin\SavePostHandler; +use Classifai\Features\TitleGeneration; + use function Classifai\find_provider_class; use WP_REST_Server; use WP_REST_Request; @@ -515,11 +517,10 @@ public function generate_post_title( WP_REST_Request $request ) { $provider = ''; // Find the right provider class. - foreach ( $this->provider_classes as $provider_class ) { - if ( 'ChatGPT' === $provider_class->provider_service_name ) { - $provider = $provider_class; - } - } + $title_generation = new TitleGeneration(); + $feature_settings = $title_generation->get_settings(); + $provider_id = $feature_settings['provider']; + $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); // Ensure we have a provider class. Should never happen but :shrug: if ( ! $provider ) { @@ -550,9 +551,11 @@ public function generate_post_title( WP_REST_Request $request ) { * @return WP_Error|bool */ public function generate_post_title_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $provider = find_provider_class( $this->provider_classes ?? [], 'ChatGPT' ); - $settings = \Classifai\get_plugin_settings( 'language_processing', 'ChatGPT' ); + $post_id = $request->get_param( 'id' ); + $title_generation = new TitleGeneration(); + $settings = $title_generation->get_settings(); + $provider_id = $settings['provider']; + $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); // Ensure we have a logged in user that can edit the item. if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { @@ -568,7 +571,7 @@ public function generate_post_title_permissions_check( WP_REST_Request $request } // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { + if ( empty( $settings ) || ( isset( $settings[ $provider_id ]['authenticated'] ) && false === $settings[ $provider_id ]['authenticated'] ) ) { return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); } diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 6c52f8793..521cf4249 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -74,16 +74,10 @@ public function init() { $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); - $provider_classes_with_id = []; - - foreach ( $this->provider_classes as $provider_class ) { - $provider_classes_with_id[ $provider_class::ID ] = $provider_class; - } - if ( ! empty( $this->features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $this->feature_classes[] = new $feature( $provider_classes_with_id ); + $this->feature_classes[] = new $feature(); } } } From 09e7e880380b88efb8687f5ceec3d4f202872d00 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 14:00:06 +0530 Subject: [PATCH 007/127] update language processing handlers --- .../Classifai/Features/ContentResizing.php | 25 +++++++ .../Classifai/Features/ExcerptGeneration.php | 25 +++++++ includes/Classifai/Features/Feature.php | 4 +- .../Classifai/Features/TitleGeneration.php | 25 +++++++ .../Classifai/Providers/OpenAI/ChatGPT.php | 69 +++++-------------- .../Classifai/Services/LanguageProcessing.php | 59 ++++++++-------- 6 files changed, 124 insertions(+), 83 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index eb2d82e86..e4ca06911 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -104,6 +104,31 @@ public function setup_fields_sections() { ); } + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles' ] ?? []; + + // Check if user has access to the feature and the feature is turned on. + if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { + $access = true; + } + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + public function get_default_settings() { return [ 'status' => '0', diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 742d17374..156b3a189 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -102,6 +102,31 @@ public function setup_fields_sections() { ); } + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles' ] ?? []; + + // Check if user has access to the feature and the feature is turned on. + if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { + $access = true; + } + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + public function get_default_settings() { return [ 'status' => '0', diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index be0c1a4a1..991e38d88 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -50,9 +50,7 @@ abstract public function get_providers(); abstract public function sanitize_settings( $settings ); - public function is_feature_enabled() { - - } + abstract public function is_feature_enabled(); public function get_option_name() { return 'classifai_' . static::ID; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 53bafb61a..613426a11 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -110,6 +110,31 @@ public function setup_fields_sections() { ); } + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles' ] ?? []; + + // Check if user has access to the feature and the feature is turned on. + if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { + $access = true; + } + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + public function get_default_settings() { return [ 'status' => '0', diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 5c246a297..08b4f363b 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -5,6 +5,8 @@ namespace Classifai\Providers\OpenAI; +use Classifai\Features\ContentResizing; +use Classifai\Features\ExcerptGeneration; use Classifai\Features\TitleGeneration; use Classifai\Providers\Provider; use Classifai\Watson\Normalizer; @@ -186,50 +188,6 @@ public function sanitize_number_of_responses_field( $key, $settings ) { return 1; } - /** - * Determine if the current user can access the feature - * - * @param string $feature Feature to check. - * @return bool - */ - public function is_feature_enabled( string $feature = '' ) { - $access = false; - $settings = $this->get_settings(); - $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = []; - - $role_keys = [ - 'enable_excerpt' => 'roles', - 'enable_titles' => 'title_roles', - 'enable_resize_content' => 'resize_content_roles', - ]; - - if ( isset( $role_keys[ $feature ] ) ) { - $feature_roles = $settings[ $role_keys[ $feature ] ] ?? []; - } - - // Check if user has access to the feature and the feature is turned on. - if ( - ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) - && ( isset( $settings[ $feature ] ) && 1 === (int) $settings[ $feature ] ) - ) { - $access = true; - } - - /** - * Filter to override permission to a ChatGPT generate feature. - * - * @since 2.3.0 - * @hook classifai_openai_chatgpt_{$feature} - * - * @param {bool} $access Current access value. - * @param {array} $settings Current feature settings. - * - * @return {bool} Should the user have access? - */ - return apply_filters( "classifai_openai_chatgpt_{$feature}", $access, $settings ); - } - /** * Register what we need for the plugin. * @@ -604,6 +562,9 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) ); } + $feature = new ExcerptGeneration(); + $settings = $feature->get_settings(); + $settings = $this->get_settings(); $args = wp_parse_args( array_filter( $args ), @@ -615,15 +576,15 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { // These checks (and the one above) happen in the REST permission_callback, // but we run them again here in case this method is called directly. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) || ( ! $this->is_feature_enabled( 'enable_excerpt' ) && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) ) { + if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ( ! $feature->is_feature_enabled() && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) ) { return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } $excerpt_length = absint( $settings['length'] ?? 55 ); - $request = new APIRequest( $settings['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); - $excerpt_prompt = esc_textarea( $this->get_default_prompt( $settings['generate_excerpt_prompt'] ) ?? $this->generate_excerpt_prompt ); + $excerpt_prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_excerpt_prompt'] ) ?? $this->generate_excerpt_prompt ); // Replace our variables in the prompt. $prompt_search = array( '{{WORDS}}', '{{TITLE}}' ); @@ -721,7 +682,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { // These checks happen in the REST permission_callback, // but we run them again here in case this method is called directly. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ! $this->is_feature_enabled( 'enable_titles' ) ) { + if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ! $feature->is_feature_enabled() ) { return new WP_Error( 'not_enabled', esc_html__( 'Title generation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } @@ -816,20 +777,22 @@ public function resize_content( int $post_id, array $args = array() ) { return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to resize content.', 'classifai' ) ); } - $settings = $this->get_settings(); + $feature = new ContentResizing(); + $settings = $feature->get_settings(); + $args = wp_parse_args( array_filter( $args ), [ - 'num' => $settings['number_resize_content'] ?? 1, + 'num' => $settings[ static::ID ]['number_of_suggestions'] ?? 1, ] ); - $request = new APIRequest( $settings['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); if ( 'shrink' === $args['resize_type'] ) { - $prompt = esc_textarea( $this->get_default_prompt( $settings['shrink_content_prompt'] ) ?? $this->shrink_content_prompt ); + $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['shrink_content_prompt'] ) ?? $this->shrink_content_prompt ); } else { - $prompt = esc_textarea( $this->get_default_prompt( $settings['grow_content_prompt'] ) ?? $this->grow_content_prompt ); + $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['grow_content_prompt'] ) ?? $this->grow_content_prompt ); } /** diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 7bade1aa8..73b1c7d99 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -6,6 +6,8 @@ namespace Classifai\Services; use Classifai\Admin\SavePostHandler; +use Classifai\Features\ExcerptGeneration; +use Classifai\Features\ContentResizing; use Classifai\Features\TitleGeneration; use function Classifai\find_provider_class; @@ -314,8 +316,10 @@ public function generate_post_excerpt( WP_REST_Request $request ) { $content = $request->get_param( 'content' ); $title = $request->get_param( 'title' ); - // Find the right provider class. - $provider = find_provider_class( $this->provider_classes ?? [], 'ChatGPT' ); + $feature = new ExcerptGeneration(); + $feature_settings = $feature->get_settings(); + $provider_id = $feature_settings['provider']; + $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); // Ensure we have a provider class. Should never happen but :shrug: if ( is_wp_error( $provider ) ) { @@ -346,9 +350,11 @@ public function generate_post_excerpt( WP_REST_Request $request ) { * @return WP_Error|bool */ public function generate_post_excerpt_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $provider = find_provider_class( $this->provider_classes ?? [], 'ChatGPT' ); - $settings = \Classifai\get_plugin_settings( 'language_processing', 'ChatGPT' ); + $post_id = $request->get_param( 'id' ); + $feature = new ExcerptGeneration(); + $settings = $feature->get_settings(); + $provider_id = $settings['provider']; + $provider = find_provider_class( $this->provider_classes ?? [], 'ChatGPT' ); // Ensure we have a provider class. Should never happen but :shrug: if ( is_wp_error( $provider ) ) { @@ -369,12 +375,12 @@ public function generate_post_excerpt_permissions_check( WP_REST_Request $reques } // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { + if ( empty( $settings ) || ( isset( $settings[ $provider_id ]['authenticated'] ) && false === $settings[ $provider_id ]['authenticated'] ) ) { return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); } // Ensure the feature is enabled. Also runs a user check. - if ( ! $provider->is_feature_enabled( 'enable_excerpt' ) ) { + if ( ! $feature->is_feature_enabled() ) { return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation not currently enabled.', 'classifai' ) ); } @@ -517,8 +523,8 @@ public function generate_post_title( WP_REST_Request $request ) { $provider = ''; // Find the right provider class. - $title_generation = new TitleGeneration(); - $feature_settings = $title_generation->get_settings(); + $feature = new TitleGeneration(); + $feature_settings = $feature->get_settings(); $provider_id = $feature_settings['provider']; $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); @@ -551,11 +557,10 @@ public function generate_post_title( WP_REST_Request $request ) { * @return WP_Error|bool */ public function generate_post_title_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $title_generation = new TitleGeneration(); - $settings = $title_generation->get_settings(); - $provider_id = $settings['provider']; - $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); + $post_id = $request->get_param( 'id' ); + $feature = new TitleGeneration(); + $settings = $feature->get_settings(); + $provider_id = $settings['provider']; // Ensure we have a logged in user that can edit the item. if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { @@ -576,8 +581,8 @@ public function generate_post_title_permissions_check( WP_REST_Request $request } // Ensure the feature is enabled. Also runs a user check. - if ( ! $provider->is_feature_enabled( 'enable_titles' ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation not currently enabled.', 'classifai' ) ); + if ( ! $feature->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Title generation not currently enabled.', 'classifai' ) ); } return true; @@ -594,11 +599,10 @@ public function resize_content( WP_REST_Request $request ) { $provider = ''; // Find the right provider class. - foreach ( $this->provider_classes as $provider_class ) { - if ( 'ChatGPT' === $provider_class->provider_service_name ) { - $provider = $provider_class; - } - } + $feature = new ContentResizing(); + $feature_settings = $feature->get_settings(); + $provider_id = $feature_settings['provider']; + $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); // Ensure we have a provider class. Should never happen but :shrug: if ( ! $provider ) { @@ -624,7 +628,10 @@ public function resize_content( WP_REST_Request $request ) { * @return WP_Error|bool */ public function resize_content_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); + $post_id = $request->get_param( 'id' ); + $feature = new ContentResizing(); + $settings = $feature->get_settings(); + $provider_id = $settings['provider']; // Ensure we have a logged in user that can edit the item. if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { @@ -639,20 +646,18 @@ public function resize_content_permissions_check( WP_REST_Request $request ) { return false; } - $settings = \Classifai\get_plugin_settings( 'language_processing', 'ChatGPT' ); - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { + if ( empty( $settings ) || ( isset( $settings[ $provider_id ]['authenticated'] ) && false === $settings[ $provider_id ]['authenticated'] ) ) { return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); } // Check if resize content feature is turned on. - if ( empty( $settings ) || ( isset( $settings['enable_resize_content'] ) && 'no' === $settings['enable_resize_content'] ) ) { + if ( empty( $settings ) || ( isset( $settings[ $provider_id ]['status'] ) && 'no' === $settings[ $provider_id ]['status'] ) ) { return new WP_Error( 'not_enabled', esc_html__( 'Content resizing not currently enabled.', 'classifai' ) ); } // Check if the current user's role is allowed. - $roles = $settings['resize_content_roles'] ?? []; + $roles = $settings[ $provider_id ]['roles'] ?? []; $user_roles = wp_get_current_user()->roles ?? []; if ( empty( $roles ) || ! empty( array_diff( $user_roles, $roles ) ) ) { From 3868b80a88f107603a27082eced3605064fecc9c Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 18:15:17 +0530 Subject: [PATCH 008/127] pass feature instance to provider --- .../Classifai/Features/ContentResizing.php | 9 ++-- .../Classifai/Features/ExcerptGeneration.php | 7 ++- .../Classifai/Features/TitleGeneration.php | 8 ++-- .../Classifai/Providers/OpenAI/ChatGPT.php | 44 +++++++++++-------- includes/Classifai/Providers/Provider.php | 4 +- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index e4ca06911..0b3e04805 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -74,10 +74,9 @@ public function setup_fields_sections() { ] ); - $chat_gpt = new ChatGPT( null ); - $chat_gpt->add_api_key_field( $this ); + $chat_gpt = new ChatGPT( $this ); + $chat_gpt->add_api_key_field(); $chat_gpt->add_number_of_responses_field( - $this, [ 'id' => 'number_of_suggestions', 'label' => esc_html__( 'Number of suggestions', 'classifai' ), @@ -85,7 +84,6 @@ public function setup_fields_sections() { ] ); $chat_gpt->add_prompt_field( - $this, [ 'id' => 'condense_text_prompt', 'label' => esc_html__( 'Condense text prompt', 'classifai' ), @@ -94,7 +92,6 @@ public function setup_fields_sections() { ] ); $chat_gpt->add_prompt_field( - $this, [ 'id' => 'expand_text_prompt', 'label' => esc_html__( 'Expand text prompt'), @@ -161,7 +158,7 @@ public function render_section() { public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); - $chat_gpt = new ChatGPT( null ); + $chat_gpt = new ChatGPT( $this ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 156b3a189..f45233864 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -90,10 +90,9 @@ public function setup_fields_sections() { ] ); - $chat_gpt = new ChatGPT( null ); - $chat_gpt->add_api_key_field( $this ); + $chat_gpt = new ChatGPT( $this ); + $chat_gpt->add_api_key_field(); $chat_gpt->add_prompt_field( - $this, [ 'id' => 'generate_excerpt_prompt', 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), @@ -148,7 +147,7 @@ public function get_default_settings() { public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); - $chat_gpt = new ChatGPT( null ); + $chat_gpt = new ChatGPT( $this ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 613426a11..19cd7f856 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -90,10 +90,9 @@ public function setup_fields_sections() { ] ); - $chat_gpt = new ChatGPT( null ); - $chat_gpt->add_api_key_field( $this ); + $chat_gpt = new ChatGPT( $this ); + $chat_gpt->add_api_key_field(); $chat_gpt->add_number_of_responses_field( - $this, [ 'id' => 'number_of_titles', 'label' => esc_html__( 'Number of titles', 'classifai' ), @@ -101,7 +100,6 @@ public function setup_fields_sections() { ] ); $chat_gpt->add_prompt_field( - $this, [ 'id' => 'generate_title_prompt', 'prompt_placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), @@ -157,7 +155,7 @@ public function get_default_settings() { public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); - $chat_gpt = new ChatGPT( null ); + $chat_gpt = new ChatGPT( $this ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 08b4f363b..339185b4d 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -68,17 +68,23 @@ class ChatGPT extends Provider { */ protected $grow_content_prompt = 'Increase the content length no more than 2 to 4 sentences.'; + /** + * Feature instance. + * + * @var \Classifai\Features\Feature + */ + protected $feature_instance = null; + /** * OpenAI ChatGPT constructor. * * @param string $service The service this class belongs to. */ - public function __construct( $service ) { + public function __construct( $feature_instance ) { parent::__construct( 'OpenAI ChatGPT', 'ChatGPT', - 'openai_chatgpt', - $service + 'openai_chatgpt' ); // Set the onboarding options. @@ -91,21 +97,23 @@ public function __construct( $service ) { 'enable_resize_content' => __( 'Content resizing', 'classifai' ), ), ); + + $this->feature_instance = $feature_instance; } /** * Adds a prompt repeater field. */ - public function add_prompt_field( $feature_instance, $args = [] ) { - $default_settings = $feature_instance->get_default_settings(); + public function add_prompt_field( $args = [] ) { + $default_settings = $this->feature_instance->get_default_settings(); $default_settings = $default_settings[ static::ID ]; add_settings_field( $args['id'], $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), - [ $feature_instance, 'render_prompt_repeater_field' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', + [ $this->feature_instance, 'render_prompt_repeater_field' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, 'label_for' => $args['id'], @@ -119,16 +127,16 @@ public function add_prompt_field( $feature_instance, $args = [] ) { /** * Adds an api key field. */ - public function add_api_key_field( $feature_instance ) { - $default_settings = $feature_instance->get_default_settings(); + public function add_api_key_field() { + $default_settings = $this->feature_instance->get_default_settings(); $default_settings = $default_settings[ static::ID ]; add_settings_field( 'api_key', esc_html__( 'API Key', 'classifai' ), - [ $feature_instance, 'render_input' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, 'label_for' => 'api_key', @@ -144,16 +152,16 @@ public function add_api_key_field( $feature_instance ) { /** * Adds number of responses number field. */ - public function add_number_of_responses_field( $feature_instance, $args = [] ) { - $default_settings = $feature_instance->get_default_settings(); + public function add_number_of_responses_field( $args = [] ) { + $default_settings = $this->feature_instance->get_default_settings(); $default_settings = $default_settings[ static::ID ]; add_settings_field( $args['id'], $args['label'], - [ $feature_instance, 'render_input' ], - $feature_instance->get_option_name(), - $feature_instance->get_option_name() . '_section', + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, 'label_for' => $args['id'], diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 4d7d181d4..2eea97137 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -41,13 +41,11 @@ abstract class Provider { * @param string $provider_name The name of the Provider that will appear in the admin tab * @param string $provider_service_name The name of the Service. * @param string $option_name Name of the option where the provider settings are stored. - * @param string $service What service does this provider belong to. */ - public function __construct( $provider_name, $provider_service_name, $option_name, $service ) { + public function __construct( $provider_name, $provider_service_name, $option_name ) { $this->provider_name = $provider_name; $this->provider_service_name = $provider_service_name; $this->option_name = $option_name; - $this->service = $service; $this->onboarding_options = array(); } From a765c73a9b3886c3d1f78222facdd9a772c6b4d3 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 19:26:38 +0530 Subject: [PATCH 009/127] fix OpenAI trait --- .../Classifai/Features/ContentResizing.php | 5 +++- .../Classifai/Features/ExcerptGeneration.php | 5 +++- .../Classifai/Features/TitleGeneration.php | 21 +++------------ .../Classifai/Providers/OpenAI/ChatGPT.php | 13 +++------ .../Classifai/Providers/OpenAI/OpenAI.php | 27 ++++++++++--------- includes/Classifai/Providers/Provider.php | 6 +++++ 6 files changed, 35 insertions(+), 42 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 0b3e04805..95491637b 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -134,6 +134,7 @@ public function get_default_settings() { ChatGPT::ID => [ 'api_key' => '', 'number_of_suggestions' => 1, + 'authenticated' => false, 'condense_text_prompt' => array( array( 'title' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), @@ -179,7 +180,9 @@ public function sanitize_settings( $settings ) { } if ( isset( $settings[ ChatGPT::ID ] ) ) { - $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; + $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; $new_settings[ ChatGPT::ID ]['number_of_suggestions'] = $chat_gpt->sanitize_number_of_responses_field( 'number_of_suggestions', $settings ); $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $chat_gpt->sanitize_prompts( 'condense_text_prompt', $settings ); $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $chat_gpt->sanitize_prompts( 'expand_text_prompt', $settings ); diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index f45233864..ca077dc48 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -134,6 +134,7 @@ public function get_default_settings() { 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ChatGPT::ID => [ 'api_key' => '', + 'authenticated' => false, 'generate_excerpt_prompt' => array( array( 'title' => esc_html__( 'Default', 'classifai' ), @@ -174,7 +175,9 @@ public function sanitize_settings( $settings ) { } if ( isset( $settings[ ChatGPT::ID ] ) ) { - $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; + $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $chat_gpt->sanitize_prompts( 'generate_excerpt_prompt', $settings ); } diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 19cd7f856..fd21343a2 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -61,22 +61,6 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'length', - esc_html__( 'Excerpt length', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'length', - 'input_type' => 'number', - 'min' => 1, - 'step' => 1, - 'default_value' => $default_settings['length'], - 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), - ] - ); - add_settings_field( 'provider', esc_html__( 'Select a provider', 'classifai' ), @@ -142,6 +126,7 @@ public function get_default_settings() { ChatGPT::ID => [ 'api_key' => '', 'number_of_titles' => 1, + 'authenticated' => false, 'generate_title_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), @@ -176,7 +161,9 @@ public function sanitize_settings( $settings ) { } if ( isset( $settings[ ChatGPT::ID ] ) ) { - $new_settings[ ChatGPT::ID ]['api_key'] = $chat_gpt->sanitize_api_key( $settings ); + $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; + $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; $new_settings[ ChatGPT::ID ]['number_of_titles'] = $chat_gpt->sanitize_number_of_responses_field( 'number_of_titles', $settings ); $new_settings[ ChatGPT::ID ]['generate_title_prompt'] = $chat_gpt->sanitize_prompts( 'generate_title_prompt', $settings ); } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 339185b4d..439bc7d30 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -68,13 +68,6 @@ class ChatGPT extends Provider { */ protected $grow_content_prompt = 'Increase the content length no more than 2 to 4 sentences.'; - /** - * Feature instance. - * - * @var \Classifai\Features\Feature - */ - protected $feature_instance = null; - /** * OpenAI ChatGPT constructor. * @@ -590,7 +583,7 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { $excerpt_length = absint( $settings['length'] ?? 55 ); - $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); $excerpt_prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_excerpt_prompt'] ) ?? $this->generate_excerpt_prompt ); @@ -694,7 +687,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { return new WP_Error( 'not_enabled', esc_html__( 'Title generation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } - $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_title_prompt'] ) ?? $this->generate_title_prompt ); @@ -795,7 +788,7 @@ public function resize_content( int $post_id, array $args = array() ) { ] ); - $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); if ( 'shrink' === $args['resize_type'] ) { $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['shrink_content_prompt'] ) ?? $this->shrink_content_prompt ); diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index 149542263..df8b48190 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -25,12 +25,12 @@ trait OpenAI { * @param string $default_api_key Default API key. */ protected function setup_api_fields( string $default_api_key = '' ) { - $existing_settings = $this->get_settings(); + $existing_settings = $this->feature_instance->get_settings(); $description = ''; // Add the settings section. add_settings_section( - $this->get_option_name(), + $this->feature_instance->get_option_name(), $this->provider_service_name, function() { printf( @@ -47,7 +47,7 @@ function() { esc_url( 'https://platform.openai.com/signup' ) ); }, - $this->get_option_name() + $this->feature_instance->get_option_name() ); // Determine which other OpenAI provider to look for an API key in. @@ -77,8 +77,8 @@ function() { 'api-key', esc_html__( 'API Key', 'classifai' ), [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name(), [ 'label_for' => 'api_key', 'input_type' => 'password', @@ -95,17 +95,18 @@ function() { * @param array $old_settings Existing settings, if any. * @return array */ - protected function sanitize_api_key_settings( array $new_settings = [], array $old_settings = [] ) { - $authenticated = $this->authenticate_credentials( $old_settings['api_key'] ?? '' ); + public function sanitize_api_key_settings( array $old_settings = [] ) { + $new_settings = $this->feature_instance->get_settings(); + $authenticated = $this->authenticate_credentials( $old_settings[ static::ID ]['api_key'] ?? '' ); if ( is_wp_error( $authenticated ) ) { - $new_settings['authenticated'] = false; - $error_message = $authenticated->get_error_message(); + $new_settings[ static::ID ]['authenticated'] = false; + $error_message = $authenticated->get_error_message(); // For response code 429, credentials are valid but rate limit is reached. if ( 429 === (int) $authenticated->get_error_code() ) { - $new_settings['authenticated'] = true; - $error_message = str_replace( 'plan and billing details', 'plan and billing details', $error_message ); + $new_settings[ static::ID ]['authenticated'] = true; + $error_message = str_replace( 'plan and billing details', 'plan and billing details', $error_message ); } else { $error_message = str_replace( 'https://platform.openai.com/account/api-keys', 'https://platform.openai.com/account/api-keys', $error_message ); } @@ -117,10 +118,10 @@ protected function sanitize_api_key_settings( array $new_settings = [], array $o 'error' ); } else { - $new_settings['authenticated'] = true; + $new_settings[ static::ID ]['authenticated'] = true; } - $new_settings['api_key'] = sanitize_text_field( $old_settings['api_key'] ?? '' ); + $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $old_settings[ static::ID ]['api_key'] ?? '' ); return $new_settings; } diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 2eea97137..d36d43df8 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -34,6 +34,12 @@ abstract class Provider { */ public $onboarding_options; + /** + * Feature instance. + * + * @var \Classifai\Features\Feature + */ + protected $feature_instance = null; /** * Provider constructor. From 695c73124a8bbc7cb97509492c004cec714b93bc Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 23:23:43 +0530 Subject: [PATCH 010/127] working title, excerpt gen and content resizing --- includes/Classifai/Features/Feature.php | 74 +++++++++++++++---- .../Classifai/Providers/OpenAI/ChatGPT.php | 30 ++++---- .../Classifai/Services/LanguageProcessing.php | 4 +- includes/Classifai/Services/Service.php | 13 +++- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 991e38d88..822926adb 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -2,12 +2,23 @@ namespace Classifai\Features; +use function Classifai\find_provider_class; + abstract class Feature { const ID = ''; public $roles = []; - public function __construct() { + public $provider_classes = []; + + public function __construct( $provider_classes = [] ) { + $this->provider_classes = $provider_classes; + add_action( 'admin_init', [ $this, 'setup_roles' ] ); + add_action( 'admin_init', [ $this, 'register_setting' ] ); + add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); + } + + public function setup_roles() { $default_settings = $this->get_default_settings(); $this->roles = get_editable_roles() ?? []; $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); @@ -24,19 +35,6 @@ public function __construct() { * @return {array} Roles array. */ $this->roles = apply_filters( 'classifai_chatgpt_allowed_roles', $this->roles, $default_settings ); - - add_action( 'admin_init', [ $this, 'register_setting' ] ); - add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); - } - - public function register_setting() { - register_setting( - $this->get_option_name(), - $this->get_option_name(), - [ - 'sanitize_callback' => [ $this, 'sanitize_settings' ], - ] - ); } /** @@ -52,6 +50,30 @@ abstract public function sanitize_settings( $settings ); abstract public function is_feature_enabled(); + public function register() { + if ( ! $this->can_register() ) { + return; + } + + $feature_settings = $this->get_settings(); + $provider_id = $feature_settings['provider']; + $provider_instance = find_provider_class( $this->provider_classes, $provider_id ); + $provider_class = get_class( $provider_instance ); + $provider_instance = new $provider_class( $this ); + + $provider_instance->register(); + } + + public function register_setting() { + register_setting( + $this->get_option_name(), + $this->get_option_name(), + [ + 'sanitize_callback' => [ $this, 'sanitize_settings' ], + ] + ); + } + public function get_option_name() { return 'classifai_' . static::ID; } @@ -68,6 +90,30 @@ public function get_settings( $index = false ) { return $settings; } + /** + * Returns whether the provider is configured or not. + * + * @return bool + */ + public function is_configured() { + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $is_configured = false; + + if ( ! empty( $settings ) && ! empty( $settings[ $provider_id ]['authenticated'] ) ) { + $is_configured = true; + } + + return $is_configured; + } + + /** + * Can the feature be initialized? + */ + public function can_register() { + return $this->is_configured(); + } + protected function get_data_attribute( $args ) { $data_attr = $args['data_attr'] ?? []; $data_attr_str = ''; diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 439bc7d30..cc830c6ad 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -59,14 +59,14 @@ class ChatGPT extends Provider { * * @var string */ - protected $shrink_content_prompt = 'Decrease the content length no more than 2 to 4 sentences.'; + protected $condense_text_prompt = 'Decrease the content length no more than 2 to 4 sentences.'; /** * Prompt for growing content * * @var string */ - protected $grow_content_prompt = 'Increase the content length no more than 2 to 4 sentences.'; + protected $expand_text_prompt = 'Increase the content length no more than 2 to 4 sentences.'; /** * OpenAI ChatGPT constructor. @@ -230,7 +230,7 @@ public function enqueue_editor_assets() { return; } - if ( $this->is_feature_enabled( 'enable_excerpt' ) ) { + if ( $this->feature_instance->is_feature_enabled() ) { // This script removes the core excerpt panel and replaces it with our own. wp_enqueue_script( 'classifai-post-excerpt', @@ -241,7 +241,7 @@ public function enqueue_editor_assets() { ); } - if ( $this->is_feature_enabled( 'enable_titles' ) ) { + if ( $this->feature_instance->is_feature_enabled() ) { wp_enqueue_script( 'classifai-post-status-info', CLASSIFAI_PLUGIN_URL . 'dist/post-status-info.js', @@ -260,7 +260,7 @@ public function enqueue_editor_assets() { ); } - if ( $this->is_feature_enabled( 'enable_resize_content' ) ) { + if ( $this->feature_instance->is_feature_enabled() ) { wp_enqueue_script( 'classifai-content-resizing-plugin-js', CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.js', @@ -314,7 +314,7 @@ static function () { if ( $screen && ! $screen->is_block_editor() ) { if ( post_type_supports( $screen->post_type, 'title' ) && - $this->is_feature_enabled( 'enable_titles' ) + $this->feature_instance->is_feature_enabled() ) { wp_enqueue_style( 'classifai-generate-title-classic-css', @@ -344,7 +344,7 @@ static function () { if ( post_type_supports( $screen->post_type, 'excerpt' ) && - $this->is_feature_enabled( 'enable_excerpt' ) + $this->feature_instance->is_feature_enabled() ) { wp_enqueue_style( 'classifai-generate-title-classic-css', @@ -471,17 +471,17 @@ public function get_default_settings() { 'enable_resize_content' => false, 'resize_content_roles' => array_keys( $editable_roles ), 'suggestion_count' => 1, - 'shrink_content_prompt' => array( + 'condense_text_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => $this->shrink_content_prompt, + 'prompt' => $this->condense_text_prompt, 'original' => 1, ), ), - 'grow_content_prompt' => array( + 'expand_text_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => $this->grow_content_prompt, + 'prompt' => $this->expand_text_prompt, 'original' => 1, ), ), @@ -565,8 +565,6 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { $feature = new ExcerptGeneration(); $settings = $feature->get_settings(); - - $settings = $this->get_settings(); $args = wp_parse_args( array_filter( $args ), [ @@ -676,7 +674,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { $args = wp_parse_args( array_filter( $args ), [ - 'num' => $settings[ static::ID ]['number_titles'] ?? 1, + 'num' => $settings[ static::ID ]['number_of_titles'] ?? 1, 'content' => '', ] ); @@ -791,9 +789,9 @@ public function resize_content( int $post_id, array $args = array() ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); if ( 'shrink' === $args['resize_type'] ) { - $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['shrink_content_prompt'] ) ?? $this->shrink_content_prompt ); + $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['condense_text_prompt'] ) ?? $this->condense_text_prompt ); } else { - $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['grow_content_prompt'] ) ?? $this->grow_content_prompt ); + $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['expand_text_prompt'] ) ?? $this->expand_text_prompt ); } /** diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 73b1c7d99..f28fb6e70 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -354,7 +354,7 @@ public function generate_post_excerpt_permissions_check( WP_REST_Request $reques $feature = new ExcerptGeneration(); $settings = $feature->get_settings(); $provider_id = $settings['provider']; - $provider = find_provider_class( $this->provider_classes ?? [], 'ChatGPT' ); + $provider = find_provider_class( $this->provider_classes ?? [], $provider_id ); // Ensure we have a provider class. Should never happen but :shrug: if ( is_wp_error( $provider ) ) { @@ -657,7 +657,7 @@ public function resize_content_permissions_check( WP_REST_Request $request ) { } // Check if the current user's role is allowed. - $roles = $settings[ $provider_id ]['roles'] ?? []; + $roles = $settings['roles'] ?? []; $user_roles = wp_get_current_user()->roles ?? []; if ( empty( $roles ) || ! empty( array_diff( $user_roles, $roles ) ) ) { diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 521cf4249..b3d38f45f 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -69,7 +69,7 @@ public function init() { $this->provider_classes[] = new $provider( $this->menu_slug ); } } - $this->register_providers(); + // $this->register_providers(); } $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); @@ -77,9 +77,10 @@ public function init() { if ( ! empty( $this->features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $this->feature_classes[] = new $feature(); + $this->feature_classes[] = new $feature( $this->provider_classes ); } } + $this->register_features(); } add_filter( 'classifai_debug_information', [ $this, 'add_service_debug_information' ] ); @@ -99,6 +100,14 @@ public function register_providers() { } } + public function register_features() { + if ( ! empty( $this->feature_classes ) ) { + foreach ( $this->feature_classes as $feature ) { + $feature->register(); + } + } + } + /** * Get the menu slug * From 5572af797760842932a701ad64e285a54c956c55 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 23:52:46 +0530 Subject: [PATCH 011/127] phpcs fixes --- .../Classifai/Features/ContentResizing.php | 96 +++++++++++++++---- .../Classifai/Features/ExcerptGeneration.php | 83 +++++++++++++--- includes/Classifai/Features/Feature.php | 82 +++++++++++++++- .../Classifai/Features/TitleGeneration.php | 81 +++++++++++++--- includes/Classifai/Helpers.php | 2 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 55 ++++++++--- 6 files changed, 331 insertions(+), 68 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 95491637b..579aeed39 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -4,9 +4,22 @@ use \Classifai\Providers\OpenAI\ChatGPT; +/** + * Class ContentResizing + */ class ContentResizing extends Feature { + /** + * ID of the current feature. + * + * @var string + */ const ID = 'feature_content_resizing'; + /** + * Returns the label of the feature. + * + * @return string + */ public function get_label() { return apply_filters( 'classifai_' . static::ID . '_label', @@ -14,7 +27,17 @@ public function get_label() { ); } + /** + * Returns the providers supported by the feature. + * + * @return array + */ public function get_providers() { + /* + * Filter to add or remove providers from the feature. + * + * @param array $providers Array of providers. + */ return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -23,6 +46,9 @@ public function get_providers() { ); } + /** + * Sets up the fields and sections for the feature. + */ public function setup_fields_sections() { $default_settings = $this->get_default_settings(); @@ -74,13 +100,19 @@ public function setup_fields_sections() { ] ); + /* + * The following fields are specific to the OpenAI ChatGPT provider. + * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. + * + * If the feature supports multiple providers, then the fields should be added for each provider. + */ $chat_gpt = new ChatGPT( $this ); $chat_gpt->add_api_key_field(); $chat_gpt->add_number_of_responses_field( [ 'id' => 'number_of_suggestions', 'label' => esc_html__( 'Number of suggestions', 'classifai' ), - 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ) + 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), ] ); $chat_gpt->add_prompt_field( @@ -88,24 +120,29 @@ public function setup_fields_sections() { 'id' => 'condense_text_prompt', 'label' => esc_html__( 'Condense text prompt', 'classifai' ), 'prompt_placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), ] ); $chat_gpt->add_prompt_field( [ 'id' => 'expand_text_prompt', - 'label' => esc_html__( 'Expand text prompt'), + 'label' => esc_html__( 'Expand text prompt' ), 'prompt_placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), ] ); } + /** + * Returns true if the feature meets all the criteria to be enabled. False otherwise. + * + * @return boolean + */ public function is_feature_enabled() { $access = false; $settings = $this->get_settings(); $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles' ] ?? []; + $feature_roles = $settings['roles'] ?? []; // Check if user has access to the feature and the feature is turned on. if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { @@ -126,26 +163,36 @@ public function is_feature_enabled() { return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); } + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ public function get_default_settings() { return [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'status' => '0', + 'roles' => $this->roles, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ChatGPT::ID => [ - 'api_key' => '', + 'api_key' => '', 'number_of_suggestions' => 1, - 'authenticated' => false, - 'condense_text_prompt' => array( + 'authenticated' => false, + 'condense_text_prompt' => array( array( - 'title' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'prompt' => '', + 'title' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), + 'prompt' => '', 'original' => 1, ), ), - 'expand_text_prompt' => array( + 'expand_text_prompt' => array( array( - 'title' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'prompt' => '', + 'title' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), + 'prompt' => '', 'original' => 1, ), ), @@ -153,10 +200,13 @@ public function get_default_settings() { ]; } - public function render_section() { - return; - } - + /** + * Sanitizes the settings before saving. + * + * @param array $settings The settings to be sanitized on save. + * + * @return array + */ public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); $chat_gpt = new ChatGPT( $this ); @@ -179,6 +229,12 @@ public function sanitize_settings( $settings ) { $new_settings['provider'] = ChatGPT::ID; } + /* + * These are the sanitization methods specific to the OpenAI ChatGPT provider. + * They sanitize the settings for the provider and then merge them into the new settings array. + * + * When multiple providers are supported, the sanitization methods for each provider should be called here. + */ if ( isset( $settings[ ChatGPT::ID ] ) ) { $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index ca077dc48..113895992 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -4,9 +4,22 @@ use \Classifai\Providers\OpenAI\ChatGPT; +/** + * Class ExcerptGeneration + */ class ExcerptGeneration extends Feature { + /** + * ID of the current feature. + * + * @var string + */ const ID = 'feature_excerpt_generation'; + /** + * Returns the label of the feature. + * + * @return string + */ public function get_label() { return apply_filters( 'classifai_' . static::ID . '_label', @@ -14,6 +27,11 @@ public function get_label() { ); } + /** + * Returns the providers supported by the feature. + * + * @return array + */ public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', @@ -23,6 +41,9 @@ public function get_providers() { ); } + /** + * Sets up the fields and sections for the feature. + */ public function setup_fields_sections() { $default_settings = $this->get_default_settings(); @@ -90,22 +111,33 @@ public function setup_fields_sections() { ] ); + /* + * The following fields are specific to the OpenAI ChatGPT provider. + * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. + * + * If the feature supports multiple providers, then the fields should be added for each provider. + */ $chat_gpt = new ChatGPT( $this ); $chat_gpt->add_api_key_field(); $chat_gpt->add_prompt_field( [ 'id' => 'generate_excerpt_prompt', 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), - 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ) + 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), ] ); } + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ public function is_feature_enabled() { $access = false; $settings = $this->get_settings(); $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles' ] ?? []; + $feature_roles = $settings['roles'] ?? []; // Check if user has access to the feature and the feature is turned on. if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { @@ -126,26 +158,43 @@ public function is_feature_enabled() { return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); } + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ public function get_default_settings() { return [ - 'status' => '0', - 'roles' => $this->roles, - 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'status' => '0', + 'roles' => $this->roles, + 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ChatGPT::ID => [ - 'api_key' => '', - 'authenticated' => false, + 'api_key' => '', + 'authenticated' => false, 'generate_excerpt_prompt' => array( array( - 'title' => esc_html__( 'Default', 'classifai' ), - 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'title' => esc_html__( 'Default', 'classifai' ), + 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), 'original' => 1, ), - ) + ), ], ]; } + /** + * Sanitizes the settings before saving. + * + * @param array $settings The settings to be sanitized on save. + * + * @return array + */ public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); $chat_gpt = new ChatGPT( $this ); @@ -174,10 +223,16 @@ public function sanitize_settings( $settings ) { $new_settings['length'] = 55; } + /* + * These are the sanitization methods specific to the OpenAI ChatGPT provider. + * They sanitize the settings for the provider and then merge them into the new settings array. + * + * When multiple providers are supported, the sanitization methods for each provider should be called here. + */ if ( isset( $settings[ ChatGPT::ID ] ) ) { - $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); - $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; - $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; + $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; + $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $chat_gpt->sanitize_prompts( 'generate_excerpt_prompt', $settings ); } diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 822926adb..2051ddcb3 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -5,19 +5,44 @@ use function Classifai\find_provider_class; abstract class Feature { + /** + * ID of the current feature. + * + * To be set in the subclass. + * + * @var string + */ const ID = ''; + /** + * User role array. + * + * @var array + */ public $roles = []; - public $provider_classes = []; + /** + * Array of provider classes. + * + * @var \Classifai\Providers\Provider[] + */ + public $provider_instances = []; - public function __construct( $provider_classes = [] ) { - $this->provider_classes = $provider_classes; + /** + * Feature constructor. + * + * @param \Classifai\Providers\Provider[] $provider_instances Array of provider instances. + */ + public function __construct( $provider_instances = [] ) { + $this->provider_instances = $provider_instances; add_action( 'admin_init', [ $this, 'setup_roles' ] ); add_action( 'admin_init', [ $this, 'register_setting' ] ); add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); } + /** + * Assigns user roles to the $roles array. + */ public function setup_roles() { $default_settings = $this->get_default_settings(); $this->roles = get_editable_roles() ?? []; @@ -42,14 +67,40 @@ public function setup_roles() { */ abstract public function setup_fields_sections(); - abstract public function get_default_settings() ; + /** + * Returns the default settings for the feature. + * + * @return array + */ + abstract public function get_default_settings(); + /** + * Returns the providers supported by the feature. + * + * @return array + */ abstract public function get_providers(); + /** + * Sanitizes the settings before saving. + * + * @param array $settings The settings to be sanitized on save. + * + * @return array + */ abstract public function sanitize_settings( $settings ); + /** + * Returns true if the feature meets all the criteria to be enabled. False otherwise. + * + * @return boolean + */ abstract public function is_feature_enabled(); + /** + * Calls the register method for the provider set for the feature. + * The Provider::register() method usually loads the JS assets. + */ public function register() { if ( ! $this->can_register() ) { return; @@ -57,13 +108,16 @@ public function register() { $feature_settings = $this->get_settings(); $provider_id = $feature_settings['provider']; - $provider_instance = find_provider_class( $this->provider_classes, $provider_id ); + $provider_instance = find_provider_class( $this->provider_instances, $provider_id ); $provider_class = get_class( $provider_instance ); $provider_instance = new $provider_class( $this ); $provider_instance->register(); } + /** + * Registers the settings for the feature. + */ public function register_setting() { register_setting( $this->get_option_name(), @@ -74,10 +128,22 @@ public function register_setting() { ); } + /** + * Returns the option name for the feature. + * + * @return string + */ public function get_option_name() { return 'classifai_' . static::ID; } + /** + * Returns the settings for the feature. + * + * @param string $index The index of the setting to return. + * + * @return array + */ public function get_settings( $index = false ) { $defaults = $this->get_default_settings(); $settings = get_option( $this->get_option_name(), [] ); @@ -114,6 +180,12 @@ public function can_register() { return $this->is_configured(); } + /** + * Returns the data attribute string for an input. + * + * @param array $args The args passed to add_settings_field. + * @return string + */ protected function get_data_attribute( $args ) { $data_attr = $args['data_attr'] ?? []; $data_attr_str = ''; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index fd21343a2..efb8d2cf5 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -4,9 +4,22 @@ use \Classifai\Providers\OpenAI\ChatGPT; +/** + * Class TitleGeneration + */ class TitleGeneration extends Feature { + /** + * ID of the current feature. + * + * @var string + */ const ID = 'feature_title_generation'; + /** + * Returns the label of the feature. + * + * @return string + */ public function get_label() { return apply_filters( 'classifai_' . static::ID . '_label', @@ -14,6 +27,11 @@ public function get_label() { ); } + /** + * Returns the providers supported by the feature. + * + * @return array + */ public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', @@ -23,6 +41,9 @@ public function get_providers() { ); } + /** + * Sets up the fields and sections for the feature. + */ public function setup_fields_sections() { $default_settings = $this->get_default_settings(); @@ -74,29 +95,40 @@ public function setup_fields_sections() { ] ); + /* + * The following fields are specific to the OpenAI ChatGPT provider. + * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. + * + * If the feature supports multiple providers, then the fields should be added for each provider. + */ $chat_gpt = new ChatGPT( $this ); $chat_gpt->add_api_key_field(); $chat_gpt->add_number_of_responses_field( [ 'id' => 'number_of_titles', 'label' => esc_html__( 'Number of titles', 'classifai' ), - 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ) + 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), ] ); $chat_gpt->add_prompt_field( [ 'id' => 'generate_title_prompt', 'prompt_placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), - 'description' => esc_html__( "Enter a custom prompt, if desired.", 'classifai' ) + 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), ] ); } + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ public function is_feature_enabled() { $access = false; $settings = $this->get_settings(); $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles' ] ?? []; + $feature_roles = $settings['roles'] ?? []; // Check if user has access to the feature and the feature is turned on. if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { @@ -117,27 +149,44 @@ public function is_feature_enabled() { return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); } + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ public function get_default_settings() { return [ - 'status' => '0', - 'roles' => $this->roles, - 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'status' => '0', + 'roles' => $this->roles, + 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ChatGPT::ID => [ - 'api_key' => '', - 'number_of_titles' => 1, - 'authenticated' => false, + 'api_key' => '', + 'number_of_titles' => 1, + 'authenticated' => false, 'generate_title_prompt' => array( array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), 'original' => 1, ), - ) + ), ], ]; } + /** + * Sanitizes the settings before saving. + * + * @param array $settings The settings to be sanitized on save. + * + * @return array + */ public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); $chat_gpt = new ChatGPT( $this ); @@ -160,6 +209,12 @@ public function sanitize_settings( $settings ) { $new_settings['provider'] = ChatGPT::ID; } + /* + * These are the sanitization methods specific to the OpenAI ChatGPT provider. + * They sanitize the settings for the provider and then merge them into the new settings array. + * + * When multiple providers are supported, the sanitization methods for each provider should be called here. + */ if ( isset( $settings[ ChatGPT::ID ] ) ) { $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index ab68e7011..68662b3a7 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -698,7 +698,7 @@ function clean_input( string $key = '', bool $is_get = false, string $sanitize_c * Find the provider class that a service belongs to. * * @param array $provider_classes Provider classes to look in. - * @param string $service_name Service name to look for. + * @param string $provider_id ID of the provider. * @return Provider|WP_Error */ function find_provider_class( array $provider_classes = [], string $provider_id = '' ) { diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index cc830c6ad..b133d5018 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -71,7 +71,7 @@ class ChatGPT extends Provider { /** * OpenAI ChatGPT constructor. * - * @param string $service The service this class belongs to. + * @param \Classifai\Features\Feature $feature_instance The feature instance. */ public function __construct( $feature_instance ) { parent::__construct( @@ -96,6 +96,11 @@ public function __construct( $feature_instance ) { /** * Adds a prompt repeater field. + * The prompt fields allow users to add their own prompts for ChatGPT. + * + * This is an optional field and depends on the feature. + * + * @param array $args Arguments passed in. */ public function add_prompt_field( $args = [] ) { $default_settings = $this->feature_instance->get_default_settings(); @@ -119,6 +124,9 @@ public function add_prompt_field( $args = [] ) { /** * Adds an api key field. + * + * ChatGPT requires an API key to be set. + * Any feature that supports ChatGPT will require this key to be set. */ public function add_api_key_field() { $default_settings = $this->feature_instance->get_default_settings(); @@ -136,7 +144,7 @@ public function add_api_key_field() { 'input_type' => 'password', 'default_value' => $default_settings['api_key'], 'data_attr' => [ - 'provider-scope' => [ static::ID ] + 'provider-scope' => [ static::ID ], ], ] ); @@ -144,6 +152,13 @@ public function add_api_key_field() { /** * Adds number of responses number field. + * ChatGPT is capable of returning variable number of responses. + * + * This field is helpful to set the number of responses to be returned. + * + * This is an optional field and depends on the feature. + * + * @param array $args Arguments passed in. */ public function add_number_of_responses_field( $args = [] ) { $default_settings = $this->feature_instance->get_default_settings(); @@ -169,10 +184,14 @@ public function add_number_of_responses_field( $args = [] ) { /** * Sanitisation callback for api key. + * + * @param array $settings The settings array. + * + * @return string */ public function sanitize_api_key( $settings ) { - if ( isset( $settings[ ChatGPT::ID ]['api_key'] ) ) { - return sanitize_text_field( $settings[ ChatGPT::ID ]['api_key'] ); + if ( isset( $settings[ self::ID ]['api_key'] ) ) { + return sanitize_text_field( $settings[ self::ID ]['api_key'] ); } return ''; @@ -180,10 +199,15 @@ public function sanitize_api_key( $settings ) { /** * Sanitisation callback for number of responses. + * + * @param string $key The key of the value we are sanitizing. + * @param array $settings The settings array. + * + * @return integer */ public function sanitize_number_of_responses_field( $key, $settings ) { - if ( isset( $settings[ ChatGPT::ID ][ $key ] ) ) { - return absint( $settings[ ChatGPT::ID ][ $key ] ); + if ( isset( $settings[ self::ID ][ $key ] ) ) { + return absint( $settings[ self::ID ][ $key ] ); } return 1; @@ -471,14 +495,14 @@ public function get_default_settings() { 'enable_resize_content' => false, 'resize_content_roles' => array_keys( $editable_roles ), 'suggestion_count' => 1, - 'condense_text_prompt' => array( + 'condense_text_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), 'prompt' => $this->condense_text_prompt, 'original' => 1, ), ), - 'expand_text_prompt' => array( + 'expand_text_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), 'prompt' => $this->expand_text_prompt, @@ -779,7 +803,7 @@ public function resize_content( int $post_id, array $args = array() ) { $feature = new ContentResizing(); $settings = $feature->get_settings(); - $args = wp_parse_args( + $args = wp_parse_args( array_filter( $args ), [ 'num' => $settings[ static::ID ]['number_of_suggestions'] ?? 1, @@ -938,14 +962,15 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use * * @since 2.4.0 * - * @param array $prompts Prompt data. + * @param array $prompt_key Prompt key. + * @param array $settings Settings data. * * @return array Sanitized prompt data. */ public function sanitize_prompts( $prompt_key = '', array $settings ): array { - if ( isset( $settings[ ChatGPT::ID ][ $prompt_key ] ) && is_array( $settings[ ChatGPT::ID ][ $prompt_key ] ) ) { + if ( isset( $settings[ self::ID ][ $prompt_key ] ) && is_array( $settings[ self::ID ][ $prompt_key ] ) ) { - $prompts = $settings[ ChatGPT::ID ][ $prompt_key ]; + $prompts = $settings[ self::ID ][ $prompt_key ]; // Remove any prompts that don't have a title and prompt. $prompts = array_filter( @@ -967,9 +992,9 @@ function ( $prompt ) use ( &$has_default ) { } return array( - 'title' => sanitize_text_field( $prompt['title'] ), - 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), - 'default' => absint( $default ), + 'title' => sanitize_text_field( $prompt['title'] ), + 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), + 'default' => absint( $default ), 'original' => absint( $prompt['original'] ), ); }, From 24fbc927e9cb3351dc39dab699d86a44b26c24d4 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 7 Nov 2023 23:57:10 +0530 Subject: [PATCH 012/127] phpcs fixes --- .../Classifai/Providers/OpenAI/OpenAI.php | 1 - includes/Classifai/Services/Service.php | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index df8b48190..aaf8b2b93 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -91,7 +91,6 @@ function() { /** * Sanitize the API key, showing an error message if needed. * - * @param array $new_settings New settings being saved. * @param array $old_settings Existing settings, if any. * @return array */ diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index b3d38f45f..101022ee9 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -26,12 +26,18 @@ abstract class Service { protected $providers; /** - * @var array Array of class instances. + * @var array Array of provider instances. */ public $provider_classes; + /** + * @var string[] array Array of feature classes for this service + */ public $features = []; + /** + * @var \Classifai\Features\Feature[] Array of feature instances. + */ public $feature_classes = []; /** @@ -69,7 +75,6 @@ public function init() { $this->provider_classes[] = new $provider( $this->menu_slug ); } } - // $this->register_providers(); } $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); @@ -100,6 +105,9 @@ public function register_providers() { } } + /** + * Initializes the functionality for this services features. + */ public function register_features() { if ( ! empty( $this->feature_classes ) ) { foreach ( $this->feature_classes as $feature ) { @@ -130,9 +138,9 @@ public function get_display_name() { * Render the start of a settings page. The rest is added by the providers */ public function render_settings_page() { - $active_tab = $this->provider_classes ? $this->provider_classes[0]->get_settings_section() : ''; - $active_tab = isset( $_GET['provider'] ) ? sanitize_text_field( wp_unslash( $_GET['provider'] ) ) : $active_tab; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $base_url = add_query_arg( + $active_tab = $this->provider_classes ? $this->provider_classes[0]->get_settings_section() : ''; + $active_tab = isset( $_GET['provider'] ) ? sanitize_text_field( wp_unslash( $_GET['provider'] ) ) : $active_tab; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $base_url = add_query_arg( array( 'page' => 'classifai', 'tab' => $this->get_menu_slug(), @@ -140,7 +148,7 @@ public function render_settings_page() { admin_url( 'tools.php' ) ); $active_feature = $this->feature_classes ? $this->feature_classes[0]::ID : ''; - $active_feature = isset( $_GET['feature'] ) ? sanitize_text_field( wp_unslash( $_GET['feature'] ) ) : $active_feature; + $active_feature = isset( $_GET['feature'] ) ? sanitize_text_field( wp_unslash( $_GET['feature'] ) ) : $active_feature; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
Date: Wed, 8 Nov 2023 18:27:58 +0530 Subject: [PATCH 013/127] migrate text to speech under language processing --- includes/Classifai/Admin/BulkActions.php | 10 +- includes/Classifai/Admin/SavePostHandler.php | 26 +- .../Classifai/Command/ClassifaiCommand.php | 4 +- .../Classifai/Features/ContentResizing.php | 22 +- .../Classifai/Features/ExcerptGeneration.php | 16 +- includes/Classifai/Features/Feature.php | 34 +- includes/Classifai/Features/TextToSpeech.php | 257 +++++++++++++++ .../Classifai/Features/TitleGeneration.php | 18 +- includes/Classifai/Helpers.php | 16 +- .../Azure/{TextToSpeech.php => Speech.php} | 310 +++++++++++++----- .../Classifai/Providers/OpenAI/ChatGPT.php | 96 +----- includes/Classifai/Providers/Provider.php | 40 ++- .../Classifai/Services/LanguageProcessing.php | 2 +- .../Classifai/Services/ServicesManager.php | 1 + 14 files changed, 606 insertions(+), 246 deletions(-) create mode 100644 includes/Classifai/Features/TextToSpeech.php rename includes/Classifai/Providers/Azure/{TextToSpeech.php => Speech.php} (73%) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index e01e75f9d..efae92ebb 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -2,7 +2,7 @@ namespace Classifai\Admin; use Classifai\Providers\Azure\ComputerVision; -use Classifai\Providers\Azure\TextToSpeech; +use Classifai\Providers\Azure\Speech; use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Providers\OpenAI\Embeddings; use Classifai\Providers\OpenAI\Whisper; @@ -51,7 +51,7 @@ public function can_register() { private $whisper; /** - * @var \Classifai\Providers\Azure\TextToSpeech + * @var \Classifai\Providers\Azure\Speech */ private $text_to_speech; @@ -160,7 +160,7 @@ public function register_bulk_actions( $bulk_actions ) { } if ( - is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) && + is_a( $this->text_to_speech, '\Classifai\Providers\Azure\Speech' ) && in_array( get_current_screen()->post_type, $this->text_to_speech->get_supported_post_types(), true ) ) { $bulk_actions['text_to_speech'] = __( 'Text to speech', 'classifai' ); @@ -250,7 +250,7 @@ public function bulk_action_handler( $redirect_to, $doaction, $post_ids ) { if ( 'text_to_speech' === $doaction ) { // Handle Azure Text to Speech generation. if ( - is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) && + is_a( $this->text_to_speech, '\Classifai\Providers\Azure\Speech' ) && is_a( $this->save_post_handler, '\Classifai\Admin\SavePostHandler' ) ) { $action = 'text_to_speech'; @@ -415,7 +415,7 @@ public function register_row_action( $actions, $post ) { } } - if ( is_a( $this->text_to_speech, '\Classifai\Providers\Azure\TextToSpeech' ) ) { + if ( is_a( $this->text_to_speech, '\Classifai\Providers\Azure\Speech' ) ) { if ( in_array( $post->post_type, $this->text_to_speech->get_supported_post_types(), true ) ) { $actions['text_to_speech'] = sprintf( '%s', diff --git a/includes/Classifai/Admin/SavePostHandler.php b/includes/Classifai/Admin/SavePostHandler.php index 0b8ba9f03..304708a8c 100644 --- a/includes/Classifai/Admin/SavePostHandler.php +++ b/includes/Classifai/Admin/SavePostHandler.php @@ -2,7 +2,8 @@ namespace Classifai\Admin; -use \Classifai\Providers\Azure\TextToSpeech; +use Classifai\Features\TextToSpeech; +use \Classifai\Providers\Azure\Speech; use \Classifai\Watson\Normalizer; /** @@ -173,6 +174,10 @@ public function classify( $post_id ) { /** * Synthesizes speech from the post title and content. * + * @todo: This method is copied to the Azure\Speech provider. + * Once all the speech-synthesize changed are migrated, we should + * remove this method. + * * @param int $post_id Post ID. * @return bool|int|WP_Error */ @@ -193,11 +198,12 @@ public function synthesize_speech( $post_id ) { } $normalizer = new Normalizer(); - $settings = \Classifai\get_plugin_settings( 'language_processing', TextToSpeech::FEATURE_NAME ); + $feature = new TextToSpeech(); + $settings = $feature->get_settings(); $post = get_post( $post_id ); $post_content = $normalizer->normalize_content( $post->post_content, $post->post_title, $post_id ); - $content_hash = get_post_meta( $post_id, TextToSpeech::AUDIO_HASH_KEY, true ); - $saved_attachment_id = (int) get_post_meta( $post_id, TextToSpeech::AUDIO_ID_KEY, true ); + $content_hash = get_post_meta( $post_id, Speech::AUDIO_HASH_KEY, true ); + $saved_attachment_id = (int) get_post_meta( $post_id, Speech::AUDIO_ID_KEY, true ); // Don't regenerate the audio file it it already exists and the content hasn't changed. if ( $saved_attachment_id ) { @@ -248,7 +254,7 @@ public function synthesize_speech( $post_id ) { ), ); - $remote_url = sprintf( '%s%s', $settings['credentials']['url'], TextToSpeech::API_PATH ); + $remote_url = sprintf( '%s%s', $settings['credentials']['url'], Speech::API_PATH ); $response = wp_remote_post( $remote_url, $request_params ); if ( is_wp_error( $response ) ) { @@ -272,8 +278,8 @@ public function synthesize_speech( $post_id ) { // If audio already exists for this post, delete it. if ( $saved_attachment_id ) { wp_delete_attachment( $saved_attachment_id, true ); - delete_post_meta( $post_id, TextToSpeech::AUDIO_ID_KEY ); - delete_post_meta( $post_id, TextToSpeech::AUDIO_TIMESTAMP_KEY ); + delete_post_meta( $post_id, Speech::AUDIO_ID_KEY ); + delete_post_meta( $post_id, Speech::AUDIO_TIMESTAMP_KEY ); } // The audio file name. @@ -315,9 +321,9 @@ public function synthesize_speech( $post_id ) { ); } - update_post_meta( $post_id, TextToSpeech::AUDIO_ID_KEY, absint( $attachment_id ) ); - update_post_meta( $post_id, TextToSpeech::AUDIO_TIMESTAMP_KEY, time() ); - update_post_meta( $post_id, TextToSpeech::AUDIO_HASH_KEY, md5( $post_content ) ); + update_post_meta( $post_id, Speech::AUDIO_ID_KEY, absint( $attachment_id ) ); + update_post_meta( $post_id, Speech::AUDIO_TIMESTAMP_KEY, time() ); + update_post_meta( $post_id, Speech::AUDIO_HASH_KEY, md5( $post_content ) ); return $attachment_id; } diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index bc677063c..38f48e2a7 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -9,7 +9,7 @@ use Classifai\PostClassifier; use Classifai\Providers\Azure\ComputerVision; use Classifai\Providers\Azure\SmartCropping; -use Classifai\Providers\Azure\TextToSpeech; +use Classifai\Providers\Azure\Speech; use Classifai\Providers\OpenAI\Whisper; use Classifai\Providers\OpenAI\Whisper\Transcribe; use Classifai\Providers\OpenAI\ChatGPT; @@ -244,7 +244,7 @@ public function text_to_speech( $args = [], $opts = [] ) { $opts = wp_parse_args( $opts, $defaults ); $opts['per_page'] = (int) $opts['per_page'] > 0 ? $opts['per_page'] : 100; - $allowed_post_types = TextToSpeech::get_supported_post_types(); + $allowed_post_types = Speech::get_supported_post_types(); $count = 0; $errors = 0; diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 579aeed39..28cdc4007 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -50,7 +50,7 @@ public function get_providers() { * Sets up the fields and sections for the feature. */ public function setup_fields_sections() { - $default_settings = $this->get_default_settings(); + $settings = $this->get_settings(); add_settings_section( $this->get_option_name() . '_section', @@ -68,8 +68,8 @@ public function setup_fields_sections() { [ 'label_for' => 'status', 'input_type' => 'checkbox', - 'default_value' => $default_settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + 'default_value' => $settings['status'], + 'description' => __( '"Condense this text" and "Expand this text" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ), ] ); @@ -82,8 +82,8 @@ public function setup_fields_sections() { [ 'label_for' => 'roles', 'options' => $this->roles, - 'default_values' => $default_settings['roles'], - 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to use this feature.', 'classifai' ), ] ); @@ -96,7 +96,7 @@ public function setup_fields_sections() { [ 'label_for' => 'provider', 'options' => $this->get_providers(), - 'default_value' => $default_settings['provider'], + 'default_value' => $settings['provider'], ] ); @@ -209,7 +209,6 @@ public function get_default_settings() { */ public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); - $chat_gpt = new ChatGPT( $this ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; @@ -236,12 +235,13 @@ public function sanitize_settings( $settings ) { * When multiple providers are supported, the sanitization methods for each provider should be called here. */ if ( isset( $settings[ ChatGPT::ID ] ) ) { - $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $provider_instance = new ChatGPT( $this ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_suggestions'] = $chat_gpt->sanitize_number_of_responses_field( 'number_of_suggestions', $settings ); - $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $chat_gpt->sanitize_prompts( 'condense_text_prompt', $settings ); - $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $chat_gpt->sanitize_prompts( 'expand_text_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['number_of_suggestions'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_suggestions', $settings ); + $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $provider_instance->sanitize_prompts( 'condense_text_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $provider_instance->sanitize_prompts( 'expand_text_prompt', $settings ); } return $new_settings; diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 113895992..1349d8911 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -45,7 +45,7 @@ public function get_providers() { * Sets up the fields and sections for the feature. */ public function setup_fields_sections() { - $default_settings = $this->get_default_settings(); + $settings = $this->get_settings(); add_settings_section( $this->get_option_name() . '_section', @@ -63,7 +63,7 @@ public function setup_fields_sections() { [ 'label_for' => 'status', 'input_type' => 'checkbox', - 'default_value' => $default_settings['status'], + 'default_value' => $settings['status'], 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), ] ); @@ -77,7 +77,7 @@ public function setup_fields_sections() { [ 'label_for' => 'roles', 'options' => $this->roles, - 'default_values' => $default_settings['roles'], + 'default_values' => $settings['roles'], 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), ] ); @@ -93,7 +93,7 @@ public function setup_fields_sections() { 'input_type' => 'number', 'min' => 1, 'step' => 1, - 'default_value' => $default_settings['length'], + 'default_value' => $settings['length'], 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), ] ); @@ -107,7 +107,7 @@ public function setup_fields_sections() { [ 'label_for' => 'provider', 'options' => $this->get_providers(), - 'default_value' => $default_settings['provider'], + 'default_value' => $settings['provider'], ] ); @@ -197,7 +197,6 @@ public function get_default_settings() { */ public function sanitize_settings( $settings ) { $new_settings = $this->get_settings(); - $chat_gpt = new ChatGPT( $this ); if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { $new_settings['status'] = 'no'; @@ -230,10 +229,11 @@ public function sanitize_settings( $settings ) { * When multiple providers are supported, the sanitization methods for each provider should be called here. */ if ( isset( $settings[ ChatGPT::ID ] ) ) { - $api_key_settings = $chat_gpt->sanitize_api_key_settings( $settings ); + $provider_instance = new ChatGPT( $this ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $chat_gpt->sanitize_prompts( 'generate_excerpt_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $provider_instance->sanitize_prompts( 'generate_excerpt_prompt', $settings ); } return $new_settings; diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 2051ddcb3..758092720 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -106,12 +106,7 @@ public function register() { return; } - $feature_settings = $this->get_settings(); - $provider_id = $feature_settings['provider']; - $provider_instance = find_provider_class( $this->provider_instances, $provider_id ); - $provider_class = get_class( $provider_instance ); - $provider_instance = new $provider_class( $this ); - + $provider_instance = $this->get_feature_provider_instance(); $provider_instance->register(); } @@ -156,6 +151,28 @@ public function get_settings( $index = false ) { return $settings; } + /** + * Returns the instance of the provider set for the feature. + * + * @param string $provider_id The ID of the provider. + * + * @return \Classifai\Providers + */ + public function get_feature_provider_instance( $provider_id = '' ) { + $new_settings = $this->get_settings(); + $provider_id = $provider_id ? $provider_id : $new_settings['provider']; + $provider_instance = find_provider_class( $this->provider_instances ?? [], $provider_id ); + + if ( is_wp_error( $provider_instance ) ) { + return null; + } + + $provider_class = get_class( $provider_instance ); + $provider_instance = new $provider_class( $this ); + + return $provider_instance; + } + /** * Returns whether the provider is configured or not. * @@ -376,7 +393,8 @@ class="button-secondary js-classifai-add-prompt-fieldset"> * @param array $args The args passed to add_settings_field. */ public function render_select( $args ) { - $setting_index = $this->get_settings(); + $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; + $setting_index = $this->get_settings( $option_index ); $saved = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; $data_attr = isset( $args['data_attr'] ) ?: []; @@ -387,7 +405,7 @@ public function render_select( $args ) { /> @@ -849,8 +856,7 @@ public function save_post_metadata( $post_id ) { } if ( isset( $_POST['classifai_synthesize_speech'] ) ) { - $save_post_handler = new SavePostHandler(); - $save_post_handler->synthesize_speech( $post_id ); + $this->synthesize_speech( $post_id ); } } @@ -1014,4 +1020,75 @@ protected function get_post_types_select_options() { return $options; } + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'synthesize-speech/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'synthesize_speech_from_text' ), + 'args' => array( + 'id' => array( + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'ID of post to run text to speech conversion on.', 'classifai' ), + ), + ), + 'permission_callback' => [ $this, 'speech_synthesis_permissions_check' ], + ] + ); + } + + /** + * Generates text to speech for a post using REST. + * + * @param WP_REST_Request $request Full data about the request. + * @return \WP_REST_Response|WP_Error + */ + public function synthesize_speech_from_text( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + $attachment_id = $this->synthesize_speech( $post_id ); + + if ( is_wp_error( $attachment_id ) ) { + return rest_ensure_response( + array( + 'success' => false, + 'code' => $attachment_id->get_error_code(), + 'message' => $attachment_id->get_error_message(), + ) + ); + } + + return rest_ensure_response( + array( + 'success' => true, + 'audio_id' => $attachment_id, + ) + ); + } + + /** + * Check if a given request has access to generate audio for the post. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function speech_synthesis_permissions_check( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + + if ( ! empty( $post_id ) && current_user_can( 'edit_post', $post_id ) ) { + $post_type = get_post_type( $post_id ); + $supported = \Classifai\get_tts_supported_post_types(); + + // Check if processing allowed. + if ( ! in_array( $post_type, $supported, true ) ) { + return new WP_Error( 'not_enabled', esc_html__( 'Azure Speech synthesis is not enabled for current post.', 'classifai' ) ); + } + + return true; + } + + return false; + } } diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 9b5fd5968..221e6675b 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -73,24 +73,6 @@ public function register_endpoints() { 'permission_callback' => [ $this, 'generate_post_tags_permissions_check' ], ] ); - - register_rest_route( - 'classifai/v1', - 'synthesize-speech/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'synthesize_speech_from_text' ), - 'args' => array( - 'id' => array( - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'ID of post to run text to speech conversion on.', 'classifai' ), - ), - ), - 'permission_callback' => [ $this, 'speech_synthesis_permissions_check' ], - ] - ); } /** @@ -182,57 +164,4 @@ public function generate_post_tags_permissions_check( WP_REST_Request $request ) return true; } - - /** - * Generates text to speech for a post using REST. - * - * @param WP_REST_Request $request Full data about the request. - * @return \WP_REST_Response|WP_Error - */ - public function synthesize_speech_from_text( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $save_post_handler = new SavePostHandler(); - $attachment_id = $save_post_handler->synthesize_speech( $post_id ); - - if ( is_wp_error( $attachment_id ) ) { - return rest_ensure_response( - array( - 'success' => false, - 'code' => $attachment_id->get_error_code(), - 'message' => $attachment_id->get_error_message(), - ) - ); - } - - return rest_ensure_response( - array( - 'success' => true, - 'audio_id' => $attachment_id, - ) - ); - } - - /** - * Check if a given request has access to generate audio for the post. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function speech_synthesis_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - if ( ! empty( $post_id ) && current_user_can( 'edit_post', $post_id ) ) { - $post_type = get_post_type( $post_id ); - $supported = \Classifai\get_tts_supported_post_types(); - - // Check if processing allowed. - if ( ! in_array( $post_type, $supported, true ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Azure Speech synthesis is not enabled for current post.', 'classifai' ) ); - } - - return true; - } - - return false; - } } From 24b6f45d4d096f99fb1d1fd44d99a956df80bd01 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 10 Nov 2023 16:34:17 +0530 Subject: [PATCH 020/127] fix is_feature_enabled method --- .../Features/AudioTranscriptsGeneration.php | 45 ++++++++++--------- .../Classifai/Features/ContentResizing.php | 19 ++++---- .../Classifai/Features/ExcerptGeneration.php | 9 ++-- includes/Classifai/Features/TextToSpeech.php | 18 ++++---- .../Classifai/Features/TitleGeneration.php | 21 ++++----- includes/Classifai/Providers/Azure/Speech.php | 8 +--- .../Classifai/Providers/OpenAI/ChatGPT.php | 27 +++-------- .../Classifai/Providers/OpenAI/Whisper.php | 21 ++------- 8 files changed, 68 insertions(+), 100 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index c3ca9e9ea..5d144371c 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -2,6 +2,7 @@ namespace Classifai\Features; +use Classifai\Providers\Azure\Speech; use \Classifai\Providers\OpenAI\Whisper; /** @@ -113,27 +114,29 @@ public function setup_fields_sections() { * @return boolean */ public function is_feature_enabled() { - $settings = $this->get_settings(); - - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) ) { - return new \WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); - } - - // Check if the current user has permission. - $roles = $settings['roles'] ?? []; - $user_roles = wp_get_current_user()->roles ?? []; - - if ( empty( $roles ) || ! empty( array_diff( $user_roles, $roles ) ) ) { - return new \WP_Error( 'no_permission', esc_html__( 'User role does not have permission.', 'classifai' ) ); - } - - // Ensure feature is turned on. - if ( ! isset( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - return new \WP_Error( 'not_enabled', esc_html__( 'Transcripts are not enabled.', 'classifai' ) ); - } - - return true; + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? Speech::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); } /** diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 7c07f21fd..2afe3c88a 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -136,15 +136,16 @@ public function setup_fields_sections() { * @return boolean */ public function is_feature_enabled() { - $access = false; - $settings = $this->get_settings(); - $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles'] ?? []; - - // Check if user has access to the feature and the feature is turned on. - if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { - $access = true; - } + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ChatGPT::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; /** * Filter to override permission to the generate title feature. diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 596340f90..cba231106 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -138,13 +138,14 @@ public function setup_fields_sections() { public function is_feature_enabled() { $access = false; $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ChatGPT::ID; $user_roles = wp_get_current_user()->roles ?? []; $feature_roles = $settings['roles'] ?? []; - // Check if user has access to the feature and the feature is turned on. - if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { - $access = true; - } + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; /** * Filter to override permission to the generate title feature. diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 75eb36f2e..e7b63c9c5 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -139,15 +139,15 @@ public function setup_fields_sections() { * @return boolean */ public function is_feature_enabled() { - $access = false; - $settings = $this->get_settings(); - $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles'] ?? []; - - // Check if user has access to the feature and the feature is turned on. - if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { - $access = true; - } + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? Speech::ID; + $feature_roles = $settings['roles'] ?? []; + $user_roles = wp_get_current_user()->roles ?? []; + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; /** * Filter to override permission to the generate title feature. diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index b4e815c20..6bc876a27 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -118,7 +118,7 @@ public function setup_fields_sections() { ] ); - do_action( 'classifai_' . static::ID . 'provider_setup_fields_sections', $this ); + do_action( 'classifai_' . static::ID . '_provider_setup_fields_sections', $this ); } /** @@ -127,15 +127,16 @@ public function setup_fields_sections() { * @return boolean */ public function is_feature_enabled() { - $access = false; - $settings = $this->get_settings(); - $user_roles = wp_get_current_user()->roles ?? []; - $feature_roles = $settings['roles'] ?? []; - - // Check if user has access to the feature and the feature is turned on. - if ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ) { - $access = true; - } + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ChatGPT::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; /** * Filter to override permission to the generate title feature. diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index eb72bd168..0f3dcccbb 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -106,12 +106,6 @@ public function enqueue_editor_assets() { return; } - $supported_post_types = get_tts_supported_post_types(); - - if ( ! in_array( $post->post_type, $supported_post_types, true ) ) { - return; - } - wp_enqueue_script( 'classifai-gutenberg-plugin', CLASSIFAI_PLUGIN_URL . 'dist/gutenberg-plugin.js', @@ -134,7 +128,7 @@ public function enqueue_editor_assets() { * Register the actions needed. */ public function register() { - if ( $this->feature_instance instanceof \Classifai\Features\TextToSpeech && ! $this->feature_instance->is_feature_enabled() ) { + if ( $this->feature_instance instanceof \Classifai\Features\TextToSpeech && $this->feature_instance->is_feature_enabled() ) { add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'rest_api_init', [ $this, 'add_synthesize_speech_meta_to_rest_api' ] ); add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index a2c35a503..57c4acf3c 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -1111,12 +1111,6 @@ public function generate_post_title_permissions_check( WP_REST_Request $request } $feature = new TitleGeneration(); - $settings = $feature->get_settings(); - - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); - } // Ensure the feature is enabled. Also runs a user check. if ( ! $feature->is_feature_enabled() ) { @@ -1177,12 +1171,6 @@ public function generate_post_excerpt_permissions_check( WP_REST_Request $reques } $feature = new ExcerptGeneration(); - $settings = $feature->get_settings(); - - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); - } // Ensure the feature is enabled. Also runs a user check. if ( ! $feature->is_feature_enabled() ) { @@ -1238,16 +1226,6 @@ public function resize_post_content_permissions_check( WP_REST_Request $request $feature = new ContentResizing(); $settings = $feature->get_settings(); - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); - } - - // Check if resize content feature is turned on. - if ( empty( $settings ) || ( isset( $settings['status'] ) && 'no' === $settings['status'] ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Content resizing not currently enabled.', 'classifai' ) ); - } - // Check if the current user's role is allowed. $roles = $settings['roles'] ?? []; $user_roles = wp_get_current_user()->roles ?? []; @@ -1256,6 +1234,11 @@ public function resize_post_content_permissions_check( WP_REST_Request $request return false; } + // Ensure the feature is enabled. Also runs a user check. + if ( ! $feature->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Content resizing is not currently enabled.', 'classifai' ) ); + } + return true; } } diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index a3dc9362b..4a2e112a1 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -292,25 +292,10 @@ public function generate_audio_transcript_permissions_check( WP_REST_Request $re return false; } - $feature = new AudioTranscriptsGeneration(); - $settings = $feature->get_settings(); + $feature = new AudioTranscriptsGeneration(); - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with OpenAI.', 'classifai' ) ); - } - - // Check if transcription is turned on. - if ( empty( $settings ) || ( isset( $settings['status'] ) && 'no' === $settings['status'] ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Transcription is not currently enabled.', 'classifai' ) ); - } - - // Check if the current user's role is allowed. - $roles = $settings['roles'] ?? []; - $user_roles = wp_get_current_user()->roles ?? []; - - if ( empty( $roles ) || ! empty( array_diff( $user_roles, $roles ) ) ) { - return false; + if ( ! $feature->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Audio transciption is not currently enabled.', 'classifai' ) ); } return true; From fec148d65ec925339006d2ddc498cb7186d9ce2e Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 10 Nov 2023 18:51:33 +0530 Subject: [PATCH 021/127] fix is_feature_enabled for audio transcription --- .../Classifai/Providers/OpenAI/Whisper.php | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 4a2e112a1..3f09786d5 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -9,7 +9,7 @@ use Classifai\Providers\Provider; use Classifai\Providers\OpenAI\Whisper\Transcribe; use function Classifai\clean_input; - +use function Classifai\get_asset_info; use WP_REST_Server; use WP_REST_Request; use WP_Error; @@ -58,10 +58,13 @@ public function __construct( $feature_instance ) { * This only fires if can_register returns true. */ public function register() { - add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); - add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); - add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); - add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); + if ( $this->feature_instance instanceof \Classifai\Features\AudioTranscriptsGeneration && $this->feature_instance->is_feature_enabled() ) { + add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); + add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); + } } public function setup_fields_sections() {} @@ -70,6 +73,16 @@ public function reset_settings() {} public function sanitize_settings( $settings ) {} + public function enqueue_media_scripts() { + wp_enqueue_script( + 'classifai-media-script', + CLASSIFAI_PLUGIN_URL . 'dist/media.js', + array_merge( get_asset_info( 'media', 'dependencies' ), array( 'jquery', 'media-editor', 'lodash' ) ), + get_asset_info( 'media', 'version' ), + true + ); + } + /** * Start the audio transcription process. * @@ -81,13 +94,14 @@ public function transcribe_audio( $attachment_id = 0 ) { return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to edit this attachment.', 'classifai' ) ); } - $enabled = $this->feature_instance->is_feature_enabled(); + $feature = new AudioTranscriptsGeneration(); + $enabled = $feature->is_feature_enabled(); if ( is_wp_error( $enabled ) ) { return $enabled; } - $settings = $this->feature_instance->get_settings( static::ID ); + $settings = $feature->get_settings( static::ID ); $transcribe = new Transcribe( intval( $attachment_id ), $settings ); return $transcribe->process(); @@ -101,29 +115,22 @@ public function transcribe_audio( $attachment_id = 0 ) { * @return array */ public function add_buttons_to_media_modal( $form_fields, $attachment ) { - $enabled = $this->feature_instance->is_feature_enabled( $attachment->ID ); - - if ( is_wp_error( $enabled ) ) { - return $form_fields; - } - - $settings = $this->feature_instance->get_settings(); + $feature = new AudioTranscriptsGeneration(); + $settings = $feature->get_settings(); $transcribe = new Transcribe( $attachment->ID, $settings[ static::ID ] ); if ( ! $transcribe->should_process( $attachment->ID ) ) { return $form_fields; } - if ( is_array( $settings ) && isset( $settings['status'] ) && '1' === $settings['status'] ) { - $text = empty( get_the_content( null, false, $attachment ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); + $text = empty( get_the_content( null, false, $attachment ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); - $form_fields['retranscribe'] = [ - 'label' => __( 'Transcribe audio', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } + $form_fields['retranscribe'] = [ + 'label' => __( 'Transcribe audio', 'classifai' ), + 'input' => 'html', + 'html' => '', + 'show_in_edit' => false, + ]; return $form_fields; } @@ -134,29 +141,22 @@ public function add_buttons_to_media_modal( $form_fields, $attachment ) { * @param \WP_Post $post Post object. */ public function setup_attachment_meta_box( $post ) { - $enabled = $this->feature_instance->is_feature_enabled( $post->ID ); - - if ( is_wp_error( $enabled ) ) { - return; - } - - $settings = $this->feature_instance->get_settings(); + $feature = new AudioTranscriptsGeneration(); + $settings = $feature->get_settings(); $transcribe = new Transcribe( $post->ID, $settings[ static::ID ] ); if ( ! $transcribe->should_process( $post->ID ) ) { return; } - if ( is_array( $settings ) && isset( $settings[ static::ID ]['status'] ) && '1' === $settings[ static::ID ]['status'] ) { - add_meta_box( - 'attachment_meta_box', - __( 'ClassifAI Audio Processing', 'classifai' ), - [ $this, 'attachment_meta_box' ], - 'attachment', - 'side', - 'high' - ); - } + add_meta_box( + 'attachment_meta_box', + __( 'ClassifAI Audio Processing', 'classifai' ), + [ $this, 'attachment_meta_box' ], + 'attachment', + 'side', + 'high' + ); } /** @@ -192,7 +192,8 @@ public function maybe_transcribe_audio( $attachment_id ) { return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to edit this attachment.', 'classifai' ) ); } - $enabled = $this->feature_instance->is_feature_enabled(); + $feature = new AudioTranscriptsGeneration(); + $enabled = $feature->is_feature_enabled(); if ( is_wp_error( $enabled ) ) { return; From 6290a54bec12dd6cc01cce42c5ed3ee5918eb27c Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 10 Nov 2023 20:07:18 +0530 Subject: [PATCH 022/127] add conditional fields --- includes/Classifai/Features/AudioTranscriptsGeneration.php | 3 ++- includes/Classifai/Features/ContentResizing.php | 3 ++- includes/Classifai/Features/ExcerptGeneration.php | 3 ++- includes/Classifai/Features/TextToSpeech.php | 3 ++- includes/Classifai/Features/TitleGeneration.php | 3 ++- includes/Classifai/Providers/Azure/Speech.php | 2 ++ includes/Classifai/Providers/OpenAI/ChatGPT.php | 2 ++ includes/Classifai/Providers/Provider.php | 4 +--- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 5d144371c..a8f4e6ad6 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -204,7 +204,8 @@ public function sanitize_settings( $settings ) { return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', - $new_settings + $new_settings, + $settings ); } } diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 2afe3c88a..031e49469 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -244,7 +244,8 @@ public function sanitize_settings( $settings ) { return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', - $new_settings + $new_settings, + $settings ); } } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index cba231106..fc072b4fd 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -241,7 +241,8 @@ public function sanitize_settings( $settings ) { return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', - $new_settings + $new_settings, + $settings ); } } diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index e7b63c9c5..5bc905a07 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -256,7 +256,8 @@ public function sanitize_settings( $settings ) { return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', - $new_settings + $new_settings, + $settings ); } } diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 6bc876a27..606b84219 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -232,7 +232,8 @@ public function sanitize_settings( $settings ) { return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', - $new_settings + $new_settings, + $settings ); } } diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 0f3dcccbb..c42c4c42e 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -158,6 +158,7 @@ public function add_endpoint_url_field( $args = [] ) { 'input_type' => 'text', 'default_value' => $settings[ $id ], 'description' => $args[ 'description' ] ?? __( 'Text to Speech region endpoint, e.g., https://LOCATION.tts.speech.microsoft.com/. Replace LOCATION with the Location/Region you selected for the resource in Azure.', 'classifai' ), + 'class' => 'classifai-provider-field hidden', // Important to add this. ] ); } @@ -178,6 +179,7 @@ public function add_voices_options_field( $args = [] ) { 'label_for' => 'voice', 'options' => $voices_options, 'default_value' => $settings['voice'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 57c4acf3c..f49bb0feb 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -122,6 +122,7 @@ public function add_prompt_field( $args = [] ) { 'placeholder' => $args['prompt_placeholder'], 'default_value' => $settings[ $args['id'] ], 'description' => $args['description'], + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } @@ -153,6 +154,7 @@ public function add_number_of_responses_field( $args = [] ) { 'step' => 1, 'default_value' => $settings[ $args['id'] ], 'description' => $args['description'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 4f42dd033..36bdfe112 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -239,9 +239,7 @@ public function add_api_key_field( $args = [] ) { 'label_for' => $id, 'input_type' => 'password', 'default_value' => $default_settings[ $id ], - 'data_attr' => [ - 'provider-scope' => [ static::ID ], - ], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } From d7aac0315181874d6978fa564a3e92442eab75b4 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 10 Nov 2023 22:50:20 +0530 Subject: [PATCH 023/127] update sanitize_settings --- .../Features/AudioTranscriptsGeneration.php | 36 ++++--------- .../Classifai/Features/ContentResizing.php | 35 ++++--------- .../Classifai/Features/ExcerptGeneration.php | 45 +++++----------- includes/Classifai/Features/TextToSpeech.php | 36 ++++--------- .../Classifai/Features/TitleGeneration.php | 34 ++++-------- includes/Classifai/Providers/Azure/Speech.php | 52 +++++++++---------- .../Classifai/Providers/OpenAI/ChatGPT.php | 30 +++++------ .../Classifai/Providers/OpenAI/OpenAI.php | 12 +++-- 8 files changed, 100 insertions(+), 180 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index a8f4e6ad6..d25259e25 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -164,30 +164,16 @@ public function get_default_settings() { /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. + * @param array $new_settings The settings to be sanitized on save. * * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->get_settings(); - - if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - $new_settings['status'] = 'no'; - } else { - $new_settings['status'] = '1'; - } - - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); - if ( isset( $settings['provider'] ) ) { - $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); - } else { - $new_settings['provider'] = Whisper::ID; - } + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; /* * These are the sanitization methods specific to the OpenAI ChatGPT provider. @@ -195,11 +181,11 @@ public function sanitize_settings( $settings ) { * * When multiple providers are supported, the sanitization methods for each provider should be called here. */ - if ( isset( $settings[ Whisper::ID ] ) ) { - $provider_instance = new Whisper( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); - $new_settings[ Whisper::ID ]['api_key'] = $api_key_settings[ Whisper::ID ]['api_key']; - $new_settings[ Whisper::ID ]['authenticated'] = $api_key_settings[ Whisper::ID ]['authenticated']; + if ( isset( $new_settings[ Whisper::ID ] ) ) { + $provider_instance = new Whisper( $this ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); + $new_settings[ Whisper::ID ]['api_key'] = $api_key_settings[ Whisper::ID ]['api_key']; + $new_settings[ Whisper::ID ]['authenticated'] = $api_key_settings[ Whisper::ID ]['authenticated']; } return apply_filters( diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 031e49469..e7b42ec90 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -201,30 +201,17 @@ public function get_default_settings() { /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. + * @param array $new_settings The settings to be sanitized on save. * * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->get_settings(); + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); - if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - $new_settings['status'] = 'no'; - } else { - $new_settings['status'] = '1'; - } + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } - - if ( isset( $settings['provider'] ) ) { - $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); - } else { - $new_settings['provider'] = ChatGPT::ID; - } /* * These are the sanitization methods specific to the OpenAI ChatGPT provider. @@ -232,14 +219,14 @@ public function sanitize_settings( $settings ) { * * When multiple providers are supported, the sanitization methods for each provider should be called here. */ - if ( isset( $settings[ ChatGPT::ID ] ) ) { + if ( isset( $new_settings[ ChatGPT::ID ] ) ) { $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_suggestions'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_suggestions', $settings ); - $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $provider_instance->sanitize_prompts( 'condense_text_prompt', $settings ); - $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $provider_instance->sanitize_prompts( 'expand_text_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); + $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $provider_instance->sanitize_prompts( 'condense_text_prompt', $new_settings ); + $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $provider_instance->sanitize_prompts( 'expand_text_prompt', $new_settings ); } return apply_filters( diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index fc072b4fd..fbf47e17f 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -194,36 +194,16 @@ public function get_default_settings() { /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. + * @param array $new_settings The settings to be sanitized on save. * * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->get_settings(); - - if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - $new_settings['status'] = 'no'; - } else { - $new_settings['status'] = '1'; - } - - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } - - if ( isset( $settings['provider'] ) ) { - $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); - } else { - $new_settings['provider'] = ChatGPT::ID; - } + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); - if ( isset( $settings['length'] ) && is_numeric( $settings['length'] ) && (int) $settings['length'] >= 0 ) { - $new_settings['length'] = absint( $settings['length'] ); - } else { - $new_settings['length'] = 55; - } + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; /* * These are the sanitization methods specific to the OpenAI ChatGPT provider. @@ -231,12 +211,13 @@ public function sanitize_settings( $settings ) { * * When multiple providers are supported, the sanitization methods for each provider should be called here. */ - if ( isset( $settings[ ChatGPT::ID ] ) ) { - $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); - $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; - $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $provider_instance->sanitize_prompts( 'generate_excerpt_prompt', $settings ); + if ( isset( $new_settings[ ChatGPT::ID ] ) ) { + $provider_instance = new ChatGPT( $this ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); + $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; + $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; + $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); + $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $provider_instance->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); } return apply_filters( diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 5bc905a07..620791ade 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -203,38 +203,24 @@ public function get_default_settings() { /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. + * @param array $new_settings The settings to be sanitized on save. * * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->get_settings(); - - if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - $new_settings['status'] = 'no'; - } else { - $new_settings['status'] = '1'; - } - - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); - if ( isset( $settings['provider'] ) ) { - $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); - } else { - $new_settings['provider'] = Speech::ID; - } + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; $post_types = \Classifai\get_post_types_for_language_settings(); foreach ( $post_types as $post_type ) { - if ( isset( $settings['post_types'][ $post_type->name ] ) ) { - $new_settings['post_types'][ $post_type->name ] = $settings['post_types'][ $post_type->name ]; + if ( ! isset( $new_settings['post_types'][ $post_type->name ] ) ) { + $new_settings['post_types'][ $post_type->name ] = $settings['post_types']; } else { - $new_settings['post_types'][ $post_type->name ] = null; + $new_settings['post_types'][ $post_type->name ] = sanitize_text_field( $new_settings['post_types'][ $post_type->name ] ); } } @@ -244,9 +230,9 @@ public function sanitize_settings( $settings ) { * * When multiple providers are supported, the sanitization methods for each provider should be called here. */ - if ( isset( $settings[ Speech::ID ] ) ) { + if ( isset( $new_settings[ Speech::ID ] ) ) { $provider_instance = new Speech( $this ); - $api_key_settings = $provider_instance->sanitize_settings( $settings ); + $api_key_settings = $provider_instance->sanitize_settings( $new_settings ); $new_settings[ Speech::ID ]['api_key'] = $api_key_settings[ Speech::ID ]['api_key']; $new_settings[ Speech::ID ]['endpoint_url'] = $api_key_settings[ Speech::ID ]['endpoint_url']; $new_settings[ Speech::ID ]['authenticated'] = $api_key_settings[ Speech::ID ]['authenticated']; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 606b84219..a6b4dbd66 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -190,30 +190,16 @@ public function get_default_settings() { /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. + * @param array $new_settings The settings to be sanitized on save. * * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->get_settings(); - - if ( empty( $settings['status'] ) || 1 !== (int) $settings['status'] ) { - $new_settings['status'] = 'no'; - } else { - $new_settings['status'] = '1'; - } - - if ( isset( $settings['roles'] ) && is_array( $settings['roles'] ) ) { - $new_settings['roles'] = array_map( 'sanitize_text_field', $settings['roles'] ); - } else { - $new_settings['roles'] = array_keys( get_editable_roles() ?? [] ); - } + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); - if ( isset( $settings['provider'] ) ) { - $new_settings['provider'] = sanitize_text_field( $settings['provider'] ); - } else { - $new_settings['provider'] = ChatGPT::ID; - } + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; /* * These are the sanitization methods specific to the OpenAI ChatGPT provider. @@ -221,13 +207,13 @@ public function sanitize_settings( $settings ) { * * When multiple providers are supported, the sanitization methods for each provider should be called here. */ - if ( isset( $settings[ ChatGPT::ID ] ) ) { + if ( isset( $new_settings[ ChatGPT::ID ] ) ) { $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $settings ); + $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_titles', $settings ); - $new_settings[ ChatGPT::ID ]['generate_title_prompt'] = $provider_instance->sanitize_prompts( 'generate_title_prompt', $settings ); + $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); + $new_settings[ ChatGPT::ID ]['generate_title_prompt'] = $provider_instance->sanitize_prompts( 'generate_title_prompt', $new_settings ); } return apply_filters( diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index c42c4c42e..f1d942413 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -158,7 +158,7 @@ public function add_endpoint_url_field( $args = [] ) { 'input_type' => 'text', 'default_value' => $settings[ $id ], 'description' => $args[ 'description' ] ?? __( 'Text to Speech region endpoint, e.g., https://LOCATION.tts.speech.microsoft.com/. Replace LOCATION with the Location/Region you selected for the resource in Azure.', 'classifai' ), - 'class' => 'classifai-provider-field hidden', // Important to add this. + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } @@ -195,44 +195,44 @@ public function reset_settings() {} /** * Sanitization callback for settings. * - * @param array $settings The settings being saved. + * @param array $new_settings The settings being saved. * @return array */ - public function sanitize_settings( $settings ) { - $current_settings = wp_parse_args( - $this->feature_instance->get_settings(), - $this->feature_instance->get_default_settings() - ); + public function sanitize_settings( $new_settings ) { + $settings = $this->feature_instance->get_settings(); $is_credentials_changed = false; - if ( ! empty( $settings[ static::ID ]['endpoint_url'] ) && ! empty( $settings[ static::ID ]['api_key'] ) ) { - $new_url = trailingslashit( esc_url_raw( $settings[ static::ID ]['endpoint_url'] ) ); - $new_key = sanitize_text_field( $settings[ static::ID ]['api_key'] ); + $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; + $new_settings[ static::ID ]['voices'] = $settings[ static::ID ]['voices']; + + if ( ! empty( $new_settings[ static::ID ]['endpoint_url'] ) && ! empty( $new_settings[ static::ID ]['api_key'] ) ) { + $new_url = trailingslashit( esc_url_raw( $new_settings[ static::ID ]['endpoint_url'] ) ); + $new_key = sanitize_text_field( $new_settings[ static::ID ]['api_key'] ); - if ( $new_url !== $current_settings[ static::ID ]['endpoint_url'] || $new_key !== $current_settings[ static::ID ]['api_key'] ) { + if ( $new_url !== $settings[ static::ID ]['endpoint_url'] || $new_key !== $settings[ static::ID ]['api_key'] ) { $is_credentials_changed = true; } if ( $is_credentials_changed ) { - $current_settings[ static::ID ]['endpoint_url'] = $new_url; - $current_settings[ static::ID ]['api_key'] = $new_key; - $current_settings[ static::ID ]['voices'] = $this->connect_to_service( + $new_settings[ static::ID ]['endpoint_url'] = $new_url; + $new_settings[ static::ID ]['api_key'] = $new_key; + $new_settings[ static::ID ]['voices'] = $this->connect_to_service( array( 'endpoint_url' => $new_url, 'api_key' => $new_key, ) ); - if ( ! empty( $current_settings[ static::ID ]['voices'] ) ) { - $current_settings[ static::ID ]['authenticated'] = true; + if ( ! empty( $new_settings[ static::ID ]['voices'] ) ) { + $new_settings[ static::ID ]['authenticated'] = true; } else { - $current_settings[ static::ID ]['voices'] = []; - $current_settings[ static::ID ]['authenticated'] = false; + $new_settings[ static::ID ]['voices'] = []; + $new_settings[ static::ID ]['authenticated'] = false; } } } else { - $current_settings[ static::ID ]['endpoint_url'] = ''; - $current_settings[ static::ID ]['api_key'] = ''; + $new_settings[ static::ID ]['endpoint_url'] = $settings[ static::ID ]['endpoint_url']; + $new_settings[ static::ID ]['api_key'] = $settings[ static::ID ]['api_key']; add_settings_error( $this->feature_instance->get_option_name(), @@ -246,18 +246,16 @@ public function sanitize_settings( $settings ) { $post_types = get_post_types_for_language_settings(); foreach ( $post_types as $post_type ) { - if ( isset( $settings['post_types'][ $post_type->name ] ) ) { - $current_settings['post_types'][ $post_type->name ] = $settings['post_types'][ $post_type->name ]; + if ( isset( $new_settings['post_types'][ $post_type->name ] ) ) { + $new_settings['post_types'][ $post_type->name ] = sanitize_text_field( $new_settings['post_types'][ $post_type->name ] ); } else { - $current_settings['post_types'][ $post_type->name ] = null; + $new_settings['post_types'][ $post_type->name ] = $settings['post_types']; } } - if ( isset( $settings[ static::ID ]['voice'] ) && ! empty( $settings[ static::ID ]['voice'] ) ) { - $current_settings[ static::ID ]['voice'] = sanitize_text_field( $settings[ static::ID ]['voice'] ); - } + $new_settings[ static::ID ]['voice'] = sanitize_text_field( $new_settings[ static::ID ]['voice'] ?? $settings[ static::ID ]['voice'] ); - return $current_settings; + return $new_settings; } /** diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index f49bb0feb..0c34f8abc 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -162,32 +162,26 @@ public function add_number_of_responses_field( $args = [] ) { /** * Sanitisation callback for api key. * - * @param array $settings The settings array. + * @param array $new_settings The settings array. * * @return string */ - public function sanitize_api_key( $settings ) { - if ( isset( $settings[ self::ID ]['api_key'] ) ) { - return sanitize_text_field( $settings[ self::ID ]['api_key'] ); - } - - return ''; + public function sanitize_api_key( $new_settings ) { + $settings = $this->feature_instance->get_settings(); + return sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ?? '' ); } /** * Sanitisation callback for number of responses. * * @param string $key The key of the value we are sanitizing. - * @param array $settings The settings array. + * @param array $new_settings The settings array. + * @param array $settings Current array. * * @return integer */ - public function sanitize_number_of_responses_field( $key, $settings ) { - if ( isset( $settings[ self::ID ][ $key ] ) ) { - return absint( $settings[ self::ID ][ $key ] ); - } - - return 1; + public function sanitize_number_of_responses_field( $key, $new_settings, $settings ) { + return absint( $new_settings[ static::ID ][ $key ] ?? $settings[ static::ID ][ $key ] ?? '' ); } /** @@ -870,14 +864,14 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use * @since 2.4.0 * * @param array $prompt_key Prompt key. - * @param array $settings Settings data. + * @param array $new_settings Settings data. * * @return array Sanitized prompt data. */ - public function sanitize_prompts( $prompt_key = '', array $settings ): array { - if ( isset( $settings[ self::ID ][ $prompt_key ] ) && is_array( $settings[ self::ID ][ $prompt_key ] ) ) { + public function sanitize_prompts( $prompt_key = '', array $new_settings ): array { + if ( isset( $new_settings[ self::ID ][ $prompt_key ] ) && is_array( $new_settings[ self::ID ][ $prompt_key ] ) ) { - $prompts = $settings[ self::ID ][ $prompt_key ]; + $prompts = $new_settings[ self::ID ][ $prompt_key ]; // Remove any prompts that don't have a title and prompt. $prompts = array_filter( diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index aaf8b2b93..546ed3ed4 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -91,12 +91,14 @@ function() { /** * Sanitize the API key, showing an error message if needed. * - * @param array $old_settings Existing settings, if any. + * @param array $new_settings Incoming settings, if any. + * @param array $settings Current settings, if any. * @return array */ - public function sanitize_api_key_settings( array $old_settings = [] ) { - $new_settings = $this->feature_instance->get_settings(); - $authenticated = $this->authenticate_credentials( $old_settings[ static::ID ]['api_key'] ?? '' ); + public function sanitize_api_key_settings( array $new_settings = [], $settings ) { + $authenticated = $this->authenticate_credentials( $new_settings[ static::ID ]['api_key'] ?? '' ); + + $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; if ( is_wp_error( $authenticated ) ) { $new_settings[ static::ID ]['authenticated'] = false; @@ -120,7 +122,7 @@ public function sanitize_api_key_settings( array $old_settings = [] ) { $new_settings[ static::ID ]['authenticated'] = true; } - $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $old_settings[ static::ID ]['api_key'] ?? '' ); + $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ); return $new_settings; } From 6bb3d18e832c4e4bbbdf32995ca3349d5e3406a0 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 10 Nov 2023 23:10:18 +0530 Subject: [PATCH 024/127] add conditional fields JS support --- src/js/admin.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/js/admin.js b/src/js/admin.js index 786eb8058..33875bb75 100644 --- a/src/js/admin.js +++ b/src/js/admin.js @@ -302,3 +302,25 @@ document.addEventListener( 'DOMContentLoaded', function () { return $newPromptFieldset; } } )(); + +/** Feature-first refactor settings field: */ +( function ( $ ) { + $( function() { + const providerSelectEl = $( 'select#provider' ); + + providerSelectEl.on( 'change', function() { + const providerId = $( this ).val(); + const providerRows = $( '.classifai-provider-field' ); + const providerClass = `.provider-scope-${ providerId }`; + + providerRows.addClass( 'hidden' ); + providerRows.find( ':input' ).prop( 'disabled', true ); + + $( providerClass ).removeClass( 'hidden' ); + $( providerClass ).find( ':input' ).prop( 'disabled', false ); + } ); + + // Trigger 'change' on page load. + providerSelectEl.trigger( 'change' ); + } ); +} ( jQuery ) ); From b7ae2dd66e8f8b0e9b22458b3e5b0ffff8c078f2 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 16 Nov 2023 21:48:55 +0530 Subject: [PATCH 025/127] add helper methods --- includes/Classifai/Features/Feature.php | 45 ++++++++++++++----- includes/Classifai/Features/TextToSpeech.php | 18 ++++++++ includes/Classifai/Providers/Azure/Speech.php | 2 +- includes/Classifai/Providers/Provider.php | 9 ---- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index fb2303d6a..f0189c658 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -79,14 +79,14 @@ abstract public function setup_fields_sections(); * * @return array */ - abstract public function get_default_settings(); + abstract protected function get_default_settings(); /** * Returns the providers supported by the feature. * * @return array */ - abstract public function get_providers(); + abstract protected function get_providers(); /** * Sanitizes the settings before saving. @@ -95,7 +95,7 @@ abstract public function get_providers(); * * @return array */ - abstract public function sanitize_settings( $settings ); + abstract protected function sanitize_settings( $settings ); /** * Returns true if the feature meets all the criteria to be enabled. False otherwise. @@ -149,7 +149,7 @@ public function get_option_name() { public function get_settings( $index = false ) { $defaults = $this->get_default_settings(); $settings = get_option( $this->get_option_name(), [] ); - $settings = wp_parse_args( $settings, $defaults ); + $settings = $this->merge_settings( $settings, $defaults ); if ( $index && isset( $settings[ $index ] ) ) { return $settings[ $index ]; @@ -158,6 +158,30 @@ public function get_settings( $index = false ) { return $settings; } + /** + * Merges the data settings with the default settings recursively, + * + * @internal + * + * @param array $settings Settings data from the database. + * @param array $default Default feature and providers settings data. + * + * @return array + */ + protected function merge_settings( $settings = [], $default = [] ) { + foreach ( $default as $key => $value ) { + if ( ! isset( $settings[ $key ] ) ) { + $settings[ $key ] = $default[ $key ]; + } else { + if ( is_array( $value ) ) { + $settings[ $key ] = $this->merge_settings( $settings[ $key ], $default[ $key ] ); + } + } + } + + return $settings; + } + /** * Returns the instance of the provider set for the feature. * @@ -165,9 +189,8 @@ public function get_settings( $index = false ) { * * @return \Classifai\Providers */ - public function get_feature_provider_instance( $provider_id = '' ) { - $new_settings = $this->get_settings(); - $provider_id = $provider_id ? $provider_id : $new_settings['provider']; + protected function get_feature_provider_instance( $provider_id = '' ) { + $provider_id = $provider_id ? $provider_id : $this->get_settings( 'provider' ); $provider_instance = find_provider_class( $this->provider_instances ?? [], $provider_id ); if ( is_wp_error( $provider_instance ) ) { @@ -434,6 +457,7 @@ public function render_select( $args ) { * @param array $args The args passed to add_settings_field */ public function render_checkbox_group( array $args = array() ) { + $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; $setting_index = $this->get_settings(); // Iterate through all of our options. @@ -455,12 +479,13 @@ public function render_checkbox_group( array $args = array() ) { printf( '

', esc_attr( $this->get_option_name() ), + $option_index ? '[' . esc_attr( $option_index ) . ']' : '', esc_attr( $args['label_for'] ), esc_attr( $option_value ), checked( $value, $option_value, false ), diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 620791ade..f541fb397 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -200,6 +200,24 @@ public function get_default_settings() { ]; } + /** + * The list of post types that TTS supports. + * + * @return array Supported Post Types. + */ + public function get_tts_supported_post_types() { + $selected = $this->get_settings( 'post_types' ); + $post_types = []; + + foreach ( $selected as $post_type => $enabled ) { + if ( ! empty( $enabled ) ) { + $post_types[] = $post_type; + } + } + + return $post_types; + } + /** * Sanitizes the settings before saving. * diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index f1d942413..4b1dcfe8b 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -134,7 +134,7 @@ public function register() { add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); add_action( 'save_post', [ $this, 'save_post_metadata' ], 5 ); - foreach ( get_tts_supported_post_types() as $post_type ) { + foreach ( $this->feature_instance->get_tts_supported_post_types() as $post_type ) { add_action( 'rest_insert_' . $post_type, [ $this, 'rest_handle_audio' ], 10, 2 ); } diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 36bdfe112..be4b4218a 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -160,15 +160,6 @@ public function get_settings( $index = false ) { return $settings; } - /** - * Returns the default settings. - * - * @return array - */ - public function get_default_settings() { - return []; - } - /** * Common entry point for all REST endpoints for this provider. * This is called by the Service. From 25cab08ef3137cac86c8412830c964e76028855a Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 16 Nov 2023 22:06:56 +0530 Subject: [PATCH 026/127] fix access levels --- includes/Classifai/Features/ContentResizing.php | 6 +++--- includes/Classifai/Features/ExcerptGeneration.php | 4 ++-- includes/Classifai/Features/Feature.php | 7 ++++++- includes/Classifai/Features/TextToSpeech.php | 6 +++--- includes/Classifai/Features/TitleGeneration.php | 6 +++--- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index e7b42ec90..2b999ecb6 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -32,7 +32,7 @@ public function get_label() { * * @return array */ - public function get_providers() { + protected function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -171,7 +171,7 @@ public function is_feature_enabled() { * * @return array */ - public function get_default_settings() { + protected function get_default_settings() { return [ 'status' => '0', 'roles' => $this->roles, @@ -205,7 +205,7 @@ public function get_default_settings() { * * @return array */ - public function sanitize_settings( $new_settings ) { + protected function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index fbf47e17f..e30cbd055 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -171,7 +171,7 @@ public function is_feature_enabled() { * * @return array */ - public function get_default_settings() { + protected function get_default_settings() { return [ 'status' => '0', 'roles' => $this->roles, @@ -198,7 +198,7 @@ public function get_default_settings() { * * @return array */ - public function sanitize_settings( $new_settings ) { + protected function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index f0189c658..8e972cf23 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -71,12 +71,15 @@ abstract public function get_label(); /** * Set up the fields for each section. + * + * @internal */ - abstract public function setup_fields_sections(); + abstract protected function setup_fields_sections(); /** * Returns the default settings for the feature. * + * @internal * @return array */ abstract protected function get_default_settings(); @@ -84,6 +87,7 @@ abstract protected function get_default_settings(); /** * Returns the providers supported by the feature. * + * @internal * @return array */ abstract protected function get_providers(); @@ -93,6 +97,7 @@ abstract protected function get_providers(); * * @param array $settings The settings to be sanitized on save. * + * @internal * @return array */ abstract protected function sanitize_settings( $settings ); diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index f541fb397..cc556d1c5 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -35,7 +35,7 @@ public function get_label() { * * @return array */ - public function get_providers() { + protected function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -184,7 +184,7 @@ protected function get_post_types_select_options() { * * @return array */ - public function get_default_settings() { + protected function get_default_settings() { return [ 'status' => '0', 'roles' => $this->roles, @@ -225,7 +225,7 @@ public function get_tts_supported_post_types() { * * @return array */ - public function sanitize_settings( $new_settings ) { + protected function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index a6b4dbd66..34c19aaf2 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -32,7 +32,7 @@ public function get_label() { * * @return array */ - public function get_providers() { + protected function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -162,7 +162,7 @@ public function is_feature_enabled() { * * @return array */ - public function get_default_settings() { + protected function get_default_settings() { return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -194,7 +194,7 @@ public function get_default_settings() { * * @return array */ - public function sanitize_settings( $new_settings ) { + protected function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; From 7d80a69409bc8281b1c56f967e584d99f72cf893 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 17 Nov 2023 15:18:24 +0530 Subject: [PATCH 027/127] refactor provider fields --- .../Features/AudioTranscriptsGeneration.php | 64 ++--- .../Classifai/Features/ContentResizing.php | 100 +++----- .../Classifai/Features/ExcerptGeneration.php | 74 +++--- includes/Classifai/Features/Feature.php | 23 +- includes/Classifai/Features/TextToSpeech.php | 67 +++-- .../Classifai/Features/TitleGeneration.php | 89 +++---- includes/Classifai/Providers/Azure/Speech.php | 61 +++-- .../Classifai/Providers/OpenAI/ChatGPT.php | 230 +++++++++++++++--- .../Classifai/Providers/OpenAI/Whisper.php | 46 +++- .../Classifai/Services/LanguageProcessing.php | 4 +- includes/Classifai/Services/Service.php | 2 +- 11 files changed, 464 insertions(+), 296 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index d25259e25..73147513e 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -2,6 +2,7 @@ namespace Classifai\Features; +use Classifai\Services\LanguageProcessing; use Classifai\Providers\Azure\Speech; use \Classifai\Providers\OpenAI\Whisper; @@ -16,6 +17,13 @@ class AudioTranscriptsGeneration extends Feature { */ const ID = 'feature_audio_transcripts_generation'; + public function __construct() { + parent::__construct(); + + $service_providers = LanguageProcessing::get_service_providers(); + $this->provider_instances = $this->get_provider_instances( $service_providers ); + } + /** * Returns the label of the feature. * @@ -65,7 +73,7 @@ public function setup_fields_sections() { 'label_for' => 'status', 'input_type' => 'checkbox', 'default_value' => $settings['status'], - 'description' => __( 'Enabling this will automatically generate transcripts for supported audio files..', 'classifai' ), + 'description' => __( 'Enabling this will automatically generate transcripts for supported audio files.', 'classifai' ), ] ); @@ -96,16 +104,13 @@ public function setup_fields_sections() { ] ); - /* - * The following fields are specific to the OpenAI ChatGPT provider. - * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. - * - * If the feature supports multiple providers, then the fields should be added for each provider. - */ - $azure_speech = new Whisper( $this ); - $azure_speech->add_api_key_field(); + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); - do_action( 'classifai_' . static::ID . 'provider_setup_fields_sections', $this ); + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } } /** @@ -150,15 +155,24 @@ public function is_feature_enabled() { * @return array */ public function get_default_settings() { - return [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => Whisper::ID, - Whisper::ID => [ - 'api_key' => '', - 'authenticated' => false, - ], + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; + + $provider_instance = $this->get_feature_provider_instance( Whisper::ID ); + $provider_settings[ Whisper::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); } /** @@ -175,18 +189,8 @@ public function sanitize_settings( $new_settings ) { $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; - /* - * These are the sanitization methods specific to the OpenAI ChatGPT provider. - * They sanitize the settings for the provider and then merge them into the new settings array. - * - * When multiple providers are supported, the sanitization methods for each provider should be called here. - */ - if ( isset( $new_settings[ Whisper::ID ] ) ) { - $provider_instance = new Whisper( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); - $new_settings[ Whisper::ID ]['api_key'] = $api_key_settings[ Whisper::ID ]['api_key']; - $new_settings[ Whisper::ID ]['authenticated'] = $api_key_settings[ Whisper::ID ]['authenticated']; - } + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 2b999ecb6..43be576ab 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -3,6 +3,7 @@ namespace Classifai\Features; use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Services\LanguageProcessing; /** * Class ContentResizing @@ -15,6 +16,13 @@ class ContentResizing extends Feature { */ const ID = 'feature_content_resizing'; + public function __construct() { + parent::__construct(); + + $service_providers = LanguageProcessing::get_service_providers(); + $this->provider_instances = $this->get_provider_instances( $service_providers ); + } + /** * Returns the label of the feature. * @@ -95,39 +103,13 @@ public function setup_fields_sections() { ] ); - /* - * The following fields are specific to the OpenAI ChatGPT provider. - * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. - * - * If the feature supports multiple providers, then the fields should be added for each provider. - */ - $chat_gpt = new ChatGPT( $this ); - $chat_gpt->add_api_key_field(); - $chat_gpt->add_number_of_responses_field( - [ - 'id' => 'number_of_suggestions', - 'label' => esc_html__( 'Number of suggestions', 'classifai' ), - 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), - ] - ); - $chat_gpt->add_prompt_field( - [ - 'id' => 'condense_text_prompt', - 'label' => esc_html__( 'Condense text prompt', 'classifai' ), - 'prompt_placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); - $chat_gpt->add_prompt_field( - [ - 'id' => 'expand_text_prompt', - 'label' => esc_html__( 'Expand text prompt' ), - 'prompt_placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); - do_action( 'classifai_' . static::ID . 'provider_setup_fields_sections', $this ); + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } } /** @@ -172,30 +154,24 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - return [ + $provider_settings = []; + $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, - ChatGPT::ID => [ - 'api_key' => '', - 'authenticated' => false, - 'number_of_suggestions' => 1, - 'condense_text_prompt' => array( - array( - 'title' => esc_html__( 'Condense text prompt', 'classifai' ), - 'prompt' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'original' => 1, - ), - ), - 'expand_text_prompt' => array( - array( - 'title' => esc_html__( 'Expand text prompt', 'classifai' ), - 'prompt' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'original' => 1, - ), - ), - ], ]; + + $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); + $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); } /** @@ -205,29 +181,15 @@ protected function get_default_settings() { * * @return array */ - protected function sanitize_settings( $new_settings ) { + public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; - - /* - * These are the sanitization methods specific to the OpenAI ChatGPT provider. - * They sanitize the settings for the provider and then merge them into the new settings array. - * - * When multiple providers are supported, the sanitization methods for each provider should be called here. - */ - if ( isset( $new_settings[ ChatGPT::ID ] ) ) { - $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; - $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['condense_text_prompt'] = $provider_instance->sanitize_prompts( 'condense_text_prompt', $new_settings ); - $new_settings[ ChatGPT::ID ]['expand_text_prompt'] = $provider_instance->sanitize_prompts( 'expand_text_prompt', $new_settings ); - } + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index e30cbd055..9d0403116 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -2,6 +2,7 @@ namespace Classifai\Features; +use Classifai\Services\LanguageProcessing; use \Classifai\Providers\OpenAI\ChatGPT; /** @@ -15,6 +16,13 @@ class ExcerptGeneration extends Feature { */ const ID = 'feature_excerpt_generation'; + public function __construct() { + parent::__construct(); + + $service_providers = LanguageProcessing::get_service_providers(); + $this->provider_instances = $this->get_provider_instances( $service_providers ); + } + /** * Returns the label of the feature. * @@ -111,23 +119,13 @@ public function setup_fields_sections() { ] ); - /* - * The following fields are specific to the OpenAI ChatGPT provider. - * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. - * - * If the feature supports multiple providers, then the fields should be added for each provider. - */ - $chat_gpt = new ChatGPT( $this ); - $chat_gpt->add_api_key_field(); - $chat_gpt->add_prompt_field( - [ - 'id' => 'generate_excerpt_prompt', - 'prompt_placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), - 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), - ] - ); + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); - do_action( 'classifai_' . static::ID . 'provider_setup_fields_sections', $this ); + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } } /** @@ -172,23 +170,25 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - return [ + $provider_settings = []; + $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, - ChatGPT::ID => [ - 'api_key' => '', - 'authenticated' => false, - 'generate_excerpt_prompt' => array( - array( - 'title' => esc_html__( 'Default', 'classifai' ), - 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), - 'original' => 1, - ), - ), - ], ]; + + $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); + $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); } /** @@ -198,27 +198,15 @@ protected function get_default_settings() { * * @return array */ - protected function sanitize_settings( $new_settings ) { + public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; - /* - * These are the sanitization methods specific to the OpenAI ChatGPT provider. - * They sanitize the settings for the provider and then merge them into the new settings array. - * - * When multiple providers are supported, the sanitization methods for each provider should be called here. - */ - if ( isset( $new_settings[ ChatGPT::ID ] ) ) { - $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; - $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['generate_excerpt_prompt'] = $provider_instance->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); - } + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 8e972cf23..c016ecf0b 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -33,8 +33,7 @@ abstract class Feature { * * @param \Classifai\Providers\Provider[] $provider_instances Array of provider instances. */ - public function __construct( $provider_instances = [] ) { - $this->provider_instances = $provider_instances; + public function __construct() { add_action( 'admin_init', [ $this, 'setup_roles' ] ); add_action( 'admin_init', [ $this, 'register_setting' ] ); add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); @@ -100,7 +99,7 @@ abstract protected function get_providers(); * @internal * @return array */ - abstract protected function sanitize_settings( $settings ); + abstract public function sanitize_settings( $settings ); /** * Returns true if the feature meets all the criteria to be enabled. False otherwise. @@ -187,6 +186,24 @@ protected function merge_settings( $settings = [], $default = [] ) { return $settings; } + /** + * Returns array of instances of provider classes registered for the service. + * + * @internal + * + * @param array $services Array of provider classes. + * @return array + */ + protected function get_provider_instances( $services ) { + $provider_instances = []; + + foreach ( $services as $provider_class ) { + $provider_instances[] = new $provider_class( $this ); + } + + return $provider_instances; + } + /** * Returns the instance of the provider set for the feature. * diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index cc556d1c5..25746ce3b 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Services\LanguageProcessing; use \Classifai\Providers\Azure\Speech; use function Classifai\find_provider_class; @@ -18,6 +18,13 @@ class TextToSpeech extends Feature { */ const ID = 'feature_text_to_speech'; + public function __construct() { + parent::__construct(); + + $service_providers = LanguageProcessing::get_service_providers(); + $this->provider_instances = $this->get_provider_instances( $service_providers ); + } + /** * Returns the label of the feature. * @@ -119,18 +126,13 @@ public function setup_fields_sections() { ] ); - /* - * The following fields are specific to the OpenAI ChatGPT provider. - * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. - * - * If the feature supports multiple providers, then the fields should be added for each provider. - */ - $azure_speech = new Speech( $this ); - $azure_speech->add_api_key_field(); - $azure_speech->add_endpoint_url_field(); - $azure_speech->add_voices_options_field(); + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); - do_action( 'classifai_' . static::ID . 'provider_setup_fields_sections', $this ); + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } } /** @@ -185,19 +187,25 @@ protected function get_post_types_select_options() { * @return array */ protected function get_default_settings() { - return [ + $provider_settings = []; + $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'post_types' => [], 'provider' => Speech::ID, - Speech::ID => [ - 'endpoint_url' => '', - 'api_key' => '', - 'authenticated' => false, - 'voice' => '', - 'voices' => [], - ], ]; + + $provider_instance = $this->get_feature_provider_instance( Speech::ID ); + $provider_settings[ Speech::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); } /** @@ -225,7 +233,7 @@ public function get_tts_supported_post_types() { * * @return array */ - protected function sanitize_settings( $new_settings ) { + public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; @@ -242,21 +250,8 @@ protected function sanitize_settings( $new_settings ) { } } - /* - * These are the sanitization methods specific to the OpenAI ChatGPT provider. - * They sanitize the settings for the provider and then merge them into the new settings array. - * - * When multiple providers are supported, the sanitization methods for each provider should be called here. - */ - if ( isset( $new_settings[ Speech::ID ] ) ) { - $provider_instance = new Speech( $this ); - $api_key_settings = $provider_instance->sanitize_settings( $new_settings ); - $new_settings[ Speech::ID ]['api_key'] = $api_key_settings[ Speech::ID ]['api_key']; - $new_settings[ Speech::ID ]['endpoint_url'] = $api_key_settings[ Speech::ID ]['endpoint_url']; - $new_settings[ Speech::ID ]['authenticated'] = $api_key_settings[ Speech::ID ]['authenticated']; - $new_settings[ Speech::ID ]['voices'] = $api_key_settings[ Speech::ID ]['voices']; - $new_settings[ Speech::ID ]['voice'] = $api_key_settings[ Speech::ID ]['voice']; - } + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 34c19aaf2..2cc2ace41 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -2,6 +2,7 @@ namespace Classifai\Features; +use Classifai\Services\LanguageProcessing; use \Classifai\Providers\OpenAI\ChatGPT; /** @@ -15,6 +16,13 @@ class TitleGeneration extends Feature { */ const ID = 'feature_title_generation'; + public function __construct() { + parent::__construct(); + + $service_providers = LanguageProcessing::get_service_providers(); + $this->provider_instances = $this->get_provider_instances( $service_providers ); + } + /** * Returns the label of the feature. * @@ -32,7 +40,7 @@ public function get_label() { * * @return array */ - protected function get_providers() { + public function get_providers() { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -95,30 +103,13 @@ public function setup_fields_sections() { ] ); - /* - * The following fields are specific to the OpenAI ChatGPT provider. - * These fields will only be displayed if the provider is selected, and will remain hidden otherwise. - * - * If the feature supports multiple providers, then the fields should be added for each provider. - */ - $chat_gpt = new ChatGPT( $this ); - $chat_gpt->add_api_key_field(); - $chat_gpt->add_number_of_responses_field( - [ - 'id' => 'number_of_titles', - 'label' => esc_html__( 'Number of titles', 'classifai' ), - 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), - ] - ); - $chat_gpt->add_prompt_field( - [ - 'id' => 'generate_title_prompt', - 'prompt_placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), - 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), - ] - ); + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); - do_action( 'classifai_' . static::ID . '_provider_setup_fields_sections', $this ); + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } } /** @@ -163,27 +154,23 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + ]; + + $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); + $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); + return apply_filters( 'classifai_' . static::ID . '_get_default_settings', - [ - 'status' => '0', - 'roles' => $this->roles, - 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, - ChatGPT::ID => [ - 'api_key' => '', - 'number_of_titles' => 1, - 'authenticated' => false, - 'generate_title_prompt' => array( - array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), - 'original' => 1, - ), - ), - ], - ] + array_merge( + $feature_settings, + $provider_settings + ) ); } @@ -194,27 +181,15 @@ protected function get_default_settings() { * * @return array */ - protected function sanitize_settings( $new_settings ) { + public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; - /* - * These are the sanitization methods specific to the OpenAI ChatGPT provider. - * They sanitize the settings for the provider and then merge them into the new settings array. - * - * When multiple providers are supported, the sanitization methods for each provider should be called here. - */ - if ( isset( $new_settings[ ChatGPT::ID ] ) ) { - $provider_instance = new ChatGPT( $this ); - $api_key_settings = $provider_instance->sanitize_api_key_settings( $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['api_key'] = $api_key_settings[ ChatGPT::ID ]['api_key']; - $new_settings[ ChatGPT::ID ]['authenticated'] = $api_key_settings[ ChatGPT::ID ]['authenticated']; - $new_settings[ ChatGPT::ID ]['number_of_titles'] = $provider_instance->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); - $new_settings[ ChatGPT::ID ]['generate_title_prompt'] = $provider_instance->sanitize_prompts( 'generate_title_prompt', $new_settings ); - } + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); return apply_filters( 'classifai_' . static::ID . '_sanitize_settings', diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 4b1dcfe8b..fc86f11bb 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -142,29 +142,40 @@ public function register() { } } - public function add_endpoint_url_field( $args = [] ) { + public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); - $id = $args['id'] ?? 'endpoint_url'; add_settings_field( - $id, + 'endpoint_url', esc_html__( 'Endpoint URL', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, - 'label_for' => $id, + 'label_for' => 'endpoint_url', 'input_type' => 'text', - 'default_value' => $settings[ $id ], - 'description' => $args[ 'description' ] ?? __( 'Text to Speech region endpoint, e.g., https://LOCATION.tts.speech.microsoft.com/. Replace LOCATION with the Location/Region you selected for the resource in Azure.', 'classifai' ), + 'default_value' => $settings['endpoint_url'], + 'description' => __( 'Text to Speech region endpoint, e.g., https://LOCATION.tts.speech.microsoft.com/. Replace LOCATION with the Location/Region you selected for the resource in Azure.', 'classifai' ), 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); - } - public function add_voices_options_field( $args = [] ) { - $settings = $this->feature_instance->get_settings( static::ID ); + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $settings['api_key'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + $voices_options = $this->get_voices_select_options(); if ( ! empty( $voices_options ) ) { @@ -183,6 +194,25 @@ public function add_voices_options_field( $args = [] ) { ] ); } + + do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); + } + + public function get_default_provider_settings() { + $common_settings = [ + 'api_key' => '', + 'endpoint_url' => '', + 'authenticated' => false, + 'voices' => [], + 'voice' => '', + ]; + + switch ( $this->feature_instance::ID ) { + case TextToSpeech::ID: + return $common_settings; + } + + return []; } /** @@ -412,18 +442,7 @@ public function get_provider_debug_information( $settings = null, $configured = /** * Returns the default settings. */ - public function get_default_settings() { - return [ - 'credentials' => array( - 'url' => '', - 'api_key' => '', - ), - 'voices' => array(), - 'voice' => '', - 'authenticated' => false, - 'post_types' => array(), - ]; - } + public function get_default_settings() {} /** * Initial audio generation state. diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 0c34f8abc..8b51d3268 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -99,64 +99,232 @@ public function __construct( $feature_instance ) { do_action( 'classifai_' . static::ID . '_init', $this ); } - /** - * Adds a prompt repeater field. - * The prompt fields allow users to add their own prompts for ChatGPT. - * - * This is an optional field and depends on the feature. - * - * @param array $args Arguments passed in. - */ - public function add_prompt_field( $args = [] ) { + public function render_provider_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $settings['api_key'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + switch ( $this->feature_instance::ID ) { + case TitleGeneration::ID: + $this->add_title_generation_fields(); + break; + + case ExcerptGeneration::ID: + $this->add_excerpt_generation_fields(); + break; + + case ContentResizing::ID: + $this->add_content_resizing_fields(); + break; + } + + do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); + } + + private function add_title_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - $args['id'], + 'number_of_titles', + esc_html__( 'Number of titles', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'number_of_titles', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $settings['number_of_titles'], + 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + add_settings_field( + 'generate_title_prompt', $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), [ $this->feature_instance, 'render_prompt_repeater_field' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, - 'label_for' => $args['id'], - 'placeholder' => $args['prompt_placeholder'], - 'default_value' => $settings[ $args['id'] ], - 'description' => $args['description'], + 'label_for' => 'generate_title_prompt', + 'placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), + 'default_value' => $settings['generate_title_prompt'], + 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); } - /** - * Adds number of responses number field. - * ChatGPT is capable of returning variable number of responses. - * - * This field is helpful to set the number of responses to be returned. - * - * This is an optional field and depends on the feature. - * - * @param array $args Arguments passed in. - */ - public function add_number_of_responses_field( $args = [] ) { + private function add_excerpt_generation_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + add_settings_field( + 'generate_excerpt_prompt', + $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), + [ $this->feature_instance, 'render_prompt_repeater_field' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'generate_excerpt_prompt', + 'placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'default_value' => $settings['generate_excerpt_prompt'], + 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + } + + private function add_content_resizing_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - $args['id'], - $args['label'], + 'number_of_suggestions', + esc_html__( 'Number of titles', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, - 'label_for' => $args['id'], + 'label_for' => 'number_of_suggestions', 'input_type' => 'number', 'min' => 1, 'step' => 1, - 'default_value' => $settings[ $args['id'] ], - 'description' => $args['description'], + 'default_value' => $settings['number_of_suggestions'], + 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. ] ); + + add_settings_field( + 'condense_text_prompt', + $args['label'] ?? esc_html__( 'Condense text prompt', 'classifai' ), + [ $this->feature_instance, 'render_prompt_repeater_field' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'condense_text_prompt', + 'placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), + 'default_value' => $settings['condense_text_prompt'], + 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + add_settings_field( + 'expand_text_prompt', + $args['label'] ?? esc_html__( 'Expand text prompt', 'classifai' ), + [ $this->feature_instance, 'render_prompt_repeater_field' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'expand_text_prompt', + 'placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), + 'default_value' => $settings['expand_text_prompt'], + 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + } + + public function get_default_provider_settings() { + $common_settings = [ + 'api_key' => '', + 'authenticated' => false, + ]; + + switch ( $this->feature_instance::ID ) { + case TitleGeneration::ID: + return array_merge( + $common_settings, + [ + 'number_of_titles' => 1, + 'generate_title_prompt' => array( + array( + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), + 'original' => 1, + ), + ), + ] + ); + + case ExcerptGeneration::ID: + return array_merge( + $common_settings, + [ + 'generate_excerpt_prompt' => array( + array( + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), + 'original' => 1, + ), + ), + ] + ); + + case ContentResizing::ID: + return array_merge( + $common_settings, + [ + 'number_of_suggestions' => 1, + 'condense_text_prompt' => array( + array( + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Descrease the content length no more than 2 to 4 sentences.', 'classifai' ), + 'original' => 1, + ), + ), + 'expand_text_prompt' => array( + array( + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), + 'original' => 1, + ), + ), + ] + ); + } + + return []; + } + + public function sanitize_settings( $new_settings ) { + $settings = $this->feature_instance->get_settings(); + $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); + $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; + $new_settings[ static::ID ]['authenticated'] = $api_key_settings[ static::ID ]['authenticated']; + + if ( $this->feature_instance instanceof TitleGeneration ) { + $new_settings[ static::ID ]['number_of_titles'] = $this->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); + $new_settings[ static::ID ]['generate_title_prompt'] = $this->sanitize_prompts( 'generate_title_prompt', $new_settings ); + } + + if ( $this->feature_instance instanceof ExcerptGeneration ) { + $new_settings[ static::ID ]['length'] = $this->sanitize_number_of_responses_field( 'length', $new_settings, $settings ); + $new_settings[ static::ID ]['generate_excerpt_prompt'] = $this->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); + } + + return $new_settings; } /** @@ -404,8 +572,6 @@ public function setup_fields_sections() {} public function reset_settings() {} - public function sanitize_settings( $settings ) {} - /** * Default settings for ChatGPT * diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 3f09786d5..0adc376cf 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -67,12 +67,54 @@ public function register() { } } + public function render_provider_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $settings['api_key'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); + } + + public function get_default_provider_settings() { + $common_settings = [ + 'api_key' => '', + 'authenticated' => false, + ]; + + switch ( $this->feature_instance::ID ) { + case AudioTranscriptsGeneration::ID: + return $common_settings; + } + + return []; + } + + public function sanitize_settings( $new_settings ) { + $settings = $this->feature_instance->get_settings(); + $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); + $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; + $new_settings[ static::ID ]['authenticated'] = $api_key_settings[ static::ID ]['authenticated']; + + return $new_settings; + } + public function setup_fields_sections() {} public function reset_settings() {} - public function sanitize_settings( $settings ) {} - public function enqueue_media_scripts() { wp_enqueue_script( 'classifai-media-script', diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 221e6675b..cfc272029 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -25,7 +25,7 @@ public function __construct() { parent::__construct( __( 'Language Processing', 'classifai' ), 'language_processing', - $this->register_service_providers() + self::get_service_providers() ); } @@ -37,7 +37,7 @@ public function init() { add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } - public function register_service_providers() { + public static function get_service_providers() { return apply_filters( 'classifai_language_processing_service_providers', [ diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 101022ee9..fdfa514b9 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -82,7 +82,7 @@ public function init() { if ( ! empty( $this->features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $this->feature_classes[] = new $feature( $this->provider_classes ); + $this->feature_classes[] = new $feature(); } } $this->register_features(); From bcfdaf30aca39fd1e5b034bb8dceda5de5f905d2 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 19 Nov 2023 13:19:51 +0530 Subject: [PATCH 028/127] add smart cropping and text extraction features --- .../Features/DescriptiveTextGenerator.php | 206 +++++++++ .../Classifai/Features/ImageTagsGenerator.php | 206 +++++++++ includes/Classifai/Features/ImageToText.php | 206 +++++++++ includes/Classifai/Features/SmartCropping.php | 206 +++++++++ includes/Classifai/Plugin.php | 2 +- .../Providers/Azure/ComputerVision.php | 419 +++++++++++++----- .../Providers/Azure/Personalizer.php | 2 +- includes/Classifai/Providers/Azure/Speech.php | 2 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 2 +- includes/Classifai/Providers/OpenAI/DallE.php | 2 +- .../Classifai/Providers/OpenAI/Whisper.php | 2 +- .../Classifai/Services/ImageProcessing.php | 9 + includes/Classifai/Services/Service.php | 2 +- .../Classifai/Services/ServicesManager.php | 14 +- 14 files changed, 1168 insertions(+), 112 deletions(-) create mode 100644 includes/Classifai/Features/DescriptiveTextGenerator.php create mode 100644 includes/Classifai/Features/ImageTagsGenerator.php create mode 100644 includes/Classifai/Features/ImageToText.php create mode 100644 includes/Classifai/Features/SmartCropping.php diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php new file mode 100644 index 000000000..04048b314 --- /dev/null +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -0,0 +1,206 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'Descriptive Text Generator', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @internal + * + * @return array + */ + protected function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable descriptive text generation', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate titles.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } + } + + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ComputerVision::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @internal + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => ComputerVision::ID, + ]; + + $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); + $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @internal + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } +} diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php new file mode 100644 index 000000000..3c15adf80 --- /dev/null +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -0,0 +1,206 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'Image Tags Generator', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @internal + * + * @return array + */ + protected function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable image tag generation', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'Image tags will be added automatically.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate image tags.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } + } + + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ComputerVision::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @internal + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => ComputerVision::ID, + ]; + + $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); + $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @internal + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } +} diff --git a/includes/Classifai/Features/ImageToText.php b/includes/Classifai/Features/ImageToText.php new file mode 100644 index 000000000..aabce1093 --- /dev/null +++ b/includes/Classifai/Features/ImageToText.php @@ -0,0 +1,206 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'Text extraction from images', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @internal + * + * @return array + */ + protected function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable text extraction from images', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'OCR detects text in images (e.g., handwritten notes) and saves that as post content.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate image tags.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } + } + + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ComputerVision::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @internal + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => ComputerVision::ID, + ]; + + $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); + $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @internal + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } +} diff --git a/includes/Classifai/Features/SmartCropping.php b/includes/Classifai/Features/SmartCropping.php new file mode 100644 index 000000000..916e8a3e0 --- /dev/null +++ b/includes/Classifai/Features/SmartCropping.php @@ -0,0 +1,206 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'Smart Cropping', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @internal + * + * @return array + */ + protected function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable smart cropping', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'AI Vision detects and saves the most visually interesting part of your image (i.e., faces, animals, notable text).', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate image tags.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } + } + + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ComputerVision::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @internal + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = []; + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => ComputerVision::ID, + ]; + + $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); + $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @internal + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } +} diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 502c3c3d4..255b29db9 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -107,7 +107,7 @@ public function init_services() { 'classifai_services', [ 'language_processing' => 'Classifai\Services\LanguageProcessing', - // 'image_processing' => 'Classifai\Services\ImageProcessing', + 'image_processing' => 'Classifai\Services\ImageProcessing', 'personalizer' => 'Classifai\Services\Personalizer', ] ); diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index e441083ae..6d10d27a5 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -5,6 +5,8 @@ namespace Classifai\Providers\Azure; +use Classifai\Features\DescriptiveTextGenerator; +use Classifai\Features\ImageTagsGenerator; use Classifai\Providers\Provider; use DOMDocument; use WP_Error; @@ -30,12 +32,11 @@ class ComputerVision extends Provider { * * @param string $service The service this class belongs to. */ - public function __construct( $service ) { + public function __construct( $feature_instance = null ) { parent::__construct( 'Microsoft Azure', 'AI Vision', - 'computer_vision', - $service + 'computer_vision' ); // Set the onboarding options. @@ -50,6 +51,8 @@ public function __construct( $service ) { 'enable_read_pdf' => __( 'Scan PDFs for text', 'classifai' ), ), ); + + $this->feature_instance = $feature_instance; } /** @@ -59,6 +62,316 @@ public function reset_settings() { update_option( $this->get_option_name(), $this->get_default_settings() ); } + public function render_provider_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + add_settings_field( + 'endpoint_url', + esc_html__( 'Endpoint URL', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'endpoint_url', + 'input_type' => 'text', + 'default_value' => $settings['endpoint_url'], + 'description' => __( 'Supported protocol and hostname endpoints, e.g., https://REGION.api.cognitive.microsoft.com or https://EXAMPLE.cognitiveservices.azure.com. This can look different based on your setting choices in Azure.', 'classifai' ), + 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + add_settings_field( + 'api_key', + esc_html__( 'API Key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'api_key', + 'input_type' => 'password', + 'default_value' => $settings['api_key'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + switch ( $this->feature_instance::ID ) { + case DescriptiveTextGenerator::ID: + $this->add_descriptive_text_generation_fields(); + break; + + case ImageTagsGenerator::ID: + $this->add_image_tags_generation_fields(); + break; + } + + do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); + } + + public function add_descriptive_text_generation_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + $checkbox_options = array( + 'alt' => esc_html__( 'Alt text', 'classifai' ), + 'caption' => esc_html__( 'Image caption', 'classifai' ), + 'description' => esc_html__( 'Image description', 'classifai' ), + ); + + add_settings_field( + 'descriptive_text_fields', + esc_html__( 'Generate descriptive text', 'classifai' ), + [ $this->feature_instance, 'render_checkbox_group' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'descriptive_text_fields', + 'options' => $checkbox_options, + 'default_values' => $settings['descriptive_text_fields'], + 'description' => __( 'Choose image fields where the generated captions should be applied.', 'classifai' ), + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + add_settings_field( + 'descriptive_confidence_threshold', + esc_html__( 'Descriptive text confidence threshold', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'descriptive_confidence_threshold', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $settings['descriptive_confidence_threshold'], + 'description' => esc_html__( 'Minimum confidence score for automatically added alt text, numeric value from 0-100. Recommended to be set to at least 75.', 'classifai' ), + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + } + + public function add_image_tags_generation_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); + $options = []; + + foreach ( $attachment_taxonomies as $name => $taxonomy ) { + $options[ $name ] = $taxonomy->label; + } + + add_settings_field( + 'tag_taxonomy', + esc_html__( 'Tag taxonomy', 'classifai' ), + [ $this->feature_instance, 'render_select' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'tag_taxonomy', + 'options' => $options, + 'default_value' => $settings['tag_taxonomy'], + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + + add_settings_field( + 'tag_confidence_threshold', + esc_html__( 'Tag confidence threshold', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'tag_confidence_threshold', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $settings['tag_confidence_threshold'], + 'description' => esc_html__( 'Minimum confidence score for automatically added image tags, numeric value from 0-100. Recommended to be set to at least 70.', 'classifai' ), + 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + ] + ); + } + + public function get_default_provider_settings() { + $common_settings = [ + 'endpoint_url' => '', + 'api_key' => '', + 'authenticated' => false, + ]; + + switch ( $this->feature_instance::ID ) { + case DescriptiveTextGenerator::ID: + return array_merge( + $common_settings, + [ + 'descriptive_text_fields' => [ + 'alt' => 0, + 'caption' => 0, + 'description' => 0, + ], + 'descriptive_confidence_threshold' => 75 + ] + ); + + case ImageTagsGenerator::ID: + $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); + $options = []; + + foreach ( $attachment_taxonomies as $name => $taxonomy ) { + $options[ $name ] = $taxonomy->label; + } + + return array_merge( + $common_settings, + [ + 'tag_confidence_threshold' => 70, + 'tag_taxonomy' => array_key_first( $options ), + ] + ); + } + + return $common_settings; + } + + /** + * Sanitization + * + * @param array $settings The settings being saved. + * + * @return array|mixed + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->feature_instance->get_settings( static::ID ); + + if ( ! empty( $new_settings[ static::ID ]['endpoint_url'] ) && ! empty( $new_settings[ static::ID ]['api_key'] ) ) { + $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; + $new_settings[ static::ID ]['endpoint_url'] = esc_url_raw( $settings['url'] ); + $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $settings['api_key'] ); + + $auth_check = $this->authenticate_credentials( + $new_settings[ static::ID ]['endpoint_url'], + $new_settings[ static::ID ]['api_key'] + ); + + if ( is_wp_error( $auth_check ) ) { + $new_settings[ static::ID ]['authenticated'] = false; + } else { + $new_settings[ static::ID ]['authenticated'] = true; + } + } else { + $new_settings[ static::ID ]['endpoint_url'] = $settings[ static::ID ]['endpoint_url']; + $new_settings[ static::ID ]['api_key'] = $settings[ static::ID ]['api_key']; + } + + if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { + $new_settings[ static::ID ][ 'descriptive_confidence_threshold' ] = absint( $new_settings[ static::ID ]['descriptive_confidence_threshold'] ?? $settings[ static::ID ]['descriptive_confidence_threshold'] ); + $new_settings[ static::ID ][ 'descriptive_text_fields' ] = array_map( 'sanitize_text_field', $new_settings[ static::ID ]['descriptive_text_fields'] ?? $settings[ static::ID ]['descriptive_text_fields'] ); + } + + if ( $this->feature_instance instanceof ImageTagsGenerator ) { + $new_settings[ static::ID ][ 'tag_confidence_threshold' ] = absint( $new_settings[ static::ID ]['tag_confidence_threshold'] ?? $settings[ static::ID ]['descriptive_confidence_threshold'] ); + $new_settings[ static::ID ][ 'tag_taxonomy' ] = array_map( 'sanitize_text_field', $new_settings[ static::ID ]['tag_taxonomy'] ?? $settings[ static::ID ]['descriptive_text_fields'] ); + } + + return $new_settings; + + $new_settings = []; + if ( ! empty( $settings['url'] ) && ! empty( $settings['api_key'] ) ) { + $auth_check = $this->authenticate_credentials( $settings['url'], $settings['api_key'] ); + if ( is_wp_error( $auth_check ) ) { + $settings_errors['classifai-registration-credentials-error'] = $auth_check->get_error_message(); + $new_settings['authenticated'] = false; + } else { + $new_settings['authenticated'] = true; + } + $new_settings['url'] = esc_url_raw( $settings['url'] ); + $new_settings['api_key'] = sanitize_text_field( $settings['api_key'] ); + } else { + $new_settings['valid'] = false; + $new_settings['url'] = ''; + $new_settings['api_key'] = ''; + + $settings_errors['classifai-registration-credentials-empty'] = __( 'Please enter your credentials', 'classifai' ); + } + + $checkbox_settings = [ + 'enable_image_tagging', + 'enable_smart_cropping', + 'enable_ocr', + 'enable_read_pdf', + ]; + + foreach ( $checkbox_settings as $checkbox_setting ) { + + if ( empty( $settings[ $checkbox_setting ] ) || 1 !== (int) $settings[ $checkbox_setting ] ) { + $new_settings[ $checkbox_setting ] = 'no'; + } else { + $new_settings[ $checkbox_setting ] = '1'; + } + } + + if ( isset( $settings['caption_threshold'] ) && is_numeric( $settings['caption_threshold'] ) && (int) $settings['caption_threshold'] >= 0 && (int) $settings['caption_threshold'] <= 100 ) { + $new_settings['caption_threshold'] = absint( $settings['caption_threshold'] ); + } else { + $new_settings['caption_threshold'] = 75; + } + + if ( isset( $settings['tag_threshold'] ) && is_numeric( $settings['tag_threshold'] ) && (int) $settings['tag_threshold'] >= 0 && (int) $settings['tag_threshold'] <= 100 ) { + $new_settings['tag_threshold'] = absint( $settings['tag_threshold'] ); + } else { + $new_settings['tag_threshold'] = 75; + } + + if ( isset( $settings['image_tag_taxonomy'] ) && taxonomy_exists( $settings['image_tag_taxonomy'] ) ) { + $new_settings['image_tag_taxonomy'] = $settings['image_tag_taxonomy']; + } elseif ( taxonomy_exists( 'classifai-image-tags' ) ) { + $new_settings['image_tag_taxonomy'] = 'classifai-image-tags'; + } + + if ( isset( $settings['enable_image_captions'] ) ) { + if ( is_array( $settings['enable_image_captions'] ) ) { + $new_settings['enable_image_captions'] = $settings['enable_image_captions']; + } elseif ( 1 === (int) $settings['enable_image_captions'] ) { + // Handle submission from onboarding wizard. + $new_settings['enable_image_captions'] = array( + 'alt' => 'alt', + 'caption' => 0, + 'description' => 0, + ); + } + } else { + $new_settings['enable_image_captions'] = array( + 'alt' => 0, + 'caption' => 0, + 'description' => 0, + ); + } + + if ( ! empty( $settings_errors ) ) { + + $registered_settings_errors = wp_list_pluck( get_settings_errors( $this->get_option_name() ), 'code' ); + + foreach ( $settings_errors as $code => $message ) { + + if ( ! in_array( $code, $registered_settings_errors, true ) ) { + add_settings_error( + $this->get_option_name(), + $code, + esc_html( $message ), + 'error' + ); + } + } + } + + return $new_settings; + } + /** * Default settings for ComputerVision * @@ -1094,106 +1407,6 @@ public function setup_fields_sections() { ); } - /** - * Sanitization - * - * @param array $settings The settings being saved. - * - * @return array|mixed - */ - public function sanitize_settings( $settings ) { - $new_settings = []; - if ( ! empty( $settings['url'] ) && ! empty( $settings['api_key'] ) ) { - $auth_check = $this->authenticate_credentials( $settings['url'], $settings['api_key'] ); - if ( is_wp_error( $auth_check ) ) { - $settings_errors['classifai-registration-credentials-error'] = $auth_check->get_error_message(); - $new_settings['authenticated'] = false; - } else { - $new_settings['authenticated'] = true; - } - $new_settings['url'] = esc_url_raw( $settings['url'] ); - $new_settings['api_key'] = sanitize_text_field( $settings['api_key'] ); - } else { - $new_settings['valid'] = false; - $new_settings['url'] = ''; - $new_settings['api_key'] = ''; - - $settings_errors['classifai-registration-credentials-empty'] = __( 'Please enter your credentials', 'classifai' ); - } - - $checkbox_settings = [ - 'enable_image_tagging', - 'enable_smart_cropping', - 'enable_ocr', - 'enable_read_pdf', - ]; - - foreach ( $checkbox_settings as $checkbox_setting ) { - - if ( empty( $settings[ $checkbox_setting ] ) || 1 !== (int) $settings[ $checkbox_setting ] ) { - $new_settings[ $checkbox_setting ] = 'no'; - } else { - $new_settings[ $checkbox_setting ] = '1'; - } - } - - if ( isset( $settings['caption_threshold'] ) && is_numeric( $settings['caption_threshold'] ) && (int) $settings['caption_threshold'] >= 0 && (int) $settings['caption_threshold'] <= 100 ) { - $new_settings['caption_threshold'] = absint( $settings['caption_threshold'] ); - } else { - $new_settings['caption_threshold'] = 75; - } - - if ( isset( $settings['tag_threshold'] ) && is_numeric( $settings['tag_threshold'] ) && (int) $settings['tag_threshold'] >= 0 && (int) $settings['tag_threshold'] <= 100 ) { - $new_settings['tag_threshold'] = absint( $settings['tag_threshold'] ); - } else { - $new_settings['tag_threshold'] = 75; - } - - if ( isset( $settings['image_tag_taxonomy'] ) && taxonomy_exists( $settings['image_tag_taxonomy'] ) ) { - $new_settings['image_tag_taxonomy'] = $settings['image_tag_taxonomy']; - } elseif ( taxonomy_exists( 'classifai-image-tags' ) ) { - $new_settings['image_tag_taxonomy'] = 'classifai-image-tags'; - } - - if ( isset( $settings['enable_image_captions'] ) ) { - if ( is_array( $settings['enable_image_captions'] ) ) { - $new_settings['enable_image_captions'] = $settings['enable_image_captions']; - } elseif ( 1 === (int) $settings['enable_image_captions'] ) { - // Handle submission from onboarding wizard. - $new_settings['enable_image_captions'] = array( - 'alt' => 'alt', - 'caption' => 0, - 'description' => 0, - ); - } - } else { - $new_settings['enable_image_captions'] = array( - 'alt' => 0, - 'caption' => 0, - 'description' => 0, - ); - } - - if ( ! empty( $settings_errors ) ) { - - $registered_settings_errors = wp_list_pluck( get_settings_errors( $this->get_option_name() ), 'code' ); - - foreach ( $settings_errors as $code => $message ) { - - if ( ! in_array( $code, $registered_settings_errors, true ) ) { - add_settings_error( - $this->get_option_name(), - $code, - esc_html( $message ), - 'error' - ); - } - } - } - - return $new_settings; - } - /** * Authenticates our credentials. * diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 0f826d4ea..cf5bd7175 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -34,7 +34,7 @@ class Personalizer extends Provider { * * @param string $service The service this class belongs to. */ - public function __construct( $service ) { + public function __construct( $service = null ) { parent::__construct( 'Microsoft Azure', 'AI Personalizer', diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index fc86f11bb..6c9bc630d 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -72,7 +72,7 @@ class Speech extends Provider { * * @param \Classifai\Features\Feature $feature_instance The feature instance. */ - public function __construct( $feature_instance ) { + public function __construct( $feature_instance = null ) { parent::__construct( 'Microsoft Azure', self::FEATURE_NAME, diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 8b51d3268..ced19e054 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -75,7 +75,7 @@ class ChatGPT extends Provider { * * @param \Classifai\Features\Feature $feature_instance The feature instance. */ - public function __construct( $feature_instance ) { + public function __construct( $feature_instance = null ) { parent::__construct( 'OpenAI ChatGPT', 'ChatGPT', diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 32f23bd09..8f8eba38e 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -35,7 +35,7 @@ class DallE extends Provider { * * @param string $service The service this class belongs to. */ - public function __construct( $service ) { + public function __construct( $service = null ) { parent::__construct( 'OpenAI', 'DALLĀ·E', diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 0adc376cf..5fd5f3bfe 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -30,7 +30,7 @@ class Whisper extends Provider { * * @param \Classifai\Features\Feature $feature_instance The feature instance. */ - public function __construct( $feature_instance ) { + public function __construct( $feature_instance = null ) { parent::__construct( 'OpenAI Whisper', 'Whisper', diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index e5621359e..600e79532 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -39,6 +39,15 @@ public function init() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); } + public static function get_service_providers() { + return apply_filters( + 'classifai_language_processing_service_providers', + [ + 'Classifai\Providers\Azure\ComputerVision', + ] + ); + } + /** * Enqueue the script for the media modal. * diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index fdfa514b9..21eaf2a00 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -72,7 +72,7 @@ public function init() { if ( ! empty( $this->providers ) && is_array( $this->providers ) ) { foreach ( $this->providers as $provider ) { if ( class_exists( $provider ) ) { - $this->provider_classes[] = new $provider( $this->menu_slug ); + $this->provider_classes[] = new $provider(); } } } diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index b8ad0215c..172adc747 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -42,7 +42,8 @@ public function __construct( $services = [] ) { * Register the actions required for the settings page. */ public function register() { - add_filter( 'language_processing_features', [ $this, 'language_processing_features' ] ); + add_filter( 'language_processing_features', [ $this, 'register_language_processing_features' ] ); + add_filter( 'image_processing_features', [ $this, 'register_image_processing_features' ] ); foreach ( $this->services as $key => $service ) { if ( class_exists( $service ) ) { @@ -59,7 +60,7 @@ public function register() { add_filter( 'classifai_debug_information', [ $this, 'add_debug_information' ], 1 ); } - public function language_processing_features() { + public function register_language_processing_features() { return [ '\Classifai\Features\TitleGeneration', '\Classifai\Features\ExcerptGeneration', @@ -69,6 +70,15 @@ public function language_processing_features() { ]; } + public function register_image_processing_features() { + return [ + '\Classifai\Features\DescriptiveTextGenerator', + '\Classifai\Features\ImageTagsGenerator', + '\Classifai\Features\SmartCropping', + '\Classifai\Features\ImageToText', + ]; + } + /** * Get general ClassifAI settings * From 0f21a59b02cc29bdd58be49b4359232149caf102 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 19 Nov 2023 17:37:25 +0530 Subject: [PATCH 029/127] add methods to wrap common functionality --- .../Features/AudioTranscriptsGeneration.php | 28 +++++++++++-------- .../Classifai/Features/ContentResizing.php | 28 +++++++++++-------- .../Features/DescriptiveTextGenerator.php | 28 +++++++++++-------- .../Classifai/Features/ExcerptGeneration.php | 28 +++++++++++-------- includes/Classifai/Features/Feature.php | 24 ++++++++++++++++ .../Classifai/Features/ImageTagsGenerator.php | 28 +++++++++++-------- includes/Classifai/Features/ImageToText.php | 28 +++++++++++-------- includes/Classifai/Features/SmartCropping.php | 28 +++++++++++-------- includes/Classifai/Features/TextToSpeech.php | 28 +++++++++++-------- .../Classifai/Features/TitleGeneration.php | 28 +++++++++++-------- 10 files changed, 177 insertions(+), 99 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 73147513e..c620362bb 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -17,9 +17,16 @@ class AudioTranscriptsGeneration extends Feature { */ const ID = 'feature_audio_transcripts_generation'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = LanguageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -56,6 +63,9 @@ public function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -104,13 +114,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -155,16 +162,13 @@ public function is_feature_enabled() { * @return array */ public function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; - $provider_instance = $this->get_feature_provider_instance( Whisper::ID ); - $provider_settings[ Whisper::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -185,10 +189,12 @@ public function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 43be576ab..3db364eb4 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -16,9 +16,16 @@ class ContentResizing extends Feature { */ const ID = 'feature_content_resizing'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = LanguageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -55,6 +62,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -103,13 +113,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -154,16 +161,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); - $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -184,10 +188,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 04048b314..1a61337f7 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -16,9 +16,16 @@ class DescriptiveTextGenerator extends Feature { */ const ID = 'feature_descriptive_text_generator'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = ImageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -57,6 +64,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -105,13 +115,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -158,16 +165,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => ComputerVision::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); - $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -190,10 +194,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 9d0403116..bfcccae8d 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -16,9 +16,16 @@ class ExcerptGeneration extends Feature { */ const ID = 'feature_excerpt_generation'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = LanguageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -55,6 +62,9 @@ public function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -119,13 +129,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -170,7 +177,7 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, @@ -178,9 +185,6 @@ protected function get_default_settings() { 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); - $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -201,10 +205,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index c016ecf0b..61686e621 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -162,6 +162,30 @@ public function get_settings( $index = false ) { return $settings; } + public function get_provider_default_settings() { + $provider_settings = []; + + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'get_default_provider_settings' ) ) { + $provider_settings[ $provider_id ] = $provider->get_default_provider_settings(); + } + } + + return $provider_settings; + } + + public function render_provider_fields() { + foreach( array_keys( $this->get_providers() ) as $provider_id ) { + $provider = $this->get_feature_provider_instance( $provider_id ); + + if ( method_exists( $provider, 'render_provider_fields' ) ) { + $provider->render_provider_fields(); + } + } + } + /** * Merges the data settings with the default settings recursively, * diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 3c15adf80..f57ab4c73 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -16,9 +16,16 @@ class ImageTagsGenerator extends Feature { */ const ID = 'feature_image_tags_generator'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = ImageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -57,6 +64,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -105,13 +115,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -158,16 +165,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => ComputerVision::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); - $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -190,10 +194,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/ImageToText.php b/includes/Classifai/Features/ImageToText.php index aabce1093..ae09d6cd9 100644 --- a/includes/Classifai/Features/ImageToText.php +++ b/includes/Classifai/Features/ImageToText.php @@ -16,9 +16,16 @@ class ImageToText extends Feature { */ const ID = 'feature_image_to_text_cropping'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = ImageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -57,6 +64,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -105,13 +115,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -158,16 +165,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => ComputerVision::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); - $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -190,10 +194,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/SmartCropping.php b/includes/Classifai/Features/SmartCropping.php index 916e8a3e0..6618f272c 100644 --- a/includes/Classifai/Features/SmartCropping.php +++ b/includes/Classifai/Features/SmartCropping.php @@ -16,9 +16,16 @@ class SmartCropping extends Feature { */ const ID = 'feature_smart_cropping'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = ImageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -57,6 +64,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -105,13 +115,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -158,16 +165,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => ComputerVision::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ComputerVision::ID ); - $provider_settings[ ComputerVision::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -190,10 +194,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 25746ce3b..1b6010e58 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -18,9 +18,16 @@ class TextToSpeech extends Feature { */ const ID = 'feature_text_to_speech'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = LanguageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -57,6 +64,9 @@ protected function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -126,13 +136,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -187,7 +194,7 @@ protected function get_post_types_select_options() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, @@ -195,9 +202,6 @@ protected function get_default_settings() { 'provider' => Speech::ID, ]; - $provider_instance = $this->get_feature_provider_instance( Speech::ID ); - $provider_settings[ Speech::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -236,6 +240,7 @@ public function get_tts_supported_post_types() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; @@ -250,6 +255,7 @@ public function sanitize_settings( $new_settings ) { } } + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 2cc2ace41..e8a9509a4 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -16,9 +16,16 @@ class TitleGeneration extends Feature { */ const ID = 'feature_title_generation'; + /** + * Constructor. + */ public function __construct() { parent::__construct(); + /** + * Every feature must set the `provider_instances` variable with the list of provider instances + * that are registered to a service. + */ $service_providers = LanguageProcessing::get_service_providers(); $this->provider_instances = $this->get_provider_instances( $service_providers ); } @@ -55,6 +62,9 @@ public function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); + /* These are the feature-level fields that are + * independent of the provider. + */ add_settings_section( $this->get_option_name() . '_section', esc_html__( 'Feature settings', 'classifai' ), @@ -103,13 +113,10 @@ public function setup_fields_sections() { ] ); - foreach( array_keys( $this->get_providers() ) as $provider_id ) { - $provider = $this->get_feature_provider_instance( $provider_id ); - - if ( method_exists( $provider, 'render_provider_fields' ) ) { - $provider->render_provider_fields(); - } - } + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); } /** @@ -154,16 +161,13 @@ public function is_feature_enabled() { * @return array */ protected function get_default_settings() { - $provider_settings = []; + $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'status' => '0', 'roles' => $this->roles, 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; - $provider_instance = $this->get_feature_provider_instance( ChatGPT::ID ); - $provider_settings[ ChatGPT::ID ] = $provider_instance->get_default_provider_settings(); - return apply_filters( 'classifai_' . static::ID . '_get_default_settings', @@ -184,10 +188,12 @@ protected function get_default_settings() { public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); + // Sanitization of the feature-level settings. $new_settings['status'] = $new_settings['status'] ?? $settings['status']; $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); $new_settings = $provider_instance->sanitize_settings( $new_settings ); From 33ef8cd734ad2fa07b69231f43ce0111f6311a5a Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 20 Nov 2023 23:32:30 +0530 Subject: [PATCH 030/127] working image processing features, except Dall.E --- .../Features/DescriptiveTextGenerator.php | 2 +- ...mageToText.php => ImageTextExtraction.php} | 4 +- .../Classifai/Features/PDFTextExtraction.php | 212 ++++++++ .../Providers/Azure/ComputerVision.php | 500 +++++++++--------- includes/Classifai/Providers/Azure/OCR.php | 2 +- includes/Classifai/Providers/Azure/Read.php | 2 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 17 +- .../Classifai/Providers/OpenAI/Whisper.php | 3 +- .../Classifai/Services/ImageProcessing.php | 95 ---- .../Classifai/Services/ServicesManager.php | 9 +- 10 files changed, 493 insertions(+), 353 deletions(-) rename includes/Classifai/Features/{ImageToText.php => ImageTextExtraction.php} (98%) create mode 100644 includes/Classifai/Features/PDFTextExtraction.php diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 1a61337f7..8f35b5080 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -84,7 +84,7 @@ public function setup_fields_sections() { 'label_for' => 'status', 'input_type' => 'checkbox', 'default_value' => $settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + 'description' => __( 'Enable this to generate descriptive text for images.', 'classifai' ), ] ); diff --git a/includes/Classifai/Features/ImageToText.php b/includes/Classifai/Features/ImageTextExtraction.php similarity index 98% rename from includes/Classifai/Features/ImageToText.php rename to includes/Classifai/Features/ImageTextExtraction.php index ae09d6cd9..ae4b793ff 100644 --- a/includes/Classifai/Features/ImageToText.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -8,7 +8,7 @@ /** * Class TitleGeneration */ -class ImageToText extends Feature { +class ImageTextExtraction extends Feature { /** * ID of the current feature. * @@ -38,7 +38,7 @@ public function __construct() { public function get_label() { return apply_filters( 'classifai_' . static::ID . '_label', - __( 'Text extraction from images', 'classifai' ) + __( 'Image Text Extraction', 'classifai' ) ); } diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php new file mode 100644 index 000000000..b0a293764 --- /dev/null +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -0,0 +1,212 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'PDF Text Extraction', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @internal + * + * @return array + */ + protected function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + /* These are the feature-level fields that are + * independent of the provider. + */ + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable text extraction from images', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'Extract visible text from multi-pages PDF documents. Store the result as the attachment description.', 'classifai' ), + ] + ); + + add_settings_field( + 'roles', + esc_html__( 'Allowed roles', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'roles', + 'options' => $this->roles, + 'default_values' => $settings['roles'], + 'description' => __( 'Choose which roles are allowed to generate image tags.', 'classifai' ), + ] + ); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + /* The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); + } + + /** + * Returns true if the feature meets all the criteria to be enabled. + * + * @return boolean + */ + public function is_feature_enabled() { + $access = false; + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ComputerVision::ID; + $user_roles = wp_get_current_user()->roles ?? []; + $feature_roles = $settings['roles'] ?? []; + + $user_access = ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ); + $provider_access = $settings[ $provider_id ]['authenticated'] ?? false; + $feature_status = isset( $settings['status'] ) && '1' === $settings['status']; + $access = $user_access && $provider_access && $feature_status; + + /** + * Filter to override permission to the generate title feature. + * + * @since 2.3.0 + * @hook classifai_openai_chatgpt_{$feature} + * + * @param {bool} $access Current access value. + * @param {array} $settings Current feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $access, $settings ); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @internal + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = $this->get_provider_default_settings(); + $feature_settings = [ + 'status' => '0', + 'roles' => $this->roles, + 'provider' => ComputerVision::ID, + ]; + + return + apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @internal + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + // Sanitization of the feature-level settings. + $new_settings['status'] = $new_settings['status'] ?? $settings['status']; + $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + + // Sanitization of the provider-level settings. + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } +} diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 6d10d27a5..c9d864133 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -7,9 +7,14 @@ use Classifai\Features\DescriptiveTextGenerator; use Classifai\Features\ImageTagsGenerator; +use Classifai\Features\ImageTextExtraction; +use Classifai\Features\PDFTextExtraction; +use Classifai\Features\SmartCropping; use Classifai\Providers\Provider; use DOMDocument; use WP_Error; +use WP_REST_Server; +use WP_REST_Request; use function Classifai\computer_vision_max_filesize; use function Classifai\get_largest_acceptable_image_url; @@ -53,6 +58,8 @@ public function __construct( $feature_instance = null ) { ); $this->feature_instance = $feature_instance; + + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } /** @@ -62,11 +69,14 @@ public function reset_settings() { update_option( $this->get_option_name(), $this->get_default_settings() ); } + /** + * Renders the provider fields. + */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'endpoint_url', + static::ID . '_endpoint_url', esc_html__( 'Endpoint URL', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -82,7 +92,7 @@ public function render_provider_fields() { ); add_settings_field( - 'api_key', + static::ID . '_api_key', esc_html__( 'API Key', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -109,6 +119,9 @@ public function render_provider_fields() { do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } + /** + * Renders fields for the Descriptive Text Feature. + */ public function add_descriptive_text_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -119,7 +132,7 @@ public function add_descriptive_text_generation_fields() { ); add_settings_field( - 'descriptive_text_fields', + static::ID . '_descriptive_text_fields', esc_html__( 'Generate descriptive text', 'classifai' ), [ $this->feature_instance, 'render_checkbox_group' ], $this->feature_instance->get_option_name(), @@ -135,7 +148,7 @@ public function add_descriptive_text_generation_fields() { ); add_settings_field( - 'descriptive_confidence_threshold', + static::ID . '_descriptive_confidence_threshold', esc_html__( 'Descriptive text confidence threshold', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -153,6 +166,9 @@ public function add_descriptive_text_generation_fields() { ); } + /** + * Renders fields for the Image Tags Feature. + */ public function add_image_tags_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -164,7 +180,7 @@ public function add_image_tags_generation_fields() { } add_settings_field( - 'tag_taxonomy', + static::ID . '_tag_taxonomy', esc_html__( 'Tag taxonomy', 'classifai' ), [ $this->feature_instance, 'render_select' ], $this->feature_instance->get_option_name(), @@ -179,7 +195,7 @@ public function add_image_tags_generation_fields() { ); add_settings_field( - 'tag_confidence_threshold', + static::ID . '_tag_confidence_threshold', esc_html__( 'Tag confidence threshold', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -197,6 +213,12 @@ public function add_image_tags_generation_fields() { ); } + /** + * Returns the default settings for the current provider + * and the settings needed for the feature which uses this provider. + * + * @return array + */ public function get_default_provider_settings() { $common_settings = [ 'endpoint_url' => '', @@ -246,22 +268,28 @@ public function get_default_provider_settings() { * @return array|mixed */ public function sanitize_settings( $new_settings ) { - $settings = $this->feature_instance->get_settings( static::ID ); + $settings = $this->feature_instance->get_settings(); if ( ! empty( $new_settings[ static::ID ]['endpoint_url'] ) && ! empty( $new_settings[ static::ID ]['api_key'] ) ) { $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; - $new_settings[ static::ID ]['endpoint_url'] = esc_url_raw( $settings['url'] ); - $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $settings['api_key'] ); + $new_settings[ static::ID ]['endpoint_url'] = esc_url_raw( $new_settings[ static::ID ]['endpoint_url'] ?? $settings[ static::ID ]['endpoint_url'] ); + $new_settings[ static::ID ]['api_key'] = sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ); - $auth_check = $this->authenticate_credentials( - $new_settings[ static::ID ]['endpoint_url'], - $new_settings[ static::ID ]['api_key'] - ); + $is_authenticated = $new_settings[ static::ID ]['authenticated']; + $is_endpoint_same = $new_settings[ static::ID ]['endpoint_url'] === $settings[ static::ID ]['endpoint_url']; + $is_api_key_same = $new_settings[ static::ID ]['api_key'] === $settings[ static::ID ]['api_key']; - if ( is_wp_error( $auth_check ) ) { - $new_settings[ static::ID ]['authenticated'] = false; - } else { - $new_settings[ static::ID ]['authenticated'] = true; + if ( ! ( $is_authenticated && $is_endpoint_same && $is_api_key_same ) ) { + $auth_check = $this->authenticate_credentials( + $new_settings[ static::ID ]['endpoint_url'], + $new_settings[ static::ID ]['api_key'] + ); + + if ( is_wp_error( $auth_check ) ) { + $new_settings[ static::ID ]['authenticated'] = false; + } else { + $new_settings[ static::ID ]['authenticated'] = true; + } } } else { $new_settings[ static::ID ]['endpoint_url'] = $settings[ static::ID ]['endpoint_url']; @@ -377,25 +405,7 @@ public function sanitize_settings( $new_settings ) { * * @return array */ - public function get_default_settings() { - return [ - 'valid' => false, - 'url' => '', - 'api_key' => '', - 'enable_image_captions' => array( - 'alt' => 0, - 'caption' => 0, - 'description' => 0, - ), - 'enable_image_tagging' => true, - 'enable_smart_cropping' => false, - 'enable_ocr' => false, - 'enable_read_pdf' => false, - 'caption_threshold' => 75, - 'tag_threshold' => 70, - 'image_tag_taxonomy' => 'classifai-image-tags', - ]; - } + public function get_default_settings() {} /** * Returns an array of fields enabled to be set to store image captions. @@ -403,22 +413,23 @@ public function get_default_settings() { * @return array */ public function get_alt_text_settings() { - $settings = $this->get_settings(); + $alt_generator = new DescriptiveTextGenerator(); + $settings = $alt_generator->get_settings( static::ID ); $enabled_fields = array(); - if ( ! isset( $settings['enable_image_captions'] ) ) { + if ( ! isset( $settings['descriptive_text_fields'] ) ) { return array(); } - if ( ! is_array( $settings['enable_image_captions'] ) ) { + if ( ! is_array( $settings['descriptive_text_fields'] ) ) { return array( - 'alt' => 'no' === $settings['enable_image_captions'] ? 0 : 'alt', + 'alt' => 'no' === $settings['descriptive_text_fields']['caption'] ? 0 : 'alt', 'caption' => 0, 'description' => 0, ); } - foreach ( $settings['enable_image_captions'] as $key => $value ) { + foreach ( $settings['descriptive_text_fields'] as $key => $value ) { if ( 0 !== $value && '0' !== $value ) { $enabled_fields[] = $key; } @@ -438,15 +449,16 @@ public function register() { add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_alt_tags' ], 8, 2 ); add_filter( 'posts_clauses', [ $this, 'filter_attachment_query_keywords' ], 10, 1 ); - $settings = $this->get_settings(); + $image_to_text = new ImageTextExtraction(); + $pdf_to_text = new PDFTextExtraction(); - if ( isset( $settings['enable_ocr'] ) && '1' === $settings['enable_ocr'] ) { + if ( $this->feature_instance instanceof ImageTextExtraction && $image_to_text->is_feature_enabled() ) { add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); add_filter( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); } - if ( isset( $settings['enable_read_pdf'] ) && '1' === $settings['enable_read_pdf'] ) { + if ( $this->feature_instance instanceof PDFTextExtraction && $pdf_to_text->is_feature_enabled() ) { add_action( 'add_attachment', [ $this, 'read_pdf' ] ); add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); add_action( 'wp_ajax_classifai_get_read_status', [ $this, 'get_read_status_ajax' ] ); @@ -656,9 +668,14 @@ public function attachment_pdf_data_meta_box( $post ) { * @param \WP_post $post Post object for the attachment being viewed. */ public function add_rescan_button_to_media_modal( $form_fields, $post ) { - $settings = $this->get_settings(); - - if ( attachment_is_pdf( $post ) && is_array( $settings ) && isset( $settings['enable_read_pdf'] ) && '1' === $settings['enable_read_pdf'] ) { + $pdf_to_text = new PDFTextExtraction(); + $alt_generator = new DescriptiveTextGenerator(); + $image_to_text = new ImageTextExtraction(); + $smart_crop = new SmartCropping(); + $image_tagging = new ImageTagsGenerator(); + + // PDF to text. + if ( $pdf_to_text->is_feature_enabled() && attachment_is_pdf( $post ) ) { $read_text = empty( get_the_content( null, false, $post ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); $status = get_post_meta( $post->ID, '_classifai_azure_read_status', true ); if ( ! empty( $status['status'] ) && 'running' === $status['status'] ) { @@ -675,7 +692,8 @@ public function add_rescan_button_to_media_modal( $form_fields, $post ) { ]; } - if ( wp_attachment_is_image( $post ) ) { + // Description generator. + if ( $alt_generator->is_feature_enabled() && wp_attachment_is_image( $post ) ) { $alt_tags_text = empty( get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); $image_tags_text = empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); $ocr_text = empty( get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); @@ -690,32 +708,36 @@ public function add_rescan_button_to_media_modal( $form_fields, $post ) { ]; } - if ( is_array( $settings ) && isset( $settings['enable_image_tagging'] ) && '1' === $settings['enable_image_tagging'] ) { - $form_fields['rescan_captions'] = [ - 'label' => __( 'Image tags', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } + } - if ( is_array( $settings ) && isset( $settings['enable_smart_cropping'] ) && '1' === $settings['enable_smart_cropping'] ) { - $form_fields['rescan_smart_crop'] = [ - 'label' => __( 'Smart thumbnail', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } + // Image tagging. + if ( $image_tagging->is_feature_enabled() && wp_attachment_is_image( $post ) ) { + $form_fields['rescan_captions'] = [ + 'label' => __( 'Image tags', 'classifai' ), + 'input' => 'html', + 'html' => '', + 'show_in_edit' => false, + ]; + } - if ( is_array( $settings ) && isset( $settings['enable_ocr'] ) && '1' === $settings['enable_ocr'] ) { - $form_fields['rescan_ocr'] = [ - 'label' => __( 'Scan image for text', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } + // Smart crop. + if ( $smart_crop->is_feature_enabled() && wp_attachment_is_image( $post ) ) { + $form_fields['rescan_smart_crop'] = [ + 'label' => __( 'Smart thumbnail', 'classifai' ), + 'input' => 'html', + 'html' => '', + 'show_in_edit' => false, + ]; + } + + // Image to text. + if ( $image_to_text->is_feature_enabled() && wp_attachment_is_image( $post ) ) { + $form_fields['rescan_ocr'] = [ + 'label' => __( 'Scan image for text', 'classifai' ), + 'input' => 'html', + 'html' => '', + 'show_in_edit' => false, + ]; } return $form_fields; @@ -851,12 +873,14 @@ public function maybe_rescan_image( $attachment_id ) { */ public function smart_crop_image( $metadata, $attachment_id ) { $settings = $this->get_settings(); + $feature = new SmartCropping(); + $settings = $feature->get_settings( static::ID ); if ( ! is_array( $metadata ) || ! is_array( $settings ) ) { return $metadata; } - $should_smart_crop = isset( $settings['enable_smart_cropping'] ) && '1' === $settings['enable_smart_cropping']; + $should_smart_crop = $feature->is_feature_enabled(); /** * Filters whether to apply smart cropping to the current image. @@ -884,7 +908,7 @@ public function smart_crop_image( $metadata, $attachment_id ) { return $metadata; } - $smart_cropping = new SmartCropping( $settings ); + $smart_cropping = new \Classifai\Providers\Azure\SmartCropping( $settings ); return $smart_cropping->generate_attachment_metadata( $metadata, intval( $attachment_id ) ); } @@ -960,13 +984,14 @@ public function generate_image_alt_tags( $metadata, $attachment_id ) { * @return array Filtered attachment metadata. */ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bool $force = false, $scan = false ) { - $settings = $this->get_settings(); + $feature = new ImageTextExtraction(); + $settings = $feature->get_settings( static::ID ); if ( ! is_array( $metadata ) || ! is_array( $settings ) ) { return $metadata; } - $should_ocr_scan = isset( $settings['enable_ocr'] ) && '1' === $settings['enable_ocr']; + $should_ocr_scan = $feature->is_feature_enabled(); /** * Filters whether to run OCR scanning on the current image. @@ -1003,11 +1028,12 @@ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bo * @return bool|object|WP_Error */ protected function scan_image( $image_url, array $routes = [] ) { - $settings = $this->get_settings(); + $feature = new DescriptiveTextGenerator(); + $settings = $feature->get_settings( static::ID ); // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with Azure.', 'classifai' ) ); + if ( $feature->is_feature_enabled() ) { + return new WP_Error( 'feature_disabled', esc_html__( 'Feature not enabled.', 'classifai' ) ); } $url = $this->prep_api_url( $routes ); @@ -1054,18 +1080,20 @@ protected function scan_image( $image_url, array $routes = [] ) { * @return string */ protected function prep_api_url( array $routes = [] ) { - $settings = $this->get_settings(); + $alt_generator = new DescriptiveTextGenerator(); + $image_tagging = new ImageTagsGenerator(); + $settings = $alt_generator->get_settings( static::ID ); $api_features = []; if ( in_array( 'alt-tags', $routes, true ) || ! empty( $this->get_alt_text_settings() ) ) { $api_features[] = 'Description'; } - if ( in_array( 'image-tags', $routes, true ) || ( isset( $settings['enable_image_tagging'] ) && 'no' !== $settings['enable_image_tagging'] ) ) { + if ( in_array( 'image-tags', $routes, true ) || $image_tagging->is_feature_enabled() ) { $api_features[] = 'Tags'; } - $endpoint = add_query_arg( 'visualFeatures', implode( ',', $api_features ), trailingslashit( $settings['url'] ) . $this->analyze_url ); + $endpoint = add_query_arg( 'visualFeatures', implode( ',', $api_features ), trailingslashit( $settings['endpoint_url'] ) . $this->analyze_url ); return $endpoint; } @@ -1097,7 +1125,9 @@ protected function generate_alt_tags( $captions, $attachment_id ) { // If $captions isn't an array, don't save them. if ( is_array( $captions ) && ! empty( $captions ) ) { - $threshold = $this->get_settings( 'caption_threshold' ); + $alt_generator = new DescriptiveTextGenerator(); + $settings = $alt_generator->get_settings( static::ID ); + $threshold = $settings['descriptive_confidence_threshold']; // Save the first caption as the alt text if it passes the threshold. if ( $captions[0]->confidence * 100 > $threshold ) { @@ -1150,13 +1180,9 @@ protected function generate_alt_tags( $captions, $attachment_id ) { * @param int $attachment_id Attachment ID. */ public function read_pdf( $attachment_id ) { - $settings = $this->get_settings(); - - if ( ! is_array( $settings ) ) { - return new WP_Error( 'invalid_settings', 'Can not retrieve the plugin settings.' ); - } - - $should_read_pdf = isset( $settings['enable_read_pdf'] ) && '1' === $settings['enable_read_pdf']; + $pdf_to_text = new PDFTextExtraction(); + $settings = $pdf_to_text->get_settings( static::ID ); + $should_read_pdf = $pdf_to_text->is_feature_enabled(); if ( ! $should_read_pdf ) { return false; @@ -1200,10 +1226,11 @@ public function do_read_cron( $operation_url, $attachment_id ) { */ protected function generate_image_tags( $tags, $attachment_id ) { $rtn = ''; - $settings = $this->get_settings(); + $feature = new ImageTagsGenerator(); + $settings = $feature->get_settings( static::ID ); // Don't save tags if the setting is disabled. - if ( ! is_array( $settings ) || ! isset( $settings['enable_image_tagging'] ) || '1' !== $settings['enable_image_tagging'] ) { + if ( ! $feature->is_feature_enabled() ) { return new WP_Error( 'invalid_settings', esc_html__( 'Image tagging is disabled.', 'classifai' ) ); } @@ -1221,8 +1248,8 @@ protected function generate_image_tags( $tags, $attachment_id ) { // If $tags isn't an array, don't save them. if ( is_array( $tags ) && ! empty( $tags ) ) { - $threshold = $this->get_settings( 'tag_threshold' ); - $taxonomy = $this->get_settings( 'image_tag_taxonomy' ); + $threshold = $settings['tag_confidence_threshold']; + $taxonomy = $settings['tag_taxonomy']; $custom_tags = []; // Save the first caption as the alt text if it passes the threshold. @@ -1259,153 +1286,7 @@ protected function generate_image_tags( $tags, $attachment_id ) { /** * Setup fields */ - public function setup_fields_sections() { - add_settings_section( $this->get_option_name(), $this->provider_service_name, '', $this->get_option_name() ); - $default_settings = $this->get_default_settings(); - add_settings_field( - 'url', - esc_html__( 'Endpoint URL', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'url', - 'input_type' => 'text', - 'default_value' => $default_settings['url'], - 'description' => __( 'Supported protocol and hostname endpoints, e.g., https://REGION.api.cognitive.microsoft.com or https://EXAMPLE.cognitiveservices.azure.com. This can look different based on your setting choices in Azure.', 'classifai' ), - ] - ); - add_settings_field( - 'api-key', - esc_html__( 'API Key', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'api_key', - 'input_type' => 'password', - 'default_value' => $default_settings['api_key'], - ] - ); - add_settings_field( - 'enable-image-captions', - esc_html__( 'Generate descriptive text', 'classifai' ), - [ $this, 'render_auto_caption_fields' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_image_captions', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_image_captions'], - 'description' => __( 'Choose image fields where the generated captions should be applied.', 'classifai' ), - ] - ); - add_settings_field( - 'caption-threshold', - esc_html__( 'Descriptive text confidence threshold', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'caption_threshold', - 'input_type' => 'number', - 'default_value' => $default_settings['caption_threshold'], - 'description' => __( 'Minimum confidence score for automatically added alt text, numeric value from 0-100. Recommended to be set to at least 75.', 'classifai' ), - ] - ); - add_settings_field( - 'enable-image-tagging', - esc_html__( 'Tag images', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_image_tagging', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_image_tagging'], - 'description' => __( 'Image tags will be added automatically.', 'classifai' ), - ] - ); - add_settings_field( - 'image-tag-threshold', - esc_html__( 'Tag confidence threshold', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'tag_threshold', - 'input_type' => 'number', - 'default_value' => $default_settings['tag_threshold'], - 'description' => __( 'Minimum confidence score for automatically added image tags, numeric value from 0-100. Recommended to be set to at least 70.', 'classifai' ), - ] - ); - // What taxonomy should we tag images with? - $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); - $options = []; - foreach ( $attachment_taxonomies as $name => $taxonomy ) { - $options[ $name ] = $taxonomy->label; - } - add_settings_field( - 'image-tag-taxonomy', - esc_html__( 'Tag taxonomy', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'image_tag_taxonomy', - 'options' => $options, - 'default_value' => $default_settings['image_tag_taxonomy'], - ] - ); - add_settings_field( - 'enable-smart-cropping', - esc_html__( 'Enable smart cropping', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_smart_cropping', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_smart_cropping'], - 'description' => __( - 'AI Vision detects and saves the most visually interesting part of your image (i.e., faces, animals, notable text).', - 'classifai' - ), - ] - ); - add_settings_field( - 'enable-ocr', - esc_html__( 'Scan images for text', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_ocr', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_ocr'], - 'description' => __( - 'OCR detects text in images (e.g., handwritten notes) and saves that as post content.', - 'classifai' - ), - ] - ); - add_settings_field( - 'enable-read-pdf', - esc_html__( 'Enable scanning PDF', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_read_pdf', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_read_pdf'], - 'description' => __( - 'Extract visible text from multi-pages PDF documents. Store the result as the attachment description.', - 'classifai' - ), - ] - ); - } + public function setup_fields_sections() {} /** * Authenticates our credentials. @@ -1440,6 +1321,143 @@ protected function authenticate_credentials( $url, $api_key ) { return $rtn; } + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'alt-tags/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'computer_vision_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), + ], + 'route' => [ 'alt-tags' ], + ], + 'permission_callback' => [ $this, 'descriptive_text_generator_permissions_check' ], + ] + ); + + register_rest_route( + 'classifai/v1', + 'image-tags/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'computer_vision_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), + ], + 'route' => [ 'image-tags' ], + ], + 'permission_callback' => [ $this, 'image_tags_generator_permissions_check' ], + ] + ); + + register_rest_route( + 'classifai/v1', + 'smart-crop/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'computer_vision_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate smart crop.', 'classifai' ), + ], + 'route' => [ 'smart-crop' ], + ], + 'permission_callback' => [ $this, 'smart_crop_permissions_check' ], + ] + ); + + register_rest_route( + 'classifai/v1', + 'read-pdf/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'computer_vision_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), + ], + 'route' => [ 'read-pdf' ], + ], + 'permission_callback' => [ $this, 'pdf_read_permissions_check' ], + ] + ); + + register_rest_route( + 'classifai/v1', + 'ocr/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'computer_vision_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate text from.', 'classifai' ), + ], + 'route' => [ 'ocr' ], + ], + 'permission_callback' => [ $this, 'image_text_extractor_permissions_check' ], + ] + ); + } + + public function computer_vision_endpoint_callback( $request ) { + $attachment_id = $request->get_param( 'id' ); + $custom_atts = $request->get_attributes(); + $route_to_call = empty( $custom_atts['args']['route'] ) ? false : strtolower( $custom_atts['args']['route'][0] ); + + // Check to be sure the post both exists and is an attachment. + if ( ! get_post( $attachment_id ) || 'attachment' !== get_post_type( $attachment_id ) ) { + /* translators: %1$s: the attachment ID */ + return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $attachment_id ), [ 'status' => 404 ] ); + } + + // If no args, we can't pass the call into the active provider. + if ( false === $route_to_call ) { + return new WP_Error( 'no_route', esc_html__( 'No route indicated for the provider class to use.', 'classifai' ), [ 'status' => 404 ] ); + } + + // Call the provider endpoint function + return rest_ensure_response( $this->rest_endpoint_callback( $attachment_id, $route_to_call ) ); + } + + public function descriptive_text_generator_permissions_check( $request ) { + return true; + } + + public function image_tags_generator_permissions_check( $request ) { + return true; + } + + public function smart_crop_permissions_check( $request ) { + return true; + } + + public function pdf_read_permissions_check( $request ) { + return true; + } + + public function image_text_extractor_permissions_check( $request ) { + return true; + } + /** * Provides debug information related to the provider. * diff --git a/includes/Classifai/Providers/Azure/OCR.php b/includes/Classifai/Providers/Azure/OCR.php index 2cccfc89a..b121568ba 100644 --- a/includes/Classifai/Providers/Azure/OCR.php +++ b/includes/Classifai/Providers/Azure/OCR.php @@ -95,7 +95,7 @@ public function __construct( array $settings, $scan, bool $force ) { * @return string */ public function get_api_url() { - return sprintf( '%s%s', trailingslashit( $this->settings['url'] ), static::API_PATH ); + return sprintf( '%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH ); } /** diff --git a/includes/Classifai/Providers/Azure/Read.php b/includes/Classifai/Providers/Azure/Read.php index c1a5abb62..5ff006850 100644 --- a/includes/Classifai/Providers/Azure/Read.php +++ b/includes/Classifai/Providers/Azure/Read.php @@ -69,7 +69,7 @@ public function __construct( array $settings, $attachment_id, bool $force = fals * @return string */ public function get_api_url( $path = '' ) { - return sprintf( '%s%s%s', trailingslashit( $this->settings['url'] ), static::API_PATH, $path ); + return sprintf( '%s%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH, $path ); } /** diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index ced19e054..ed7febaa6 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -96,14 +96,13 @@ public function __construct( $feature_instance = null ) { $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - do_action( 'classifai_' . static::ID . '_init', $this ); } public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'api_key', + static::ID . '_api_key', esc_html__( 'API Key', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -138,7 +137,7 @@ private function add_title_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'number_of_titles', + static::ID . '_number_of_titles', esc_html__( 'Number of titles', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -156,7 +155,7 @@ private function add_title_generation_fields() { ); add_settings_field( - 'generate_title_prompt', + static::ID . '_generate_title_prompt', $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), [ $this->feature_instance, 'render_prompt_repeater_field' ], $this->feature_instance->get_option_name(), @@ -176,7 +175,7 @@ private function add_excerpt_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'generate_excerpt_prompt', + static::ID . '_generate_excerpt_prompt', $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), [ $this->feature_instance, 'render_prompt_repeater_field' ], $this->feature_instance->get_option_name(), @@ -196,7 +195,7 @@ private function add_content_resizing_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'number_of_suggestions', + static::ID . '_number_of_suggestions', esc_html__( 'Number of titles', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), @@ -214,7 +213,7 @@ private function add_content_resizing_fields() { ); add_settings_field( - 'condense_text_prompt', + static::ID . '_condense_text_prompt', $args['label'] ?? esc_html__( 'Condense text prompt', 'classifai' ), [ $this->feature_instance, 'render_prompt_repeater_field' ], $this->feature_instance->get_option_name(), @@ -230,7 +229,7 @@ private function add_content_resizing_fields() { ); add_settings_field( - 'expand_text_prompt', + static::ID . '_expand_text_prompt', $args['label'] ?? esc_html__( 'Expand text prompt', 'classifai' ), [ $this->feature_instance, 'render_prompt_repeater_field' ], $this->feature_instance->get_option_name(), @@ -1272,7 +1271,7 @@ public function generate_post_title_permissions_check( WP_REST_Request $request return false; } - $feature = new TitleGeneration(); + $feature = new TitleGeneration(); // Ensure the feature is enabled. Also runs a user check. if ( ! $feature->is_feature_enabled() ) { diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 5fd5f3bfe..f3aae353b 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -49,7 +49,6 @@ public function __construct( $feature_instance = null ) { $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - do_action( 'classifai_' . static::ID . '_init', $this ); } /** @@ -71,7 +70,7 @@ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); add_settings_field( - 'api_key', + static::ID . '_api_key', esc_html__( 'API Key', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index 600e79532..f833d4af7 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -80,101 +80,6 @@ public function enqueue_media_scripts() { * Create endpoints for services */ public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'alt-tags/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'alt-tags' ], - ], - 'permission_callback' => [ $this, 'computer_vision_endpoint_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'image-tags/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'image-tags' ], - ], - 'permission_callback' => [ $this, 'computer_vision_endpoint_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'ocr/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'ocr' ], - ], - 'permission_callback' => [ $this, 'computer_vision_endpoint_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'smart-crop/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'smart-crop' ], - ], - 'permission_callback' => [ $this, 'computer_vision_endpoint_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'read-pdf/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'read-pdf' ], - ], - 'permission_callback' => [ $this, 'computer_vision_endpoint_permissions_check' ], - ] - ); - register_rest_route( 'classifai/v1/openai', 'generate-image', diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index 172adc747..19d486012 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -60,6 +60,9 @@ public function register() { add_filter( 'classifai_debug_information', [ $this, 'add_debug_information' ], 1 ); } + /** + * Registers providers under the Language Processing Service. + */ public function register_language_processing_features() { return [ '\Classifai\Features\TitleGeneration', @@ -70,12 +73,16 @@ public function register_language_processing_features() { ]; } + /** + * Registers providers under the Image Processing Service. + */ public function register_image_processing_features() { return [ '\Classifai\Features\DescriptiveTextGenerator', '\Classifai\Features\ImageTagsGenerator', '\Classifai\Features\SmartCropping', - '\Classifai\Features\ImageToText', + '\Classifai\Features\ImageTextExtraction', + '\Classifai\Features\PDFTextExtraction', ]; } From f6de77f17ae83f0882b4e638abed5e5499d6b4ed Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 20 Nov 2023 23:34:04 +0530 Subject: [PATCH 031/127] >>> settings error, need to revisit and apply to all providers --- .../Providers/Azure/ComputerVision.php | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index c9d864133..46350c683 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -307,97 +307,6 @@ public function sanitize_settings( $new_settings ) { } return $new_settings; - - $new_settings = []; - if ( ! empty( $settings['url'] ) && ! empty( $settings['api_key'] ) ) { - $auth_check = $this->authenticate_credentials( $settings['url'], $settings['api_key'] ); - if ( is_wp_error( $auth_check ) ) { - $settings_errors['classifai-registration-credentials-error'] = $auth_check->get_error_message(); - $new_settings['authenticated'] = false; - } else { - $new_settings['authenticated'] = true; - } - $new_settings['url'] = esc_url_raw( $settings['url'] ); - $new_settings['api_key'] = sanitize_text_field( $settings['api_key'] ); - } else { - $new_settings['valid'] = false; - $new_settings['url'] = ''; - $new_settings['api_key'] = ''; - - $settings_errors['classifai-registration-credentials-empty'] = __( 'Please enter your credentials', 'classifai' ); - } - - $checkbox_settings = [ - 'enable_image_tagging', - 'enable_smart_cropping', - 'enable_ocr', - 'enable_read_pdf', - ]; - - foreach ( $checkbox_settings as $checkbox_setting ) { - - if ( empty( $settings[ $checkbox_setting ] ) || 1 !== (int) $settings[ $checkbox_setting ] ) { - $new_settings[ $checkbox_setting ] = 'no'; - } else { - $new_settings[ $checkbox_setting ] = '1'; - } - } - - if ( isset( $settings['caption_threshold'] ) && is_numeric( $settings['caption_threshold'] ) && (int) $settings['caption_threshold'] >= 0 && (int) $settings['caption_threshold'] <= 100 ) { - $new_settings['caption_threshold'] = absint( $settings['caption_threshold'] ); - } else { - $new_settings['caption_threshold'] = 75; - } - - if ( isset( $settings['tag_threshold'] ) && is_numeric( $settings['tag_threshold'] ) && (int) $settings['tag_threshold'] >= 0 && (int) $settings['tag_threshold'] <= 100 ) { - $new_settings['tag_threshold'] = absint( $settings['tag_threshold'] ); - } else { - $new_settings['tag_threshold'] = 75; - } - - if ( isset( $settings['image_tag_taxonomy'] ) && taxonomy_exists( $settings['image_tag_taxonomy'] ) ) { - $new_settings['image_tag_taxonomy'] = $settings['image_tag_taxonomy']; - } elseif ( taxonomy_exists( 'classifai-image-tags' ) ) { - $new_settings['image_tag_taxonomy'] = 'classifai-image-tags'; - } - - if ( isset( $settings['enable_image_captions'] ) ) { - if ( is_array( $settings['enable_image_captions'] ) ) { - $new_settings['enable_image_captions'] = $settings['enable_image_captions']; - } elseif ( 1 === (int) $settings['enable_image_captions'] ) { - // Handle submission from onboarding wizard. - $new_settings['enable_image_captions'] = array( - 'alt' => 'alt', - 'caption' => 0, - 'description' => 0, - ); - } - } else { - $new_settings['enable_image_captions'] = array( - 'alt' => 0, - 'caption' => 0, - 'description' => 0, - ); - } - - if ( ! empty( $settings_errors ) ) { - - $registered_settings_errors = wp_list_pluck( get_settings_errors( $this->get_option_name() ), 'code' ); - - foreach ( $settings_errors as $code => $message ) { - - if ( ! in_array( $code, $registered_settings_errors, true ) ) { - add_settings_error( - $this->get_option_name(), - $code, - esc_html( $message ), - 'error' - ); - } - } - } - - return $new_settings; } /** From 11c9adeac0a4799d100585790516ff8480302119 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 22 Nov 2023 15:06:54 +0530 Subject: [PATCH 032/127] media edit screen working --- includes/Classifai/Features/Feature.php | 7 + .../Providers/Azure/ComputerVision.php | 282 +++++++++--------- .../Classifai/Services/ImageProcessing.php | 2 +- includes/Classifai/Services/Service.php | 21 +- 4 files changed, 148 insertions(+), 164 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 61686e621..a6cb116c0 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -342,6 +342,13 @@ public function render_input( $args ) { break; case 'checkbox': $attrs = ' value="1"' . checked( '1', $value, false ); + ?> + + feature_instance = $feature_instance; - - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } /** @@ -302,8 +300,8 @@ public function sanitize_settings( $new_settings ) { } if ( $this->feature_instance instanceof ImageTagsGenerator ) { - $new_settings[ static::ID ][ 'tag_confidence_threshold' ] = absint( $new_settings[ static::ID ]['tag_confidence_threshold'] ?? $settings[ static::ID ]['descriptive_confidence_threshold'] ); - $new_settings[ static::ID ][ 'tag_taxonomy' ] = array_map( 'sanitize_text_field', $new_settings[ static::ID ]['tag_taxonomy'] ?? $settings[ static::ID ]['descriptive_text_fields'] ); + $new_settings[ static::ID ][ 'tag_confidence_threshold' ] = absint( $new_settings[ static::ID ]['tag_confidence_threshold'] ?? $settings[ static::ID ]['tag_confidence_threshold'] ); + $new_settings[ static::ID ][ 'tag_taxonomy' ] = $new_settings[ static::ID ]['tag_taxonomy'] ?? $settings[ static::ID ]['tag_taxonomy']; } return $new_settings; @@ -354,24 +352,29 @@ public function register() { add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); - add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); - add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_alt_tags' ], 8, 2 ); add_filter( 'posts_clauses', [ $this, 'filter_attachment_query_keywords' ], 10, 1 ); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); - $image_to_text = new ImageTextExtraction(); - $pdf_to_text = new PDFTextExtraction(); - if ( $this->feature_instance instanceof ImageTextExtraction && $image_to_text->is_feature_enabled() ) { - add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); - add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); - add_filter( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); + if ( ( new SmartCropping() )->is_feature_enabled() ) { + add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); + } + + if ( ( new DescriptiveTextGenerator() )->is_feature_enabled() ) { + add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_alt_tags' ], 8, 2 ); } - if ( $this->feature_instance instanceof PDFTextExtraction && $pdf_to_text->is_feature_enabled() ) { + if ( ( new PDFTextExtraction )->is_feature_enabled() ) { add_action( 'add_attachment', [ $this, 'read_pdf' ] ); add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); add_action( 'wp_ajax_classifai_get_read_status', [ $this, 'get_read_status_ajax' ] ); } + + if ( ( new ImageTextExtraction )->is_feature_enabled() ) { + add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); + add_filter( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); + } } /** @@ -468,8 +471,6 @@ public function add_ocr_aria_describedby( $content ) { * @param \WP_Post $post The post object. */ public function setup_attachment_meta_box( $post ) { - $settings = $this->get_settings(); - if ( wp_attachment_is_image( $post ) ) { add_meta_box( 'attachment_meta_box', @@ -481,7 +482,7 @@ public function setup_attachment_meta_box( $post ) { ); } - if ( attachment_is_pdf( $post ) && is_array( $settings ) && isset( $settings['enable_read_pdf'] ) && '1' === $settings['enable_read_pdf'] ) { + if ( ( new PDFTextExtraction() )->is_feature_enabled() && attachment_is_pdf( $post ) ) { add_meta_box( 'attachment_meta_box', __( 'ClassifAI PDF Processing', 'classifai' ), @@ -499,7 +500,11 @@ public function setup_attachment_meta_box( $post ) { * @param \WP_Post $post The post object. */ public function attachment_data_meta_box( $post ) { - $settings = $this->get_settings(); + $alt_generator = new DescriptiveTextGenerator(); + $image_tagging = new ImageTagsGenerator(); + $image_to_text = new ImageTextExtraction(); + $crop_generator = new SmartCropping(); + $captions = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ? __( 'No descriptive text? Rescan image', 'classifai' ) : __( 'Generate descriptive text', 'classifai' ); $tags = ! empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Rescan image for new tags', 'classifai' ) : __( 'Generate image tags', 'classifai' ); $ocr = get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ? __( 'Rescan for text', 'classifai' ) : __( 'Scan image for text', 'classifai' ); @@ -507,7 +512,7 @@ public function attachment_data_meta_box( $post ) { ?>
- get_alt_text_settings() ) ) : ?> + is_feature_enabled() && ! empty( $this->get_alt_text_settings() ) ) : ?>
- + is_feature_enabled() ) : ?>
- + is_feature_enabled() ) : ?>
- + is_feature_enabled() ) : ?>
+ ' . wp_kses_post( $args['description'] ) . ''; + } + } } diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index f871176cf..be775799f 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -62,7 +62,8 @@ public function get_providers() { public function setup_fields_sections() { $settings = $this->get_settings(); - /* These are the feature-level fields that are + /* + * These are the feature-level fields that are * independent of the provider. */ add_settings_section( @@ -86,19 +87,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to generate titles.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); add_settings_field( 'provider', @@ -113,7 +103,8 @@ public function setup_fields_sections() { ] ); - /* The following renders the fields of all the providers + /* + * The following renders the fields of all the providers * that are registered to the feature. */ $this->render_provider_fields(); @@ -163,19 +154,17 @@ public function is_feature_enabled() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; - return - apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - $feature_settings, - $provider_settings - ) - ); + return apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + parent::get_default_settings(), + $feature_settings, + $provider_settings + ) + ); } /** @@ -189,9 +178,7 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + $new_settings = parent::sanitize_settings( $new_settings ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); From cee08caa8db24053803464ea229934a713d08dec Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 8 Dec 2023 14:18:09 +0530 Subject: [PATCH 043/127] Add access control fields to feature settings. --- .../Features/AudioTranscriptsGeneration.php | 24 ++------ .../Classifai/Features/Classification.php | 22 ++----- .../Classifai/Features/ContentResizing.php | 24 ++------ .../Features/DescriptiveTextGenerator.php | 24 ++------ .../Classifai/Features/ExcerptGeneration.php | 24 ++------ includes/Classifai/Features/Feature.php | 6 +- includes/Classifai/Features/ImageCropping.php | 24 ++------ .../Classifai/Features/ImageGeneration.php | 58 +++++++++---------- .../Classifai/Features/ImageTagsGenerator.php | 24 ++------ .../Features/ImageTextExtraction.php | 24 ++------ .../Classifai/Features/PDFTextExtraction.php | 24 ++------ includes/Classifai/Features/TextToSpeech.php | 25 ++------ 12 files changed, 78 insertions(+), 225 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 064d49230..e16a90580 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -87,19 +87,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to use this feature.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); add_settings_field( 'provider', @@ -164,15 +153,14 @@ public function is_feature_enabled() { public function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => Whisper::ID, + 'provider' => Whisper::ID, ]; return apply_filters( 'classifai_' . static::ID . '_get_default_settings', array_merge( + parent::get_default_settings(), $feature_settings, $provider_settings ) @@ -190,9 +178,7 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + $new_settings = parent::sanitize_settings( $new_settings ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 18d48f41e..6b66985d8 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -88,19 +88,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to use this feature.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); $post_statuses = get_post_statuses_for_language_settings(); @@ -202,8 +191,6 @@ public function is_feature_enabled() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, 'post_statuses' => [], 'post_types' => [], 'provider' => NLU::ID, @@ -213,6 +200,7 @@ protected function get_default_settings() { apply_filters( 'classifai_' . static::ID . '_get_default_settings', array_merge( + parent::get_default_settings(), $feature_settings, $provider_settings ) @@ -230,11 +218,9 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; + $new_settings = parent::sanitize_settings( $new_settings ); $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 145bc2761..35420ad46 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -86,19 +86,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to use this feature.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); add_settings_field( 'provider', @@ -163,15 +152,14 @@ public function is_feature_enabled() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, ]; return apply_filters( 'classifai_' . static::ID . '_get_default_settings', array_merge( + parent::get_default_settings(), $feature_settings, $provider_settings ) @@ -189,9 +177,7 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + $new_settings = parent::sanitize_settings( $new_settings ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 2a1b37a8c..5f13a2ee8 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -88,19 +88,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to generate titles.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); add_settings_field( 'provider', @@ -167,15 +156,14 @@ public function is_feature_enabled() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, - 'provider' => ComputerVision::ID, + 'provider' => ComputerVision::ID, ]; return apply_filters( 'classifai_' . static::ID . '_get_default_settings', array_merge( + parent::get_default_settings(), $feature_settings, $provider_settings ) @@ -195,9 +183,7 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + $new_settings = parent::sanitize_settings( $new_settings ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 6c5d01afe..1de3a0400 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -86,19 +86,8 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'roles', - esc_html__( 'Allowed roles', 'classifai' ), - [ $this, 'render_checkbox_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'roles', - 'options' => $this->roles, - 'default_values' => $settings['roles'], - 'description' => __( 'Choose which roles are allowed to generate excerpts.', 'classifai' ), - ] - ); + // Add user/role-based access fields. + $this->add_access_control_fields(); $post_types = \Classifai\get_post_types_for_language_settings(); $post_type_options = array(); @@ -202,8 +191,6 @@ public function is_feature_enabled() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'status' => '0', - 'roles' => $this->roles, 'post_types' => [], 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, @@ -213,6 +200,7 @@ protected function get_default_settings() { apply_filters( 'classifai_' . static::ID . '_get_default_settings', array_merge( + parent::get_default_settings(), $feature_settings, $provider_settings ) @@ -230,10 +218,8 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings['status'] = $new_settings['status'] ?? $settings['status']; - $new_settings['roles'] = isset( $new_settings['roles'] ) ? array_map( 'sanitize_text_field', $new_settings['roles'] ) : $settings['roles']; - $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); - $new_settings['provider'] = isset( $new_settings['provider'] ) ? sanitize_text_field( $new_settings['provider'] ) : $settings['provider']; + $new_settings = parent::sanitize_settings( $new_settings ); + $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); $post_types = \Classifai\get_post_types_for_language_settings(); diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 9a08d89e5..624f2843d 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -51,14 +51,14 @@ public function setup_roles() { * Filter the allowed WordPress roles for ChatGTP * * @since 2.3.0 - * @hook classifai_chatgpt_allowed_roles + * @hook classifai_{feature}_roles * * @param {array} $roles Array of arrays containing role information. * @param {array} $default_settings Default setting values. * * @return {array} Roles array. */ - $this->roles = apply_filters( 'classifai_chatgpt_allowed_roles', $this->roles, $default_settings ); + $this->roles = apply_filters( 'classifai_' . static::ID . '_roles', $this->roles, $default_settings ); } /** @@ -665,7 +665,7 @@ public function render_checkbox_group( array $args = array() ) { // Render checkbox. printf( '

-

    - +

    diff --git a/src/js/gutenberg-plugin.js b/src/js/gutenberg-plugin.js index f5287a328..a9550acff 100644 --- a/src/js/gutenberg-plugin.js +++ b/src/js/gutenberg-plugin.js @@ -281,7 +281,7 @@ const ClassifAIGenerateTagsButton = () => { { __( 'Save', 'classifai' ) }
    - + ); diff --git a/src/js/gutenberg-plugins/content-resizing-plugin.js b/src/js/gutenberg-plugins/content-resizing-plugin.js index e99c95e60..a86012fe2 100644 --- a/src/js/gutenberg-plugins/content-resizing-plugin.js +++ b/src/js/gutenberg-plugins/content-resizing-plugin.js @@ -317,7 +317,7 @@ const ContentResizingPlugin = () => {
    - + ); diff --git a/src/js/openai/classic-editor-excerpt-generator.js b/src/js/openai/classic-editor-excerpt-generator.js index 86e50d76c..e093271d8 100644 --- a/src/js/openai/classic-editor-excerpt-generator.js +++ b/src/js/openai/classic-editor-excerpt-generator.js @@ -43,7 +43,7 @@ const classifaiExcerptData = window.classifaiGenerateExcerpt || {}; // Append disable feature link. if ( ClassifAI?.opt_out_enabled_features?.includes( - 'excerpt_generation' + 'feature_excerpt_generation' ) ) { $( '', { diff --git a/src/js/post-excerpt/panel.js b/src/js/post-excerpt/panel.js index e3d4ba1fe..61b38c6f4 100644 --- a/src/js/post-excerpt/panel.js +++ b/src/js/post-excerpt/panel.js @@ -106,7 +106,7 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { { error } ) } - +
    ); } diff --git a/src/scss/admin.scss b/src/scss/admin.scss index 7a87b94f2..ac6ec7919 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -276,6 +276,10 @@ input.classifai-button { outline: 2px solid transparent; } + .components-form-token-field__suggestion { + box-sizing: border-box; + } + .components-form-token-field__suggestion.is-selected { background: var(--classifai-highlight-color); } From e210c6c481fc74618ff373dd6a8b123ff81f607b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 8 Dec 2023 16:42:41 +0530 Subject: [PATCH 048/127] Remove features from providers. --- includes/Classifai/Providers/Azure/ComputerVision.php | 9 --------- includes/Classifai/Providers/Azure/Personalizer.php | 5 ----- includes/Classifai/Providers/Azure/Speech.php | 5 ----- includes/Classifai/Providers/OpenAI/ChatGPT.php | 7 ------- includes/Classifai/Providers/OpenAI/DallE.php | 5 ----- includes/Classifai/Providers/OpenAI/Embeddings.php | 5 ----- includes/Classifai/Providers/OpenAI/Whisper.php | 5 ----- includes/Classifai/Providers/Watson/NLU.php | 5 ----- 8 files changed, 46 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index f136dab0d..cdfb76135 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -44,15 +44,6 @@ public function __construct( $feature_instance = null ) { 'computer_vision' ); - // Features provided by this provider. - $this->features = array( - 'image_captions' => __( 'Generate descriptive text', 'classifai' ), - 'image_tagging' => __( 'Generate tags', 'classifai' ), - 'smart_cropping' => __( 'Smart cropping', 'classifai' ), - 'ocr' => __( 'Scan images for text', 'classifai' ), - 'read_pdf' => __( 'Scan PDF', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'Microsoft Azure AI Vision', 'classifai' ), diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index ba2f34fd1..1bdf97020 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -42,11 +42,6 @@ public function __construct( $service = null ) { $service ); - // Features provided by this provider. - $this->features = array( - 'recommended_content' => __( 'Recommended content block', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'Microsoft Azure AI Personalizer', 'classifai' ), diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 54de6c700..bbae1b20f 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -79,11 +79,6 @@ public function __construct( $feature_instance = null ) { 'azure_text_to_speech' ); - // Features provided by this provider. - $this->features = array( - 'text_to_speech' => __( 'Text to speech', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'Microsoft Azure Text to Speech', 'classifai' ), diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 6029eda8a..927c65d10 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -82,13 +82,6 @@ public function __construct( $feature_instance = null ) { 'openai_chatgpt' ); - // Features provided by this provider. - $this->features = array( - 'title_generation' => __( 'Generate titles', 'classifai' ), - 'excerpt_generation' => __( 'Generate excerpts', 'classifai' ), - 'resize_content' => __( 'Resize content', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'OpenAI ChatGPT', 'classifai' ), diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index af32867a5..feb333907 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -47,11 +47,6 @@ public function __construct( $feature_instance = null ) { 'openai_dalle' ); - // Features provided by this provider. - $this->features = array( - 'image_generation' => __( 'Generate images', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'OpenAI DALLĀ·E', 'classifai' ), diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 84b22f5e7..f5cbd0e8b 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -53,11 +53,6 @@ public function __construct( $service ) { $service ); - // Features provided by this provider. - $this->features = array( - 'classification' => __( 'Content classification', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'OpenAI Embeddings', 'classifai' ), diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index ab076cb04..e40fb2e9b 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -37,11 +37,6 @@ public function __construct( $feature_instance = null ) { 'openai_whisper' ); - // Features provided by this provider. - $this->features = array( - 'speech_to_text' => __( 'Generate transcripts', 'classifai' ), - ); - // Set the onboarding options. $this->onboarding_options = array( 'title' => __( 'OpenAI Whisper', 'classifai' ), diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 6cef7a075..1936706b2 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -51,11 +51,6 @@ public function __construct( $feature = null ) { 'watson_nlu' ); - // Features provided by this provider. - $this->features = array( - 'content_classification' => __( 'Classify content', 'classifai' ), - ); - $this->nlu_features = [ 'category' => [ 'feature' => __( 'Category', 'classifai' ), From 680945b56e51b0da381954c5c701fcc4c485c5df Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 8 Dec 2023 16:49:44 +0530 Subject: [PATCH 049/127] Remove unwanted function. --- includes/Classifai/Helpers.php | 23 ------------------- .../Providers/Azure/ComputerVisionTest.php | 9 -------- 2 files changed, 32 deletions(-) diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index b7321d816..a5469a525 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -812,29 +812,6 @@ function check_term_permissions( string $tax = '' ) { return true; } -/** - * Get the default settings for a feature. - * - * @since 2.4.0 - * - * @param string $feature Feature key. - * @return array - */ -function get_feature_default_settings( string $feature ) { - if ( ! function_exists( 'get_editable_roles' ) ) { - require_once ABSPATH . 'wp-admin/includes/user.php'; - } - $editable_roles = get_editable_roles() ?? []; - - return array( - $feature . '_role_based_access' => 1, - $feature . '_roles' => array_keys( $editable_roles ), - $feature . '_user_based_access' => 'no', - $feature . '_user_based_opt_out' => 'no', - $feature . '_users' => array(), - ); -} - /** * Renders a link to disable a specific feature. * diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index fd3fdb460..f8ff6ca9a 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -8,8 +8,6 @@ use \WP_UnitTestCase; use Classifai\Providers\Azure\ComputerVision; -use function Classifai\get_feature_default_settings; - /** * Class ComputerVisionTest * @package Classifai\Tests\Providers\Azure; @@ -89,13 +87,6 @@ public function test_no_computer_vision_option_set() { delete_option( 'classifai_computer_vision' ); $defaults = []; - $features = $this->get_computer_vision()->get_features() ?? []; - foreach ( $features as $feature => $title ) { - $defaults = array_merge( - $defaults, - get_feature_default_settings( $feature ) - ); - } $expected = array_merge( $defaults, From 73ff71f6942ef224c156204b7504d5a08ac9b509 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 8 Dec 2023 17:12:11 +0530 Subject: [PATCH 050/127] Fix front-end permission issue for tts. --- includes/Classifai/Providers/Azure/Speech.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index bbae1b20f..980e8fbf6 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -138,7 +138,9 @@ public function register() { foreach ( $tts->get_tts_supported_post_types() as $post_type ) { add_action( 'rest_insert_' . $post_type, [ $this, 'rest_handle_audio' ], 10, 2 ); } + } + if ( $tts->is_enabled() ) { add_filter( 'the_content', [ $this, 'render_post_audio_controls' ] ); } } From 21b4d9ba34cc7d11c70204cabfa0b4d7bb90ac29 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 14 Dec 2023 12:18:38 +0530 Subject: [PATCH 051/127] Add permission check in rest api. --- .../Providers/Azure/ComputerVision.php | 110 +++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index cdfb76135..17608a0fa 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -20,6 +20,7 @@ use function Classifai\get_largest_acceptable_image_url; use function Classifai\get_modified_image_source_url; use function Classifai\attachment_is_pdf; +use function Classifai\check_term_permissions; use function Classifai\get_asset_info; use function Classifai\clean_input; @@ -471,7 +472,15 @@ public function add_ocr_aria_describedby( $content ) { * @param \WP_Post $post The post object. */ public function setup_attachment_meta_box( $post ) { - if ( wp_attachment_is_image( $post ) ) { + if ( + wp_attachment_is_image( $post ) && + ( + ( new DescriptiveTextGenerator() )->is_feature_enabled() || + ( new ImageTagsGenerator() )->is_feature_enabled() || + ( new ImageTextExtraction() )->is_feature_enabled() || + ( new ImageCropping() )->is_feature_enabled() + ) + ) { add_meta_box( 'attachment_meta_box', __( 'ClassifAI Image Processing', 'classifai' ), @@ -1387,29 +1396,126 @@ public function rest_endpoint_callback( $post_id, $route_to_call = [], $args = [ $feature = new ImageTagsGenerator(); return $feature->run( $post_id ); - case 'ocr': + case 'smart-crop': $feature = new ImageCropping(); return $feature->run( $metadata, $post_id ); } } public function descriptive_text_generator_permissions_check( $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! ( new DescriptiveTextGenerator() )->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image descriptive text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); + } + return true; } public function image_tags_generator_permissions_check( $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + $image_tags_feature = new ImageTagsGenerator(); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $image_tags_feature->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image tagging is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); + } + + $settings = $image_tags_feature->get_settings(); + if ( ! empty( $settings ) && isset( $settings[ static::ID ]['tag_taxonomy'] ) ) { + $permission = check_term_permissions( $settings[ static::ID ]['tag_taxonomy'] ); + + if ( is_wp_error( $permission ) ) { + return $permission; + } + } else { + return new WP_Error( 'invalid_settings', esc_html__( 'Ensure the service settings have been saved.', 'classifai' ) ); + } + return true; } public function smart_crop_permissions_check( $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! ( new PDFTextExtraction() )->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Smart cropping is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); + } + return true; } public function pdf_read_permissions_check( $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! ( new DescriptiveTextGenerator() )->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'PDF Text Extraction is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); + } + return true; } public function image_text_extractor_permissions_check( $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! ( new ImageCropping() )->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Scan image for text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); + } + return true; } From d4cc13a386337327a7b8827c1f7d52f590863d37 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 14 Dec 2023 12:20:55 +0530 Subject: [PATCH 052/127] Remove unwanted code. --- .../Classifai/Services/ImageProcessing.php | 99 ------------------- 1 file changed, 99 deletions(-) diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index e46a022c7..c26743331 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -6,9 +6,6 @@ namespace Classifai\Services; use Classifai\Taxonomy\ImageTagTaxonomy; -use WP_REST_Server; -use WP_REST_Request; -use WP_Error; use function Classifai\get_asset_info; use function Classifai\find_provider_class; @@ -23,7 +20,6 @@ public function __construct() { 'image_processing', [ 'Classifai\Providers\Azure\ComputerVision', - // 'Classifai\Providers\OpenAI\DallE', ] ); } @@ -81,101 +77,6 @@ public function enqueue_media_scripts() { */ public function register_endpoints() {} - /** - * Single callback to pass the route callback to the Computer Vision provider. - * - * @param WP_REST_Request $request The full request object. - * @return array|bool|string|WP_Error - */ - public function computer_vision_endpoint_callback( $request ) { - $attachment_id = $request->get_param( 'id' ); - $custom_atts = $request->get_attributes(); - $route_to_call = empty( $custom_atts['args']['route'] ) ? false : strtolower( $custom_atts['args']['route'][0] ); - - // Check to be sure the post both exists and is an attachment. - if ( ! get_post( $attachment_id ) || 'attachment' !== get_post_type( $attachment_id ) ) { - /* translators: %1$s: the attachment ID */ - return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $attachment_id ), [ 'status' => 404 ] ); - } - - // If no args, we can't pass the call into the active provider. - if ( false === $route_to_call ) { - return new WP_Error( 'no_route', esc_html__( 'No route indicated for the provider class to use.', 'classifai' ), [ 'status' => 404 ] ); - } - - // Find the right provider class. - $provider = find_provider_class( $this->provider_classes ?? [], 'AI Vision' ); - - // Ensure we have a provider class. Should never happen but :shrug: - if ( is_wp_error( $provider ) ) { - return $provider; - } - - // Call the provider endpoint function - return rest_ensure_response( $provider->rest_endpoint_callback( $attachment_id, $route_to_call ) ); - } - - /** - * Check if a given request has access to generate an excerpt. - * - * This check ensures the current user making the request has - * proper capabilities for media and that we are properly - * authenticated with Azure. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function computer_vision_endpoint_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $custom_atts = $request->get_attributes(); - $route_to_call = empty( $custom_atts['args']['route'] ) ? false : strtolower( $custom_atts['args']['route'][0] ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - $settings = \Classifai\get_plugin_settings( 'image_processing', 'AI Vision' ); - $provider = find_provider_class( $this->provider_classes ?? [], 'AI Vision' ); - - // Permission check for user access. - if ( 'ocr' === $route_to_call && ! $provider->is_feature_enabled( 'ocr' ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Scan image for text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } elseif ( 'alt-tags' === $route_to_call && ! $provider->is_feature_enabled( 'image_captions' ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Image descriptive text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } elseif ( 'image-tags' === $route_to_call && ! $provider->is_feature_enabled( 'image_tagging' ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Image tagging is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } elseif ( 'smart-crop' === $route_to_call && ! $provider->is_feature_enabled( 'smart_cropping' ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Smart cropping is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - // For the image-tags route, ensure the taxonomy is valid and the user has permission to assign terms. - if ( 'image-tags' === $route_to_call ) { - if ( ! empty( $settings ) && isset( $settings['image_tag_taxonomy'] ) ) { - $permission = $this->check_term_permissions( $settings['image_tag_taxonomy'] ); - - if ( is_wp_error( $permission ) ) { - return $permission; - } - } else { - return new WP_Error( 'invalid_settings', esc_html__( 'Ensure the service settings have been saved.', 'classifai' ) ); - } - } - - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with Azure.', 'classifai' ) ); - } - - return true; - } - /** * Register a common image tag taxonomy */ From 8cb10d4517043e9eccb5ffcadd4d5da20448ff2b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 14 Dec 2023 13:14:50 +0530 Subject: [PATCH 053/127] Fix console error. --- src/js/editor-ocr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/editor-ocr.js b/src/js/editor-ocr.js index 1daab3da2..3d5f8b514 100644 --- a/src/js/editor-ocr.js +++ b/src/js/editor-ocr.js @@ -4,7 +4,7 @@ /* eslint-disable @wordpress/no-unused-vars-before-return */ import { select, dispatch, subscribe } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; -import { apiFetch } from '@wordpress/api-fetch'; +import apiFetch from '@wordpress/api-fetch'; import { addFilter } from '@wordpress/hooks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { BlockControls } from '@wordpress/block-editor'; From 10c2047dd8ee97a7156f45ddc36a82a98d7fb07e Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 14 Dec 2023 13:29:25 +0530 Subject: [PATCH 054/127] Fix Descriptive text rescan in media model. --- includes/Classifai/Services/ImageProcessing.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index c26743331..2dc1d7d74 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -5,6 +5,7 @@ namespace Classifai\Services; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Taxonomy\ImageTagTaxonomy; use function Classifai\get_asset_info; use function Classifai\find_provider_class; @@ -58,7 +59,7 @@ public function enqueue_media_scripts() { true ); - $provider = find_provider_class( $this->provider_classes ?? [], 'AI Vision' ); + $provider = find_provider_class( $this->provider_classes ?? [], ComputerVision::ID ); if ( ! is_wp_error( $provider ) ) { wp_add_inline_script( 'classifai-media-script', From af6415f45b9270947d52753b484b9183a28a4fd7 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 14 Dec 2023 13:37:23 +0530 Subject: [PATCH 055/127] Remove unwanted code. --- .../Classifai/Providers/AccessControl.php | 174 ------------------ includes/Classifai/Providers/Provider.php | 70 ------- 2 files changed, 244 deletions(-) diff --git a/includes/Classifai/Providers/AccessControl.php b/includes/Classifai/Providers/AccessControl.php index 5b6b09808..9fd93b0d8 100644 --- a/includes/Classifai/Providers/AccessControl.php +++ b/includes/Classifai/Providers/AccessControl.php @@ -167,180 +167,6 @@ public function get_allowed_users() { return array_map( 'absint', $users ); } - /** - * Add settings fields for Role/User based access. - * - * @param string $section Settings section. - * @return void - */ - public function add_settings( string $section = '' ) { - $default_settings = $this->provider->get_default_settings(); - $settings = $this->get_settings(); - - $option_name = $this->provider->get_option_name(); - if ( empty( $section ) ) { - $section = $this->provider->get_option_name(); - } - - // Backward compatibility for old roles keys. - $backward_compatible_roles_key = ''; - switch ( $this->feature ) { - case 'title_generation': - $backward_compatible_roles_key = 'title_roles'; - break; - - case 'excerpt_generation': - case 'speech_to_text': - case 'image_generation': - $backward_compatible_roles_key = 'roles'; - break; - - default: - break; - } - - $default_settings = array_merge( - $this->provider->get_default_settings(), - $default_settings, - ); - - add_settings_field( - $this->role_based_access_key, - esc_html__( 'Enable role-based access', 'classifai' ), - [ $this->provider, 'render_input' ], - $option_name, - $section, - [ - 'label_for' => $this->role_based_access_key, - 'input_type' => 'checkbox', - 'default_value' => $default_settings[ $this->role_based_access_key ], - 'description' => __( 'Enables ability to select which roles can access this feature.', 'classifai' ), - 'class' => 'classifai-role-based-access', - ] - ); - - // Add hidden class if role-based access is disabled. - $class = 'allowed_roles_row'; - if ( ! isset( $settings[ $this->role_based_access_key ] ) || '1' !== $settings[ $this->role_based_access_key ] ) { - $class .= ' hidden'; - } - - add_settings_field( - $this->roles_key, - esc_html__( 'Allowed roles', 'classifai' ), - [ $this->provider, 'render_checkbox_group' ], - $option_name, - $section, - [ - 'label_for' => $this->roles_key, - 'options' => $this->provider->get_roles(), - 'default_values' => $default_settings[ $this->roles_key ], - 'description' => __( 'Choose which roles are allowed to access this feature.', 'classifai' ), - 'class' => $class, - 'backward_compatible_key' => $backward_compatible_roles_key, - ] - ); - - add_settings_field( - $this->user_based_access_key, - esc_html__( 'Enable user-based access', 'classifai' ), - [ $this->provider, 'render_input' ], - $option_name, - $section, - [ - 'label_for' => $this->user_based_access_key, - 'input_type' => 'checkbox', - 'default_value' => $default_settings[ $this->user_based_access_key ], - 'description' => __( 'Enables ability to select which users can access this feature.', 'classifai' ), - 'class' => 'classifai-user-based-access', - ] - ); - - // Add hidden class if user-based access is disabled. - $users_class = 'allowed_users_row'; - if ( ! isset( $settings[ $this->user_based_access_key ] ) || '1' !== $settings[ $this->user_based_access_key ] ) { - $users_class .= ' hidden'; - } - - add_settings_field( - $this->users_key, - esc_html__( 'Allowed users', 'classifai' ), - [ $this->provider, 'render_allowed_users' ], - $option_name, - $section, - [ - 'label_for' => $this->users_key, - 'default_value' => $default_settings[ $this->users_key ], - 'description' => __( 'Users who have access to this feature.', 'classifai' ), - 'class' => $users_class, - ] - ); - - add_settings_field( - $this->user_based_opt_out_key, - esc_html__( 'Enable user-based opt-out', 'classifai' ), - [ $this->provider, 'render_input' ], - $option_name, - $section, - [ - 'label_for' => $this->user_based_opt_out_key, - 'input_type' => 'checkbox', - 'default_value' => $default_settings[ $this->user_based_opt_out_key ], - 'description' => __( 'Enables ability for users to opt-out from their user profile page.', 'classifai' ), - 'class' => 'classifai-user-based-opt-out', - ] - ); - } - - /** - * Sanitization for the roles/users access options being saved. - * - * @param array $settings Array of settings about to be saved. - * - * @return array The sanitized settings to be saved. - */ - public function sanitize_settings( array $settings ) { - $new_settings = []; - - if ( empty( $settings[ $this->role_based_access_key ] ) || 1 !== (int) $settings[ $this->role_based_access_key ] ) { - $new_settings[ $this->role_based_access_key ] = 'no'; - } else { - $new_settings[ $this->role_based_access_key ] = '1'; - } - - // Allowed roles. - if ( isset( $settings[ $this->roles_key ] ) && is_array( $settings[ $this->roles_key ] ) ) { - $new_settings[ $this->roles_key ] = array_map( 'sanitize_text_field', $settings[ $this->roles_key ] ); - } else { - $new_settings[ $this->roles_key ] = array_keys( get_editable_roles() ?? [] ); - } - - if ( empty( $settings[ $this->user_based_access_key ] ) || 1 !== (int) $settings[ $this->user_based_access_key ] ) { - $new_settings[ $this->user_based_access_key ] = 'no'; - } else { - $new_settings[ $this->user_based_access_key ] = '1'; - } - - // Allowed users. - if ( isset( $settings[ $this->users_key ] ) && ! empty( $settings[ $this->users_key ] ) ) { - if ( is_array( $settings[ $this->users_key ] ) ) { - $new_settings[ $this->users_key ] = array_map( 'absint', $settings[ $this->users_key ] ); - } else { - $new_settings[ $this->users_key ] = array_map( 'absint', explode( ',', $settings[ $this->users_key ] ) ); - } - } else { - $new_settings[ $this->users_key ] = array(); - } - - // User-based opt-out. - if ( empty( $settings[ $this->user_based_opt_out_key ] ) || 1 !== (int) $settings[ $this->user_based_opt_out_key ] ) { - $new_settings[ $this->user_based_opt_out_key ] = 'no'; - } else { - $new_settings[ $this->user_based_opt_out_key ] = '1'; - } - return $new_settings; - } - /** * Determine if the current user has access of the feature * diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 5546c28ec..a1342059c 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -5,8 +5,6 @@ namespace Classifai\Providers; -use function Classifai\get_feature_default_settings; - abstract class Provider { /** @@ -50,11 +48,6 @@ abstract class Provider { */ protected $feature_instance = null; - /** - * @var array $features Array of features provided by this provider. - */ - protected $features = array(); - /** * Provider constructor. * @@ -95,15 +88,6 @@ public function get_option_name() { return 'classifai_' . $this->option_name; } - /** - * Get provider features. - * - * @return array - */ - public function get_features() { - return $this->features; - } - /** * Get the onboarding options. * @@ -251,31 +235,6 @@ public function add_api_key_field( $args = [] ) { ); } - /** - * Add settings fields for Role/User based access. - * - * @param string $feature Feature. - * @param string $section Settings section. - * @return void - */ - protected function add_access_settings( string $feature, string $section = '' ) { - $access_control = new AccessControl( $this, $feature ); - $access_control->add_settings( $section ); - } - - /** - * Sanitization for the roles/users access options being saved. - * - * @param array $settings Array of settings about to be saved. - * @param string $feature Feature key. - * - * @return array The sanitized settings to be saved. - */ - protected function sanitize_access_settings( array $settings, string $feature ) { - $access_control = new AccessControl( $this, $feature ); - return $access_control->sanitize_settings( $settings ); - } - /** * Determine if the current user has access of the feature * @@ -287,35 +246,6 @@ protected function has_access( string $feature ) { return $access_control->has_access(); } - /** - * Retrieves the allowed WordPress roles for ClassifAI. - * - * @since 2.4.0 - * - * @return array An associative array where the keys are role keys and the values are role names. - */ - public function get_roles() { - $default_settings = $this->get_default_settings(); - $editable_roles = get_editable_roles() ?? []; - $roles = array_combine( array_keys( $editable_roles ), array_column( $editable_roles, 'name' ) ); - - /** - * Filter the allowed WordPress roles for ClassifAI - * - * @since 2.4.0 - * @hook classifai_allowed_roles - * - * @param {array} $roles Array of arrays containing role information. - * @param {string} $option_name Option name. - * @param {array} $default_settings Default setting values. - * - * @return {array} Roles array. - */ - $roles = apply_filters( 'classifai_allowed_roles', $roles, $this->get_option_name(), $default_settings ); - - return $roles; - } - /** * Determine if the feature is enabled and current user can access the feature * From 8e507d2b49f36a981c8531b3c01c02cb0e747e99 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 16:34:52 +0530 Subject: [PATCH 056/127] Fix fatal error in classifai setup. --- includes/Classifai/Providers/OpenAI/Whisper.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 7708afde9..ba05242c2 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -147,6 +147,8 @@ public function setup_fields_sections() {} public function reset_settings() {} + public function get_default_settings() { } + public function enqueue_media_scripts() { wp_enqueue_script( 'classifai-media-script', From ef29b88f485f782e2e7b311c3673031067aebc13 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 17:10:07 +0530 Subject: [PATCH 057/127] started working on fix onboarding. --- includes/Classifai/Admin/Onboarding.php | 350 +++++++++--------- .../Admin/templates/onboarding-step-one.php | 51 +-- .../Classifai/Providers/OpenAI/ChatGPT.php | 11 +- 3 files changed, 199 insertions(+), 213 deletions(-) diff --git a/includes/Classifai/Admin/Onboarding.php b/includes/Classifai/Admin/Onboarding.php index be329cca8..0c2f067f1 100644 --- a/includes/Classifai/Admin/Onboarding.php +++ b/includes/Classifai/Admin/Onboarding.php @@ -12,6 +12,11 @@ class Onboarding { */ protected $setup_url; + /** + * @var array $features The list of features. + */ + public $features = array(); + /** * Register the actions needed. */ @@ -114,7 +119,7 @@ public function render_setup_page() { -
    +
    get_features( false ); $onboarding_options = array( 'status' => 'inprogress', ); @@ -184,34 +190,19 @@ public function handle_step_submission() { // Disable unchecked features. $configured_features = $this->get_configured_features(); - $providers = $this->get_providers(); - foreach ( $configured_features as $provider_key => $data ) { - $save_needed = false; - $provider = $providers[ $provider_key ]; - if ( empty( $provider ) ) { - continue; - } - $settings = $provider->get_settings(); - - foreach ( $data as $feature_key ) { - $enabled = isset( $enabled_features[ $provider_key ][ $feature_key ] ); - $keys = explode( '__', $feature_key ); - if ( count( $keys ) > 1 ) { - $enabled = isset( $enabled_features[ $provider_key ][ $keys[0] ][ $keys[1] ] ); - } + foreach ( $configured_features as $feature_key ) { + $enabled = isset( $enabled_features[ $feature_key ] ); - if ( ! $enabled ) { - unset( $settings[ $feature_key ] ); - if ( count( $keys ) > 1 ) { - unset( $settings[ $keys[0] ][ $keys[1] ] ); - } - $save_needed = true; + if ( ! $enabled ) { + $feature_class = $features[ $feature_key ] ?? null; + if ( ! $feature_class instanceof \Classifai\Features\Feature ) { + continue; } - } - // Save settings - if ( $save_needed ) { - update_option( $provider->get_option_name(), $settings ); + $settings = $feature_class->get_settings(); + // Disable the feature. + $settings['status'] = '0'; + update_option( $feature_class->get_option_name(), $settings ); } } @@ -221,9 +212,9 @@ public function handle_step_submission() { $step = 2; } - $onboarding_options['step_completed'] = $step; - $onboarding_options['enabled_features'] = $enabled_features; - $onboarding_options['configured_providers'] = array(); + $onboarding_options['step_completed'] = $step; + $onboarding_options['enabled_features'] = $enabled_features; + $onboarding_options['configured_features'] = array(); break; case 2: @@ -243,42 +234,38 @@ public function handle_step_submission() { case 3: // Bail if no provider provided. - if ( empty( $_POST['classifai-setup-provider'] ) ) { + if ( empty( $_POST['classifai-setup-feature'] ) ) { return; } - $providers = $this->get_providers(); - $provider_option = sanitize_text_field( wp_unslash( $_POST['classifai-setup-provider'] ) ); - $provider = $providers[ $provider_option ]; + $features = $this->get_features( false ); + $feature_key = sanitize_text_field( wp_unslash( $_POST['classifai-setup-feature'] ) ); + $feature = $features[ $feature_key ] ?? null; - if ( empty( $provider ) ) { + if ( ! $feature instanceof \Classifai\Features\Feature ) { return; } + $feature_option = $feature->get_option_name(); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $form_data = isset( $_POST[ $provider_option ] ) ? $this->classifai_sanitize( wp_unslash( $_POST[ $provider_option ] ) ) : array(); - - $settings = $provider->get_settings(); - $options = self::get_onboarding_options(); - $features = $options['enabled_features'] ?? array(); - $feature_data = $features[ $provider_option ] ?? array(); - - // Remove all features from the settings. - foreach ( $this->get_features() as $value ) { - $provider_features = $value['features'][ $provider_option ] ?? array(); - foreach ( $provider_features as $feature => $name ) { - if ( ! isset( $settings[ $feature ] ) ) { - continue; - } - unset( $settings[ $feature ] ); - } + $form_data = isset( $_POST[ $feature_option ] ) ? $this->classifai_sanitize( wp_unslash( $_POST[ $feature_option ] ) ) : array(); + + $settings = $feature->get_settings(); + $options = $this->get_onboarding_options(); + $enabled_features = $options['enabled_features'] ?? array(); + $is_enabled = isset( $enabled_features[ $feature_key ] ); + + if ( $is_enabled ) { + // Enable the feature. + $settings['status'] = '1'; } // Update the settings with the new values. - $settings = array_merge( $settings, $form_data, $feature_data ); + $settings = array_merge( $settings, $form_data ); // Save the ClassifAI settings. - update_option( $provider_option, $settings ); + update_option( $feature_option, $settings ); $setting_errors = get_settings_errors(); if ( ! empty( $setting_errors ) ) { @@ -286,21 +273,21 @@ public function handle_step_submission() { return; } - $onboarding_options = self::get_onboarding_options(); - $configured_providers = $onboarding_options['configured_providers'] ?? array(); + $onboarding_options = $this->get_onboarding_options(); + $configured_features = $onboarding_options['configured_features'] ?? array(); - $onboarding_options['configured_providers'] = array_unique( array_merge( $configured_providers, array( $provider_option ) ) ); + $onboarding_options['configured_features'] = array_unique( array_merge( $configured_features, array( $feature_key ) ) ); // Save the options to use it later steps. $this->update_onboarding_options( $onboarding_options ); // Redirect to next provider setup step. - $next_provider = $this->get_next_provider( $provider_option ); - if ( ! empty( $next_provider ) ) { + $next_feature = $this->get_next_feature( $feature_key ); + if ( ! empty( $next_feature ) ) { wp_safe_redirect( add_query_arg( array( 'step' => $step, - 'tab' => $next_provider, + 'tab' => $next_feature, ), $this->setup_url ) @@ -346,7 +333,7 @@ public function classifai_sanitize( $var ) { * @param string[] $fields The fields to render. * @return void */ - public static function render_classifai_setup_settings( $setting_name, $fields ) { + public function render_classifai_setup_settings( $setting_name, $fields = array() ) { global $wp_settings_sections, $wp_settings_fields; if ( ! isset( $wp_settings_fields[ $setting_name ][ $setting_name ] ) ) { @@ -366,7 +353,9 @@ public static function render_classifai_setup_settings( $setting_name, $fields ) if ( $section['title'] ) { ?> -

    +

    + +

    services; - if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { - return []; + public function render_classifai_setup_feature( $feature ) { + global $wp_settings_fields; + $features = $this->get_features( false ); + $feature_class = $features[ $feature ] ?? null; + if ( ! $feature_class instanceof \Classifai\Features\Feature ) { + return; } - /** @var ServicesManager $service_manager Instance of the services manager class. */ - $service_manager = $services['service_manager']; - $onboarding_features = []; + $setting_name = $feature_class->get_option_name(); + $section_name = $feature_class->get_option_name() . '_section'; + if ( ! isset( $wp_settings_fields[ $setting_name ][ $section_name ] ) ) { + return; + } - foreach ( $service_manager->service_classes as $service ) { - $display_name = $service->get_display_name(); - $service_slug = $service->get_menu_slug(); - $features = array(); + // Render the fields. + $skip = true; + $setting_fields = $wp_settings_fields[ $setting_name ][ $section_name ]; + foreach ( $setting_fields as $field_name => $field ) { + if ( 'provider' === $field_name ) { + $skip = false; + } - if ( empty( $service->provider_classes ) ) { + if ( $skip ) { continue; } - foreach ( $service->provider_classes as $provider_class ) { - $options = $provider_class->get_onboarding_options(); - if ( ! empty( $options ) && ! empty( $options['features'] ) ) { - $features[ $provider_class->get_option_name() ] = $options['features']; - } + if ( ! isset( $field['callback'] ) || ! is_callable( $field['callback'] ) ) { + continue; } - if ( ! empty( $features ) ) { - $onboarding_features[ $service_slug ] = array( - 'title' => $display_name, - 'features' => $features, - ); + $label_for = $field['args']['label_for'] ?? ''; + $class = $field['args']['class'] ?? ''; + + if ( 'ibm_watson_nlu_toggle' === $field_name ) { + ?> + + + + + + + + + + + + + + + + + services; - if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { - return []; - } - - /** @var ServicesManager $service_manager Instance of the services manager class. */ - $service_manager = $services['service_manager']; - $providers = []; - - foreach ( $service_manager->service_classes as $service ) { - if ( empty( $service->provider_classes ) ) { - continue; + public function get_features( $nested = true ) { + if ( empty( $this->features ) ) { + $services = Plugin::$instance->services; + if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { + return []; } - foreach ( $service->provider_classes as $provider_class ) { - $providers[ $provider_class->get_option_name() ] = $provider_class; - } - } + /** @var ServicesManager $service_manager Instance of the services manager class. */ + $service_manager = $services['service_manager']; + $onboarding_features = []; - return $providers; - } + foreach ( $service_manager->service_classes as $service ) { + $display_name = $service->get_display_name(); + $service_slug = $service->get_menu_slug(); + $features = array(); + if ( empty( $service->feature_classes ) ) { + continue; + } - /** - * Get Default features values. - * - * @return array The default feature values. - */ - public function get_default_features() { - $features = $this->get_features(); - $providers = $this->get_providers(); - $defaults = array(); - - foreach ( $features as $service_slug => $service ) { - foreach ( $service['features'] as $provider_slug => $provider ) { - $settings = $providers[ $provider_slug ]->get_settings(); - foreach ( $provider as $feature_slug => $feature ) { - $value = $settings[ $feature_slug ] ?? 'no'; - if ( count( explode( '__', $feature_slug ) ) > 1 ) { - $keys = explode( '__', $feature_slug ); - $value = $settings[ $keys[0] ][ $keys[1] ] ?? 'no'; - } elseif ( 'enable_image_captions' === $feature_slug ) { - $value = 'alt' === $settings['enable_image_captions']['alt'] ? 1 : 'no'; + foreach ( $service->feature_classes as $feature_class ) { + if ( ! empty( $feature_class ) ) { + $features[ $feature_class::ID ] = $feature_class; } - $defaults[ $provider_slug ][ $feature_slug ] = $value; } + + $onboarding_features[ $service_slug ] = array( + 'title' => $display_name, + 'features' => $features, + ); } + + $this->features = $onboarding_features; + } + + if ( $nested ) { + return $this->features; + } + + if ( empty( $this->features ) ) { + return []; } - return $defaults; + $features = []; + foreach ( $this->features as $service_slug => $service ) { + foreach ( $service['features'] as $feature_slug => $feature ) { + $features[ $feature_slug ] = $feature; + } + } + return $features; } /** @@ -508,7 +525,7 @@ public function get_default_features() { * * @return array The onboarding options. */ - public static function get_onboarding_options() { + public function get_onboarding_options() { return get_option( 'classifai_onboarding_options', array() ); } @@ -517,8 +534,8 @@ public static function get_onboarding_options() { * * @return bool True if onboarding is completed, false otherwise. */ - public static function is_onboarding_completed() { - $options = self::get_onboarding_options(); + public function is_onboarding_completed() { + $options = $this->get_onboarding_options(); return isset( $options['status'] ) && 'completed' === $options['status']; } @@ -532,7 +549,7 @@ public function update_onboarding_options( $options ) { return; } - $onboarding_options = self::get_onboarding_options(); + $onboarding_options = $this->get_onboarding_options(); $onboarding_options = array_merge( $onboarding_options, $options ); // Update options. @@ -569,31 +586,31 @@ public function handle_skip_setup_step() { * * @return array Array of enabled providers. */ - public function get_enabled_providers() { - $providers = $this->get_providers(); - $onboarding_options = self::get_onboarding_options(); + public function get_enabled_features() { + $features = $this->get_features( false ); + $onboarding_options = $this->get_onboarding_options(); $enabled_features = $onboarding_options['enabled_features'] ?? array(); - $enabled_providers = []; - - foreach ( array_keys( $enabled_features ) as $provider ) { - if ( ! empty( $providers[ $provider ] ) ) { - $enabled_providers[ $provider ] = $providers[ $provider ]->get_onboarding_options(); + foreach ( $enabled_features as $feature_key => $value ) { + if ( ! isset( $features[ $feature_key ] ) ) { + unset( $enabled_features[ $feature_key ] ); + continue; } + $enabled_features[ $feature_key ] = $features[ $feature_key ] ?? null; } - return $enabled_providers; + return $enabled_features; } /** - * Get next provider to setup. + * Get next feature to setup. * - * @param string $current_provider Current provider. + * @param string $current_feature Current feature. * @return string|bool Next provider to setup or false if none. */ - public function get_next_provider( $current_provider ) { - $enabled_providers = $this->get_enabled_providers(); - $keys = array_keys( $enabled_providers ); - $index = array_search( $current_provider, $keys, true ); + public function get_next_feature( $current_feature ) { + $enabled_features = $this->get_enabled_features(); + $keys = array_keys( $enabled_features ); + $index = array_search( $current_feature, $keys, true ); if ( false === $index ) { return false; @@ -616,8 +633,8 @@ public function prevent_direct_step_visits() { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $step = absint( wp_unslash( $_GET['step'] ) ); - $onboarding_options = self::get_onboarding_options(); + $step = absint( wp_unslash( $_GET['step'] ) ); + $onboarding_options = $this->get_onboarding_options(); $step_completed = isset( $onboarding_options['step_completed'] ) ? absint( $onboarding_options['step_completed'] ) : 0; if ( ( $step_completed + 1 ) < $step ) { @@ -625,39 +642,22 @@ public function prevent_direct_step_visits() { } } - /** - * Check if any of the providers are configured. - * - * @return boolean - */ - public function has_configured_providers() { - $providers = $this->get_providers(); - - foreach ( $providers as $provider ) { - if ( $provider->is_configured() ) { - return true; - } - } - - return false; - } - /** * Get configured features. * * @return array */ public function get_configured_features() { - $features = $this->get_features(); + $features = $this->get_features( false ); $configured_features = array(); - foreach ( $features as $feature ) { - foreach ( $feature['features'] as $provider_key => $provider_features ) { - foreach ( $provider_features as $feature_key => $feature_options ) { - if ( $feature_options['enabled'] ) { - $configured_features[ $provider_key ][] = $feature_key; - } - } + foreach ( $features as $feature_key => $feature_class ) { + if ( ! $feature_class instanceof \Classifai\Features\Feature ) { + continue; + } + $settings = $feature_class->get_settings(); + if ( '1' === $settings['status'] ) { + $configured_features[] = $feature_key; } } diff --git a/includes/Classifai/Admin/templates/onboarding-step-one.php b/includes/Classifai/Admin/templates/onboarding-step-one.php index 10a53573e..ad4dcb420 100644 --- a/includes/Classifai/Admin/templates/onboarding-step-one.php +++ b/includes/Classifai/Admin/templates/onboarding-step-one.php @@ -7,7 +7,6 @@ $onboarding = new Classifai\Admin\Onboarding(); $features = $onboarding->get_features(); -$has_configured = $onboarding->has_configured_providers(); $onboarding_options = Classifai\Admin\Onboarding::get_onboarding_options(); $enabled_features = $onboarding_options['enabled_features'] ?? array(); @@ -45,40 +44,24 @@
      $provider_features ) { - foreach ( $provider_features as $feature_key => $feature_options ) { - $checked = false; - if ( $has_configured ) { - // For existing users, enable features based on their saved configuration. - $checked = $feature_options['enabled'] ?? false; - } elseif ( ! empty( $enabled_features ) ) { - // Enable features based on user selection. - $checked = isset( $enabled_features[ $provider ][ $feature_key ] ); - if ( count( explode( '__', $feature_key ) ) > 1 ) { - $keys = explode( '__', $feature_key ); - $checked = isset( $enabled_features[ $provider ][ $keys[0] ][ $keys[1] ] ); - } - } else { - // Enable all features by default. - $checked = true; - if ( strpos( $feature_key, 'post_types__' ) !== false ) { - if ( ! in_array( str_replace( 'post_types__', '', $feature_key ), array( 'post', 'page' ), true ) ) { - $checked = false; - } - } - } - ?> -
    • - -
    • - $feature_class ) { + if ( ! $feature_class instanceof Classifai\Features\Feature ) { + continue; } + $feature_label = $feature_class->get_label(); + $settings = $feature_class->get_settings(); + $checked = isset( $enabled_features[ $feature_key ] ) ? $enabled_features[ $feature_key ] : $settings['status']; + ?> +
    • + +
    • +
    diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 21df7b9eb..b9a1237e6 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -461,10 +461,13 @@ public function enqueue_editor_assets() { public function enqueue_admin_assets( string $hook_suffix ) { // Load asset in OpenAI ChatGPT settings page. if ( - 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['provider'] ) ) // phpcs:ignore - && 'language_processing' === $_GET['tab'] // phpcs:ignore - && 'openai_chatgpt' === $_GET['provider'] // phpcs:ignore + ( + 'tools_page_classifai' === $hook_suffix + && ( isset( $_GET['tab'], $_GET['provider'] ) ) // phpcs:ignore + && 'language_processing' === $_GET['tab'] // phpcs:ignore + && 'openai_chatgpt' === $_GET['provider'] // phpcs:ignore + ) || + 'admin_page_classifai_setup' === $hook_suffix ) { wp_enqueue_script( 'jquery-ui-dialog' ); wp_enqueue_style( 'wp-jquery-ui-dialog' ); From 3f8f4a3b75cccae5b378845ad48c481455f8169f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 17:10:23 +0530 Subject: [PATCH 058/127] Updated step-1 --- includes/Classifai/Admin/templates/onboarding-step-one.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Classifai/Admin/templates/onboarding-step-one.php b/includes/Classifai/Admin/templates/onboarding-step-one.php index ad4dcb420..c4c940bc7 100644 --- a/includes/Classifai/Admin/templates/onboarding-step-one.php +++ b/includes/Classifai/Admin/templates/onboarding-step-one.php @@ -7,7 +7,7 @@ $onboarding = new Classifai\Admin\Onboarding(); $features = $onboarding->get_features(); -$onboarding_options = Classifai\Admin\Onboarding::get_onboarding_options(); +$onboarding_options = $onboarding->get_onboarding_options(); $enabled_features = $onboarding_options['enabled_features'] ?? array(); $args = array( From e5cd51a9715bef212eb38feaa008dd74f0addc0a Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 17:10:59 +0530 Subject: [PATCH 059/127] Updated step-2 & 3 --- includes/Classifai/Admin/Notifications.php | 3 +- .../Admin/templates/onboarding-step-three.php | 80 +++++++++++-------- .../Admin/templates/onboarding-step-two.php | 5 +- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/includes/Classifai/Admin/Notifications.php b/includes/Classifai/Admin/Notifications.php index 52219ace7..0e3fb8812 100644 --- a/includes/Classifai/Admin/Notifications.php +++ b/includes/Classifai/Admin/Notifications.php @@ -50,7 +50,8 @@ public function maybe_render_notices() { $needs_setup = get_transient( 'classifai_activation_notice' ); if ( $needs_setup ) { - if ( Onboarding::is_onboarding_completed() ) { + $onboarding = new Onboarding(); + if ( $onboarding->is_onboarding_completed() ) { delete_transient( 'classifai_activation_notice' ); return; } diff --git a/includes/Classifai/Admin/templates/onboarding-step-three.php b/includes/Classifai/Admin/templates/onboarding-step-three.php index 72d7a322c..c96a10787 100644 --- a/includes/Classifai/Admin/templates/onboarding-step-three.php +++ b/includes/Classifai/Admin/templates/onboarding-step-three.php @@ -5,13 +5,15 @@ * @package ClassifAI */ -$base_url = admin_url( 'admin.php?page=classifai_setup&step=3' ); -$onboarding = new Classifai\Admin\Onboarding(); -$enabled_providers = $onboarding->get_enabled_providers(); -$current_provider = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : array_key_first( $enabled_providers ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -$next_provider = $onboarding->get_next_provider( $current_provider ); -$skip_url = add_query_arg( 'tab', $next_provider, $base_url ); -if ( empty( $next_provider ) ) { +$base_url = admin_url( 'admin.php?page=classifai_setup&step=3' ); +$onboarding = new Classifai\Admin\Onboarding(); +$enabled_features = $onboarding->get_enabled_features(); +$onboarding_options = $onboarding->get_onboarding_options(); +$configured_features = $onboarding_options['configured_features'] ?? array(); +$current_feature = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : array_key_first( $enabled_features ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended +$next_feature = $onboarding->get_next_feature( $current_feature ); +$skip_url = add_query_arg( 'tab', $next_feature, $base_url ); +if ( empty( $next_feature ) ) { $skip_url = wp_nonce_url( admin_url( 'admin-post.php?action=classifai_skip_step&step=3' ), 'classifai_skip_step_action', 'classifai_skip_step_nonce' ); } @@ -31,34 +33,46 @@ // Header require_once 'onboarding-header.php'; ?> - -
    - $provider ) { - $provider_url = add_query_arg( 'tab', $key, $base_url ); - $is_active = ( $current_provider === $key ) ? 'active' : ''; - ?> - - - +
    +
    -
    -
    - - $feature_class ) { + $is_configured = in_array( $key, $configured_features, true ) ? true : false; + $feature_url = add_query_arg( 'tab', $key, $base_url ); + $is_active = ( $current_feature === $key ) ? 'active' : ''; + $icon_class = 'dashicons-clock'; + if ( $is_configured ) { + $icon_class = 'dashicons-yes-alt'; + } elseif ( array_search( $current_feature, $feature_keys, true ) > array_search( $key, $feature_keys, true ) ) { + $icon_class = 'dashicons-warning'; + } + ?> + + + get_label() ); ?> + + -

    - -

    - +
    +
    + + + render_classifai_setup_feature( $current_feature ); + } else { + ?> +

    + +

    + +
    +
    2, 'title' => __( 'Register ClassifAI', 'classifai' ), 'left_link' => array( @@ -24,7 +25,7 @@
    render_classifai_setup_settings( 'classifai_settings', array( 'email', 'registration-key' ) ); ?>
    From e2a80793a1a0e2fc0b99a4925a32c8264621c51f Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 17:11:21 +0530 Subject: [PATCH 060/127] Step 4 and styling improvements. --- .../Admin/templates/onboarding-step-four.php | 46 +++++----- src/scss/admin.scss | 86 ++++++++++++------- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/includes/Classifai/Admin/templates/onboarding-step-four.php b/includes/Classifai/Admin/templates/onboarding-step-four.php index f394f06b5..00e44f0bf 100644 --- a/includes/Classifai/Admin/templates/onboarding-step-four.php +++ b/includes/Classifai/Admin/templates/onboarding-step-four.php @@ -5,11 +5,11 @@ * @package ClassifAI */ -$onboarding_options = get_option( 'classifai_onboarding_options', array() ); -$enabled_features = $onboarding_options['enabled_features'] ?? array(); -$configured_providers = $onboarding_options['configured_providers'] ?? array(); -$onboarding = new Classifai\Admin\Onboarding(); -$features = $onboarding->get_features(); +$onboarding_options = get_option( 'classifai_onboarding_options', array() ); +$enabled_features = $onboarding_options['enabled_features'] ?? array(); +$configured_features = $onboarding_options['configured_features'] ?? array(); +$onboarding = new Classifai\Admin\Onboarding(); +$features = $onboarding->get_features(); $args = array( 'step' => 4, @@ -46,28 +46,22 @@
      $provider_features ) { - foreach ( $provider_features as $feature_key => $feature_options ) { - $enabled = isset( $enabled_features[ $provider ][ $feature_key ] ); - if ( count( explode( '__', $feature_key ) ) > 1 ) { - $keys = explode( '__', $feature_key ); - $enabled = isset( $enabled_features[ $provider ][ $keys[0] ][ $keys[1] ] ); - } - - if ( ! $enabled ) { - continue; - } - - $icon_class = ( $feature_options['enabled'] ) ? 'dashicons-yes-alt' : 'dashicons-dismiss'; - ?> -
    • - - -
    • - $feature_class ) { + $enabled = isset( $enabled_features[ $feature_key ] ); + if ( ! $enabled ) { + continue; } + + $is_configured = $feature_class->is_feature_enabled(); + $icon_class = $is_configured ? 'dashicons-yes-alt' : 'dashicons-dismiss'; + ?> +
    • + + +
    • +
    diff --git a/src/scss/admin.scss b/src/scss/admin.scss index 7a87b94f2..7cd6290aa 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -515,9 +515,11 @@ input.classifai-button { margin-left: -10px; } - &__wrapper { + &__wrapper, + .classifai-wrap { max-width: 1232px; margin: 0px auto; + padding: 0px; } &__content { @@ -655,21 +657,23 @@ input.classifai-button { } .classifai-setup-form { - .classifai-step3-content & { margin: 0 auto; - max-width: 480px; + } + + .classifai-setup-form-field-label { + text-align: left; } .classifai-setup-form-field { - margin-top: 40px; + padding: 0px; } - label { + .classifai-setup-form-field-label > label { display: block; font-weight: 700; text-transform: uppercase; - margin-bottom: 20px; + margin-bottom: 0px; } input[type="text"], @@ -687,28 +691,45 @@ input.classifai-button { .classifai-step3-content { margin: 0 auto; + max-width: 840px; + width: 100%; .classifai-setup-title { text-align: center; margin-bottom: 12px; } - .classifai-setup-form, - .classifai-setup-footer { - margin: 0 auto; - max-width: 480px; + .classifai-setup-form { + padding-left: 48px; + width: 70%; + box-sizing: border-box; + + @media screen and (max-width: 782px) { + padding-left: 18px; + } + + @media screen and (max-width: 600px) { + width: 100%; + padding-left: 0px; + margin-bottom: 20px; + } } .classifai-setup-footer { - margin-top: 40px; + margin-top: 20px; } } .classifai-tabs { display: block; + width: 30%; + box-sizing: border-box; + + @media screen and (max-width: 600px) { + width: 100%; + } &.tabs-center { - margin: auto; margin-bottom: 24px; } @@ -720,12 +741,11 @@ input.classifai-button { a.tab { text-decoration: none; position: relative; - display: inline-block; - transition: all ease .3s; + display: block; + transition: all ease 0.3s; padding: 16px 12px; transform: translate3d(0, 0, 0); color: #1d2327; - white-space: nowrap; cursor: pointer; font-size: 14px; @@ -737,24 +757,10 @@ input.classifai-button { color: var(--classifai-admin-theme-color); } - &:after { - transition: all .3s cubic-bezier(1, 0, 0, 1); - will-change: transform, box-shadow, opacity; - position: absolute; - content: ''; - height: 3px; - bottom: 0px; - left: 0px; - right: 0px; - border-radius: 3px 3px 0px 0px; - background: var(--classifai-admin-theme-color); - box-shadow: 0px 4px 10px 3px rgba(var(--classifai-admin-theme-color--rgb), .15); - opacity: 0; - transform: scale(0, 1); - } - &.active { - color: var(--classifai-admin-theme-color); + background: #f0f0f0; + box-shadow: none; + border-radius: 4px; font-weight: 600; &:after { @@ -765,6 +771,12 @@ input.classifai-button { } } + .classifai-providers-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + } + p.classifai-setup-error { text-align: center; color: red; @@ -773,6 +785,16 @@ input.classifai-button { span.description { font-size: 12px; } + + .dashicons-clock { + color: #a7aaad; + } + .dashicons-yes-alt{ + color: #48BE1E; + } + .dashicons-warning { + color: #dba617; + } } } From be8955cf328a720e2cc7ed56bc7f8d5599c86c74 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 15 Dec 2023 17:25:27 +0530 Subject: [PATCH 061/127] Remove unwanted code. --- .../Providers/Azure/ComputerVision.php | 13 ------ .../Providers/Azure/Personalizer.php | 9 ----- includes/Classifai/Providers/Azure/Speech.php | 9 ----- .../Classifai/Providers/OpenAI/ChatGPT.php | 11 ----- includes/Classifai/Providers/OpenAI/DallE.php | 9 ----- .../Classifai/Providers/OpenAI/Embeddings.php | 9 ----- .../Classifai/Providers/OpenAI/Whisper.php | 9 ----- includes/Classifai/Providers/Provider.php | 40 ------------------- includes/Classifai/Providers/Watson/NLU.php | 15 ------- 9 files changed, 124 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index f136dab0d..d80740477 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -53,19 +53,6 @@ public function __construct( $feature_instance = null ) { 'read_pdf' => __( 'Scan PDF', 'classifai' ), ); - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'Microsoft Azure AI Vision', 'classifai' ), - 'fields' => array( 'url', 'api-key' ), - 'features' => array( - 'enable_image_captions' => __( 'Automatically add alt-text to images', 'classifai' ), - 'enable_image_tagging' => __( 'Automatically tag images', 'classifai' ), - 'enable_smart_cropping' => __( 'Smart crop images', 'classifai' ), - 'enable_ocr' => __( 'Scan images for text', 'classifai' ), - 'enable_read_pdf' => __( 'Scan PDFs for text', 'classifai' ), - ), - ); - $this->feature_instance = $feature_instance; } diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index ba2f34fd1..0fa51010a 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -46,15 +46,6 @@ public function __construct( $service = null ) { $this->features = array( 'recommended_content' => __( 'Recommended content block', 'classifai' ), ); - - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'Microsoft Azure AI Personalizer', 'classifai' ), - 'fields' => array( 'url', 'api-key' ), - 'features' => array( - 'enable_recommended_content' => __( 'Recommended content block', 'classifai' ), - ), - ); } /** diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 54de6c700..db19755fa 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -84,15 +84,6 @@ public function __construct( $feature_instance = null ) { 'text_to_speech' => __( 'Text to speech', 'classifai' ), ); - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'Microsoft Azure Text to Speech', 'classifai' ), - 'fields' => array( 'url', 'api-key' ), - 'features' => array( - 'enable_text_to_speech' => __( 'Generate speech for post content', 'classifai' ), - ), - ); - $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index b9a1237e6..94063d3b3 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -89,17 +89,6 @@ public function __construct( $feature_instance = null ) { 'resize_content' => __( 'Resize content', 'classifai' ), ); - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'OpenAI ChatGPT', 'classifai' ), - 'fields' => array( 'api-key' ), - 'features' => array( - 'enable_excerpt' => __( 'Excerpt generation', 'classifai' ), - 'enable_titles' => __( 'Title generation', 'classifai' ), - 'enable_resize_content' => __( 'Content resizing', 'classifai' ), - ), - ); - $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 86f9d82cc..7fc4a4339 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -52,15 +52,6 @@ public function __construct( $feature_instance = null ) { 'image_generation' => __( 'Generate images', 'classifai' ), ); - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'OpenAI DALLĀ·E', 'classifai' ), - 'fields' => array( 'api-key' ), - 'features' => array( - 'enable_image_gen' => __( 'Image generation', 'classifai' ), - ), - ); - $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 84b22f5e7..dc9e67707 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -57,15 +57,6 @@ public function __construct( $service ) { $this->features = array( 'classification' => __( 'Content classification', 'classifai' ), ); - - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'OpenAI Embeddings', 'classifai' ), - 'fields' => array( 'api-key' ), - 'features' => array( - 'enable_classification' => __( 'Classify content within existing term structure', 'classifai' ), - ), - ); } /** diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index ba05242c2..197b68543 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -42,15 +42,6 @@ public function __construct( $feature_instance = null ) { 'speech_to_text' => __( 'Generate transcripts', 'classifai' ), ); - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'OpenAI Whisper', 'classifai' ), - 'fields' => array( 'api-key' ), - 'features' => array( - 'enable_transcripts' => __( 'Generate transcripts from audio files', 'classifai' ), - ), - ); - $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 5546c28ec..aca5ccfb8 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -38,11 +38,6 @@ abstract class Provider { */ protected $service; - /** - * @var array $onboarding The onboarding options for this provider. - */ - public $onboarding_options; - /** * Feature instance. * @@ -66,7 +61,6 @@ public function __construct( $provider_name, $provider_service_name, $option_nam $this->provider_name = $provider_name; $this->provider_service_name = $provider_service_name; $this->option_name = $option_name; - $this->onboarding_options = array(); } /** @@ -104,40 +98,6 @@ public function get_features() { return $this->features; } - /** - * Get the onboarding options. - * - * @return array - */ - public function get_onboarding_options() { - if ( empty( $this->onboarding_options ) || ! isset( $this->onboarding_options['features'] ) ) { - return array(); - } - - $settings = $this->get_settings(); - $is_configured = $this->is_configured(); - - foreach ( $this->onboarding_options['features'] as $key => $title ) { - $enabled = isset( $settings[ $key ] ) ? 1 === absint( $settings[ $key ] ) : false; - if ( count( explode( '__', $key ) ) > 1 ) { - $keys = explode( '__', $key ); - $enabled = isset( $settings[ $keys[0] ][ $keys[1] ] ) ? 1 === absint( $settings[ $keys[0] ][ $keys[1] ] ) : false; - } - // Handle enable_image_captions - if ( 'enable_image_captions' === $key ) { - $enabled = isset( $settings['enable_image_captions']['alt'] ) && 'alt' === $settings['enable_image_captions']['alt']; - } - $enabled = $enabled && $is_configured; - - $this->onboarding_options['features'][ $key ] = array( - 'title' => $title, - 'enabled' => $enabled, - ); - } - - return $this->onboarding_options; - } - /** * Can the Provider be initialized? */ diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 6cef7a075..3174b03c5 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -87,21 +87,6 @@ public function __construct( $feature = null ) { ], ]; - // Set the onboarding options. - $this->onboarding_options = array( - 'title' => __( 'IBM Watson NLU', 'classifai' ), - 'fields' => array( 'url', 'username', 'password', 'toggle' ), - 'features' => array( - 'enable_content_classification' => __( 'Classify content', 'classifai' ), - ), - ); - - $post_types = get_post_types_for_language_settings(); - foreach ( $post_types as $post_type ) { - // translators: %s is the post type label. - $this->onboarding_options['features'][ 'post_types__' . $post_type->name ] = sprintf( __( 'Automatically tag %s', 'classifai' ), $post_type->label ); - } - $this->feature_instance = $feature; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); From 33f2d48af2b46e811790478c520474f7c94c3773 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 22 Dec 2023 00:35:58 +0530 Subject: [PATCH 062/127] phpcs fixes --- .../Providers/Azure/ComputerVision.php | 7 -- includes/Classifai/Providers/Azure/Speech.php | 5 -- .../Classifai/Providers/OpenAI/ChatGPT.php | 65 +++++++++----- includes/Classifai/Providers/OpenAI/DallE.php | 49 +++++----- .../Classifai/Providers/OpenAI/Whisper.php | 34 +++++-- includes/Classifai/Providers/Provider.php | 4 +- includes/Classifai/Providers/Watson/NLU.php | 89 ++++++------------- .../Classifai/Services/ImageProcessing.php | 5 ++ .../Classifai/Services/LanguageProcessing.php | 5 ++ includes/Classifai/Services/Service.php | 3 - 10 files changed, 139 insertions(+), 127 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 926cd4014..e4626a48c 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -304,13 +304,6 @@ public function sanitize_settings( $new_settings ) { return $new_settings; } - /** - * Default settings for ComputerVision - * - * @return array - */ - public function get_default_settings() {} - /** * Returns an array of fields enabled to be set to store image captions. * diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index ac2dc1dbd..47311d314 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -438,11 +438,6 @@ public function get_provider_debug_information( $settings = null, $configured = ]; } - /** - * Returns the default settings. - */ - public function get_default_settings() {} - /** * Initial audio generation state. * diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 583deeb74..d8543c05a 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -94,6 +94,11 @@ public function __construct( $feature_instance = null ) { add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } + /** + * Render the provider fields. + * + * @return void + */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -108,7 +113,7 @@ public function render_provider_fields() { 'label_for' => 'api_key', 'input_type' => 'password', 'default_value' => $settings['api_key'], - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -129,6 +134,11 @@ public function render_provider_fields() { do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } + /** + * Renders te fields for Title generation feature. + * + * @return void + */ private function add_title_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -146,7 +156,7 @@ private function add_title_generation_fields() { 'step' => 1, 'default_value' => $settings['number_of_titles'], 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -162,11 +172,16 @@ private function add_title_generation_fields() { 'placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), 'default_value' => $settings['generate_title_prompt'], 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } + /** + * Renders te fields for Excerpt generation feature. + * + * @return void + */ private function add_excerpt_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -182,11 +197,16 @@ private function add_excerpt_generation_fields() { 'placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), 'default_value' => $settings['generate_excerpt_prompt'], 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } + /** + * Renders te fields for Content resizing feature. + * + * @return void + */ private function add_content_resizing_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -204,7 +224,7 @@ private function add_content_resizing_fields() { 'step' => 1, 'default_value' => $settings['number_of_suggestions'], 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -220,7 +240,7 @@ private function add_content_resizing_fields() { 'placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), 'default_value' => $settings['condense_text_prompt'], 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -236,11 +256,16 @@ private function add_content_resizing_fields() { 'placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), 'default_value' => $settings['expand_text_prompt'], 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } + /** + * Returns the default settings for this provider. + * + * @return array + */ public function get_default_provider_settings() { $common_settings = [ 'api_key' => '', @@ -289,7 +314,7 @@ public function get_default_provider_settings() { 'original' => 1, ), ), - 'expand_text_prompt' => array( + 'expand_text_prompt' => array( array( 'title' => esc_html__( 'ClassifAI default', 'classifai' ), 'prompt' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), @@ -303,6 +328,12 @@ public function get_default_provider_settings() { return $common_settings; } + /** + * Sanitize the settings for this provider. + * + * @param array $new_settings The settings array. + * @return array + */ public function sanitize_settings( $new_settings ) { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); @@ -571,17 +602,6 @@ public function register_generated_titles_template() { get_param( 'id' ); + $post_id = $request->get_param( 'id' ); return rest_ensure_response( $this->rest_endpoint_callback( diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index d2da5dd78..3f3002ca4 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -68,6 +68,11 @@ public function register() { add_action( 'print_media_templates', [ $this, 'print_media_templates' ] ); } + /** + * Register settings for the provider. + * + * @return void + */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -82,7 +87,7 @@ public function render_provider_fields() { 'label_for' => 'api_key', 'input_type' => 'password', 'default_value' => $settings['api_key'], - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -98,7 +103,7 @@ public function render_provider_fields() { 'options' => array_combine( range( 1, 10 ), range( 1, 10 ) ), 'default_value' => $settings['number_of_images'], 'description' => __( 'Number of images that will be generated in one request. Note that each image will incur separate costs.', 'classifai' ), - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -118,11 +123,16 @@ public function render_provider_fields() { ], 'default_value' => $settings['image_size'], 'description' => __( 'Size of generated images.', 'classifai' ), - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } + /** + * Returns the default settings for the provider. + * + * @return array + */ public function get_default_provider_settings() { $common_settings = [ 'api_key' => '', @@ -179,7 +189,7 @@ public function enqueue_admin_scripts( $hook_suffix = '' ) { $image_generation = new ImageGeneration(); if ( $image_generation->is_feature_enabled() ) { - $settings = $image_generation->get_settings( static::ID ); + $settings = $image_generation->get_settings( static::ID ); $number_of_images = absint( $settings['number_of_images'] ); wp_enqueue_media(); @@ -272,9 +282,9 @@ public function print_media_templates() { if ( $image_generation->is_feature_enabled() ) : $settings = $image_generation->get_settings( static::ID ); $number_of_images = absint( $settings['number_of_images'] ); - ?> + ?> - + - + ?> - + ?> - feature_instance instanceof ImageGeneration ) { $new_settings[ static::ID ]['number_of_images'] = absint( $new_settings[ static::ID ]['number_of_images'] ?? $settings[ static::ID ]['number_of_images'] ); - if ( in_array( $new_settings[ static::ID ]['image_size'], [ '256x256', '512x512', '1024x1024' ] ) ) { + if ( in_array( $new_settings[ static::ID ]['image_size'], [ '256x256', '512x512', '1024x1024' ], true ) ) { $new_settings[ static::ID ]['image_size'] = sanitize_text_field( $new_settings[ static::ID ]['image_size'] ?? $settings[ static::ID ]['image_size'] ); } } @@ -381,13 +392,6 @@ public function reset_settings() { update_option( $this->get_option_name(), $this->get_default_settings() ); } - /** - * Default settings for ChatGPT - * - * @return array - */ - public function get_default_settings() {} - /** * Provides debug information related to the provider. * @@ -509,6 +513,11 @@ public function generate_image( string $prompt = '', array $args = [] ) { return $response; } + /** + * Registers REST endpoints for this provider. + * + * @return void + */ public function register_endpoints() { register_rest_route( 'classifai/v1/openai', diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 51ae503fb..5abbd9f56 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -62,6 +62,11 @@ public function register() { } } + /** + * Register settings for this provider. + * + * @return void + */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -76,13 +81,18 @@ public function render_provider_fields() { 'label_for' => 'api_key', 'input_type' => 'password', 'default_value' => $settings['api_key'], - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } + /** + * Get the default settings for this provider. + * + * @return array + */ public function get_default_provider_settings() { $common_settings = [ 'api_key' => '', @@ -97,6 +107,12 @@ public function get_default_provider_settings() { return []; } + /** + * Sanitize the settings for this provider. + * + * @param array $new_settings New settings. + * @return array + */ public function sanitize_settings( $new_settings ) { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); @@ -106,12 +122,11 @@ public function sanitize_settings( $new_settings ) { return $new_settings; } - public function setup_fields_sections() {} - - public function reset_settings() {} - - public function get_default_settings() { } - + /** + * Enqueue assets. + * + * @return void + */ public function enqueue_media_scripts() { wp_enqueue_script( 'classifai-media-script', @@ -276,6 +291,11 @@ public function get_provider_debug_information( $settings = null, $configured = ]; } + /** + * Register REST endpoints for this provider. + * + * @return void + */ public function register_endpoints() { register_rest_route( 'classifai/v1/openai', diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 2100fed7a..8661ede97 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -191,7 +191,7 @@ public function is_configured() { public function add_api_key_field( $args = [] ) { $default_settings = $this->feature_instance->get_settings(); $default_settings = $default_settings[ static::ID ]; - $id = $args['id'] ?? 'api_key'; + $id = $args['id'] ?? 'api_key'; add_settings_field( $id, @@ -204,7 +204,7 @@ public function add_api_key_field( $args = [] ) { 'label_for' => $id, 'input_type' => 'password', 'default_value' => $default_settings[ $id ], - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 0768d3963..00a1fd251 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -42,7 +42,7 @@ class NLU extends Provider { /** * Watson NLU constructor. * - * @param string $service The service this class belongs to. + * @param \Classifai\Features\Feature $feature Feature instance (Optional, only required in admin). */ public function __construct( $feature = null ) { parent::__construct( @@ -87,6 +87,11 @@ public function __construct( $feature = null ) { add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } + /** + * Renders settings fields for this provider. + * + * @return void + */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -130,10 +135,10 @@ public function render_provider_fields() { $this->feature_instance->get_option_name() . '_section', [ 'option_index' => static::ID, - 'label_for' => 'password', + 'label_for' => 'password', 'default_value' => $settings['password'], - 'input_type' => 'password', - 'large' => true, + 'input_type' => 'password', + 'large' => true, ] ); @@ -152,7 +157,7 @@ function( $args = [] ) { $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', [ - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -168,12 +173,17 @@ function( $args = [] ) { 'feature' => $classify_by, 'labels' => $labels, 'default_value' => $settings[ $classify_by ], - 'class' => 'classifai-provider-field hidden' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); } } + /** + * Returns the default settings for this provider. + * + * @return array + */ public function get_default_provider_settings() { $common_settings = [ 'endpoint_url' => '', @@ -217,48 +227,6 @@ public function reset_settings() { update_option( $this->get_option_name(), $settings ); } - /** - * Default settings for Watson NLU. - * - * @return array - */ - public function get_default_settings() { - $default_settings = parent::get_default_settings() ?? []; - - return array_merge( - $default_settings, - [ - 'enable_content_classification' => false, - 'post_types' => [ - 'post' => 1, - 'page' => null, - ], - 'post_statuses' => [ - 'publish' => 1, - 'draft' => null, - ], - 'features' => [ - 'category' => true, - 'category_threshold' => WATSON_CATEGORY_THRESHOLD, - 'category_taxonomy' => WATSON_CATEGORY_TAXONOMY, - - 'keyword' => true, - 'keyword_threshold' => WATSON_KEYWORD_THRESHOLD, - 'keyword_taxonomy' => WATSON_KEYWORD_TAXONOMY, - - 'concept' => false, - 'concept_threshold' => WATSON_CONCEPT_THRESHOLD, - 'concept_taxonomy' => WATSON_CONCEPT_TAXONOMY, - - 'entity' => false, - 'entity_threshold' => WATSON_ENTITY_THRESHOLD, - 'entity_taxonomy' => WATSON_ENTITY_TAXONOMY, - ], - 'classification_method' => 'recommended_terms', - ] - ); - } - /** * Register what we need for the plugin. */ @@ -283,17 +251,6 @@ public function register() { } } - /** - * Helper to get the settings and allow for settings default values. - * - * Overridden from parent to polyfill older settings storage schema. - * - * @param string|bool|mixed $index Optional. Name of the settings option index. - * - * @return array - */ - public function get_settings( $index = false ) {} - /** * Enqueue the editor scripts. */ @@ -409,13 +366,14 @@ function() { * @return bool */ protected function use_username_password() { - $settings = $this->get_settings( 'credentials' ); + $feature = new Classification(); + $settings = $feature->get_settings( static::ID ); - if ( empty( $settings['watson_username'] ) ) { + if ( empty( $settings['username'] ) ) { return false; } - return 'apikey' === $settings['watson_username']; + return 'apikey' === $settings['username']; } /** @@ -468,7 +426,7 @@ public function render_nlu_feature_settings( $args ) {
    + + %6$s + +

    ', + esc_attr( $this->feature_instance->get_option_name() ), + esc_attr( $option_index ), + esc_attr( $args['label_for'] ?? '' ), + esc_attr( $option_value ), + checked( $value, $option_value, false ), + esc_html( $option_label ) + ); + + // Render Threshold field. + if ( 'taxonomies' === $args['label_for'] ) { + $this->render_threshold_field( $args, $option_value_theshold, $threshold_value ); + } + } + + // Render description, if any. + if ( ! empty( $args['description'] ) ) { + printf( + '%s', + esc_html( $args['description'] ) + ); + } + } + + /** + * Render a threshold field. + * + * @since 2.5.0 + * + * @param array $args The args passed to add_settings_field + * @param string $option_value The option value. + * @param string $value The value. + * + * @return void + */ + public function render_threshold_field( $args, $option_value, $value ) { + printf( + '

    + +
    + +

    ', + esc_attr( $this->feature_instance->get_option_name() ), + $args['option_index'], + esc_attr( $args['label_for'] ?? '' ), + esc_attr( $option_value ), + esc_html__( 'Threshold (%)', 'classifai' ), + $value ? esc_attr( $value ) : 75 + ); + } } diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 5abbd9f56..4e6d5143e 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -37,11 +37,6 @@ public function __construct( $feature_instance = null ) { 'openai_whisper' ); - // Features provided by this provider. - $this->features = array( - 'speech_to_text' => __( 'Generate transcripts', 'classifai' ), - ); - $this->feature_instance = $feature_instance; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 00a1fd251..0d1d55ee6 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -15,7 +15,6 @@ use function Classifai\get_post_statuses_for_language_settings; use function Classifai\get_asset_info; use function Classifai\check_term_permissions; -use function Classifai\get_classification_mode; use WP_Error; use WP_REST_Request; use WP_REST_Server; @@ -107,6 +106,7 @@ public function render_provider_fields() { 'default_value' => $settings['endpoint_url'], 'input_type' => 'text', 'large' => true, + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -123,7 +123,7 @@ public function render_provider_fields() { 'input_type' => 'text', 'default_value' => 'apikey', 'large' => true, - 'class' => $this->use_username_password() ? 'hidden' : '', + 'class' => 'classifai-provider-field ' . $this->use_username_password() ? 'hidden' : '' . ' provider-scope-' . static::ID, // Important to add this. ] ); @@ -139,6 +139,7 @@ public function render_provider_fields() { 'default_value' => $settings['password'], 'input_type' => 'password', 'large' => true, + 'class' => 'classifai-provider-field ' . $this->use_username_password() ? 'hidden' : '' . ' provider-scope-' . static::ID, // Important to add this. ] ); @@ -231,7 +232,9 @@ public function reset_settings() { * Register what we need for the plugin. */ public function register() { - if ( ( new Classification() )->is_feature_enabled() ) { + $feature = new Classification(); + + if ( $feature->is_feature_enabled() && $feature->get_feature_provider_instance()::ID === static::ID ) { add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); @@ -545,114 +548,6 @@ public function sanitize_settings( $new_settings ) { return $new_settings; } - /** - * Sanitization for the options being saved. - * - * @param array $settings Array of settings about to be saved. - * - * @return array The sanitized settings to be saved. - */ - public function sanitize_settings_old( $settings ) { - $new_settings = $this->get_settings(); - $new_settings = array_merge( $new_settings, $this->sanitize_access_settings( $settings, 'content_classification' ) ); - $authenticated = $this->nlu_authentication_check( $settings ); - - if ( is_wp_error( $authenticated ) ) { - $new_settings['authenticated'] = false; - add_settings_error( - 'credentials', - 'classifai-auth', - $authenticated->get_error_message(), - 'error' - ); - } else { - $new_settings['authenticated'] = true; - } - - if ( isset( $settings['credentials']['watson_url'] ) ) { - $new_settings['credentials']['watson_url'] = esc_url_raw( $settings['credentials']['watson_url'] ); - } - - if ( isset( $settings['credentials']['watson_username'] ) ) { - $new_settings['credentials']['watson_username'] = sanitize_text_field( $settings['credentials']['watson_username'] ); - } - - if ( isset( $settings['credentials']['watson_password'] ) ) { - $new_settings['credentials']['watson_password'] = sanitize_text_field( $settings['credentials']['watson_password'] ); - } - - if ( empty( $settings['enable_content_classification'] ) || 1 !== (int) $settings['enable_content_classification'] ) { - $new_settings['enable_content_classification'] = 'no'; - } else { - $new_settings['enable_content_classification'] = '1'; - } - - if ( isset( $settings['classification_mode'] ) ) { - $new_settings['classification_mode'] = sanitize_text_field( $settings['classification_mode'] ); - } - - if ( isset( $settings['classification_method'] ) ) { - $new_settings['classification_method'] = sanitize_text_field( $settings['classification_method'] ); - } - - // Sanitize the post type checkboxes - $post_types = get_post_types( [ 'public' => true ], 'objects' ); - foreach ( $post_types as $post_type ) { - if ( isset( $settings['post_types'][ $post_type->name ] ) ) { - $new_settings['post_types'][ $post_type->name ] = absint( $settings['post_types'][ $post_type->name ] ); - } else { - $new_settings['post_types'][ $post_type->name ] = null; - } - } - - // Sanitize the post statuses checkboxes - $post_statuses = get_post_statuses_for_language_settings(); - foreach ( $post_statuses as $post_status_key => $post_status_value ) { - if ( isset( $settings['post_statuses'][ $post_status_key ] ) ) { - $new_settings['post_statuses'][ $post_status_key ] = absint( $settings['post_statuses'][ $post_status_key ] ); - } else { - $new_settings['post_statuses'][ $post_status_key ] = null; - } - } - - $feature_enabled = false; - - foreach ( $this->nlu_features as $feature => $labels ) { - // Set the enabled flag. - if ( isset( $settings['features'][ $feature ] ) ) { - $new_settings['features'][ $feature ] = absint( $settings['features'][ $feature ] ); - $feature_enabled = true; - } else { - $new_settings['features'][ $feature ] = null; - } - - // Set the threshold - if ( isset( $settings['features'][ "{$feature}_threshold" ] ) ) { - $new_settings['features'][ "{$feature}_threshold" ] = min( absint( $settings['features'][ "{$feature}_threshold" ] ), 100 ); - } - - if ( isset( $settings['features'][ "{$feature}_taxonomy" ] ) ) { - $new_settings['features'][ "{$feature}_taxonomy" ] = sanitize_text_field( $settings['features'][ "{$feature}_taxonomy" ] ); - } - } - - // Show a warning if the NLU feature and Embeddings feature are both enabled. - if ( $feature_enabled && '1' === $new_settings['enable_content_classification'] ) { - $embeddings_settings = get_plugin_settings( 'language_processing', 'Embeddings' ); - - if ( isset( $embeddings_settings['enable_classification'] ) && 1 === (int) $embeddings_settings['enable_classification'] ) { - add_settings_error( - 'features', - 'conflict', - esc_html__( 'OpenAI Embeddings classification is turned on. This may conflict with the NLU classification feature. It is possible to run both features but if they use the same taxonomies, one will overwrite the other.', 'classifai' ), - 'warning' - ); - } - } - - return $new_settings; - } - /** * Provides debug information related to the provider. * diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 2c3b33143..b5c8abecb 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -48,7 +48,7 @@ public static function get_service_providers() { [ 'Classifai\Providers\Watson\NLU', 'Classifai\Providers\OpenAI\ChatGPT', - // 'Classifai\Providers\OpenAI\Embeddings', + 'Classifai\Providers\OpenAI\Embeddings', 'Classifai\Providers\OpenAI\Whisper', 'Classifai\Providers\OpenAI\DallE', 'Classifai\Providers\Azure\Speech', From b3ccdaf9073259c0db06dd3d64c0249dd7125a9f Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 2 Jan 2024 10:54:08 +0530 Subject: [PATCH 066/127] add links to setup keys --- .../Classifai/Providers/OpenAI/ChatGPT.php | 13 +++++++++++++ includes/Classifai/Providers/OpenAI/DallE.php | 13 +++++++++++++ .../Classifai/Providers/OpenAI/Embeddings.php | 13 +++++++++++++ .../Classifai/Providers/OpenAI/Whisper.php | 13 +++++++++++++ includes/Classifai/Providers/Watson/NLU.php | 18 ++++++++++++++++-- src/js/admin.js | 13 ++++++------- 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 2a1a16b0c..0fefc8bf9 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -107,6 +107,19 @@ public function render_provider_fields() { 'input_type' => 'password', 'default_value' => $settings['api_key'], 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'description' => sprintf( + wp_kses( + /* translators: %1$s is replaced with the OpenAI sign up URL */ + __( 'Don\'t have an OpenAI account yet? Sign up for one in order to get your API key.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://platform.openai.com/signup' ) + ), ] ); diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index ccce0f4d9..c06629a9e 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -83,6 +83,19 @@ public function render_provider_fields() { 'input_type' => 'password', 'default_value' => $settings['api_key'], 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'description' => sprintf( + wp_kses( + /* translators: %1$s is replaced with the OpenAI sign up URL */ + __( 'Don\'t have an OpenAI account yet? Sign up for one in order to get your API key.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://platform.openai.com/signup' ) + ), ] ); diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 7c277755b..533f76216 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -77,6 +77,19 @@ public function render_provider_fields() { 'input_type' => 'password', 'default_value' => $settings['api_key'], 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'description' => sprintf( + wp_kses( + /* translators: %1$s is replaced with the OpenAI sign up URL */ + __( 'Don\'t have an OpenAI account yet? Sign up for one in order to get your API key.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://platform.openai.com/signup' ) + ), ] ); diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 4e6d5143e..c027d1d24 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -77,6 +77,19 @@ public function render_provider_fields() { 'input_type' => 'password', 'default_value' => $settings['api_key'], 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'description' => sprintf( + wp_kses( + /* translators: %1$s is replaced with the OpenAI sign up URL */ + __( 'Don\'t have an OpenAI account yet? Sign up for one in order to get your API key.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://platform.openai.com/signup' ) + ), ] ); diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 0d1d55ee6..9d6472c3a 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -123,7 +123,7 @@ public function render_provider_fields() { 'input_type' => 'text', 'default_value' => 'apikey', 'large' => true, - 'class' => 'classifai-provider-field ' . $this->use_username_password() ? 'hidden' : '' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field ' . ( $this->use_username_password() ? 'hidden' : '' ) . ' provider-scope-' . static::ID, // Important to add this. ] ); @@ -139,7 +139,21 @@ public function render_provider_fields() { 'default_value' => $settings['password'], 'input_type' => 'password', 'large' => true, - 'class' => 'classifai-provider-field ' . $this->use_username_password() ? 'hidden' : '' . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field provider-scope-' . static::ID, // Important to add this. + 'description' => sprintf( + wp_kses( + /* translators: %1$s is the link to register for an IBM Cloud account, %2$s is the link to setup the NLU service */ + __( 'Don\'t have an IBM Cloud account yet? Register for one and set up a Natural Language Understanding Resource to get your API key.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://cloud.ibm.com/registration' ), + esc_url( 'https://cloud.ibm.com/catalog/services/natural-language-understanding' ) + ), ] ); diff --git a/src/js/admin.js b/src/js/admin.js index b1d08d863..07536a93d 100644 --- a/src/js/admin.js +++ b/src/js/admin.js @@ -31,10 +31,9 @@ document.addEventListener( 'DOMContentLoaded', function () { } ); ( () => { + return; const $toggler = document.getElementById( 'classifai-waston-cred-toggle' ); - const $userField = document.getElementById( - 'classifai-settings-watson_username' - ); + const $userField = document.getElementById( 'username' ); if ( $toggler === null || $userField === null ) { return; @@ -50,20 +49,20 @@ document.addEventListener( 'DOMContentLoaded', function () { if ( document - .getElementById( 'classifai-settings-watson_password' ) + .getElementById( 'password' ) .closest( 'tr' ) ) { [ $passwordFieldTitle ] = document - .getElementById( 'classifai-settings-watson_password' ) + .getElementById( 'password' ) .closest( 'tr' ) .getElementsByTagName( 'label' ); } else if ( document - .getElementById( 'classifai-settings-watson_password' ) + .getElementById( 'password' ) .closest( '.classifai-setup-form-field' ) ) { [ $passwordFieldTitle ] = document - .getElementById( 'classifai-settings-watson_password' ) + .getElementById( 'password' ) .closest( '.classifai-setup-form-field' ) .getElementsByTagName( 'label' ); } From dd57777739a27d407a738f308b9e064bd922dff6 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 2 Jan 2024 18:36:17 +0530 Subject: [PATCH 067/127] add debug logger --- includes/Classifai/Features/Feature.php | 62 +++++++++++++++++- .../Providers/Azure/ComputerVision.php | 58 +++++++++-------- includes/Classifai/Providers/Azure/Speech.php | 46 +++++++------ .../Classifai/Providers/OpenAI/ChatGPT.php | 65 +++++++++---------- includes/Classifai/Providers/OpenAI/DallE.php | 25 ------- .../Classifai/Providers/OpenAI/Embeddings.php | 47 ++++++-------- .../Classifai/Providers/OpenAI/Whisper.php | 34 ++++------ includes/Classifai/Providers/Watson/NLU.php | 37 ----------- includes/Classifai/Services/Service.php | 8 +-- 9 files changed, 184 insertions(+), 198 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 345bb0797..f34b5e2f2 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -82,7 +82,7 @@ abstract protected function setup_fields_sections(); protected function get_default_settings() { return [ 'status' => '0', - 'role_based_access' => '1', + 'role_based_access' => 'no', 'roles' => array_combine( array_keys( $this->roles ), array_keys( $this->roles ) ), 'user_based_access' => 'no', 'users' => [], @@ -434,6 +434,66 @@ public function can_register() { return $this->is_configured(); } + public static function get_debug_value_text( $setting_value, $type = 0 ) { + $debug_value = ''; + + if ( empty ( $setting_value ) ) { + $boolean = false; + } else if ( 'no' === $setting_value ) { + $boolean = false; + } else { + $boolean = true; + } + + switch ( $type ) { + case 0: + $debug_value = $boolean ? __( 'Yes', 'classifai' ) : __( 'No', 'classifai' ); + break; + case 1: + $debug_value = $boolean ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + break; + } + + return $debug_value; + } + + /** + * Returns an array of feature-level debug info. + * + * @return array + */ + public function get_debug_information() { + $feature_settings = $this->get_settings(); + $provider = $this->get_feature_provider_instance(); + + $roles = array_filter( + $feature_settings['roles'], + function( $role ) { + return '0' !== $role; + } + ); + + $common_debug_info = [ + __( 'Authenticated', 'classifai' ) => self::get_debug_value_text( $this->is_configured() ), + __( 'Status', 'classifai' ) => self::get_debug_value_text( $feature_settings['status'], 1 ), + __( 'Role-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['role_based_access'], 1 ), + __( 'Allowed roles (titles)', 'classifai' ) => implode( ', ', $roles ?? [] ), + __( 'User-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_access'], 1 ), + __( 'Allowed users (titles)', 'classifai' ) => implode( ', ', $feature_settings['users'] ?? [] ), + __( 'User based opt-out', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_opt_out'], 1 ), + __( 'Provider', 'classifai' ) => $feature_settings['provider'], + ]; + + if ( method_exists( $provider, 'get_debug_information' ) ) { + $common_debug_info = array_merge( + $common_debug_info, + $provider->get_debug_information() + ); + } + + return $common_debug_info; + } + /** * Returns the data attribute string for an input. * diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 3d70c0713..c7bca7c2d 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -136,7 +136,7 @@ public function add_descriptive_text_generation_fields() { add_settings_field( static::ID . '_descriptive_confidence_threshold', - esc_html__( 'Descriptive text confidence threshold', 'classifai' ), + esc_html__( 'Confidence threshold', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', @@ -183,7 +183,7 @@ public function add_image_tags_generation_fields() { add_settings_field( static::ID . '_tag_confidence_threshold', - esc_html__( 'Tag confidence threshold', 'classifai' ), + esc_html__( 'Confidence threshold', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', @@ -1535,30 +1535,6 @@ public function image_text_extractor_permissions_check( $request ) { return true; } - /** - * Provides debug information related to the provider. - * - * @param null|array $settings Settings array. If empty, settings will be retrieved. - * @return array Keyed array of debug information. - * @since 1.4.0 - */ - public function get_provider_debug_information( $settings = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'API URL', 'classifai' ) => $settings['url'] ?? '', - __( 'Caption threshold', 'classifai' ) => $settings['caption_threshold'] ?? null, - __( 'Latest response - Image Scan', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_scan_latest_response' ) ), - __( 'Latest response - Smart Cropping', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_smart_cropping_latest_response' ) ), - __( 'Latest response - OCR', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_ocr_latest_response' ) ), - ]; - } - /** * Filter the SQL clauses of an attachment query to include tags and alt text. * @@ -1587,4 +1563,34 @@ public function filter_attachment_query_keywords( $clauses ) { return $clauses; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { + $descriptive_text = array_filter( + $provider_settings['descriptive_text_fields'], + function( $type ) { + return '0' !== $type; + } + ); + + $debug_info[ __( 'Generate descriptive text', 'classifai' ) ] = implode( ', ', $descriptive_text ); + $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['descriptive_confidence_threshold']; + } + + if ( $this->feature_instance instanceof ImageTagsGenerator ) { + $debug_info[ __( 'Tag taxonomy', 'classifai' ) ] = $provider_settings['tag_taxonomy']; + $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['tag_confidence_threshold']; + } + + return $debug_info; + } } diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index f9601f6b7..8baa52085 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -415,27 +415,6 @@ public function get_voices_select_options() { return $options; } - /** - * Provides debug information related to the provider. - * - * @param null|array $settings Settings array. If empty, settings will be retrieved. - * @param boolean $configured Whether the provider is correctly configured. If null, the option will be retrieved. - * @return array Keyed array of debug information. - */ - public function get_provider_debug_information( $settings = null, $configured = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'Yes', 'classifai' ) : __( 'No', 'classifai' ), - __( 'API URL', 'classifai' ) => $settings['url'] ?? '', - __( 'Latest response - Voices', 'classifai' ) => $this->get_formatted_latest_response( $this->get_settings( 'voices' ) ), - ]; - } - /** * Initial audio generation state. * @@ -1101,4 +1080,29 @@ public function speech_synthesis_permissions_check( WP_REST_Request $request ) { return false; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof TextToSpeech ) { + $post_types = array_filter( + $settings['post_types'], + function( $value ) { + return '0' !== $value; + } + ); + + $debug_info[ __( 'Allowed post types', 'classifai' ) ] = implode( ', ', $post_types ); + $debug_info[ __( 'Voice', 'classifai' ) ] = $provider_settings['voice']; + } + + return $debug_info; + } } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 0fefc8bf9..92d8d31cf 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -218,7 +218,7 @@ private function add_content_resizing_fields() { add_settings_field( static::ID . '_number_of_suggestions', - esc_html__( 'Number of titles', 'classifai' ), + esc_html__( 'Number of suggestions', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', @@ -352,13 +352,13 @@ public function sanitize_settings( $new_settings ) { } if ( $this->feature_instance instanceof ExcerptGeneration ) { - $new_settings[ static::ID ]['length'] = $this->sanitize_number_of_responses_field( 'length', $new_settings, $settings ); $new_settings[ static::ID ]['generate_excerpt_prompt'] = $this->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); } - + if ( $this->feature_instance instanceof ContentResizing ) { - $new_settings[ static::ID ]['condense_text_prompt'] = $this->sanitize_prompts( 'condense_text_prompt', $new_settings ); - $new_settings[ static::ID ]['expand_text_prompt'] = $this->sanitize_prompts( 'expand_text_prompt', $new_settings ); + $new_settings[ static::ID ]['number_of_suggestions'] = $this->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); + $new_settings[ static::ID ]['condense_text_prompt'] = $this->sanitize_prompts( 'condense_text_prompt', $new_settings ); + $new_settings[ static::ID ]['expand_text_prompt'] = $this->sanitize_prompts( 'expand_text_prompt', $new_settings ); } return $new_settings; @@ -608,36 +608,6 @@ public function register_generated_titles_template() { sanitize_settings( $this->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - $enable_excerpt = 1 === intval( $settings['enable_excerpt'] ?? 0 ); - $enable_titles = 1 === intval( $settings['enable_titles'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Generate excerpt', 'classifai' ) => $enable_excerpt ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Allowed roles (excerpt)', 'classifai' ) => implode( ', ', $settings['roles'] ?? [] ), - __( 'Excerpt length', 'classifai' ) => $settings['length'] ?? 55, - __( 'Generate titles', 'classifai' ) => $enable_titles ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Allowed roles (titles)', 'classifai' ) => implode( ', ', $settings['title_roles'] ?? [] ), - __( 'Number of titles', 'classifai' ) => absint( $settings['number_titles'] ?? 1 ), - __( 'Allowed roles (resize)', 'classifai' ) => implode( ', ', $settings['resize_content_roles'] ?? [] ), - __( 'Number of suggestions', 'classifai' ) => absint( $settings['number_resize_content'] ?? 1 ), - __( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_latest_response' ) ), - ]; - } - /** * Common entry point for all REST endpoints for this provider. * This is called by the Service. @@ -1428,4 +1398,29 @@ public function resize_post_content_permissions_check( WP_REST_Request $request return true; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof TitleGeneration ) { + $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles']; + $debug_info[ __( 'Generate title prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_title_prompt'] ); + } elseif ( $this->feature_instance instanceof ExcerptGeneration ) { + $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length']; + $debug_info[ __( 'Generate excerpt prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_excerpt_prompt'] ); + } elseif ( $this->feature_instance instanceof ContentResizing ) { + $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions']; + $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ); + $debug_info[ __( 'Condense text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['condense_text_prompt'] ); + } + + return $debug_info; + } } diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index c06629a9e..858a84237 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -400,31 +400,6 @@ public function reset_settings() { update_option( $this->get_option_name(), $this->get_default_settings() ); } - /** - * Provides debug information related to the provider. - * - * @param array|null $settings Settings array. If empty, settings will be retrieved. - * @param boolean $configured Whether the provider is correctly configured. If null, the option will be retrieved. - * @return string|array - */ - public function get_provider_debug_information( $settings = null, $configured = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - $enabled = 1 === intval( $settings['enable_image_gen'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Generate images', 'classifai' ) => $enabled ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Allowed roles', 'classifai' ) => implode( ', ', $settings['roles'] ?? [] ), - __( 'Number of images', 'classifai' ) => absint( $settings['number_of_images'] ?? 1 ), - __( 'Image size', 'classifai' ) => sanitize_text_field( $settings['image_size'] ?? '1024x1024' ), - __( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_openai_dalle_latest_response' ) ), - ]; - } - /** * Entry point for the generate-image REST endpoint. * diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 533f76216..35d33376e 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -265,32 +265,6 @@ public function sanitize_settings( $new_settings ) { return $new_settings; } - /** - * Provides debug information related to the provider. - * - * @param array|null $settings Settings array. If empty, settings will be retrieved. - * @param boolean $configured Whether the provider is correctly configured. If null, the option will be retrieved. - * @return string|array - */ - public function get_provider_debug_information( $settings = null, $configured = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( ( new Classification() )->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - $enable_classification = 1 === intval( $settings['enable_classification'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Classification enabled', 'classifai' ) => $enable_classification ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Post types', 'classifai' ) => implode( ', ', $settings['post_types'] ?? [] ), - __( 'Post statuses', 'classifai' ) => implode( ', ', $settings['post_statuses'] ?? [] ), - __( 'Taxonomies', 'classifai' ) => implode( ', ', $settings['taxonomies'] ?? [] ), - __( 'Number of terms', 'classifai' ) => $settings['number'] ?? 1, - __( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_openai_embeddings_latest_response' ) ), - ]; - } - /** * The list of supported post types. * @@ -1009,4 +983,25 @@ public function render_threshold_field( $args, $option_value, $value ) { $value ? esc_attr( $value ) : 75 ); } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings( static::ID ); + + return [ + __( 'Number of titles', 'classifai' ) => $settings['number_of_titles'] ?? 1, + __( 'Taxonomy (category)', 'classifai' ) => $settings['taxonomies']['category'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), + __( 'Taxonomy (category threshold)', 'classifai' ) => $settings['taxonomies']['category_threshold'], + __( 'Taxonomy (tag)', 'classifai' ) => $settings['taxonomies']['post_tag'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), + __( 'Taxonomy (tag threshold)', 'classifai' ) => $settings['taxonomies']['post_tag_threshold'], + __( 'Taxonomy (format)', 'classifai' ) => $settings['taxonomies']['post_format'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), + __( 'Taxonomy (format threshold)', 'classifai' ) => $settings['taxonomies']['post_format_threshold'], + __( 'Taxonomy (image tag)', 'classifai' ) => $settings['taxonomies']['classifai-image-tags'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), + __( 'Taxonomy (image tag threshold)', 'classifai' ) => $settings['taxonomies']['classifai-image-tags_threshold'], + ]; + } } diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index c027d1d24..076c2a387 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -276,29 +276,6 @@ public function maybe_transcribe_audio( $attachment_id ) { } } - /** - * Provides debug information related to the provider. - * - * @param array|null $settings Settings array. If empty, settings will be retrieved. - * @param boolean $configured Whether the provider is correctly configured. If null, the option will be retrieved. - * @return string|array - */ - public function get_provider_debug_information( $settings = null, $configured = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); - } - - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - $enable_transcript = 1 === intval( $settings['enable_transcripts'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Generate transcripts', 'classifai' ) => $enable_transcript ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'Allowed roles', 'classifai' ) => implode( ', ', $settings['roles'] ?? [] ), - __( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_openai_whisper_latest_response' ) ), - ]; - } - /** * Register REST endpoints for this provider. * @@ -366,4 +343,15 @@ public function generate_audio_transcript_permissions_check( WP_REST_Request $re return true; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings( static::ID ); + + return []; + } } diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 9d6472c3a..8d0d6de26 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -562,43 +562,6 @@ public function sanitize_settings( $new_settings ) { return $new_settings; } - /** - * Provides debug information related to the provider. - * - * @param array|null $settings Settings array. If empty, settings will be retrieved. - * @param boolean $configured Whether the provider is correctly configured. If null, the option will be retrieved. - * @return string|array - * @since 1.4.0 - */ - public function get_provider_debug_information( $settings = null, $configured = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); - } - - if ( is_null( $configured ) ) { - $configured = get_option( 'classifai_configured' ); - } - - $settings_post_types = $settings['post_types'] ?? []; - $post_types = array_filter( - array_keys( $settings_post_types ), - function( $post_type ) use ( $settings_post_types ) { - return 1 === intval( $settings_post_types[ $post_type ] ); - } - ); - - $credentials = $settings['credentials'] ?? []; - - return [ - __( 'Configured', 'classifai' ) => $configured ? __( 'yes', 'classifai' ) : __( 'no', 'classifai' ), - __( 'API URL', 'classifai' ) => $credentials['watson_url'] ?? '', - __( 'API username', 'classifai' ) => $credentials['watson_username'] ?? '', - __( 'Post types', 'classifai' ) => implode( ', ', $post_types ), - __( 'Features', 'classifai' ) => preg_replace( '/,"/', ', "', wp_json_encode( $settings['features'] ?? '' ) ), - __( 'Latest response', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_watson_nlu_latest_response' ) ), - ]; - } - /** * Format the result of most recent request. * diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 877b17e63..166c05c75 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -272,14 +272,14 @@ public function add_service_debug_information( $debug_information ) { * @since 1.4.0 */ public function get_service_debug_information() { - $make_line = function( $provider ) { + $make_line = function( $feature ) { return [ - 'label' => sprintf( '%s: %s', $this->get_display_name(), $provider->get_provider_name() ), - 'value' => $provider->get_provider_debug_information(), + 'label' => sprintf( '%s', $feature->get_label() ), + 'value' => $feature->get_debug_information(), ]; }; - return array_map( $make_line, $this->provider_classes ); + return array_map( $make_line, $this->feature_classes ); } /** From 2c3242a752526772bab156077c5511e233ed094e Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 2 Jan 2024 19:45:24 +0530 Subject: [PATCH 068/127] add latest response --- includes/Classifai/Features/Feature.php | 8 +++- .../Providers/Azure/ComputerVision.php | 30 +++++++++++-- includes/Classifai/Providers/Azure/Read.php | 2 + .../Providers/Azure/SmartCropping.php | 3 +- includes/Classifai/Providers/Azure/Speech.php | 12 ++++-- .../Classifai/Providers/OpenAI/ChatGPT.php | 16 +++++-- includes/Classifai/Providers/OpenAI/DallE.php | 23 ++++++++++ .../Classifai/Providers/OpenAI/Embeddings.php | 34 +++++++++------ .../Classifai/Providers/OpenAI/Whisper.php | 15 ++++++- includes/Classifai/Providers/Watson/NLU.php | 42 +++++++++++++++++-- 10 files changed, 155 insertions(+), 30 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index f34b5e2f2..1ef32ca1d 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -485,13 +485,17 @@ function( $role ) { ]; if ( method_exists( $provider, 'get_debug_information' ) ) { - $common_debug_info = array_merge( + $all_debug_info = array_merge( $common_debug_info, $provider->get_debug_information() ); } - return $common_debug_info; + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $all_debug_info, + $this, + ); } /** diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index c7bca7c2d..ff62abe54 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -497,7 +497,7 @@ public function attachment_data_meta_box( $post ) { $captions = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ? __( 'No descriptive text? Rescan image', 'classifai' ) : __( 'Generate descriptive text', 'classifai' ); $tags = ! empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Rescan image for new tags', 'classifai' ) : __( 'Generate image tags', 'classifai' ); $ocr = get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ? __( 'Rescan for text', 'classifai' ) : __( 'Scan image for text', 'classifai' ); - $smart_crop = get_transient( 'classifai_azure_computer_vision_smart_cropping_latest_response' ) ? __( 'Regenerate smart thumbnail', 'classifai' ) : __( 'Create smart thumbnail', 'classifai' ); + $smart_crop = get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ? __( 'Regenerate smart thumbnail', 'classifai' ) : __( 'Create smart thumbnail', 'classifai' ); ?>
    @@ -621,7 +621,7 @@ public function add_rescan_button_to_media_modal( $form_fields, $post ) { // Smart crop. if ( $smart_crop->is_feature_enabled() && wp_attachment_is_image( $post ) ) { - $smart_crop_text = empty( get_transient( 'classifai_azure_computer_vision_smart_cropping_latest_response' ) ) ? __( 'Generate', 'classifai' ) : __( 'Regenerate', 'classifai' ); + $smart_crop_text = empty( get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ) ? __( 'Generate', 'classifai' ) : __( 'Regenerate', 'classifai' ); $form_fields['rescan_smart_crop'] = [ 'label' => __( 'Smart thumbnail', 'classifai' ), 'input' => 'html', @@ -885,6 +885,8 @@ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bo $ocr = new OCR( $settings, $scan, $force ); $response = $ocr->generate_ocr_data( $metadata, $attachment_id ); + set_transient( 'classifai_azure_computer_vision_image_text_extraction_latest_response', $scan, DAY_IN_SECONDS * 30 ); + if ( $force ) { return $response; } @@ -984,6 +986,8 @@ public function generate_alt_tags( $attachment_id ) { $details = $this->scan_image( $image_url, $feature ); $captions = isset( $details->description->captions ) ? $details->description->captions : []; + set_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response', $details, DAY_IN_SECONDS * 30 ); + // Don't save tags if feature is disabled or user don't have access to use it. if ( ! $this->is_feature_enabled( 'image_captions' ) ) { return new WP_Error( 'invalid_settings', esc_html__( 'Image descriptive text feature is disabled.', 'classifai' ) ); @@ -1118,6 +1122,8 @@ public function generate_image_tags( $attachment_id ) { $details = $this->scan_image( $image_url, $feature ); $tags = isset( $details->tags ) ? $details->tags : []; + set_transient( 'classifai_azure_computer_vision_image_tags_latest_response', $details, DAY_IN_SECONDS * 30 ); + /** * Filter the tags returned from the API. * @@ -1584,13 +1590,31 @@ function( $type ) { $debug_info[ __( 'Generate descriptive text', 'classifai' ) ] = implode( ', ', $descriptive_text ); $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['descriptive_confidence_threshold']; + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response' ) ); } if ( $this->feature_instance instanceof ImageTagsGenerator ) { $debug_info[ __( 'Tag taxonomy', 'classifai' ) ] = $provider_settings['tag_taxonomy']; $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['tag_confidence_threshold']; + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_tags_latest_response' ) ); + } + + if ( $this->feature_instance instanceof ImageCropping ) { + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ); } - return $debug_info; + if ( $this->feature_instance instanceof ImageTextExtraction ) { + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_text_extraction_latest_response' ) ); + } + + if ( $this->feature_instance instanceof PDFTextExtraction ) { + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_pdf_text_extraction_check_result_latest_response' ) ); + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + ); } } diff --git a/includes/Classifai/Providers/Azure/Read.php b/includes/Classifai/Providers/Azure/Read.php index 5ff006850..2b7ace184 100644 --- a/includes/Classifai/Providers/Azure/Read.php +++ b/includes/Classifai/Providers/Azure/Read.php @@ -238,6 +238,8 @@ public function check_read_result( $operation_url ) { ); } + set_transient( 'classifai_azure_computer_vision_pdf_text_extraction_check_result_latest_response', $response, DAY_IN_SECONDS * 30 ); + if ( is_wp_error( $response ) ) { return $response; } diff --git a/includes/Classifai/Providers/Azure/SmartCropping.php b/includes/Classifai/Providers/Azure/SmartCropping.php index c46f86419..8333e1430 100644 --- a/includes/Classifai/Providers/Azure/SmartCropping.php +++ b/includes/Classifai/Providers/Azure/SmartCropping.php @@ -235,7 +235,8 @@ public function get_cropped_thumbnail( $attachment_id, $size_data ) { ]; $new_thumb_image = $this->request_cropped_thumbnail( $data ); - set_transient( 'classifai_azure_computer_vision_smart_cropping_latest_response', $new_thumb_image, DAY_IN_SECONDS * 30 ); + + set_transient( 'classifai_azure_computer_vision_image_cropping_latest_response', $new_thumb_image, DAY_IN_SECONDS * 30 ); if ( is_wp_error( $new_thumb_image ) ) { return $new_thumb_image; diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 8baa52085..8cbf9256e 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -1099,10 +1099,16 @@ function( $value ) { } ); - $debug_info[ __( 'Allowed post types', 'classifai' ) ] = implode( ', ', $post_types ); - $debug_info[ __( 'Voice', 'classifai' ) ] = $provider_settings['voice']; + $debug_info[ __( 'Allowed post types', 'classifai' ) ] = implode( ', ', $post_types ); + $debug_info[ __( 'Voice', 'classifai' ) ] = $provider_settings['voice']; + $debug_info[ __( 'Latest response - Voices', 'classifai' ) ] = $this->get_formatted_latest_response( $provider_settings['voices'] ); } - return $debug_info; + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); } } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 92d8d31cf..382f495e7 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -732,7 +732,7 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { ] ); - set_transient( 'classifai_openai_chatgpt_latest_response', $response, DAY_IN_SECONDS * 30 ); + set_transient( 'classifai_openai_chatgpt_excerpt_generation_latest_response', $response, DAY_IN_SECONDS * 30 ); // Extract out the text response, if it exists. if ( ! is_wp_error( $response ) && ! empty( $response['choices'] ) ) { @@ -832,7 +832,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { ] ); - set_transient( 'classifai_openai_chatgpt_latest_response', $response, DAY_IN_SECONDS * 30 ); + set_transient( 'classifai_openai_chatgpt_title_generation_latest_response', $response, DAY_IN_SECONDS * 30 ); if ( is_wp_error( $response ) ) { return $response; @@ -937,7 +937,7 @@ public function resize_content( int $post_id, array $args = array() ) { ] ); - set_transient( 'classifai_openai_chatgpt_latest_response', $response, DAY_IN_SECONDS * 30 ); + set_transient( 'classifai_openai_chatgpt_content_resizing_latest_response', $response, DAY_IN_SECONDS * 30 ); if ( is_wp_error( $response ) ) { return $response; @@ -1412,15 +1412,23 @@ public function get_debug_information() { if ( $this->feature_instance instanceof TitleGeneration ) { $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles']; $debug_info[ __( 'Generate title prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_title_prompt'] ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_title_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ExcerptGeneration ) { $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length']; $debug_info[ __( 'Generate excerpt prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_excerpt_prompt'] ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_excerpt_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ContentResizing ) { $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions']; $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ); $debug_info[ __( 'Condense text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['condense_text_prompt'] ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_content_resizing_latest_response' ) ); } - return $debug_info; + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); } } diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 858a84237..6f643ff32 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -589,4 +589,27 @@ public function generate_image_permissions_check() { return true; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof ImageGeneration ) { + $debug_info[ __( 'Number of images', 'classifai' ) ] = $provider_settings['number_of_images']; + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_dalle_latest_response' ) ); + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); + } } diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 35d33376e..10788a8ab 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -990,18 +990,28 @@ public function render_threshold_field( $args, $option_value, $value ) { * @return array */ public function get_debug_information() { - $settings = $this->feature_instance->get_settings( static::ID ); + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; - return [ - __( 'Number of titles', 'classifai' ) => $settings['number_of_titles'] ?? 1, - __( 'Taxonomy (category)', 'classifai' ) => $settings['taxonomies']['category'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), - __( 'Taxonomy (category threshold)', 'classifai' ) => $settings['taxonomies']['category_threshold'], - __( 'Taxonomy (tag)', 'classifai' ) => $settings['taxonomies']['post_tag'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), - __( 'Taxonomy (tag threshold)', 'classifai' ) => $settings['taxonomies']['post_tag_threshold'], - __( 'Taxonomy (format)', 'classifai' ) => $settings['taxonomies']['post_format'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), - __( 'Taxonomy (format threshold)', 'classifai' ) => $settings['taxonomies']['post_format_threshold'], - __( 'Taxonomy (image tag)', 'classifai' ) => $settings['taxonomies']['classifai-image-tags'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ), - __( 'Taxonomy (image tag threshold)', 'classifai' ) => $settings['taxonomies']['classifai-image-tags_threshold'], - ]; + if ( $this->feature_instance instanceof Classification ) { + $debug_info[ __( 'Number of titles', 'classifai' ) ] = $provider_settings['number_of_titles'] ?? 1; + $debug_info[ __( 'Taxonomy (category)', 'classifai' ) ] = $provider_settings['taxonomies']['category'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + $debug_info[ __( 'Taxonomy (category threshold)', 'classifai' ) ] = $provider_settings['taxonomies']['category_threshold']; + $debug_info[ __( 'Taxonomy (tag)', 'classifai' ) ] = $provider_settings['taxonomies']['post_tag'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + $debug_info[ __( 'Taxonomy (tag threshold)', 'classifai' ) ] = $provider_settings['taxonomies']['post_tag_threshold']; + $debug_info[ __( 'Taxonomy (format)', 'classifai' ) ] = $provider_settings['taxonomies']['post_format'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + $debug_info[ __( 'Taxonomy (format threshold)', 'classifai' ) ] = $provider_settings['taxonomies']['post_format_threshold']; + $debug_info[ __( 'Taxonomy (image tag)', 'classifai' ) ] = $provider_settings['taxonomies']['classifai-image-tags'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + $debug_info[ __( 'Taxonomy (image tag threshold)', 'classifai' ) ] = $provider_settings['taxonomies']['classifai-image-tags_threshold']; + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_embeddings_latest_response' ) ); + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); } } diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 076c2a387..e4c1662e7 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -350,8 +350,19 @@ public function generate_audio_transcript_permissions_check( WP_REST_Request $re * @return array */ public function get_debug_information() { - $settings = $this->feature_instance->get_settings( static::ID ); + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; - return []; + if ( $this->feature_instance instanceof AudioTranscriptsGeneration ) { + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_whisper_latest_response' ) ); + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); } } diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 8d0d6de26..f71c4cbb1 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -10,9 +10,7 @@ use Classifai\Providers\Provider; use Classifai\Taxonomy\TaxonomyFactory; use Classifai\Features\Classification; -use function Classifai\get_plugin_settings; -use function Classifai\get_post_types_for_language_settings; -use function Classifai\get_post_statuses_for_language_settings; +use Classifai\Features\Feature; use function Classifai\get_asset_info; use function Classifai\check_term_permissions; use WP_Error; @@ -993,4 +991,42 @@ public function classify( $post_id ) { return $output; } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof Classification ) { + $debug_info[ __( 'Category (status)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['category'], 1 ); + $debug_info[ __( 'Category (threshold)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['category_threshold'], 1 ); + $debug_info[ __( 'Category (taxonomy)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['category_taxonomy'], 1 ); + + $debug_info[ __( 'Keyword (status)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['keyword'], 1 ); + $debug_info[ __( 'Keyword (threshold)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['keyword_threshold'], 1 ); + $debug_info[ __( 'Keyword (taxonomy)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['keyword_taxonomy'], 1 ); + + $debug_info[ __( 'Entity (status)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['entity'], 1 ); + $debug_info[ __( 'Entity (threshold)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['entity_threshold'], 1 ); + $debug_info[ __( 'Entity (taxonomy)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['entity_taxonomy'], 1 ); + + $debug_info[ __( 'Concept (status)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['concept'], 1 ); + $debug_info[ __( 'Concept (threshold)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['concept_threshold'], 1 ); + $debug_info[ __( 'Concept (taxonomy)', 'classifai' ) ] = Feature::get_debug_value_text( $provider_settings['concept_taxonomy'], 1 ); + + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_watson_nlu_latest_response' ) ); + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); + } } From 1e22f5184686fd0112f985989b6ed3c8b35d7930 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 2 Jan 2024 21:45:40 +0530 Subject: [PATCH 069/127] fix embeddings helper methods and CLI support --- .../Classifai/Command/ClassifaiCommand.php | 15 +++++++++----- .../Classifai/Features/Classification.php | 2 +- .../Classifai/Providers/OpenAI/Embeddings.php | 10 +++++----- .../Classifai/Providers/OpenAI/OpenAI.php | 20 ++++++++++++------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index b7cfcfe5d..7a1455eef 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -2,8 +2,8 @@ namespace Classifai\Command; -use Classifai\Admin\SavePostHandler; use Classifai\Features\AudioTranscriptsGeneration; +use Classifai\Features\Classification; use Classifai\Features\ExcerptGeneration; use Classifai\Features\ImageCropping; use Classifai\Features\TextToSpeech; @@ -13,10 +13,8 @@ use Classifai\PostClassifier; use Classifai\Providers\Azure\ComputerVision; use Classifai\Providers\Azure\SmartCropping; -use Classifai\Providers\Azure\Speech; use Classifai\Providers\OpenAI\Whisper; use Classifai\Providers\OpenAI\Whisper\Transcribe; -use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Providers\OpenAI\Embeddings; use WP_Error; @@ -954,6 +952,13 @@ public function embeddings( $args = [], $opts = [] ) { 'per_page' => 100, ]; + $feature = new Classification(); + $provider = $feature->get_feature_provider_instance(); + + if ( Embeddings::ID !== $provider::ID ) { + \WP_CLI::error( 'This command is only available for the OpenAI Embeddings feature' ); + } + $embeddings = new Embeddings( false ); $opts = wp_parse_args( $opts, $defaults ); $opts['per_page'] = (int) $opts['per_page'] > 0 ? $opts['per_page'] : 100; @@ -1009,7 +1014,7 @@ public function embeddings( $args = [], $opts = [] ) { foreach ( $posts as $post_id ) { if ( ! $dry_run ) { - $result = $embeddings->generate_embeddings_for_post( $post_id ); + $result = $feature->run( $post_id ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %s', $post_id ), false ); @@ -1057,7 +1062,7 @@ public function embeddings( $args = [], $opts = [] ) { } if ( ! $dry_run ) { - $result = $embeddings->generate_embeddings_for_post( $post_id ); + $result = $feature->run( $post_id ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %s', $post_id ), false ); diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index b70acb19f..4d0dbd55d 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -227,7 +227,7 @@ public function run( ...$args ) { } else if ( Embeddings::ID === $provider_instance::ID ) { /** @var Embeddings $provider_instance */ $result = call_user_func_array( - [ $provider_instance, 'classify_post' ], + [ $provider_instance, 'generate_embeddings_for_post' ], [ ...$args ] ); } diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 10788a8ab..3ae46f9b2 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -281,7 +281,7 @@ public function supported_post_types() { * * @return {array} Array of post types. */ - return apply_filters( 'classifai_openai_embeddings_post_types', $this->get_supported_post_types() ); + return apply_filters( 'classifai_openai_embeddings_post_types', $this->get_supported_post_types( new Classification() ) ); } /** @@ -333,7 +333,7 @@ public function supported_post_statuses() { * * @return {array} Array of post statuses. */ - return apply_filters( 'classifai_openai_embeddings_post_statuses', $this->get_supported_post_statuses() ); + return apply_filters( 'classifai_openai_embeddings_post_statuses', $this->get_supported_post_statuses( new Classification() ) ); } /** @@ -352,7 +352,7 @@ public function supported_taxonomies() { * * @return {array} Array of taxonomies. */ - return apply_filters( 'classifai_openai_embeddings_taxonomies', $this->get_supported_taxonomies() ); + return apply_filters( 'classifai_openai_embeddings_taxonomies', $this->get_supported_taxonomies( new Classification() ) ); } /** @@ -779,7 +779,7 @@ public function get_content( int $id = 0, string $type = 'post' ) { * Add `classifai_process_content` to the REST API for view/edit. */ public function add_process_content_meta_to_rest_api() { - $supported_post_types = $this->supported_post_types(); + $supported_post_types = $this->supported_post_types( new Classification() ); register_rest_field( $supported_post_types, @@ -807,7 +807,7 @@ public function add_process_content_meta_to_rest_api() { * @param string $post_type Post type name. */ public function add_metabox( $post_type ) { - if ( ! in_array( $post_type, $this->get_supported_post_types(), true ) ) { + if ( ! in_array( $post_type, $this->get_supported_post_types( new Classification() ), true ) ) { return; } diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index 546ed3ed4..ec3fe1213 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -239,10 +239,12 @@ public function get_taxonomies_for_settings() { /** * The list of supported post types. * - * return array + * @param \Classifai\Features\Feature $feature + * + * @return array */ - public function get_supported_post_types() { - $settings = $this->get_settings(); + public function get_supported_post_types( $feature ) { + $settings = $feature->get_settings(); $post_types = []; if ( ! empty( $settings ) && isset( $settings['post_types'] ) ) { @@ -259,10 +261,12 @@ public function get_supported_post_types() { /** * The list of supported post statuses. * + * @param \Classifai\Features\Feature $feature + * * @return array */ - public function get_supported_post_statuses() { - $settings = $this->get_settings(); + public function get_supported_post_statuses( $feature ) { + $settings = $feature->get_settings(); $post_statuses = []; if ( ! empty( $settings ) && isset( $settings['post_statuses'] ) ) { @@ -279,10 +283,12 @@ public function get_supported_post_statuses() { /** * The list of supported taxonomies. * + * @param \Classifai\Features\Feature $feature + * * @return array */ - public function get_supported_taxonomies() { - $settings = $this->get_settings(); + public function get_supported_taxonomies( $feature ) { + $settings = $feature->get_settings(); $taxonomies = []; if ( ! empty( $settings ) && isset( $settings['taxonomies'] ) ) { From d44866ee92006e781dce484d7e99d846635168fe Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 4 Jan 2024 17:30:35 +0530 Subject: [PATCH 070/127] Add "Classify" bulk action. --- includes/Classifai/Admin/BulkActions.php | 23 +++++++++++++++++++++++ includes/Classifai/Admin/UserProfile.php | 2 +- includes/Classifai/Providers/Provider.php | 9 +++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index 852b7e041..dd4a7658f 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -2,6 +2,7 @@ namespace Classifai\Admin; use Classifai\Features\AudioTranscriptsGeneration; +use Classifai\Features\Classification; use Classifai\Features\DescriptiveTextGenerator; use Classifai\Features\ExcerptGeneration; use Classifai\Features\ImageCropping; @@ -54,6 +55,7 @@ public function register() { */ public function register_language_processing_hooks() { $this->language_processing_features = [ + new Classification(), new ExcerptGeneration(), new TextToSpeech(), ]; @@ -98,6 +100,10 @@ public function register_language_processing_actions( $bulk_actions ) { $bulk_actions[ $feature::ID ] = $feature->get_label(); switch ( $feature::ID ) { + case Classification::ID: + $bulk_actions[ $feature::ID ] = esc_html__( 'Classify', 'classifai' ); + break; + case ExcerptGeneration::ID: $bulk_actions[ $feature::ID ] = esc_html__( 'Generate Excerpt', 'classifai' ); break; @@ -137,6 +143,11 @@ function( $feature ) { foreach ( $post_ids as $post_id ) { switch ( $doaction ) { + case Classification::ID: + ( new Classification() )->run( $post_id ); + $action = $doaction; + break; + case ExcerptGeneration::ID: $excerpt = ( new ExcerptGeneration() )->run( $post_id ); $action = $doaction; @@ -186,6 +197,14 @@ public function register_language_processing_row_action( $actions, $post ) { } switch ( $feature::ID ) { + case Classification::ID: + $actions[ Classification::ID ] = sprintf( + '%s', + esc_url( wp_nonce_url( admin_url( sprintf( 'edit.php?action=%s&ids=%d&post_type=%s', Classification::ID, $post->ID, $post->post_type ) ), 'bulk-posts' ) ), + esc_html__( 'Classify', 'classifai' ) + ); + break; + case ExcerptGeneration::ID: $actions[ ExcerptGeneration::ID ] = sprintf( '%s', @@ -476,6 +495,10 @@ function( $feature ) { $post_type = 'file'; break; + case Classification::ID: + $action_text = __( 'Classification done for', 'classifai' ); + break; + } $output = '

    '; diff --git a/includes/Classifai/Admin/UserProfile.php b/includes/Classifai/Admin/UserProfile.php index 8a30f05a5..2c34f1c84 100644 --- a/includes/Classifai/Admin/UserProfile.php +++ b/includes/Classifai/Admin/UserProfile.php @@ -139,7 +139,7 @@ public function get_allowed_features( $user_id ) { } foreach ( $service_class->feature_classes as $feature_class ) { - if ( ! $feature_class instanceof Feature ) { + if ( ! $feature_class instanceof Feature || ! $feature_class->is_enabled() ) { continue; } diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 8661ede97..c5e7640f0 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -134,6 +134,15 @@ public function get_settings( $index = false ) { return $settings; } + /** + * Default settings for Provider. + * + * @return array + */ + public function get_default_settings() { + return []; + } + /** * Common entry point for all REST endpoints for this provider. * This is called by the Service. From d43049499f567d46307dc03b4a993f9b47c3fe2c Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 4 Jan 2024 19:20:01 +0530 Subject: [PATCH 071/127] Fix prompt trash error. --- includes/Classifai/Providers/OpenAI/ChatGPT.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 382f495e7..b25bde170 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -354,7 +354,7 @@ public function sanitize_settings( $new_settings ) { if ( $this->feature_instance instanceof ExcerptGeneration ) { $new_settings[ static::ID ]['generate_excerpt_prompt'] = $this->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); } - + if ( $this->feature_instance instanceof ContentResizing ) { $new_settings[ static::ID ]['number_of_suggestions'] = $this->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); $new_settings[ static::ID ]['condense_text_prompt'] = $this->sanitize_prompts( 'condense_text_prompt', $new_settings ); @@ -485,13 +485,19 @@ public function enqueue_editor_assets() { * @param string $hook_suffix The current admin page. */ public function enqueue_admin_assets( string $hook_suffix ) { - // Load asset in OpenAI ChatGPT settings page. + $prompt_features = array( + 'feature_title_generation', + 'feature_excerpt_generation', + 'feature_content_resizing', + ); + + // Load jQuery UI Dialog for prompt deletion. if ( ( 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['provider'] ) ) // phpcs:ignore - && 'language_processing' === $_GET['tab'] // phpcs:ignore - && 'openai_chatgpt' === $_GET['provider'] // phpcs:ignore + && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && in_array( $_GET['feature'], $prompt_features, true ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ) || 'admin_page_classifai_setup' === $hook_suffix ) { From 66ebf1330951fa7b972e3e4551e16bbe8cb54fdc Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 7 Jan 2024 21:24:27 +0530 Subject: [PATCH 072/127] update e2e test to support feature-first refactor --- .wp-env.json | 1 - includes/Classifai/Features/Feature.php | 2 +- .../Features/ImageTextExtraction.php | 2 +- includes/Classifai/Plugin.php | 12 + .../Providers/Azure/ComputerVision.php | 33 ++- .../admin/common-feature-fields.test.js | 61 ++++++ .../image-generation-openai-dalle.test.js | 58 +++-- .../image-processing-microsoft-azure.test.js | 206 +++++++++--------- .../image-processing/pdf-read.test.js | 48 ++-- .../classify-content-ibm-watson.test.js | 34 +-- ...lassify-content-openapi-embeddings.test.js | 81 +++---- ...excerpt-generation-openapi-chatgpt.test.js | 83 +++---- .../resize_content-openapi-chatgpt.test.js | 91 ++++---- .../speech-to-text-openapi-whisper.test.js | 48 ++-- .../text-to-speech-microsoft-azure.test.js | 54 +++-- .../title-generation-openapi-chatgpt.test.js | 74 +++---- tests/cypress/support/commands.js | 59 ++--- 17 files changed, 515 insertions(+), 432 deletions(-) create mode 100644 tests/cypress/integration/admin/common-feature-fields.test.js diff --git a/.wp-env.json b/.wp-env.json index 4b8b9bcb5..d3be216b9 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,5 +1,4 @@ { - "core": "WordPress/WordPress#6.1", "plugins": [".", "./tests/test-plugin", "https://downloads.wordpress.org/plugin/classic-editor.zip"], "env": { "tests": { diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 1ef32ca1d..ca777a68c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -82,7 +82,7 @@ abstract protected function setup_fields_sections(); protected function get_default_settings() { return [ 'status' => '0', - 'role_based_access' => 'no', + 'role_based_access' => '1', 'roles' => array_combine( array_keys( $this->roles ), array_keys( $this->roles ) ), 'user_based_access' => 'no', 'users' => [], diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index ebcce659d..8b17a76a8 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -14,7 +14,7 @@ class ImageTextExtraction extends Feature { * * @var string */ - const ID = 'feature_image_to_text_generation'; + const ID = 'feature_image_to_text_generator'; /** * Constructor. diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index c45544f02..4844f6094 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -39,6 +39,15 @@ public function enable() { add_action( 'admin_init', [ $this, 'add_privacy_policy_content' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_filter( 'plugin_action_links_' . CLASSIFAI_PLUGIN_BASENAME, array( $this, 'filter_plugin_action_links' ) ); + add_action( + 'admin_footer', + static function () { + printf( + '

    ', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + ); } /** @@ -178,6 +187,9 @@ public function enqueue_admin_assets( $hook_suffix ) { 'all' ); + wp_enqueue_script( 'jquery-ui-dialog' ); + wp_enqueue_style( 'wp-jquery-ui-dialog' ); + wp_enqueue_script( 'classifai-admin-script', CLASSIFAI_PLUGIN_URL . 'dist/admin.js', diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index ff62abe54..6e81dec96 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -353,6 +353,7 @@ public function register() { } if ( ( new ImageTextExtraction() )->is_feature_enabled() ) { + add_filter( 'wp_generate_attachment_metadata', [ $this, 'perform_ocr_processing' ], 8, 2 ); add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); add_filter( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); } @@ -745,7 +746,7 @@ public function maybe_rescan_image( $attachment_id ) { // Are we updating the OCR text? if ( clean_input( 'rescan-ocr' ) ) { $feature = new ImageTextExtraction(); - $this->ocr_processing( wp_get_attachment_metadata( $attachment_id ), $attachment_id, true ); + $feature->run( wp_get_attachment_metadata( $attachment_id ), $attachment_id, true ); } } @@ -760,6 +761,10 @@ public function maybe_rescan_image( $attachment_id ) { * @return array Filtered attachment metadata. */ public function smart_crop_image( $metadata, $attachment_id ) { + if ( ! wp_attachment_is_image( $attachment_id ) ) { + return $metadata; + } + $feature = new ImageCropping(); $settings = $feature->get_settings( static::ID ); @@ -809,6 +814,10 @@ public function smart_crop_image( $metadata, $attachment_id ) { * @return mixed */ public function generate_image_alt_tags( $metadata, $attachment_id ) { + if ( ! wp_attachment_is_image( $attachment_id ) ) { + return $metadata; + } + $feature = new ImageTagsGenerator(); if ( $feature->is_feature_enabled() ) { @@ -841,6 +850,22 @@ public function generate_image_alt_tags( $metadata, $attachment_id ) { return $metadata; } + /** + * Performs OCR processing on an image. + * + * @param array $metadata The metadata for the image. + * @param int $attachment_id Post ID for the attachment. + * + * @return void + */ + public function perform_ocr_processing( $metadata, $attachment_id ) { + if ( ! wp_attachment_is_image( $attachment_id ) ) { + return $metadata; + } + + return ( new ImageTextExtraction() )->run( $metadata, $attachment_id, true ); + } + /** * Runs text recognition on the attachment. * @@ -854,6 +879,10 @@ public function generate_image_alt_tags( $metadata, $attachment_id ) { * @return array Filtered attachment metadata. */ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bool $force = false ) { + if ( ! wp_attachment_is_image( $attachment_id ) ) { + return $metadata; + } + $feature = new ImageTextExtraction(); $settings = $feature->get_settings( static::ID ); @@ -989,7 +1018,7 @@ public function generate_alt_tags( $attachment_id ) { set_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response', $details, DAY_IN_SECONDS * 30 ); // Don't save tags if feature is disabled or user don't have access to use it. - if ( ! $this->is_feature_enabled( 'image_captions' ) ) { + if ( ! $feature->is_feature_enabled() ) { return new WP_Error( 'invalid_settings', esc_html__( 'Image descriptive text feature is disabled.', 'classifai' ) ); } diff --git a/tests/cypress/integration/admin/common-feature-fields.test.js b/tests/cypress/integration/admin/common-feature-fields.test.js new file mode 100644 index 000000000..954702d25 --- /dev/null +++ b/tests/cypress/integration/admin/common-feature-fields.test.js @@ -0,0 +1,61 @@ +describe('Common Feature Fields', () => { + beforeEach( () => { + cy.login(); + } ); + + const features = { + 'feature_classification': 'Classification', + 'feature_title_generation': 'Title Generation', + 'feature_excerpt_generation': 'Excerpt Generation', + 'feature_content_resizing': 'Content Resizing', + 'feature_text_to_speech_generation': 'Text to Speech', + 'feature_audio_transcripts_generation': 'Audio Transcripts Generation', + 'feature_image_generation': 'Image Generation', + 'feature_descriptive_text_generator': 'Descriptive Text Generator', + 'feature_image_tags_generator': 'Image Tags Generator', + 'feature_image_cropping': 'Image Cropping', + 'feature_image_to_text_generator': 'Image Text Extraction', + 'feature_pdf_to_text_generation': 'PDF Text Extraction', + }; + + const allowedRoles = [ + 'administrator', + 'editor', + 'author', + 'contributor', + 'subscriber', + ]; + + Object.keys( features ).forEach( ( feature ) => { + it( `"${ features[feature] }" feature common fields`, () => { + cy.visit( `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${feature}` ); + + cy.get( '#status' ).should( 'have.attr', 'name', `classifai_${ feature }[status]` ); + cy.get( '#role_based_access' ).should( 'have.attr', 'name', `classifai_${ feature }[role_based_access]` ); + cy.get( '#user_based_access' ).should( 'have.attr', 'name', `classifai_${ feature }[user_based_access]` ); + cy.get( '#user_based_opt_out' ).should( 'have.attr', 'name', `classifai_${ feature }[user_based_opt_out]` ); + cy.get( '#provider' ).should( 'have.attr', 'name', `classifai_${ feature }[provider]` ); + cy.get( '#role_based_access' ).check(); + + for ( let role of allowedRoles ) { + if ( 'feature_image_generation' === feature && ( 'contributor' === role || 'subscriber' === role ) ) { + continue; + } + + const roleField = cy.get( `#classifai_${ feature }_roles_${ role }` ); + roleField.should( 'be.visible' ); + roleField.should( 'have.value', role ); + roleField.should( 'have.attr', 'name', `classifai_${ feature }[roles][${ role }]` ); + } + + cy.get( '#role_based_access' ).uncheck(); + cy.get( '.allowed_roles_row' ).should( 'not.be.visible' ); + + cy.get( '#user_based_access' ).check(); + cy.get( '.allowed_users_row' ).should( 'be.visible' ); + + cy.get( '#user_based_access' ).uncheck(); + cy.get( '.allowed_users_row' ).should( 'not.be.visible' ); + } ); + } ); +}); diff --git a/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js b/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js index be4a2efc4..70a61ca79 100644 --- a/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js +++ b/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js @@ -2,9 +2,9 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); - cy.get( '#enable_image_gen' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); } ); @@ -15,15 +15,15 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can save OpenAI "Image Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_image_gen' ).check(); - cy.get( '#openai_dalle_image_generation_roles_administrator' ).check(); - cy.get( '#number' ).select( '2' ); - cy.get( '#size' ).select( '512x512' ); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_image_generation_roles_administrator' ).check(); + cy.get( '#number_of_images' ).select( '2' ); + cy.get( '#image_size' ).select( '512x512' ); cy.get( '#submit' ).click(); } ); @@ -80,9 +80,9 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); - cy.get( '#enable_image_gen' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -90,9 +90,9 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); - cy.get( '#enable_image_gen' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -101,11 +101,11 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can generate image directly in media library', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); - cy.get( '#enable_image_gen' ).check(); - cy.get( '#openai_dalle_image_generation_roles_administrator' ).check(); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_image_generation_roles_administrator' ).check(); cy.get( '#submit' ).click(); cy.visit( '/wp-admin/upload.php' ); @@ -128,16 +128,15 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=image_processing&provider=openai_dalle' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' ); - cy.get( '#enable_image_gen' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'image_generation', - [ 'administrator' ], - 'openai_dalle' + 'feature_image_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -145,9 +144,8 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'image_generation', - [ 'administrator' ], - 'openai_dalle' + 'feature_image_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -157,9 +155,8 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'image_generation', - [ 'administrator' ], - 'openai_dalle' + 'feature_image_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -167,9 +164,8 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'image_generation', - [ 'admin' ], - 'openai_dalle' + 'feature_image_generation', + [ 'admin' ] ); // Verify that the feature is available. @@ -178,16 +174,16 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'User can opt-out image generation feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'image_generation', 'openai_dalle' ); + cy.enableFeatureOptOut( 'feature_image_generation' ); // opt-out - cy.optOutFeature( 'image_generation' ); + cy.optOutFeature( 'feature_image_generation' ); // Verify that the feature is not available. cy.verifyImageGenerationEnabled( false ); // opt-in - cy.optInFeature( 'image_generation' ); + cy.optInFeature( 'feature_image_generation' ); // Verify that the feature is available. cy.verifyImageGenerationEnabled( true ); diff --git a/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js b/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js index 85de16ff2..1cd1981e1 100644 --- a/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js +++ b/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js @@ -6,13 +6,25 @@ describe( 'Image processing Tests', () => { before( () => { cy.login(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#computer_vision_enable_image_captions_alt' ).check(); - cy.get( '#computer_vision_enable_image_captions_description' ).check(); - cy.get( '#enable_image_tagging' ).check(); - cy.get( '#enable_smart_cropping' ).check(); - cy.get( '#enable_ocr' ).check(); - cy.get( '#submit' ).click(); + + const imageProcessingFeatures = [ + 'feature_descriptive_text_generator', + 'feature_image_tags_generator', + 'feature_image_cropping', + 'feature_image_to_text_generator', + 'feature_pdf_to_text_generation', + ]; + + imageProcessingFeatures.forEach( ( feature ) => { + cy.visit( `/wp-admin/tools.php?page=classifai&tab=image_processing&feature=${ feature }` ); + cy.get( '#status' ).check(); + cy.get( '#endpoint_url' ) + .clear() + .type( 'http://e2e-test-image-processing.test' ); + cy.get( '#api_key' ).clear().type( 'password' ); + cy.get( '#submit' ).click(); + } ); + cy.optInAllFeatures(); } ); @@ -20,24 +32,10 @@ describe( 'Image processing Tests', () => { cy.login(); } ); - it( 'Can save Azure AI Vision "Image Processing" settings', () => { - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - - cy.get( '#url' ) - .clear() - .type( 'http://e2e-test-image-processing.test' ); - cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#computer_vision_enable_image_captions_alt' ).check(); - cy.get( '#computer_vision_enable_image_captions_description' ).check(); - cy.get( '#enable_image_tagging' ).check(); - cy.get( '#enable_smart_cropping' ).check(); - cy.get( '#enable_ocr' ).check(); - cy.get( '#submit' ).click(); - - cy.get( '.notice' ).contains( 'Settings saved.' ); - } ); - it( 'Can see Azure AI Vision Image processing actions on edit media page and verify Generated data.', () => { + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); + cy.get( '#submit' ).click(); cy.visit( '/wp-admin/upload.php?mode=grid' ); // Ensure grid mode is enabled. cy.visit( '/wp-admin/media-new.php' ); cy.get( '#plupload-upload-ui' ).should( 'exist' ); @@ -109,26 +107,47 @@ describe( 'Image processing Tests', () => { }; // Disable features - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#computer_vision_enable_image_captions_alt' ).uncheck(); - cy.get( '#computer_vision_enable_image_captions_caption' ).uncheck(); - cy.get( - '#computer_vision_enable_image_captions_description' - ).uncheck(); - cy.get( '#enable_image_tagging' ).uncheck(); - cy.get( '#enable_smart_cropping' ).uncheck(); - cy.get( '#enable_ocr' ).uncheck(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); + cy.wait(1000); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).uncheck(); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' ).uncheck(); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' ).uncheck(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' ); + cy.get( '#status' ).uncheck(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' ); + cy.get( '#status' ).uncheck(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' ); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. cy.verifyAIVisionEnabled( false, options ); // Enable features. - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#computer_vision_enable_image_captions_alt' ).check(); - cy.get( '#enable_image_tagging' ).check(); - cy.get( '#enable_smart_cropping' ).check(); - cy.get( '#enable_ocr' ).check(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); + cy.wait(1000); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' ).check(); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' ).check(); + cy.get( '#status' ).check(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' ); + cy.get( '#status' ).check(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' ); + cy.get( '#status' ).check(); + cy.get( '#submit' ).click(); + + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' ); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -142,33 +161,28 @@ describe( 'Image processing Tests', () => { }; // Enable features. - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#computer_vision_enable_image_captions_alt' ).check(); - cy.get( '#enable_image_tagging' ).check(); - cy.get( '#enable_smart_cropping' ).check(); - cy.get( '#enable_ocr' ).check(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); + cy.wait(1000); + cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable access to admin role. cy.disableFeatureForRoles( - 'image_captions', - [ 'administrator' ], - 'computer_vision' + 'feature_descriptive_text_generator', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'image_tagging', - [ 'administrator' ], - 'computer_vision' + 'feature_image_tags_generator', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'smart_cropping', - [ 'administrator' ], - 'computer_vision' + 'feature_image_cropping', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'ocr', - [ 'administrator' ], - 'computer_vision' + 'feature_image_to_text_generator', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -176,24 +190,20 @@ describe( 'Image processing Tests', () => { // Enable access to admin role. cy.enableFeatureForRoles( - 'image_captions', - [ 'administrator' ], - 'computer_vision' + 'feature_descriptive_text_generator', + [ 'administrator' ] ); cy.enableFeatureForRoles( - 'image_tagging', - [ 'administrator' ], - 'computer_vision' + 'feature_image_tags_generator', + [ 'administrator' ] ); cy.enableFeatureForRoles( - 'smart_cropping', - [ 'administrator' ], - 'computer_vision' + 'feature_image_cropping', + [ 'administrator' ] ); cy.enableFeatureForRoles( - 'ocr', - [ 'administrator' ], - 'computer_vision' + 'feature_image_to_text_generator', + [ 'administrator' ] ); // Verify that the feature is available. @@ -208,45 +218,41 @@ describe( 'Image processing Tests', () => { // Disable access to admin role. cy.disableFeatureForRoles( - 'image_captions', - [ 'administrator' ], - 'computer_vision' + 'feature_descriptive_text_generator', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'image_tagging', - [ 'administrator' ], - 'computer_vision' + 'feature_image_tags_generator', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'smart_cropping', - [ 'administrator' ], - 'computer_vision' + 'feature_image_cropping', + [ 'administrator' ] ); cy.disableFeatureForRoles( - 'ocr', - [ 'administrator' ], - 'computer_vision' + 'feature_image_to_text_generator', + [ 'administrator' ] ); // Verify that the feature is not available. cy.verifyAIVisionEnabled( false, options ); cy.enableFeatureForUsers( - 'image_captions', - [ 'admin' ], - 'computer_vision' + 'feature_descriptive_text_generator', + [ 'admin' ] ); cy.enableFeatureForUsers( - 'image_tagging', - [ 'admin' ], - 'computer_vision' + 'feature_image_tags_generator', + [ 'admin' ] + ); + cy.enableFeatureForUsers( + 'feature_image_cropping', + [ 'admin' ] ); cy.enableFeatureForUsers( - 'smart_cropping', + 'feature_image_to_text_generator', [ 'admin' ], - 'computer_vision' ); - cy.enableFeatureForUsers( 'ocr', [ 'admin' ], 'computer_vision' ); // Verify that the feature is available. cy.verifyAIVisionEnabled( true, options ); @@ -259,25 +265,25 @@ describe( 'Image processing Tests', () => { }; // Enable user based opt-out. - cy.enableFeatureOptOut( 'image_captions', 'computer_vision' ); - cy.enableFeatureOptOut( 'image_tagging', 'computer_vision' ); - cy.enableFeatureOptOut( 'smart_cropping', 'computer_vision' ); - cy.enableFeatureOptOut( 'ocr', 'computer_vision' ); + cy.enableFeatureOptOut( 'feature_descriptive_text_generator' ); + cy.enableFeatureOptOut( 'feature_image_tags_generator' ); + cy.enableFeatureOptOut( 'feature_image_cropping' ); + cy.enableFeatureOptOut( 'feature_image_to_text_generator' ); // opt-out - cy.optOutFeature( 'image_captions' ); - cy.optOutFeature( 'image_tagging' ); - cy.optOutFeature( 'smart_cropping' ); - cy.optOutFeature( 'ocr' ); + cy.optOutFeature( 'feature_descriptive_text_generator' ); + cy.optOutFeature( 'feature_image_tags_generator' ); + cy.optOutFeature( 'feature_image_cropping' ); + cy.optOutFeature( 'feature_image_to_text_generator' ); // Verify that the feature is not available. cy.verifyAIVisionEnabled( false, options ); // opt-in - cy.optInFeature( 'image_captions' ); - cy.optInFeature( 'image_tagging' ); - cy.optInFeature( 'smart_cropping' ); - cy.optInFeature( 'ocr' ); + cy.optInFeature( 'feature_descriptive_text_generator' ); + cy.optInFeature( 'feature_image_tags_generator' ); + cy.optInFeature( 'feature_image_cropping' ); + cy.optInFeature( 'feature_image_to_text_generator' ); // Verify that the feature is available. cy.verifyAIVisionEnabled( true, options ); diff --git a/tests/cypress/integration/image-processing/pdf-read.test.js b/tests/cypress/integration/image-processing/pdf-read.test.js index 29bb2f10a..bff567c6d 100644 --- a/tests/cypress/integration/image-processing/pdf-read.test.js +++ b/tests/cypress/integration/image-processing/pdf-read.test.js @@ -3,8 +3,8 @@ import { getPDFData } from '../../plugins/functions'; describe( 'PDF read Tests', () => { before( () => { cy.login(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); - cy.get( '#enable_read_pdf' ).check(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); } ); @@ -15,13 +15,13 @@ describe( 'PDF read Tests', () => { let pdfEditLink = ''; it( 'Can save "PDF scanning" settings', () => { - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing' ); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); - cy.get( '#url' ) + cy.get( '#endpoint_url' ) .clear() .type( 'http://e2e-test-image-processing.test' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_read_pdf' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -59,9 +59,9 @@ describe( 'PDF read Tests', () => { it( 'Can enable/disable PDF scanning feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=computer_vision' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); - cy.get( '#enable_read_pdf' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -72,9 +72,9 @@ describe( 'PDF read Tests', () => { // Enable admin role. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=computer_vision' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); - cy.get( '#enable_read_pdf' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -87,16 +87,15 @@ describe( 'PDF read Tests', () => { it( 'Can enable/disable PDF scanning feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=computer_vision' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); - cy.get( '#enable_read_pdf' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'read_pdf', - [ 'administrator' ], - 'computer_vision' + 'feature_pdf_to_text_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -107,9 +106,8 @@ describe( 'PDF read Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'read_pdf', - [ 'administrator' ], - 'computer_vision' + 'feature_pdf_to_text_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -122,9 +120,8 @@ describe( 'PDF read Tests', () => { it( 'Can enable/disable PDF scanning feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'read_pdf', - [ 'administrator' ], - 'computer_vision' + 'feature_pdf_to_text_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -134,7 +131,10 @@ describe( 'PDF read Tests', () => { ); // Enable feature for admin user. - cy.enableFeatureForUsers( 'read_pdf', [ 'admin' ], 'computer_vision' ); + cy.enableFeatureForUsers( + 'feature_pdf_to_text_generation', + [ 'admin' ] + ); // Verify that the feature is available. cy.visit( pdfEditLink ); @@ -145,10 +145,10 @@ describe( 'PDF read Tests', () => { it( 'User can opt-out PDF scanning feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'read_pdf', 'computer_vision' ); + cy.enableFeatureOptOut( 'feature_pdf_to_text_generation' ); // opt-out - cy.optOutFeature( 'read_pdf' ); + cy.optOutFeature( 'feature_pdf_to_text_generation' ); // Verify that the feature is not available. cy.visit( pdfEditLink ); @@ -157,7 +157,7 @@ describe( 'PDF read Tests', () => { ); // opt-in - cy.optInFeature( 'read_pdf' ); + cy.optInFeature( 'feature_pdf_to_text_generation' ); // Verify that the feature is available. cy.visit( pdfEditLink ); diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index 4bd8e9182..ea2ac46b3 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -2,11 +2,11 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai-settings-post' ).check(); + cy.get( '#classifai_feature_classification_post_types_post' ).check(); cy.get( '#classifai-settings-publish' ).check(); - cy.get( '#classifai-settings-category' ).check(); + cy.get( '#category' ).check(); cy.get( '#watson_nlu_classification_method_recommended_terms' ).check(); cy.get( '#classifai-settings-enable_content_classification' ).check(); cy.get( '#submit' ).click(); @@ -37,18 +37,18 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () .clear() .type( 'password' ); - cy.get( '#classifai-settings-automatic_classification' ).check(); - cy.get( '#classifai-settings-post' ).check(); - cy.get( '#classifai-settings-page' ).check(); - cy.get( '#classifai-settings-draft' ).check(); - cy.get( '#classifai-settings-pending' ).check(); - cy.get( '#classifai-settings-private' ).check(); - cy.get( '#classifai-settings-publish' ).check(); - - cy.get( '#classifai-settings-category' ).check(); - cy.get( '#classifai-settings-keyword' ).check(); - cy.get( '#classifai-settings-entity' ).check(); - cy.get( '#classifai-settings-concept' ).check(); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_classification_post_types_post' ).check(); + cy.get( '#classifai_feature_classification_post_types_page' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + + cy.get( '#category' ).check(); + cy.get( '#keyword' ).check(); + cy.get( '#entity' ).check(); + cy.get( '#concept' ).check(); cy.get( '#submit' ).click(); } ); @@ -578,13 +578,13 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.get( '.notice' ).contains( 'Settings saved.' ); // opt-out - cy.optOutFeature( 'content_classification' ); + cy.optOutFeature( 'feature_classification' ); // Verify that the feature is not available. cy.verifyClassifyContentEnabled( false ); // opt-in - cy.optInFeature( 'content_classification' ); + cy.optInFeature( 'feature_classification' ); // Verify that the feature is available. cy.verifyClassifyContentEnabled( true ); diff --git a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js index 4434e10fc..d1b9c6cc4 100644 --- a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js +++ b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js @@ -2,9 +2,10 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#enable_classification' ).check(); + cy.get( '#status' ).check(); + cy.get( '#provider' ).select( 'openai_embeddings' ); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -16,16 +17,16 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can save OpenAI Embeddings "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_classification' ).check(); - cy.get( '#openai_embeddings_post_types_post' ).check(); - cy.get( '#openai_embeddings_post_statuses_publish' ).check(); - cy.get( '#openai_embeddings_taxonomies_category' ).check(); - cy.get( '#openai_embeddings_taxonomies_category_threshold' ).type( 80 ); // "Test" requires 80% confidence. At 81%, it does not apply. - cy.get( '#number' ).clear().type( 1 ); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_classification_post_types_post' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).check(); + cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category_threshold' ).type( 80 ); // "Test" requires 80% confidence. At 81%, it does not apply. + cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); } ); @@ -34,10 +35,7 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( '#classifai-settings-category' ).uncheck(); - cy.get( '#classifai-settings-keyword' ).uncheck(); - cy.get( '#classifai-settings-entity' ).uncheck(); - cy.get( '#classifai-settings-concept' ).uncheck(); + cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).uncheck(); cy.get( '#submit' ).click(); // Create test term. @@ -89,7 +87,7 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can see the preview on the settings page', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); // Click the Preview button. @@ -102,9 +100,9 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can create category and post and category will not get auto-assigned if feature turned off', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#enable_classification' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Create test term. @@ -158,14 +156,14 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#enable_classification' ).check(); - cy.get( '#openai_embeddings_post_types_post' ).check(); - cy.get( '#openai_embeddings_post_statuses_publish' ).check(); - cy.get( '#openai_embeddings_taxonomies_category' ).check(); - cy.get( '#number' ).clear().type( 1 ); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_classification_post_types_post' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).check(); + cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); cy.classicCreatePost( { @@ -183,9 +181,9 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can enable/disable content classification feature ', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#enable_classification' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -193,9 +191,9 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#enable_classification' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -207,17 +205,13 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( '#classifai-settings-category' ).uncheck(); - cy.get( '#classifai-settings-keyword' ).uncheck(); - cy.get( '#classifai-settings-entity' ).uncheck(); - cy.get( '#classifai-settings-concept' ).uncheck(); + cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'classification', - [ 'administrator' ], - 'openai_embeddings' + 'feature_classification', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -225,9 +219,8 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'classification', - [ 'administrator' ], - 'openai_embeddings' + 'feature_classification', + [ 'administrator' ] ); // Verify that the feature is available. @@ -237,9 +230,8 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can enable/disable content classification feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'classification', - [ 'administrator' ], - 'openai_embeddings' + 'feature_classification', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -247,9 +239,8 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'classification', - [ 'admin' ], - 'openai_embeddings' + 'feature_classification', + [ 'admin' ] ); // Verify that the feature is available. @@ -258,16 +249,16 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'User can opt-out content classification feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'classification', 'openai_embeddings' ); + cy.enableFeatureOptOut( 'feature_classification', 'openai_embeddings' ); // opt-out - cy.optOutFeature( 'classification' ); + cy.optOutFeature( 'feature_classification' ); // Verify that the feature is not available. cy.verifyClassifyContentEnabled( false ); // opt-in - cy.optInFeature( 'classification' ); + cy.optInFeature( 'feature_classification' ); // Verify that the feature is available. cy.verifyClassifyContentEnabled( true ); diff --git a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js index ce1aa2d88..9e2f31da7 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js @@ -4,9 +4,9 @@ describe( '[Language processing] Excerpt Generation Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); - cy.get( '#enable_excerpt' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -18,15 +18,15 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can save OpenAI ChatGPT "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_excerpt' ).check(); - cy.get( '#excerpt_generation_role_based_access' ).check(); + cy.get( '#status' ).check(); + cy.get( '#role_based_access' ).check(); cy.get( - '#openai_chatgpt_excerpt_generation_roles_administrator' + '#classifai_feature_excerpt_generation_roles_administrator' ).check(); cy.get( '#length' ).clear().type( 35 ); cy.get( '#submit' ).click(); @@ -82,9 +82,9 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); - cy.get( '#enable_excerpt' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); const data = getChatGPTData(); @@ -112,12 +112,12 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can set multiple custom excerpt generation prompts, select one as the default and delete one.', () => { cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); // Add three custom prompts. cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -125,7 +125,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { .click() .click(); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' ) .parents( 'td' ) .find( '.classifai-field-type-prompt-setting' ) @@ -133,40 +133,40 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][1][title]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][1][prompt]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom excerpt prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][2][title]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][2][prompt]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][3][title]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][3][prompt]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom excerpt prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][3][default]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -174,7 +174,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][2][default]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -183,7 +183,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_openai_chatgpt[generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -236,9 +236,9 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature', () => { // Disable features. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); - cy.get( '#enable_excerpt' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -246,9 +246,9 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); - cy.get( '#enable_excerpt' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -257,16 +257,15 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature by role', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); - cy.get( '#enable_excerpt' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'excerpt_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_excerpt_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -274,9 +273,8 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // enable admin role. cy.enableFeatureForRoles( - 'excerpt_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_excerpt_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -286,9 +284,13 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'excerpt_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_excerpt_generation', + [ 'administrator' ] + ); + + cy.enableFeatureForUsers( + 'feature_excerpt_generation', + [] ); // Verify that the feature is not available. @@ -296,9 +298,8 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'excerpt_generation', - [ 'admin' ], - 'openai_chatgpt' + 'feature_excerpt_generation', + [ 'admin' ] ); // Verify that the feature is available. @@ -307,16 +308,16 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'User can opt-out excerpt generation feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'excerpt_generation', 'openai_chatgpt' ); + cy.enableFeatureOptOut( 'feature_excerpt_generation', 'openai_chatgpt' ); // opt-out - cy.optOutFeature( 'excerpt_generation' ); + cy.optOutFeature( 'feature_excerpt_generation' ); // Verify that the feature is not available. cy.verifyExcerptGenerationEnabled( false ); // opt-in - cy.optInFeature( 'excerpt_generation' ); + cy.optInFeature( 'feature_excerpt_generation' ); // Verify that the feature is available. cy.verifyExcerptGenerationEnabled( true ); diff --git a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js index e79250670..decb348d9 100644 --- a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js @@ -2,9 +2,10 @@ describe( '[Language processing] Speech to Text Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' ); - cy.get( '#enable_resize_content' ).check(); + cy.get( '#status' ).check(); + cy.get( '#api_key' ).type( 'abc123' ); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -16,11 +17,11 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Resize content feature can grow and shrink content', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' ); - cy.get( '#enable_resize_content' ).check(); - cy.get( '#openai_chatgpt_resize_content_roles_administrator' ).check(); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_content_resizing_roles_administrator' ).check(); cy.get( '#submit' ).click(); cy.createPost( { @@ -73,12 +74,12 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can set multiple custom resize generation prompts, select one as the default and delete one.', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' ); // Add three custom shrink prompts. cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -86,7 +87,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .click() .click(); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -94,7 +95,7 @@ describe( '[Language processing] Speech to Text Tests', () => { // Add three custom grow prompts. cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset:first' ) @@ -102,7 +103,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .click() .click(); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -110,77 +111,77 @@ describe( '[Language processing] Speech to Text Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][1][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][1][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom shrink prompt' ); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][2][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][2][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][3][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][3][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom shrink prompt' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][1][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][1][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom grow prompt' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][2][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][2][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][3][title]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][3][prompt]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom grow prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][3][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) .click( { force: true } ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][3][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -188,7 +189,7 @@ describe( '[Language processing] Speech to Text Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][2][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -197,13 +198,13 @@ describe( '[Language processing] Speech to Text Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_openai_chatgpt[shrink_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) .should( 'have.length', 3 ); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][2][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -212,7 +213,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_openai_chatgpt[grow_content_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -271,9 +272,9 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable resize content feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' ); - cy.get( '#enable_resize_content' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -281,9 +282,9 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_content_resizing' ); - cy.get( '#enable_resize_content' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -293,9 +294,8 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable resize content feature by role', () => { // Disable admin role. cy.disableFeatureForRoles( - 'resize_content', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_content_resizing', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -303,9 +303,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'resize_content', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_content_resizing', + [ 'administrator' ] ); // Verify that the feature is available. @@ -315,9 +314,8 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable resize content feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'resize_content', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_content_resizing', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -325,9 +323,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'resize_content', - [ 'admin' ], - 'openai_chatgpt' + 'feature_content_resizing', + [ 'admin' ] ); // Verify that the feature is available. @@ -336,16 +333,16 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'User can opt-out resize content feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'resize_content', 'openai_chatgpt' ); + cy.enableFeatureOptOut( 'feature_content_resizing' ); // opt-out - cy.optOutFeature( 'resize_content' ); + cy.optOutFeature( 'feature_content_resizing' ); // Verify that the feature is not available. cy.verifyResizeContentEnabled( false ); // opt-in - cy.optInFeature( 'resize_content' ); + cy.optInFeature( 'feature_content_resizing' ); // Verify that the feature is available. cy.verifyResizeContentEnabled( true ); diff --git a/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js b/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js index fea371a00..c36b14825 100644 --- a/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js +++ b/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js @@ -4,9 +4,9 @@ describe( '[Language processing] Speech to Text Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_whisper' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' ); - cy.get( '#enable_transcripts' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -18,13 +18,13 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can save OpenAI Whisper "Language Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_whisper' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_transcripts' ).check(); - cy.get( '#openai_whisper_speech_to_text_roles_administrator' ).check(); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_audio_transcripts_generation_roles_administrator' ).check(); cy.get( '#submit' ).click(); } ); @@ -81,9 +81,9 @@ describe( '[Language processing] Speech to Text Tests', () => { // Disable features cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_whisper' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' ); - cy.get( '#enable_transcripts' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -91,9 +91,9 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_whisper' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' ); - cy.get( '#enable_transcripts' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -103,9 +103,9 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable speech to text feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_whisper' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_audio_transcripts_generation' ); - cy.get( '#enable_transcripts' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); const options = { @@ -115,9 +115,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Disable admin role. cy.disableFeatureForRoles( - 'speech_to_text', - [ 'administrator' ], - 'openai_whisper' + 'feature_audio_transcripts_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -125,9 +124,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'speech_to_text', - [ 'administrator' ], - 'openai_whisper' + 'feature_audio_transcripts_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -142,9 +140,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Disable admin role. cy.disableFeatureForRoles( - 'speech_to_text', - [ 'administrator' ], - 'openai_whisper' + 'feature_audio_transcripts_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -152,9 +149,8 @@ describe( '[Language processing] Speech to Text Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'speech_to_text', - [ 'admin' ], - 'openai_whisper' + 'feature_audio_transcripts_generation', + [ 'admin' ] ); // Verify that the feature is available. @@ -168,16 +164,16 @@ describe( '[Language processing] Speech to Text Tests', () => { }; // Enable user based opt-out. - cy.enableFeatureOptOut( 'speech_to_text', 'openai_whisper' ); + cy.enableFeatureOptOut( 'feature_audio_transcripts_generation' ); // opt-out - cy.optOutFeature( 'speech_to_text' ); + cy.optOutFeature( 'feature_audio_transcripts_generation' ); // Verify that the feature is not available. cy.verifySpeechToTextEnabled( false, options ); // opt-in - cy.optInFeature( 'speech_to_text' ); + cy.optInFeature( 'feature_audio_transcripts_generation' ); // Verify that the feature is available. cy.verifySpeechToTextEnabled( true, options ); diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index cfc4d24cd..10c9480c0 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -2,13 +2,13 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#azure_text_to_speech_post_types_post' ).check( 'post' ); - cy.get( '#url' ).clear(); - cy.get( '#url' ).type( 'https://service.com' ); + cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); + cy.get( '#endpoint_url' ).clear(); + cy.get( '#endpoint_url' ).type( 'https://service.com' ); cy.get( '#api_key' ).type( 'password' ); - cy.get( '#enable_text_to_speech' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.get( '#voice' ).select( 'en-AU-AnnetteNeural|Female' ); @@ -110,9 +110,9 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Disable support for post type Post', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#azure_text_to_speech_post_types_post' ).uncheck( 'post' ); + cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).uncheck( 'post' ); cy.get( '#submit' ).click(); cy.visit( '/text-to-speech-test/' ); @@ -122,9 +122,9 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#enable_text_to_speech' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -132,10 +132,10 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#enable_text_to_speech' ).check(); - cy.get( '#azure_text_to_speech_post_types_post' ).check( 'post' ); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -145,16 +145,15 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=azure_text_to_speech' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#azure_text_to_speech_post_types_post' ).check( 'post' ); + cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'text_to_speech', - [ 'administrator' ], - 'azure_text_to_speech' + 'feature_text_to_speech_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -162,9 +161,8 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => // Enable admin role. cy.enableFeatureForRoles( - 'text_to_speech', - [ 'administrator' ], - 'azure_text_to_speech' + 'feature_text_to_speech_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -174,9 +172,8 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'text_to_speech', - [ 'administrator' ], - 'azure_text_to_speech' + 'feature_text_to_speech_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -184,9 +181,8 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => // Enable feature for admin user. cy.enableFeatureForUsers( - 'text_to_speech', - [ 'admin' ], - 'azure_text_to_speech' + 'feature_text_to_speech_generation', + [ 'admin' ] ); // Verify that the feature is available. @@ -195,16 +191,16 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'User can opt-out text to speech feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'text_to_speech', 'azure_text_to_speech' ); + cy.enableFeatureOptOut( 'feature_text_to_speech_generation' ); // opt-out - cy.optOutFeature( 'text_to_speech' ); + cy.optOutFeature( 'feature_text_to_speech_generation' ); // Verify that the feature is not available. cy.verifyTextToSpeechEnabled( false ); // opt-in - cy.optInFeature( 'text_to_speech' ); + cy.optInFeature( 'feature_text_to_speech_generation' ); // Verify that the feature is available. cy.verifyTextToSpeechEnabled( true ); diff --git a/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js index 98f3f21bd..0113bff41 100644 --- a/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js @@ -13,15 +13,15 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can save OpenAI ChatGPT "Language Processing" title settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); cy.get( '#api_key' ).clear().type( 'password' ); - cy.get( '#enable_titles' ).check(); + cy.get( '#status' ).check(); cy.get( - '#openai_chatgpt_title_generation_roles_administrator' + '#classifai_feature_title_generation_roles_administrator' ).check(); - cy.get( '#number_titles' ).select( 1 ); + cy.get( '#number_of_titles' ).type( 1 ); cy.get( '#submit' ).click(); } ); @@ -92,9 +92,9 @@ describe( '[Language processing] Title Generation Tests', () => { cy.enableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); - cy.get( '#enable_titles' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); const data = getChatGPTData(); @@ -118,12 +118,12 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can set multiple custom title generation prompts, select one as the default and delete one.', () => { cy.disableClassicEditor(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); // Add three custom prompts. cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -131,7 +131,7 @@ describe( '[Language processing] Title Generation Tests', () => { .click() .click(); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -139,40 +139,40 @@ describe( '[Language processing] Title Generation Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][1][title]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][1][prompt]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom title prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][2][title]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][2][prompt]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][3][title]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][3][prompt]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom title prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][3][default]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -180,7 +180,7 @@ describe( '[Language processing] Title Generation Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][2][default]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -189,7 +189,7 @@ describe( '[Language processing] Title Generation Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_openai_chatgpt[generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -262,9 +262,9 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature', () => { // Disable features. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); - cy.get( '#enable_titles' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -272,9 +272,9 @@ describe( '[Language processing] Title Generation Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); - cy.get( '#enable_titles' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -284,16 +284,15 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_chatgpt' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_title_generation' ); - cy.get( '#enable_titles' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable admin role. cy.disableFeatureForRoles( - 'title_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_title_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -301,9 +300,8 @@ describe( '[Language processing] Title Generation Tests', () => { // Enable admin role. cy.enableFeatureForRoles( - 'title_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_title_generation', + [ 'administrator' ] ); // Verify that the feature is available. @@ -313,9 +311,8 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature by user', () => { // Disable admin role. cy.disableFeatureForRoles( - 'title_generation', - [ 'administrator' ], - 'openai_chatgpt' + 'feature_title_generation', + [ 'administrator' ] ); // Verify that the feature is not available. @@ -323,9 +320,8 @@ describe( '[Language processing] Title Generation Tests', () => { // Enable feature for admin user. cy.enableFeatureForUsers( - 'title_generation', - [ 'admin' ], - 'openai_chatgpt' + 'feature_title_generation', + [ 'admin' ] ); // Verify that the feature is available. @@ -334,16 +330,16 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'User can opt-out title generation feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'title_generation', 'openai_chatgpt' ); + cy.enableFeatureOptOut( 'feature_title_generation' ); // opt-out - cy.optOutFeature( 'title_generation' ); + cy.optOutFeature( 'feature_title_generation' ); // Verify that the feature is not available. cy.verifyTitleGenerationEnabled( false ); // opt-in - cy.optInFeature( 'title_generation' ); + cy.optInFeature( 'feature_title_generation' ); // Verify that the feature is available. cy.verifyTitleGenerationEnabled( true ); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index facdfcbea..10ffad17a 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -128,15 +128,14 @@ Cypress.Commands.add( 'optInAllFeatures', () => { * * @param {string} feature The feature to enable. * @param {string} roles The roles to enable. - * @param {string} provider The provider to enable. */ -Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles, provider ) => { +Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles ) => { cy.visit( - `/wp-admin/tools.php?page=classifai&tab=language_processing&provider=${ provider }` + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` ); - cy.get( `#${ feature }_role_based_access` ).check(); + cy.get( `#role_based_access` ).check(); roles.forEach( ( role ) => { - cy.get( `#${ provider }_${ feature }_roles_${ role }` ).check(); + cy.get( `#classifai_${ feature }_roles_${ role }` ).check(); } ); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -147,17 +146,17 @@ Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles, provider ) => { * * @param {string} feature The feature to disable. * @param {string} roles The roles to disable. - * @param {string} provider The provider to disable. */ Cypress.Commands.add( 'disableFeatureForRoles', - ( feature, roles, provider ) => { + ( feature, roles ) => { cy.visit( - `/wp-admin/tools.php?page=classifai&tab=language_processing&provider=${ provider }` + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` ); - cy.get( `#${ feature }_role_based_access` ).check(); + cy.get( '#status' ).check(); + cy.get( `#role_based_access` ).check(); roles.forEach( ( role ) => { - cy.get( `#${ provider }_${ feature }_roles_${ role }` ).uncheck(); + cy.get( `#classifai_${ feature }_roles_${ role }` ).uncheck(); } ); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -169,21 +168,21 @@ Cypress.Commands.add( * * @param {string} feature The feature to enable. * @param {string} users The users to enable. - * @param {string} provider The provider to enable. */ -Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users, provider ) => { +Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { cy.visit( - `/wp-admin/tools.php?page=classifai&tab=language_processing&provider=${ provider }` + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` ); - cy.get( `#${ feature }_user_based_access` ).check(); - cy.get( 'body' ).then( ( $body ) => { + cy.get( `#user_based_access` ).check(); + cy.wait(1000) + cy.get( '.allowed_users_row' ).then( ( $body ) => { if ( $body.find( - `#${ feature }_users-container .components-form-token-field__remove-token` + `.components-form-token-field__remove-token` ).length > 0 ) { cy.get( - `#${ feature }_users-container .components-form-token-field__remove-token` + `.components-form-token-field__remove-token` ).click( { multiple: true, } ); @@ -192,11 +191,11 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users, provider ) => { users.forEach( ( user ) => { cy.get( - `#${ feature }_users-container input.components-form-token-field__input` + `.allowed_users_row input.components-form-token-field__input` ).type( user ); - cy.wait( 1000 ); + cy.get( - 'ul.components-form-token-field__suggestions-list li:nth-child(1)' + '[aria-label="admin (admin)"]' ).click(); } ); cy.get( '#submit' ).click(); @@ -207,16 +206,15 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users, provider ) => { * Enable user based opt-out for a feature. * * @param {string} feature The feature to enable. - * @param {string} provider The provider to enable. */ -Cypress.Commands.add( 'enableFeatureOptOut', ( feature, provider ) => { +Cypress.Commands.add( 'enableFeatureOptOut', ( feature ) => { cy.visit( - `/wp-admin/tools.php?page=classifai&tab=language_processing&provider=${ provider }` + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` ); - cy.get( `#${ feature }_role_based_access` ).check(); - cy.get( `#${ provider }_${ feature }_roles_administrator` ).check(); - cy.get( `#${ feature }_user_based_access` ).uncheck(); - cy.get( `#${ feature }_user_based_opt_out` ).check(); + cy.get( `#role_based_access` ).check(); + cy.get( `#classifai_${ feature }_roles_administrator` ).check(); + cy.get( `#user_based_access` ).uncheck(); + cy.get( `#user_based_opt_out` ).check(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -324,10 +322,15 @@ Cypress.Commands.add( */ Cypress.Commands.add( 'verifyTextToSpeechEnabled', ( enabled = true ) => { const shouldExist = enabled ? 'exist' : 'not.exist'; + console.log( shouldExist ) cy.visit( '/wp-admin/edit.php' ); cy.get( '#the-list tr:nth-child(1) td.title a.row-title' ).click(); cy.closeWelcomeGuide(); - cy.get( '.classifai-panel' ).click(); + cy.get( 'body' ).then( $body => { + if ( $body.find( '.classifai-panel' ).length ) { + $body.find( '.classifai-panel' ).click(); + } + } ); cy.get( '#classifai-audio-controls__preview-btn' ).should( shouldExist ); } ); From e9ddc967e84ec6801ae21e68ed04ac74195b2a30 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 8 Jan 2024 00:51:03 +0530 Subject: [PATCH 073/127] migrate Recommended Content --- .../Features/AudioTranscriptsGeneration.php | 1 - includes/Classifai/Features/Feature.php | 2 +- .../Classifai/Features/RecommendedContent.php | 193 +++++++++++++++++ .../Providers/Azure/Personalizer.php | 196 ++++++++---------- includes/Classifai/Providers/OpenAI/DallE.php | 2 +- includes/Classifai/Services/Personalizer.php | 20 +- .../Classifai/Services/ServicesManager.php | 10 + 7 files changed, 303 insertions(+), 121 deletions(-) create mode 100644 includes/Classifai/Features/RecommendedContent.php diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index d0ac57e48..d3597d90f 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -3,7 +3,6 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use Classifai\Providers\Azure\Speech; use \Classifai\Providers\OpenAI\Whisper; /** diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 1ef32ca1d..ca777a68c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -82,7 +82,7 @@ abstract protected function setup_fields_sections(); protected function get_default_settings() { return [ 'status' => '0', - 'role_based_access' => 'no', + 'role_based_access' => '1', 'roles' => array_combine( array_keys( $this->roles ), array_keys( $this->roles ) ), 'user_based_access' => 'no', 'users' => [], diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php new file mode 100644 index 000000000..d2d9331e1 --- /dev/null +++ b/includes/Classifai/Features/RecommendedContent.php @@ -0,0 +1,193 @@ +provider_instances = $this->get_provider_instances( $service_providers ); + } + + /** + * Returns the label of the feature. + * + * @return string + */ + public function get_label() { + return apply_filters( + 'classifai_' . static::ID . '_label', + __( 'Recommended Content', 'classifai' ) + ); + } + + /** + * Returns the providers supported by the feature. + * + * @return array + */ + public function get_providers() { + return apply_filters( + 'classifai_' . static::ID . '_providers', + [ + PersonalizerProvider::ID => __( 'Microsoft AI Personalizer', 'classifai' ), + ] + ); + } + + /** + * Sets up the fields and sections for the feature. + */ + public function setup_fields_sections() { + $settings = $this->get_settings(); + + /* + * These are the feature-level fields that are + * independent of the provider. + */ + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + add_settings_field( + 'status', + esc_html__( 'Enable title generation', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + ] + ); + + // Add user/role-based access fields. + $this->add_access_control_fields(); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + + /* + * The following renders the fields of all the providers + * that are registered to the feature. + */ + $this->render_provider_fields(); + } + + /** + * Returns the default settings for the feature. + * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * + * @todo Add a filter hook to allow other plugins to add their own settings. + * + * @return array + */ + protected function get_default_settings() { + $provider_settings = $this->get_provider_default_settings(); + $feature_settings = [ + 'provider' => PersonalizerProvider::ID, + ]; + + return apply_filters( + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + parent::get_default_settings(), + $feature_settings, + $provider_settings + ) + ); + } + + /** + * Sanitizes the settings before saving. + * + * @param array $new_settings The settings to be sanitized on save. + * + * @return array + */ + public function sanitize_settings( $new_settings ) { + $settings = $this->get_settings(); + + // Sanitization of the feature-level settings. + $new_settings = parent::sanitize_settings( $new_settings ); + + // Sanitization of the provider-level settings. + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); + + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $settings + ); + } + + /** + * Runs the feature. + * + * @param mixed ...$args Arguments required by the feature depending on the provider selected. + * + * @return mixed + */ + public function run( ...$args ) { + $settings = $this->get_settings(); + $provider_id = $settings['provider'] ?? ChatGPT::ID; + $provider_instance = $this->get_feature_provider_instance( $provider_id ); + $result = ''; + + if ( ChatGPT::ID === $provider_instance::ID ) { + /** @var ChatGPT $provider_instance */ + $result = call_user_func_array( + [ $provider_instance, 'generate_titles' ], + [ ...$args ] + ); + } + + return apply_filters( + 'classifai_' . static::ID . '_run', + $result, + $provider_instance, + $args, + $this + ); + } +} diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 0fa51010a..1d642e5e1 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -7,6 +7,7 @@ use Classifai\Providers\Provider; use Classifai\Blocks; +use Classifai\Features\RecommendedContent; use WP_Error; use UAParser\Parser; @@ -32,89 +33,51 @@ class Personalizer extends Provider { /** * Personalizer constructor. * - * @param string $service The service this class belongs to. + * @param \Classifai\Features\Feature $feature_instance The feature instance. */ - public function __construct( $service = null ) { + public function __construct( $feature_instance = null ) { parent::__construct( 'Microsoft Azure', 'AI Personalizer', - 'personalizer', - $service + 'personalizer' ); - // Features provided by this provider. - $this->features = array( - 'recommended_content' => __( 'Recommended content block', 'classifai' ), - ); - } - - /** - * Resets settings for the Personalizer provider. - */ - public function reset_settings() { - update_option( $this->get_option_name(), $this->get_default_settings() ); - } - - /** - * Default settings for Personalizer - * - * @return array - */ - public function get_default_settings() { - return []; - } + $this->feature_instance = $feature_instance; - /** - * Get the settings and allow for settings default values. - * - * @param string|bool|mixed $index Optional. Name of the settings option index. - * - * @return string|array|mixed - */ - public function get_settings( $index = false ) { - $defaults = $this->get_default_settings(); - $settings = get_option( $this->get_option_name(), [] ); - - // Backward compatibility for enable feature setting. - if ( ! empty( $settings ) && ! isset( $settings['enable_recommended_content'] ) ) { - $settings['enable_recommended_content'] = $settings['authenticated'] ?? $defaults['enable_recommended_content']; - } - - $settings = wp_parse_args( $settings, $defaults ); - - if ( $index && isset( $settings[ $index ] ) ) { - return $settings[ $index ]; - } - - return $settings; + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + do_action( 'classifai_' . static::ID . '_init', $this ); } /** * Register the functionality. */ public function register() { - if ( $this->is_enabled( 'recommended_content' ) ) { + if ( ( new RecommendedContent() )->is_feature_enabled() ) { // Setup Blocks Blocks\setup(); } } /** - * Setup fields. + * Render the provider fields. + * + * @return void */ - public function setup_fields_sections() { - add_settings_section( $this->get_option_name(), $this->provider_service_name, '', $this->get_option_name() ); - $default_settings = $this->get_default_settings(); + public function render_provider_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + add_settings_field( - 'url', + 'endpoint_url', esc_html__( 'Endpoint URL', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', [ - 'label_for' => 'url', + 'option_index' => static::ID, + 'label_for' => 'endpoint_url', 'input_type' => 'text', - 'default_value' => $default_settings['url'], + 'default_value' => $settings['endpoint_url'], + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. 'description' => sprintf( wp_kses( // translators: 1 - link to create a Personalizer resource. @@ -130,73 +93,73 @@ public function setup_fields_sections() { ), ] ); + add_settings_field( - 'api-key', + 'api_key', esc_html__( 'API Key', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', [ + 'option_index' => static::ID, 'label_for' => 'api_key', 'input_type' => 'password', - 'default_value' => $default_settings['api_key'], - 'description' => __( 'Azure AI Personalizer Key.', 'classifai' ), + 'default_value' => $settings['api_key'], + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); + } - add_settings_field( - 'enable_recommended_content', - esc_html__( 'Recommend content', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name(), - [ - 'label_for' => 'enable_recommended_content', - 'input_type' => 'checkbox', - 'default_value' => $default_settings['enable_recommended_content'], - 'description' => __( 'Enables Recommended content block.', 'classifai' ), - ] - ); + /** + * Returns the default settings for this provider. + * + * @return array + */ + public function get_default_provider_settings() { + $common_settings = [ + 'endpoint_url' => '', + 'api_key' => '', + 'authenticated' => false, + ]; + + switch ( $this->feature_instance::ID ) { + case RecommendedContent::ID: + return $common_settings; + } - $this->add_access_settings( 'recommended_content' ); + return $common_settings; } /** - * Sanitize settings. - * - * @param array $settings The settings being saved. + * Sanitize the settings for this provider. * - * @return array|mixed + * @param array $new_settings The settings array. + * @return array */ - public function sanitize_settings( $settings ) { - $new_settings = $this->sanitize_access_settings( $settings, 'recommended_content' ); + public function sanitize_settings( $new_settings ) { + $settings = $this->feature_instance->get_settings(); - if ( empty( $settings['enable_recommended_content'] ) || 1 !== (int) $settings['enable_recommended_content'] ) { - $new_settings['enable_recommended_content'] = 'no'; - } else { - $new_settings['enable_recommended_content'] = '1'; - } + $new_settings['endpoint_url'] = esc_url_raw( $new_settings['endpoint_url'] ?? $settings['endpoint_url'] ); + $new_settings['api_key'] = sanitize_text_field( $new_settings['api_key'] ?? $settings['api_key'] ); + + if ( ! empty( $new_settings['endpoint_url'] ) && ! empty( $new_settings['api_key'] ) ) { + $auth_check = $this->authenticate_credentials( $new_settings['endpoint_url'], $new_settings['api_key'] ); - if ( ! empty( $settings['url'] ) && ! empty( $settings['api_key'] ) ) { - $auth_check = $this->authenticate_credentials( $settings['url'], $settings['api_key'] ); if ( is_wp_error( $auth_check ) ) { $settings_errors['classifai-registration-credentials-error'] = $auth_check->get_error_message(); $new_settings['authenticated'] = false; } else { $new_settings['authenticated'] = true; } - $new_settings['url'] = esc_url_raw( $settings['url'] ); - $new_settings['api_key'] = sanitize_text_field( $settings['api_key'] ); } else { $new_settings['authenticated'] = false; - $new_settings['url'] = ''; + $new_settings['endpoint_url'] = ''; $new_settings['api_key'] = ''; $settings_errors['classifai-registration-credentials-empty'] = __( 'Please enter your credentials', 'classifai' ); } if ( ! empty( $settings_errors ) ) { - $registered_settings_errors = wp_list_pluck( get_settings_errors( $this->get_option_name() ), 'code' ); foreach ( $settings_errors as $code => $message ) { @@ -655,9 +618,10 @@ protected function get_string_words( $string ) { * @return object|string */ protected function personalizer_get_ranked_action( $rank_request ) { - $settings = $this->get_settings(); + $feature = new RecommendedContent(); + $settings = $feature->get_settings( static::ID ); $result = wp_remote_post( - trailingslashit( $settings['url'] ) . $this->rank_endpoint, + trailingslashit( $settings['endpoint_url'] ) . $this->rank_endpoint, [ 'headers' => [ 'Ocp-Apim-Subscription-Key' => $settings['api_key'], @@ -686,10 +650,12 @@ protected function personalizer_get_ranked_action( $rank_request ) { * @return object|string */ public function personalizer_send_reward( $event_id, $reward ) { - $settings = $this->get_settings(); + $feature = new RecommendedContent(); + $settings = $feature->get_settings( static::ID ); + $reward_endpoint = str_replace( '{eventId}', sanitize_text_field( $event_id ), $this->reward_endpoint ); $result = wp_remote_post( - trailingslashit( $settings['url'] ) . $reward_endpoint, + trailingslashit( $settings['endpoint_url'] ) . $reward_endpoint, [ 'headers' => [ 'Ocp-Apim-Subscription-Key' => $settings['api_key'], @@ -745,23 +711,25 @@ protected function authenticate_credentials( $url, $api_key ) { } /** - * Provides debug information related to the provider. + * Returns the debug information for the provider settings. * - * @param null|array $settings Settings array. If empty, settings will be retrieved. - * @return array Keyed array of debug information. - * @since 1.4.0 + * @return array */ - public function get_provider_debug_information( $settings = null ) { - if ( is_null( $settings ) ) { - $settings = $this->sanitize_settings( $this->get_settings() ); + public function get_debug_information() { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof RecommendedContent ) { + $debug_info[ __( 'API URL', 'classifai' ) ] = $provider_settings['endpoint_url']; + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_personalizer_status_response' ) ); } - $authenticated = 1 === intval( $settings['authenticated'] ?? 0 ); - - return [ - __( 'Authenticated', 'classifai' ) => $authenticated ? __( 'Yes', 'classifai' ) : __( 'No', 'classifai' ), - __( 'API URL', 'classifai' ) => $settings['url'] ?? '', - __( 'Service Status', 'classifai' ) => $this->get_formatted_latest_response( get_transient( 'classifai_azure_personalizer_status_response' ) ), - ]; + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); } } diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 6f643ff32..7cd0df820 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -601,7 +601,7 @@ public function get_debug_information() { $debug_info = []; if ( $this->feature_instance instanceof ImageGeneration ) { - $debug_info[ __( 'Number of images', 'classifai' ) ] = $provider_settings['number_of_images']; + $debug_info[ __( 'Number of images', 'classifai' ) ] = $provider_settings['number_of_images']; $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_dalle_latest_response' ) ); } diff --git a/includes/Classifai/Services/Personalizer.php b/includes/Classifai/Services/Personalizer.php index 1dde8e015..bd78794d1 100644 --- a/includes/Classifai/Services/Personalizer.php +++ b/includes/Classifai/Services/Personalizer.php @@ -17,11 +17,9 @@ class Personalizer extends Service { */ public function __construct() { parent::__construct( - __( 'Recommended Content', 'classifai' ), + __( 'Recommendation Service', 'classifai' ), 'personalizer', - [ - 'Classifai\Providers\Azure\Personalizer', - ] + self::get_service_providers() ); } @@ -36,6 +34,20 @@ public function init() { add_action( 'save_post', [ $this, 'maybe_clear_transient' ] ); } + /** + * Get service providers for Recommendation service. + * + * @return array + */ + public static function get_service_providers() { + return apply_filters( + 'classifai_recommendation_service_providers', + [ + 'Classifai\Providers\Azure\Personalizer', + ] + ); + } + /** * Render Recommended Content over AJAX. * diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index 03c81b17f..7b0ae5d1a 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -44,6 +44,7 @@ public function __construct( $services = [] ) { public function register() { add_filter( 'language_processing_features', [ $this, 'register_language_processing_features' ] ); add_filter( 'image_processing_features', [ $this, 'register_image_processing_features' ] ); + add_filter( 'personalizer_features', [ $this, 'register_recommendation_service_features' ] ); foreach ( $this->services as $key => $service ) { if ( class_exists( $service ) ) { @@ -88,6 +89,15 @@ public function register_image_processing_features() { ]; } + /** + * Registers providers under the Image Processing Service. + */ + public function register_recommendation_service_features() { + return [ + '\Classifai\Features\RecommendedContent', + ]; + } + /** * Get general ClassifAI settings * From 7534f84b5011fe84db8c0a3ee5bd00d60eb66b20 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 8 Jan 2024 01:06:07 +0530 Subject: [PATCH 074/127] move features for RecommendedContent --- .../Classifai/Features/RecommendedContent.php | 12 +- .../Providers/Azure/Personalizer.php | 119 ++++++++++++- includes/Classifai/Services/Personalizer.php | 166 ------------------ 3 files changed, 124 insertions(+), 173 deletions(-) diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index d2d9331e1..64e5ec418 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -76,7 +76,7 @@ public function setup_fields_sections() { add_settings_field( 'status', - esc_html__( 'Enable title generation', 'classifai' ), + esc_html__( 'Enable Recommended content block.', 'classifai' ), [ $this, 'render_input' ], $this->get_option_name(), $this->get_option_name() . '_section', @@ -84,7 +84,7 @@ public function setup_fields_sections() { 'label_for' => 'status', 'input_type' => 'checkbox', 'default_value' => $settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), + 'description' => __( 'Enables the ability to generate recommended content data for the block.', 'classifai' ), ] ); @@ -170,14 +170,14 @@ public function sanitize_settings( $new_settings ) { */ public function run( ...$args ) { $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ChatGPT::ID; + $provider_id = $settings['provider'] ?? PersonalizerProvider::ID; $provider_instance = $this->get_feature_provider_instance( $provider_id ); $result = ''; - if ( ChatGPT::ID === $provider_instance::ID ) { - /** @var ChatGPT $provider_instance */ + if ( PersonalizerProvider::ID === $provider_instance::ID ) { + /** @var PersonalizerProvider $provider_instance */ $result = call_user_func_array( - [ $provider_instance, 'generate_titles' ], + [ $provider_instance, 'personalizer_send_reward' ], [ ...$args ] ); } diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 1d642e5e1..4c86252b4 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -9,6 +9,7 @@ use Classifai\Blocks; use Classifai\Features\RecommendedContent; use WP_Error; +use WP_REST_Server; use UAParser\Parser; class Personalizer extends Provider { @@ -53,7 +54,9 @@ public function __construct( $feature_instance = null ) { */ public function register() { if ( ( new RecommendedContent() )->is_feature_enabled() ) { - // Setup Blocks + add_action( 'wp_ajax_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); + add_action( 'wp_ajax_nopriv_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); + add_action( 'save_post', [ $this, 'maybe_clear_transient' ] ); Blocks\setup(); } } @@ -710,6 +713,120 @@ protected function authenticate_credentials( $url, $api_key ) { return $rtn; } + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'personalizer/reward/(?P[a-zA-Z0-9-]+)', + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'reward_endpoint_callback' ], + 'args' => [ + 'eventId' => [ + 'required' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'description' => esc_html__( 'Event ID to track', 'classifai' ), + ], + 'rewarded' => [ + 'required' => false, + 'type' => 'string', + 'enum' => [ + '0', + '1', + ], + 'default' => '0', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Reward we want to send', 'classifai' ), + ], + 'route' => [ + 'required' => false, + 'type' => 'string', + 'default' => 'reward', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Route we want to call', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'reward_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to send reward. + * + * This check ensures that we are properly authenticated. + * TODO: add additional checks here, maybe a nonce check or rate limiting? + * + * @return WP_Error|bool + */ + public function reward_permissions_check() { + // Check if valid authentication is in place. + if ( ( new RecommendedContent() )->is_feature_enabled() ) { + return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with Azure.', 'classifai' ) ); + } + + return true; + } + + /** + * Render Recommended Content over AJAX. + * + * @return void + */ + public function ajax_render_recommended_content() { + check_ajax_referer( 'classifai-recommended-block', 'security' ); + + if ( ! isset( $_POST['contentPostType'] ) || empty( $_POST['contentPostType'] ) ) { + esc_html_e( 'No results found.', 'classifai' ); + exit(); + } + + $attributes = array( + 'displayLayout' => isset( $_POST['displayLayout'] ) ? sanitize_text_field( wp_unslash( $_POST['displayLayout'] ) ) : 'grid', + 'contentPostType' => sanitize_text_field( wp_unslash( $_POST['contentPostType'] ) ), + 'excludeId' => isset( $_POST['excludeId'] ) ? absint( $_POST['excludeId'] ) : 0, + 'displayPostExcerpt' => isset( $_POST['displayPostExcerpt'] ) ? filter_var( wp_unslash( $_POST['displayPostExcerpt'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayAuthor' => isset( $_POST['displayAuthor'] ) ? filter_var( wp_unslash( $_POST['displayAuthor'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayPostDate' => isset( $_POST['displayPostDate'] ) ? filter_var( wp_unslash( $_POST['displayPostDate'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayFeaturedImage' => isset( $_POST['displayFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['displayFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : true, + 'addLinkToFeaturedImage' => isset( $_POST['addLinkToFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['addLinkToFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'columns' => isset( $_POST['columns'] ) ? absint( $_POST['columns'] ) : 3, + 'numberOfItems' => isset( $_POST['numberOfItems'] ) ? absint( $_POST['numberOfItems'] ) : 3, + ); + + if ( isset( $_POST['taxQuery'] ) && ! empty( $_POST['taxQuery'] ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + foreach ( $_POST['taxQuery'] as $key => $value ) { + $attributes['taxQuery'][ $key ] = array_map( 'absint', $value ); + } + } + + echo $this->render_recommended_content( $attributes ); + + exit(); + } + + /** + * Maybe clear transients for recent actions. + * + * @param int $post_id Post Id. + * @return void + */ + public function maybe_clear_transient( $post_id ) { + global $wpdb; + $post_type = get_post_type( $post_id ); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $transients = $wpdb->get_col( $wpdb->prepare( "SELECT `option_name` FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_classifai_actions_' . $post_type . '%' ) ); + // Delete all transients + if ( ! empty( $transients ) ) { + foreach ( $transients as $transient ) { + delete_transient( str_replace( '_transient_', '', $transient ) ); + } + } + } + /** * Returns the debug information for the provider settings. * diff --git a/includes/Classifai/Services/Personalizer.php b/includes/Classifai/Services/Personalizer.php index bd78794d1..f86042e7b 100644 --- a/includes/Classifai/Services/Personalizer.php +++ b/includes/Classifai/Services/Personalizer.php @@ -28,10 +28,6 @@ public function __construct() { */ public function init() { parent::init(); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - add_action( 'wp_ajax_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); - add_action( 'wp_ajax_nopriv_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); - add_action( 'save_post', [ $this, 'maybe_clear_transient' ] ); } /** @@ -47,166 +43,4 @@ public static function get_service_providers() { ] ); } - - /** - * Render Recommended Content over AJAX. - * - * @return void - */ - public function ajax_render_recommended_content() { - check_ajax_referer( 'classifai-recommended-block', 'security' ); - - if ( ! isset( $_POST['contentPostType'] ) || empty( $_POST['contentPostType'] ) ) { - esc_html_e( 'No results found.', 'classifai' ); - exit(); - } - - $attributes = array( - 'displayLayout' => isset( $_POST['displayLayout'] ) ? sanitize_text_field( wp_unslash( $_POST['displayLayout'] ) ) : 'grid', - 'contentPostType' => sanitize_text_field( wp_unslash( $_POST['contentPostType'] ) ), - 'excludeId' => isset( $_POST['excludeId'] ) ? absint( $_POST['excludeId'] ) : 0, - 'displayPostExcerpt' => isset( $_POST['displayPostExcerpt'] ) ? filter_var( wp_unslash( $_POST['displayPostExcerpt'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayAuthor' => isset( $_POST['displayAuthor'] ) ? filter_var( wp_unslash( $_POST['displayAuthor'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayPostDate' => isset( $_POST['displayPostDate'] ) ? filter_var( wp_unslash( $_POST['displayPostDate'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayFeaturedImage' => isset( $_POST['displayFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['displayFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : true, - 'addLinkToFeaturedImage' => isset( $_POST['addLinkToFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['addLinkToFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'columns' => isset( $_POST['columns'] ) ? absint( $_POST['columns'] ) : 3, - 'numberOfItems' => isset( $_POST['numberOfItems'] ) ? absint( $_POST['numberOfItems'] ) : 3, - ); - - if ( isset( $_POST['taxQuery'] ) && ! empty( $_POST['taxQuery'] ) ) { - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash - foreach ( $_POST['taxQuery'] as $key => $value ) { - $attributes['taxQuery'][ $key ] = array_map( 'absint', $value ); - } - } - - $provider = find_provider_class( $this->provider_classes ?? [], 'AI Personalizer' ); - - if ( ! is_wp_error( $provider ) ) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo $provider->render_recommended_content( $attributes ); - } - - exit(); - } - - /** - * Create endpoints for services - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'personalizer/reward/(?P[a-zA-Z0-9-]+)', - [ - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'reward_endpoint_callback' ], - 'args' => [ - 'eventId' => [ - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'description' => esc_html__( 'Event ID to track', 'classifai' ), - ], - 'rewarded' => [ - 'required' => false, - 'type' => 'string', - 'enum' => [ - '0', - '1', - ], - 'default' => '0', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Reward we want to send', 'classifai' ), - ], - 'route' => [ - 'required' => false, - 'type' => 'string', - 'default' => 'reward', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Route we want to call', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'reward_permissions_check' ], - ] - ); - } - - /** - * Single call back to pass the route callback to the provider. - * - * @param WP_REST_Request $request The full request object. - * - * @return array|bool|string|WP_Error - */ - public function reward_endpoint_callback( WP_REST_Request $request ) { - $response = true; - $event_id = $request->get_param( 'eventId' ); - $reward = ( '1' === $request->get_param( 'rewarded' ) ) ? 1 : 0; - $route = $request->get_param( 'route' ) ?? false; - - // If no args, respond 404. - if ( false === $route ) { - return new WP_Error( 'no_route', esc_html__( 'No route indicated for the provider class to use.', 'classifai' ), [ 'status' => 404 ] ); - } - - if ( 'reward' === $route ) { - if ( empty( $event_id ) ) { - return new WP_Error( 'bad_request', esc_html__( 'Event ID required.', 'classifai' ), [ 'status' => 400 ] ); - } - - // Find the right provider class. - $provider = find_provider_class( $this->provider_classes ?? [], 'AI Personalizer' ); - - // Ensure we have a provider class. Should never happen but :shrug: - if ( is_wp_error( $provider ) ) { - return $provider; - } - - // Send reward to personalizer. - $response = $provider->personalizer_send_reward( $event_id, $reward ); - } - - return rest_ensure_response( $response ); - } - - /** - * Check if a given request has access to send reward. - * - * This check ensures that we are properly authenticated. - * TODO: add additional checks here, maybe a nonce check or rate limiting? - * - * @return WP_Error|bool - */ - public function reward_permissions_check() { - $settings = \Classifai\get_plugin_settings( 'language_processing', 'Personalizer' ); - - // Check if valid authentication is in place. - if ( empty( $settings ) || ( isset( $settings['authenticated'] ) && false === $settings['authenticated'] ) ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with Azure.', 'classifai' ) ); - } - - return true; - } - - /** - * Maybe clear transients for recent actions. - * - * @param int $post_id Post Id. - * @return void - */ - public function maybe_clear_transient( $post_id ) { - global $wpdb; - $post_type = get_post_type( $post_id ); - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $transients = $wpdb->get_col( $wpdb->prepare( "SELECT `option_name` FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_classifai_actions_' . $post_type . '%' ) ); - // Delete all transients - if ( ! empty( $transients ) ) { - foreach ( $transients as $transient ) { - delete_transient( str_replace( '_transient_', '', $transient ) ); - } - } - } } From 848d68d607ec32341fdd9517cf4b8f03b766a884 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 9 Jan 2024 00:06:50 +0530 Subject: [PATCH 075/127] add Classification mode and method setting --- .../Classifai/Features/Classification.php | 48 ++++++++++++++++--- includes/Classifai/Features/Feature.php | 40 ++++++++++++++++ includes/Classifai/Helpers.php | 15 +++--- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 4d0dbd55d..07365762a 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -110,6 +110,38 @@ public function setup_fields_sections() { ] ); + add_settings_field( + 'classification_mode', + esc_html__( 'Classification mode', 'classifai' ), + [ $this, 'render_radio_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'classification_mode', + 'default_value' => $settings['classification_mode'], + 'options' => array( + 'manual_review' => __( 'Manual review', 'classifai' ), + 'automatic_classification' => __( 'Automatic classification', 'classifai' ), + ), + ] + ); + + add_settings_field( + 'classification_method', + esc_html__( 'Classification method', 'classifai' ), + [ $this, 'render_radio_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'classification_method', + 'default_value' => $settings['classification_method'], + 'options' => array( + 'recommended_terms' => __( 'Recommend terms even if they do not exist on the site', 'classifai' ), + 'existing_terms' => __( 'Only recommend terms that already exist on the site', 'classifai' ), + ), + ] + ); + $post_types = get_post_types_for_language_settings(); $post_type_options = array(); @@ -164,9 +196,11 @@ public function setup_fields_sections() { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'post_statuses' => [], - 'post_types' => [], - 'provider' => NLU::ID, + 'post_statuses' => [], + 'post_types' => [], + 'classification_mode' => 'automatic_classification', + 'classification_method' => 'existing_terms', + 'provider' => NLU::ID, ]; return apply_filters( @@ -190,9 +224,11 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; - $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; + $new_settings = parent::sanitize_settings( $new_settings ); + $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; + $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; + $new_settings['classification_method'] = sanitize_text_field( $new_settings['classification_method'] ?? $settings['classification_method'] ); + $new_settings['classification_mode'] = sanitize_text_field( $new_settings['classification_mode'] ?? $settings['classification_mode'] ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index ca777a68c..cc3aafd9c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -934,6 +934,46 @@ public function render_auto_caption_fields( $args ) { } } + /** + * Render a group of radio. + * + * @param array $args The args passed to add_settings_field + */ + public function render_radio_group( array $args = array() ) { + $setting_index = $this->get_settings(); + $value = $setting_index[ $args['label_for'] ] ?? ''; + $options = $args['options'] ?? []; + if ( ! is_array( $options ) ) { + return; + } + + // Iterate through all of our options. + foreach ( $options as $option_value => $option_label ) { + // Render radio button. + printf( + '

    + +

    ', + esc_attr( $this->get_option_name() ), + esc_attr( $args['label_for'] ), + esc_attr( $option_value ), + checked( $value, $option_value, false ), + esc_html( $option_label ) + ); + } + + // Render description, if any. + if ( ! empty( $args['description'] ) ) { + printf( + '%s', + esc_html( $args['description'] ) + ); + } + } + /** * Render allowed users input field. * diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 63dff9c1b..6cc53b399 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -180,11 +180,11 @@ function get_watson_username() { * @return string */ function get_classification_mode() { - $provider = new NLU( 'Natural Language Understanding' ); - $settings = get_plugin_settings( 'language_processing', 'Natural Language Understanding' ); - $value = isset( $settings['classification_mode'] ) ? $settings['classification_mode'] : ''; + $feature = new Classification(); + $settings = $feature->get_settings(); + $value = $settings['classification_mode'] ?? ''; - if ( $provider->is_configured() ) { + if ( $feature->is_feature_enabled() ) { if ( empty( $value ) ) { // existing users // default: automatic_classification @@ -207,11 +207,10 @@ function get_classification_mode() { * @return string */ function get_classification_method() { - $provider = new NLU( 'language_processing' ); - $settings = $provider->get_settings(); - $value = $settings['classification_method'] ?? ''; + $feature = new Classification(); + $settings = $feature->get_settings(); - return $value; + return $settings['classification_method'] ?? ''; } /** From edac3aacb6136a44b299933c569224be84534c5d Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Tue, 9 Jan 2024 01:42:59 +0530 Subject: [PATCH 076/127] fix link terms --- .../Classifai/Features/Classification.php | 4 +- includes/Classifai/Features/Feature.php | 2 +- includes/Classifai/Helpers.php | 4 +- includes/Classifai/Providers/Watson/NLU.php | 50 ++++++++++++------- src/js/gutenberg-plugin.js | 3 +- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 07365762a..99559aae4 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -227,8 +227,8 @@ public function sanitize_settings( $new_settings ) { $new_settings = parent::sanitize_settings( $new_settings ); $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; - $new_settings['classification_method'] = sanitize_text_field( $new_settings['classification_method'] ?? $settings['classification_method'] ); $new_settings['classification_mode'] = sanitize_text_field( $new_settings['classification_mode'] ?? $settings['classification_mode'] ); + $new_settings['classification_method'] = sanitize_text_field( $new_settings['classification_method'] ?? $settings['classification_method'] ); // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); @@ -257,7 +257,7 @@ public function run( ...$args ) { if ( NLU::ID === $provider_instance::ID ) { /** @var NLU $provider_instance */ $result = call_user_func_array( - [ $provider_instance, 'classify_post' ], + [ $provider_instance, 'classify' ], [ ...$args ] ); } else if ( Embeddings::ID === $provider_instance::ID ) { diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index cc3aafd9c..b08e4969b 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -953,7 +953,7 @@ public function render_radio_group( array $args = array() ) { printf( '

    ', diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 6cc53b399..c9fedb02e 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -466,8 +466,8 @@ function get_feature_threshold( $feature ) { function get_feature_taxonomy( $classify_by = '' ) { $taxonomy = 0; - $featuse = new Classification(); - $settings = $featuse->get_settings( $featuse::ID ); + $feature = new Classification(); + $settings = $feature->get_settings( NLU::ID ); if ( ! empty( $settings[ $classify_by . '_taxonomy' ] ) ) { $taxonomy = $settings[ $classify_by . '_taxonomy' ]; diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 3881d987a..f79e90291 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -743,6 +743,11 @@ public function register_endpoints() { 'sanitize_callback' => 'absint', 'description' => esc_html__( 'Post ID to generate tags.', 'classifai' ), ), + 'linkTerms' => array( + 'type' => 'boolean', + 'description' => esc_html__( 'Whether to link terms or not.', 'classifai' ), + 'default' => true, + ), ), 'permission_callback' => [ $this, 'generate_post_tags_permissions_check' ], ] @@ -757,7 +762,8 @@ public function register_endpoints() { * @return array|bool|string|WP_Error */ public function generate_post_tags( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); + $post_id = $request->get_param( 'id' ); + $link_terms = $request->get_param( 'linkTerms' ); if ( empty( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to classify post.', 'classifai' ) ); @@ -765,7 +771,10 @@ public function generate_post_tags( WP_REST_Request $request ) { $result = $this->rest_endpoint_callback( $post_id, - 'classify' + 'classify', + [ + 'link_terms' => $link_terms, + ] ); return rest_ensure_response( @@ -829,7 +838,7 @@ public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args // Handle all of our routes. switch ( $route_to_call ) { case 'classify': - $return = ( new Classification() )->run( $post_id ); + $return = ( new Classification() )->run( $post_id, $args['link_terms'] ?? true ); break; } @@ -929,10 +938,11 @@ public function generate_post_tags_permissions_check( WP_REST_Request $request ) * Existing terms relationships are removed before classification. * * @param int $post_id the post to classify & link + * @param bool $link_terms Whether to link the terms to the post. * * @return array */ - public function classify( $post_id ) { + public function classify( $post_id, $link_terms = true ) { /** * Filter whether ClassifAI should classify a post. * @@ -954,23 +964,25 @@ public function classify( $post_id ) { $classifier = new \Classifai\PostClassifier(); - if ( \Classifai\get_feature_enabled( 'category' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'category' ) ); - } - - if ( \Classifai\get_feature_enabled( 'keyword' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'keyword' ) ); - } - - if ( \Classifai\get_feature_enabled( 'concept' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'concept' ) ); - } - - if ( \Classifai\get_feature_enabled( 'entity' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'entity' ) ); + if ( $link_terms ) { + if ( \Classifai\get_feature_enabled( 'category' ) ) { + wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'category' ) ); + } + + if ( \Classifai\get_feature_enabled( 'keyword' ) ) { + wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'keyword' ) ); + } + + if ( \Classifai\get_feature_enabled( 'concept' ) ) { + wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'concept' ) ); + } + + if ( \Classifai\get_feature_enabled( 'entity' ) ) { + wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'entity' ) ); + } } - $output = $classifier->classify_and_link( $post_id ); + $output = $classifier->classify_and_link( $post_id, [], $link_terms ); if ( is_wp_error( $output ) ) { update_post_meta( diff --git a/src/js/gutenberg-plugin.js b/src/js/gutenberg-plugin.js index afef69d76..818c2f372 100644 --- a/src/js/gutenberg-plugin.js +++ b/src/js/gutenberg-plugin.js @@ -95,7 +95,6 @@ const ClassifAIGenerateTagsButton = () => { setFeatureTaxonomies( resp.feature_taxonomies ); } - resp.terms = resp.terms?.data?.terms; const taxonomies = resp.terms; const taxTerms = {}; const taxTermsExisting = {}; @@ -279,7 +278,7 @@ const ClassifAIGenerateTagsButton = () => { { __( 'Save', 'classifai' ) }
    - + ); From 535309c196aed61fccee72ccee91218f7e87e574 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 00:29:37 +0530 Subject: [PATCH 077/127] >>>> remove Feature constructor --- .../Features/AudioTranscriptsGeneration.php | 2 - .../Classifai/Features/Classification.php | 74 ++++++++++++++++++- .../Classifai/Features/ContentResizing.php | 2 - .../Features/DescriptiveTextGenerator.php | 2 - .../Classifai/Features/ExcerptGeneration.php | 2 - includes/Classifai/Features/Feature.php | 4 +- includes/Classifai/Features/ImageCropping.php | 2 - .../Classifai/Features/ImageGeneration.php | 2 - .../Classifai/Features/ImageTagsGenerator.php | 2 - .../Features/ImageTextExtraction.php | 2 - .../Classifai/Features/PDFTextExtraction.php | 2 - .../Classifai/Features/RecommendedContent.php | 2 - includes/Classifai/Features/TextToSpeech.php | 2 - .../Classifai/Features/TitleGeneration.php | 2 - includes/Classifai/Services/Service.php | 5 +- 15 files changed, 78 insertions(+), 29 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index d3597d90f..0f83ebd5a 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -20,8 +20,6 @@ class AudioTranscriptsGeneration extends Feature { * Constructor. */ public function __construct() { - parent::__construct(); - /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 99559aae4..a137fdea9 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -23,8 +23,6 @@ class Classification extends Feature { * Constructor. */ public function __construct() { - parent::__construct(); - /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -181,6 +179,78 @@ public function setup_fields_sections() { * that are registered to the feature. */ $this->render_provider_fields(); + add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] ); + } + + public function render_previewer( $active_feature = '' ) { + if ( static::ID !== $active_feature ) { + return; + } + + $settings = $this->get_settings(); + + if ( ! $settings['status'] ) { + return; + } + + ?> +
    + $supported_post_types, + 'post_status' => $supported_post_statuses, + 'posts_per_page' => 10, + ) + ); + + $features = array( + 'category' => array( + 'name' => esc_html__( 'Category', 'classifai' ), + 'enabled' => \Classifai\get_feature_enabled( 'category' ), + 'plural' => 'categories', + ), + 'keyword' => array( + 'name' => esc_html__( 'Keyword', 'classifai' ), + 'enabled' => \Classifai\get_feature_enabled( 'keyword' ), + 'plural' => 'keywords', + ), + 'entity' => array( + 'name' => esc_html__( 'Entity', 'classifai' ), + 'enabled' => \Classifai\get_feature_enabled( 'entity' ), + 'plural' => 'entities', + ), + 'concept' => array( + 'name' => esc_html__( 'Concept', 'classifai' ), + 'enabled' => \Classifai\get_feature_enabled( 'concept' ), + 'plural' => 'concepts', + ), + ); + ?> +

    +
    + + + +
    +
    + $feature ) : ?> +
    +
    +
    + +
    +
    + features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $this->feature_classes[] = new $feature(); + $feature_instance = new $feature(); + $this->feature_classes[] = $feature_instance; + $feature_instance->setup(); } } } @@ -167,6 +169,7 @@ public function render_settings_page() { submit_button(); ?> + provider_classes ?? [], 'Natural Language Understanding' ); From 5503badd60c848ce9fe54ce629182eef1ef5e8a3 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 00:44:56 +0530 Subject: [PATCH 078/127] >>>> working previewer for Watson --- .../Classifai/Admin/PreviewClassifierData.php | 10 +--- .../Classifai/Features/Classification.php | 7 +++ src/js/language-processing.js | 51 ++++++++----------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/includes/Classifai/Admin/PreviewClassifierData.php b/includes/Classifai/Admin/PreviewClassifierData.php index 42f9ea6c5..3d690c319 100644 --- a/includes/Classifai/Admin/PreviewClassifierData.php +++ b/includes/Classifai/Admin/PreviewClassifierData.php @@ -20,7 +20,7 @@ public function __construct() { public function get_post_classifier_preview_data() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; - if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-watson_nlu-action' ) ) { + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-action' ) ) { wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) ); } @@ -45,13 +45,7 @@ public function get_post_classifier_preview_data() { public function get_post_search_results() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; - if ( - ! $nonce - || ( - ! wp_verify_nonce( $nonce, 'classifai-previewer-openai_embeddings-action' ) - && ! wp_verify_nonce( $nonce, 'classifai-previewer-watson_nlu-nonce' ) - ) - ) { + if ( ! ( $nonce && wp_verify_nonce( $nonce, 'classifai-previewer-nonce' ) ) ) { wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) ); } diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index a137fdea9..c9da94ec6 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -182,6 +182,13 @@ public function setup_fields_sections() { add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] ); } + /** + * Renders the previewer window for the feature. + * + * @param string $active_feature The ID of the current feature. + * + * @return void + */ public function render_previewer( $active_feature = '' ) { if ( static::ID !== $active_feature ) { return; diff --git a/src/js/language-processing.js b/src/js/language-processing.js index 43d7e568d..496f0f0db 100644 --- a/src/js/language-processing.js +++ b/src/js/language-processing.js @@ -4,20 +4,16 @@ import '../scss/language-processing.scss'; ( () => { let featureStatuses = {}; - const nonceElementNLU = document.getElementById( - 'classifai-previewer-watson_nlu-nonce' + const nonceEl = document.getElementById( + 'classifai-previewer-nonce' ); - const nonceElementEmbeddings = document.getElementById( - 'classifai-previewer-openai_embeddings-nonce' - ); - - if ( ! nonceElementNLU && ! nonceElementEmbeddings ) { + if ( ! nonceEl ) { return; } const previewWatson = () => { - if ( ! nonceElementNLU ) { + if ( ! nonceEl ) { return; } @@ -27,21 +23,21 @@ import '../scss/language-processing.scss'; getClassifierDataBtn.addEventListener( 'click', showPreviewWatson ); /** Previewer nonce. */ - const previewerNonce = nonceElementNLU.value; + const previewerNonce = nonceEl.value; /** Feature statuses. */ featureStatuses = { categoriesStatus: document.getElementById( - 'classifai-settings-category' + 'category' ).checked, keywordsStatus: document.getElementById( - 'classifai-settings-keyword' + 'keyword' ).checked, entitiesStatus: document.getElementById( - 'classifai-settings-entity' + 'entity' ).checked, conceptsStatus: document.getElementById( - 'classifai-settings-concept' + 'concept' ).checked, }; @@ -54,23 +50,23 @@ import '../scss/language-processing.scss'; document .querySelectorAll( - '#classifai-settings-category, #classifai-settings-keyword, #classifai-settings-entity, #classifai-settings-concept' + '#category, #keyword, #entity, #concept' ) .forEach( ( item ) => { item.addEventListener( 'change', ( e ) => { - if ( 'classifai-settings-category' === e.target.id ) { + if ( 'category' === e.target.id ) { featureStatuses.categoriesStatus = e.target.checked; } - if ( 'classifai-settings-keyword' === e.target.id ) { + if ( 'keyword' === e.target.id ) { featureStatuses.keywordsStatus = e.target.checked; } - if ( 'classifai-settings-entity' === e.target.id ) { + if ( 'entity' === e.target.id ) { featureStatuses.entitiesStatus = e.target.checked; } - if ( 'classifai-settings-concept' === e.target.id ) { + if ( 'concept' === e.target.id ) { featureStatuses.conceptsStatus = e.target.checked; } @@ -101,21 +97,21 @@ import '../scss/language-processing.scss'; /** Category thresholds. */ const categoryThreshold = Number( document.querySelector( - '#classifai-settings-category_threshold' + '#category_threshold' ).value ); const keywordThreshold = Number( document.querySelector( - '#classifai-settings-keyword_threshold' + '#keyword_threshold' ).value ); const entityThreshold = Number( - document.querySelector( '#classifai-settings-entity_threshold' ) + document.querySelector( '#entity_threshold' ) .value ); const conceptThreshold = Number( document.querySelector( - '#classifai-settings-concept_threshold' + '#concept_threshold' ).value ); @@ -222,7 +218,7 @@ import '../scss/language-processing.scss'; previewWatson(); const previewEmbeddings = () => { - if ( ! nonceElementEmbeddings ) { + if ( ! nonce ) { return; } @@ -232,7 +228,7 @@ import '../scss/language-processing.scss'; getClassifierDataBtn.addEventListener( 'click', showPreviewEmeddings ); /** Previewer nonce. */ - const previewerNonce = nonceElementEmbeddings.value; + const previewerNonce = nonce.value; /** * Live preview features. @@ -372,15 +368,12 @@ import '../scss/language-processing.scss'; * @param {Object} event Choices.js's 'search' event object. */ function searchPosts( event ) { - const nonceElement = nonceElementEmbeddings - ? nonceElementEmbeddings - : nonceElementNLU; - if ( ! nonceElement ) { + if ( ! nonceEl ) { return; } /** Previewer nonce. */ - const previewerNonce = nonceElement.value; + const previewerNonce = nonceEl.value; /* * Post types. From 452a3089d257c06dfc4867a0881ae7b40d78ce33 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 02:01:44 +0530 Subject: [PATCH 079/127] >>> 1 test failing --- .../classify-content-ibm-watson.test.js | 541 +++++++++--------- 1 file changed, 269 insertions(+), 272 deletions(-) diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index ea2ac46b3..149bd38a8 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -5,10 +5,11 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); cy.get( '#classifai_feature_classification_post_types_post' ).check(); - cy.get( '#classifai-settings-publish' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); + cy.wait( 1000 ) cy.get( '#category' ).check(); - cy.get( '#watson_nlu_classification_method_recommended_terms' ).check(); - cy.get( '#classifai-settings-enable_content_classification' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -18,252 +19,248 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.login(); } ); - it( 'Can save IBM Watson "Language Processing" settings', () => { - // Disable content classification by openai. - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=openai_embeddings' - ); - cy.get( '#enable_classification' ).uncheck(); - cy.get( '#submit' ).click(); - - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' - ); - - cy.get( '#classifai-settings-watson_url' ) - .clear() - .type( 'http://e2e-test-nlu-server.test/' ); - cy.get( '#classifai-settings-watson_password' ) - .clear() - .type( 'password' ); - - cy.get( '#status' ).check(); - cy.get( '#classifai_feature_classification_post_types_post' ).check(); - cy.get( '#classifai_feature_classification_post_types_page' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); - - cy.get( '#category' ).check(); - cy.get( '#keyword' ).check(); - cy.get( '#entity' ).check(); - cy.get( '#concept' ).check(); - cy.get( '#submit' ).click(); - } ); - - it( 'Can select Watson taxonomies "Language Processing" settings', () => { - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' - ); - - cy.get( '#classifai-settings-category_taxonomy' ).select( - 'watson-category' - ); - cy.get( '#classifai-settings-keyword_taxonomy' ).select( - 'watson-keyword' - ); - cy.get( '#classifai-settings-entity_taxonomy' ).select( - 'watson-entity' - ); - cy.get( '#classifai-settings-concept_taxonomy' ).select( - 'watson-concept' - ); - cy.get( '#submit' ).click(); - } ); - - it( 'Can see Watson taxonomies under "Posts" Menu.', () => { - cy.visit( '/wp-admin/edit.php' ); - - cy.get( '#menu-posts ul.wp-submenu li' ) - .filter( ':contains("Watson Categories")' ) - .should( 'have.length', 1 ); - cy.get( '#menu-posts ul.wp-submenu li' ) - .filter( ':contains("Watson Keywords")' ) - .should( 'have.length', 1 ); - cy.get( '#menu-posts ul.wp-submenu li' ) - .filter( ':contains("Watson Entities")' ) - .should( 'have.length', 1 ); - cy.get( '#menu-posts ul.wp-submenu li' ) - .filter( ':contains("Watson Concepts")' ) - .should( 'have.length', 1 ); - } ); - - it( 'Check Classification Mode toggle button is off, display popup, then add/remove terms', () => { - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' - ); - - cy.get( '#classifai-settings-manual_review' ).check(); - cy.get( '#submit' ).click(); - - // Create Test Post - cy.createPost( { - title: 'Test Classification Mode post', - content: 'Test Classification Mode post', - } ); - - // Close post publish panel - const closePanelSelector = 'button[aria-label="Close panel"]'; - cy.get( 'body' ).then( ( $body ) => { - if ( $body.find( closePanelSelector ).length > 0 ) { - cy.get( closePanelSelector ).click(); - } - } ); - - // Open post settings sidebar - cy.openDocumentSettingsSidebar(); - - // Open Panel - const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; - cy.get( panelButtonSelector ).then( ( $button ) => { - // Find the panel container - const $panel = $button.parents( '.components-panel__body' ); - - // Open Panel. - if ( ! $panel.hasClass( 'is-opened' ) ) { - cy.wrap( $button ).click(); - } - } ); - - // Check the toggle button is off - cy.get( '.classifai-panel .components-form-toggle' ).should( - 'not.have.class', - 'is-checked' - ); - - cy.get( '#classify-post-component button' ).click(); - - // see if there is a label with "Watson Categories" text exists - cy.get( '.components-form-token-field__label' ).contains( - 'Watson Categories' - ); - - // check if a term can be removed - cy.get( - '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item' - ).then( ( listing ) => { - const totalTerms = Cypress.$( listing ).length; - - // Remove 1 term - cy.get( - '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item:first-child .components-form-token-field__remove-token' - ).click(); - - // Now confirm if the term is reduced - cy.get( listing ).should( 'have.length', totalTerms - 1 ); - - // enter a new term as input and press enter key in js - cy.get( - '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' - ).type( 'NewTestTerm' ); - - // press enter key in js - cy.get( - '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' - ).type( '{enter}' ); - - // Click the save button - cy.get( '.classify-modal .components-button' ) - .contains( 'Save' ) - .click(); - - // Save the post - cy.get( '.editor-post-publish-button__button' ).click(); - } ); - } ); - - it( 'Check Classification Mode toggle button is on', () => { - cy.deactivatePlugin( 'classic-editor' ); - - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' - ); - - cy.get( '#classifai-settings-automatic_classification' ).check(); - cy.get( '#submit' ).click(); - - // Create Test Post - cy.createPost( { - title: 'Test Classification Mode Post', - content: 'Test Classification Mode Post', - } ); - - // Close post publish panel - const closePanelSelector = 'button[aria-label="Close panel"]'; - cy.get( 'body' ).then( ( $body ) => { - if ( $body.find( closePanelSelector ).length > 0 ) { - cy.get( closePanelSelector ).click(); - } - } ); - - // Open post settings sidebar - cy.openDocumentSettingsSidebar(); - - // Open Panel - const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; - cy.get( panelButtonSelector ).then( ( $button ) => { - // Find the panel container - const $panel = $button.parents( '.components-panel__body' ); - - // Open Panel - if ( ! $panel.hasClass( 'is-opened' ) ) { - cy.wrap( $button ).click(); - } - } ); - - // Check the toggle button is on - cy.get( '.classifai-panel .components-form-toggle' ).should( - 'have.class', - 'is-checked' - ); - } ); - - it( 'Can create post and taxonomy terms get created by ClassifAI (with default threshold)', () => { - const threshold = 0.7; - // Create Test Post - cy.createPost( { - title: 'Test NLU post', - content: 'Test NLU Content', - } ); - - // Close post publish panel - const closePanelSelector = 'button[aria-label="Close panel"]'; - cy.get( 'body' ).then( ( $body ) => { - if ( $body.find( closePanelSelector ).length > 0 ) { - cy.get( closePanelSelector ).click(); - } - } ); - - // Open post settings sidebar - cy.openDocumentSettingsSidebar(); - - // Verify Each Created taxonomies. - [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( - ( taxonomy ) => { - cy.verifyPostTaxonomyTerms( taxonomy, threshold ); - } - ); - } ); + // it( 'Can save IBM Watson "Language Processing" settings', () => { + // // Disable content classification by openai. + // cy.visit( + // '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' + // ); + // cy.get( '#status' ).uncheck(); + // cy.get( '#submit' ).click(); + + // cy.get( '#endpoint_url' ) + // .clear() + // .type( 'http://e2e-test-nlu-server.test/' ); + // cy.get( '#password' ) + // .clear() + // .type( 'password' ); + + // cy.get( '#status' ).check(); + // cy.get( '#classifai_feature_classification_post_types_post' ).check(); + // cy.get( '#classifai_feature_classification_post_types_page' ).check(); + // cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); + // cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); + // cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); + // cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + + // cy.get( '#category' ).check(); + // cy.get( '#keyword' ).check(); + // cy.get( '#entity' ).check(); + // cy.get( '#concept' ).check(); + // cy.get( '#submit' ).click(); + // } ); + + // it( 'Can select Watson taxonomies "Language Processing" settings', () => { + // cy.visit( + // '/wp-admin/tools.php?page=classifai&tab=language_processing' + // ); + + // cy.get( '#classifai-settings-category_taxonomy' ).select( + // 'watson-category' + // ); + // cy.get( '#classifai-settings-keyword_taxonomy' ).select( + // 'watson-keyword' + // ); + // cy.get( '#classifai-settings-entity_taxonomy' ).select( + // 'watson-entity' + // ); + // cy.get( '#classifai-settings-concept_taxonomy' ).select( + // 'watson-concept' + // ); + // cy.get( '#submit' ).click(); + // } ); + + // it( 'Can see Watson taxonomies under "Posts" Menu.', () => { + // cy.visit( '/wp-admin/edit.php' ); + + // cy.get( '#menu-posts ul.wp-submenu li' ) + // .filter( ':contains("Watson Categories")' ) + // .should( 'have.length', 1 ); + // cy.get( '#menu-posts ul.wp-submenu li' ) + // .filter( ':contains("Watson Keywords")' ) + // .should( 'have.length', 1 ); + // cy.get( '#menu-posts ul.wp-submenu li' ) + // .filter( ':contains("Watson Entities")' ) + // .should( 'have.length', 1 ); + // cy.get( '#menu-posts ul.wp-submenu li' ) + // .filter( ':contains("Watson Concepts")' ) + // .should( 'have.length', 1 ); + // } ); + + // it( 'Check Classification Mode toggle button is off, display popup, then add/remove terms', () => { + // cy.visit( + // '/wp-admin/tools.php?page=classifai&tab=language_processing' + // ); + + // cy.get( '#classifai_feature_classification_classification_mode_manual_review' ).check(); + // cy.get( '#submit' ).click(); + + // // Create Test Post + // cy.createPost( { + // title: 'Test Classification Mode post', + // content: 'Test Classification Mode post', + // } ); + + // // Close post publish panel + // const closePanelSelector = 'button[aria-label="Close panel"]'; + // cy.get( 'body' ).then( ( $body ) => { + // if ( $body.find( closePanelSelector ).length > 0 ) { + // cy.get( closePanelSelector ).click(); + // } + // } ); + + // // Open post settings sidebar + // cy.openDocumentSettingsSidebar(); + + // // Open Panel + // const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; + // cy.get( panelButtonSelector ).then( ( $button ) => { + // // Find the panel container + // const $panel = $button.parents( '.components-panel__body' ); + + // // Open Panel. + // if ( ! $panel.hasClass( 'is-opened' ) ) { + // cy.wrap( $button ).click(); + // } + // } ); + + // // Check the toggle button is off + // cy.get( '.classifai-panel .components-form-toggle' ).should( + // 'not.have.class', + // 'is-checked' + // ); + + // cy.get( '#classify-post-component button' ).click(); + + // // see if there is a label with "Watson Categories" text exists + // cy.get( '.components-form-token-field__label' ).contains( + // 'Watson Categories' + // ); + + // // check if a term can be removed + // cy.get( + // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item' + // ).then( ( listing ) => { + // const totalTerms = Cypress.$( listing ).length; + + // // Remove 1 term + // cy.get( + // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item:first-child .components-form-token-field__remove-token' + // ).click(); + + // // Now confirm if the term is reduced + // cy.get( listing ).should( 'have.length', totalTerms - 1 ); + + // // enter a new term as input and press enter key in js + // cy.get( + // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' + // ).type( 'NewTestTerm' ); + + // // press enter key in js + // cy.get( + // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' + // ).type( '{enter}' ); + + // // Click the save button + // cy.get( '.classify-modal .components-button' ) + // .contains( 'Save' ) + // .click(); + + // // Save the post + // cy.get( '.editor-post-publish-button__button' ).click(); + // } ); + // } ); + + // it( 'Check Classification Mode toggle button is on', () => { + // cy.deactivatePlugin( 'classic-editor' ); + + // cy.visit( + // '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' + // ); + + // cy.get( '#classifai_feature_classification_classification_mode_automatic_classification' ).check(); + // cy.get( '#submit' ).click(); + + // // Create Test Post + // cy.createPost( { + // title: 'Test Classification Mode Post', + // content: 'Test Classification Mode Post', + // } ); + + // // Close post publish panel + // const closePanelSelector = 'button[aria-label="Close panel"]'; + // cy.get( 'body' ).then( ( $body ) => { + // if ( $body.find( closePanelSelector ).length > 0 ) { + // cy.get( closePanelSelector ).click(); + // } + // } ); + + // // Open post settings sidebar + // cy.openDocumentSettingsSidebar(); + + // // Open Panel + // const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; + // cy.get( panelButtonSelector ).then( ( $button ) => { + // // Find the panel container + // const $panel = $button.parents( '.components-panel__body' ); + + // // Open Panel + // if ( ! $panel.hasClass( 'is-opened' ) ) { + // cy.wrap( $button ).click(); + // } + // } ); + + // // Check the toggle button is on + // cy.get( '.classifai-panel .components-form-toggle' ).should( + // 'have.class', + // 'is-checked' + // ); + // } ); + + // it( 'Can create post and taxonomy terms get created by ClassifAI (with default threshold)', () => { + // const threshold = 0.7; + // // Create Test Post + // cy.createPost( { + // title: 'Test NLU post', + // content: 'Test NLU Content', + // } ); + + // // Close post publish panel + // const closePanelSelector = 'button[aria-label="Close panel"]'; + // cy.get( 'body' ).then( ( $body ) => { + // if ( $body.find( closePanelSelector ).length > 0 ) { + // cy.get( closePanelSelector ).click(); + // } + // } ); + + // // Open post settings sidebar + // cy.openDocumentSettingsSidebar(); + + // // Verify Each Created taxonomies. + // [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( + // ( taxonomy ) => { + // cy.verifyPostTaxonomyTerms( taxonomy, threshold ); + // } + // ); + // } ); it( 'Can create post and taxonomy terms get created by ClassifAI (with 75 threshold)', () => { const threshold = 75; // Update Threshold to 75. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai-settings-category_threshold' ) + cy.get( '#category_threshold' ) .clear() .type( threshold ); - cy.get( '#classifai-settings-keyword_threshold' ) + cy.get( '#keyword_threshold' ) .clear() .type( threshold ); - cy.get( '#classifai-settings-entity_threshold' ) + cy.get( '#entity_threshold' ) .clear() .type( threshold ); - cy.get( '#classifai-settings-concept_threshold' ) + cy.get( '#concept_threshold' ) .clear() .type( threshold ); cy.get( '#submit' ).click(); @@ -288,7 +285,7 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () // Verify Each Created taxonomies. [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( ( taxonomy ) => { - cy.verifyPostTaxonomyTerms( taxonomy, threshold / 100 ); + // cy.verifyPostTaxonomyTerms( taxonomy, threshold / 100 ); } ); } ); @@ -303,20 +300,20 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () const threshold1 = 75; // Update classification method to "Add recommended terms" and threshold value. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#watson_nlu_classification_method_recommended_terms' ).check(); - cy.get( '#classifai-settings-category_threshold' ) + cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); + cy.get( '#category_threshold' ) .clear() .type( threshold1 ); - cy.get( '#classifai-settings-keyword_threshold' ) + cy.get( '#keyword_threshold' ) .clear() .type( threshold1 ); - cy.get( '#classifai-settings-entity_threshold' ) + cy.get( '#entity_threshold' ) .clear() .type( threshold1 ); - cy.get( '#classifai-settings-concept_threshold' ) + cy.get( '#concept_threshold' ) .clear() .type( threshold1 ); cy.get( '#submit' ).click(); @@ -349,20 +346,20 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () const threshold2 = 70; // Update classification method to "Only classify based on existing terms" and threshold value. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); cy.get( '#watson_nlu_classification_method_existing_terms' ).check(); - cy.get( '#classifai-settings-category_threshold' ) + cy.get( '#category_threshold' ) .clear() .type( threshold2 ); - cy.get( '#classifai-settings-keyword_threshold' ) + cy.get( '#keyword_threshold' ) .clear() .type( threshold2 ); - cy.get( '#classifai-settings-entity_threshold' ) + cy.get( '#entity_threshold' ) .clear() .type( threshold2 ); - cy.get( '#classifai-settings-concept_threshold' ) + cy.get( '#concept_threshold' ) .clear() .type( threshold2 ); cy.get( '#submit' ).click(); @@ -393,19 +390,19 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () // Update classification method back to "Add recommended terms". cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#watson_nlu_classification_method_recommended_terms' ).check(); + cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); cy.get( '#submit' ).click(); } ); it( 'Can create post and tags get created by ClassifAI', () => { const threshold = 70; cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#watson_nlu_classification_method_recommended_terms' ).check(); + cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); cy.get( '#classifai-settings-category_taxonomy' ).select( 'post_tag' ); cy.get( '#classifai-settings-keyword_taxonomy' ).select( 'post_tag' ); cy.get( '#classifai-settings-entity_taxonomy' ).select( 'post_tag' ); @@ -436,9 +433,9 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () it( 'Can enable/disable Natural Language Understanding features.', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai-settings-enable_content_classification' ).uncheck(); + cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); // Verify that the feature is not available. @@ -446,9 +443,9 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai-settings-enable_content_classification' ).check(); + cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -458,13 +455,13 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () it( 'Can limit Natural Language Understanding features by roles', () => { // Disable access to admin role. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).check(); cy.get( - '#watson_nlu_content_classification_roles_administrator' + '#classifai_feature_classification_roles_administrator' ).uncheck(); cy.get( '#submit' ).click(); @@ -478,10 +475,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).check(); cy.get( - '#watson_nlu_content_classification_roles_administrator' + '#classifai_feature_classification_roles_administrator' ).check(); cy.get( '#submit' ).click(); @@ -497,10 +494,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).uncheck(); cy.get( - '#classifai-settings-content_classification_user_based_access' + '#user_based_access' ).uncheck(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -513,26 +510,26 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).uncheck(); cy.get( - '#classifai-settings-content_classification_user_based_access' + '#user_based_access' ).check(); cy.get( 'body' ).then( ( $body ) => { if ( $body.find( - '#content_classification_users-container .components-form-token-field__remove-token' + '.allowed_users_row .components-form-token-field__remove-token' ).length > 0 ) { cy.get( - '#content_classification_users-container .components-form-token-field__remove-token' + '.allowed_users_row .components-form-token-field__remove-token' ).click( { multiple: true, } ); } } ); cy.get( - '#content_classification_users-container input.components-form-token-field__input' + '.allowed_users_row input.components-form-token-field__input' ).type( 'admin' ); cy.wait( 1000 ); cy.get( @@ -549,10 +546,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).check(); cy.get( - '#classifai-settings-content_classification_user_based_access' + '#user_based_access' ).uncheck(); cy.get( '#submit' ).click(); @@ -565,13 +562,13 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); cy.get( - '#classifai-settings-content_classification_role_based_access' + '#role_based_access' ).check(); cy.get( - '#classifai-settings-content_classification_user_based_access' + '#user_based_access' ).check(); cy.get( - '#classifai-settings-content_classification_user_based_opt_out' + '#user_based_opt_out' ).check(); cy.get( '#submit' ).click(); From c1ade8ba29f926dedaa1248711c08f3ea1553508 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 02:02:17 +0530 Subject: [PATCH 080/127] fix variable --- src/js/language-processing.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/language-processing.js b/src/js/language-processing.js index 496f0f0db..0699e1087 100644 --- a/src/js/language-processing.js +++ b/src/js/language-processing.js @@ -218,7 +218,7 @@ import '../scss/language-processing.scss'; previewWatson(); const previewEmbeddings = () => { - if ( ! nonce ) { + if ( ! nonceEl ) { return; } @@ -228,7 +228,7 @@ import '../scss/language-processing.scss'; getClassifierDataBtn.addEventListener( 'click', showPreviewEmeddings ); /** Previewer nonce. */ - const previewerNonce = nonce.value; + const previewerNonce = nonceEl.value; /** * Live preview features. From b7286852dd80af0b7a3253fe975587402b27069a Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 02:02:53 +0530 Subject: [PATCH 081/127] uncomment tests --- .../classify-content-ibm-watson.test.js | 446 +++++++++--------- 1 file changed, 223 insertions(+), 223 deletions(-) diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index 149bd38a8..cf6847733 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -19,229 +19,229 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.login(); } ); - // it( 'Can save IBM Watson "Language Processing" settings', () => { - // // Disable content classification by openai. - // cy.visit( - // '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' - // ); - // cy.get( '#status' ).uncheck(); - // cy.get( '#submit' ).click(); - - // cy.get( '#endpoint_url' ) - // .clear() - // .type( 'http://e2e-test-nlu-server.test/' ); - // cy.get( '#password' ) - // .clear() - // .type( 'password' ); - - // cy.get( '#status' ).check(); - // cy.get( '#classifai_feature_classification_post_types_post' ).check(); - // cy.get( '#classifai_feature_classification_post_types_page' ).check(); - // cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); - // cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); - // cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); - // cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); - - // cy.get( '#category' ).check(); - // cy.get( '#keyword' ).check(); - // cy.get( '#entity' ).check(); - // cy.get( '#concept' ).check(); - // cy.get( '#submit' ).click(); - // } ); - - // it( 'Can select Watson taxonomies "Language Processing" settings', () => { - // cy.visit( - // '/wp-admin/tools.php?page=classifai&tab=language_processing' - // ); - - // cy.get( '#classifai-settings-category_taxonomy' ).select( - // 'watson-category' - // ); - // cy.get( '#classifai-settings-keyword_taxonomy' ).select( - // 'watson-keyword' - // ); - // cy.get( '#classifai-settings-entity_taxonomy' ).select( - // 'watson-entity' - // ); - // cy.get( '#classifai-settings-concept_taxonomy' ).select( - // 'watson-concept' - // ); - // cy.get( '#submit' ).click(); - // } ); - - // it( 'Can see Watson taxonomies under "Posts" Menu.', () => { - // cy.visit( '/wp-admin/edit.php' ); - - // cy.get( '#menu-posts ul.wp-submenu li' ) - // .filter( ':contains("Watson Categories")' ) - // .should( 'have.length', 1 ); - // cy.get( '#menu-posts ul.wp-submenu li' ) - // .filter( ':contains("Watson Keywords")' ) - // .should( 'have.length', 1 ); - // cy.get( '#menu-posts ul.wp-submenu li' ) - // .filter( ':contains("Watson Entities")' ) - // .should( 'have.length', 1 ); - // cy.get( '#menu-posts ul.wp-submenu li' ) - // .filter( ':contains("Watson Concepts")' ) - // .should( 'have.length', 1 ); - // } ); - - // it( 'Check Classification Mode toggle button is off, display popup, then add/remove terms', () => { - // cy.visit( - // '/wp-admin/tools.php?page=classifai&tab=language_processing' - // ); - - // cy.get( '#classifai_feature_classification_classification_mode_manual_review' ).check(); - // cy.get( '#submit' ).click(); - - // // Create Test Post - // cy.createPost( { - // title: 'Test Classification Mode post', - // content: 'Test Classification Mode post', - // } ); - - // // Close post publish panel - // const closePanelSelector = 'button[aria-label="Close panel"]'; - // cy.get( 'body' ).then( ( $body ) => { - // if ( $body.find( closePanelSelector ).length > 0 ) { - // cy.get( closePanelSelector ).click(); - // } - // } ); - - // // Open post settings sidebar - // cy.openDocumentSettingsSidebar(); - - // // Open Panel - // const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; - // cy.get( panelButtonSelector ).then( ( $button ) => { - // // Find the panel container - // const $panel = $button.parents( '.components-panel__body' ); - - // // Open Panel. - // if ( ! $panel.hasClass( 'is-opened' ) ) { - // cy.wrap( $button ).click(); - // } - // } ); - - // // Check the toggle button is off - // cy.get( '.classifai-panel .components-form-toggle' ).should( - // 'not.have.class', - // 'is-checked' - // ); - - // cy.get( '#classify-post-component button' ).click(); - - // // see if there is a label with "Watson Categories" text exists - // cy.get( '.components-form-token-field__label' ).contains( - // 'Watson Categories' - // ); - - // // check if a term can be removed - // cy.get( - // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item' - // ).then( ( listing ) => { - // const totalTerms = Cypress.$( listing ).length; - - // // Remove 1 term - // cy.get( - // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item:first-child .components-form-token-field__remove-token' - // ).click(); - - // // Now confirm if the term is reduced - // cy.get( listing ).should( 'have.length', totalTerms - 1 ); - - // // enter a new term as input and press enter key in js - // cy.get( - // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' - // ).type( 'NewTestTerm' ); - - // // press enter key in js - // cy.get( - // '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' - // ).type( '{enter}' ); - - // // Click the save button - // cy.get( '.classify-modal .components-button' ) - // .contains( 'Save' ) - // .click(); - - // // Save the post - // cy.get( '.editor-post-publish-button__button' ).click(); - // } ); - // } ); - - // it( 'Check Classification Mode toggle button is on', () => { - // cy.deactivatePlugin( 'classic-editor' ); - - // cy.visit( - // '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' - // ); - - // cy.get( '#classifai_feature_classification_classification_mode_automatic_classification' ).check(); - // cy.get( '#submit' ).click(); - - // // Create Test Post - // cy.createPost( { - // title: 'Test Classification Mode Post', - // content: 'Test Classification Mode Post', - // } ); - - // // Close post publish panel - // const closePanelSelector = 'button[aria-label="Close panel"]'; - // cy.get( 'body' ).then( ( $body ) => { - // if ( $body.find( closePanelSelector ).length > 0 ) { - // cy.get( closePanelSelector ).click(); - // } - // } ); - - // // Open post settings sidebar - // cy.openDocumentSettingsSidebar(); - - // // Open Panel - // const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; - // cy.get( panelButtonSelector ).then( ( $button ) => { - // // Find the panel container - // const $panel = $button.parents( '.components-panel__body' ); - - // // Open Panel - // if ( ! $panel.hasClass( 'is-opened' ) ) { - // cy.wrap( $button ).click(); - // } - // } ); - - // // Check the toggle button is on - // cy.get( '.classifai-panel .components-form-toggle' ).should( - // 'have.class', - // 'is-checked' - // ); - // } ); - - // it( 'Can create post and taxonomy terms get created by ClassifAI (with default threshold)', () => { - // const threshold = 0.7; - // // Create Test Post - // cy.createPost( { - // title: 'Test NLU post', - // content: 'Test NLU Content', - // } ); - - // // Close post publish panel - // const closePanelSelector = 'button[aria-label="Close panel"]'; - // cy.get( 'body' ).then( ( $body ) => { - // if ( $body.find( closePanelSelector ).length > 0 ) { - // cy.get( closePanelSelector ).click(); - // } - // } ); - - // // Open post settings sidebar - // cy.openDocumentSettingsSidebar(); - - // // Verify Each Created taxonomies. - // [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( - // ( taxonomy ) => { - // cy.verifyPostTaxonomyTerms( taxonomy, threshold ); - // } - // ); - // } ); + it( 'Can save IBM Watson "Language Processing" settings', () => { + // Disable content classification by openai. + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' + ); + cy.get( '#status' ).uncheck(); + cy.get( '#submit' ).click(); + + cy.get( '#endpoint_url' ) + .clear() + .type( 'http://e2e-test-nlu-server.test/' ); + cy.get( '#password' ) + .clear() + .type( 'password' ); + + cy.get( '#status' ).check(); + cy.get( '#classifai_feature_classification_post_types_post' ).check(); + cy.get( '#classifai_feature_classification_post_types_page' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); + cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + + cy.get( '#category' ).check(); + cy.get( '#keyword' ).check(); + cy.get( '#entity' ).check(); + cy.get( '#concept' ).check(); + cy.get( '#submit' ).click(); + } ); + + it( 'Can select Watson taxonomies "Language Processing" settings', () => { + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=language_processing' + ); + + cy.get( '#classifai-settings-category_taxonomy' ).select( + 'watson-category' + ); + cy.get( '#classifai-settings-keyword_taxonomy' ).select( + 'watson-keyword' + ); + cy.get( '#classifai-settings-entity_taxonomy' ).select( + 'watson-entity' + ); + cy.get( '#classifai-settings-concept_taxonomy' ).select( + 'watson-concept' + ); + cy.get( '#submit' ).click(); + } ); + + it( 'Can see Watson taxonomies under "Posts" Menu.', () => { + cy.visit( '/wp-admin/edit.php' ); + + cy.get( '#menu-posts ul.wp-submenu li' ) + .filter( ':contains("Watson Categories")' ) + .should( 'have.length', 1 ); + cy.get( '#menu-posts ul.wp-submenu li' ) + .filter( ':contains("Watson Keywords")' ) + .should( 'have.length', 1 ); + cy.get( '#menu-posts ul.wp-submenu li' ) + .filter( ':contains("Watson Entities")' ) + .should( 'have.length', 1 ); + cy.get( '#menu-posts ul.wp-submenu li' ) + .filter( ':contains("Watson Concepts")' ) + .should( 'have.length', 1 ); + } ); + + it( 'Check Classification Mode toggle button is off, display popup, then add/remove terms', () => { + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=language_processing' + ); + + cy.get( '#classifai_feature_classification_classification_mode_manual_review' ).check(); + cy.get( '#submit' ).click(); + + // Create Test Post + cy.createPost( { + title: 'Test Classification Mode post', + content: 'Test Classification Mode post', + } ); + + // Close post publish panel + const closePanelSelector = 'button[aria-label="Close panel"]'; + cy.get( 'body' ).then( ( $body ) => { + if ( $body.find( closePanelSelector ).length > 0 ) { + cy.get( closePanelSelector ).click(); + } + } ); + + // Open post settings sidebar + cy.openDocumentSettingsSidebar(); + + // Open Panel + const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; + cy.get( panelButtonSelector ).then( ( $button ) => { + // Find the panel container + const $panel = $button.parents( '.components-panel__body' ); + + // Open Panel. + if ( ! $panel.hasClass( 'is-opened' ) ) { + cy.wrap( $button ).click(); + } + } ); + + // Check the toggle button is off + cy.get( '.classifai-panel .components-form-toggle' ).should( + 'not.have.class', + 'is-checked' + ); + + cy.get( '#classify-post-component button' ).click(); + + // see if there is a label with "Watson Categories" text exists + cy.get( '.components-form-token-field__label' ).contains( + 'Watson Categories' + ); + + // check if a term can be removed + cy.get( + '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item' + ).then( ( listing ) => { + const totalTerms = Cypress.$( listing ).length; + + // Remove 1 term + cy.get( + '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-flex-item:first-child .components-form-token-field__remove-token' + ).click(); + + // Now confirm if the term is reduced + cy.get( listing ).should( 'have.length', totalTerms - 1 ); + + // enter a new term as input and press enter key in js + cy.get( + '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' + ).type( 'NewTestTerm' ); + + // press enter key in js + cy.get( + '.classify-modal > div > div:nth-child(2) > div:first-of-type .components-form-token-field__input' + ).type( '{enter}' ); + + // Click the save button + cy.get( '.classify-modal .components-button' ) + .contains( 'Save' ) + .click(); + + // Save the post + cy.get( '.editor-post-publish-button__button' ).click(); + } ); + } ); + + it( 'Check Classification Mode toggle button is on', () => { + cy.deactivatePlugin( 'classic-editor' ); + + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' + ); + + cy.get( '#classifai_feature_classification_classification_mode_automatic_classification' ).check(); + cy.get( '#submit' ).click(); + + // Create Test Post + cy.createPost( { + title: 'Test Classification Mode Post', + content: 'Test Classification Mode Post', + } ); + + // Close post publish panel + const closePanelSelector = 'button[aria-label="Close panel"]'; + cy.get( 'body' ).then( ( $body ) => { + if ( $body.find( closePanelSelector ).length > 0 ) { + cy.get( closePanelSelector ).click(); + } + } ); + + // Open post settings sidebar + cy.openDocumentSettingsSidebar(); + + // Open Panel + const panelButtonSelector = `.components-panel__body .components-panel__body-title button:contains("ClassifAI")`; + cy.get( panelButtonSelector ).then( ( $button ) => { + // Find the panel container + const $panel = $button.parents( '.components-panel__body' ); + + // Open Panel + if ( ! $panel.hasClass( 'is-opened' ) ) { + cy.wrap( $button ).click(); + } + } ); + + // Check the toggle button is on + cy.get( '.classifai-panel .components-form-toggle' ).should( + 'have.class', + 'is-checked' + ); + } ); + + it( 'Can create post and taxonomy terms get created by ClassifAI (with default threshold)', () => { + const threshold = 0.7; + // Create Test Post + cy.createPost( { + title: 'Test NLU post', + content: 'Test NLU Content', + } ); + + // Close post publish panel + const closePanelSelector = 'button[aria-label="Close panel"]'; + cy.get( 'body' ).then( ( $body ) => { + if ( $body.find( closePanelSelector ).length > 0 ) { + cy.get( closePanelSelector ).click(); + } + } ); + + // Open post settings sidebar + cy.openDocumentSettingsSidebar(); + + // Verify Each Created taxonomies. + [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( + ( taxonomy ) => { + cy.verifyPostTaxonomyTerms( taxonomy, threshold ); + } + ); + } ); it( 'Can create post and taxonomy terms get created by ClassifAI (with 75 threshold)', () => { const threshold = 75; From e2632a372138dcdad40b7103ffa17e3158e0c9e0 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 10 Jan 2024 21:46:08 +0530 Subject: [PATCH 082/127] fix OpenAI Embeddings --- .../Classifai/Features/Classification.php | 48 +++--------------- includes/Classifai/Helpers.php | 2 +- .../Classifai/Providers/OpenAI/Embeddings.php | 18 ++++--- .../Classifai/Providers/OpenAI/OpenAI.php | 18 ++----- includes/Classifai/Providers/Watson/NLU.php | 49 +++++++++++++++++-- 5 files changed, 67 insertions(+), 68 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index c9da94ec6..8b108166d 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -108,38 +108,6 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'classification_mode', - esc_html__( 'Classification mode', 'classifai' ), - [ $this, 'render_radio_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'classification_mode', - 'default_value' => $settings['classification_mode'], - 'options' => array( - 'manual_review' => __( 'Manual review', 'classifai' ), - 'automatic_classification' => __( 'Automatic classification', 'classifai' ), - ), - ] - ); - - add_settings_field( - 'classification_method', - esc_html__( 'Classification method', 'classifai' ), - [ $this, 'render_radio_group' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'classification_method', - 'default_value' => $settings['classification_method'], - 'options' => array( - 'recommended_terms' => __( 'Recommend terms even if they do not exist on the site', 'classifai' ), - 'existing_terms' => __( 'Only recommend terms that already exist on the site', 'classifai' ), - ), - ] - ); - $post_types = get_post_types_for_language_settings(); $post_type_options = array(); @@ -273,11 +241,9 @@ public function render_previewer( $active_feature = '' ) { protected function get_default_settings() { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ - 'post_statuses' => [], - 'post_types' => [], - 'classification_mode' => 'automatic_classification', - 'classification_method' => 'existing_terms', - 'provider' => NLU::ID, + 'post_statuses' => [], + 'post_types' => [], + 'provider' => NLU::ID, ]; return apply_filters( @@ -301,11 +267,9 @@ public function sanitize_settings( $new_settings ) { $settings = $this->get_settings(); // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; - $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; - $new_settings['classification_mode'] = sanitize_text_field( $new_settings['classification_mode'] ?? $settings['classification_mode'] ); - $new_settings['classification_method'] = sanitize_text_field( $new_settings['classification_method'] ?? $settings['classification_method'] ); + $new_settings = parent::sanitize_settings( $new_settings ); + $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; + $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; // Sanitization of the provider-level settings. $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index c9fedb02e..965821f12 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -182,7 +182,7 @@ function get_watson_username() { function get_classification_mode() { $feature = new Classification(); $settings = $feature->get_settings(); - $value = $settings['classification_mode'] ?? ''; + $value = $settings[ NLU::ID ]['classification_mode'] ?? ''; if ( $feature->is_feature_enabled() ) { if ( empty( $value ) ) { diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index a2b8287cd..1f67c772d 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -253,11 +253,11 @@ public function sanitize_settings( $new_settings ) { // Sanitize the taxonomy checkboxes. $taxonomies = $this->get_taxonomies_for_settings(); foreach ( $taxonomies as $taxonomy_key => $taxonomy_value ) { - if ( isset( $new_settings['taxonomies'][ $taxonomy_key ] ) && '0' !== $new_settings['taxonomies'][ $taxonomy_key ] ) { - $new_settings['taxonomies'][ $taxonomy_key ] = sanitize_text_field( $new_settings['taxonomies'][ $taxonomy_key ] ?? $settings['taxonomies'][ $taxonomy_key ] ); + if ( isset( $new_settings[ static::ID ]['taxonomies'][ $taxonomy_key ] ) && '0' !== $new_settings[ static::ID ]['taxonomies'][ $taxonomy_key ] ) { + $new_settings[ static::ID ]['taxonomies'][ $taxonomy_key ] = sanitize_text_field( $new_settings[ static::ID ]['taxonomies'][ $taxonomy_key ] ?? $settings[ static::ID ]['taxonomies'][ $taxonomy_key ] ); $this->trigger_taxonomy_update( $taxonomy_key ); } else { - $new_settings['taxonomies'][ $taxonomy_key ] = '0'; + $new_settings[ static::ID ]['taxonomies'][ $taxonomy_key ] = '0'; } } } @@ -365,7 +365,7 @@ public function supported_taxonomies() { public function get_post_classifier_embeddings_preview_data() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; - if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-openai_embeddings-action' ) ) { + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-action' ) ) { wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) ); } @@ -476,7 +476,7 @@ private function get_terms( array $embedding = [] ) { } $settings = ( new Classification() )->get_settings(); - $number_to_add = $settings['number_of_terms'] ?? 1; + $number_to_add = $settings[ static::ID ]['number_of_terms'] ?? 1; $embedding_similarity = $this->get_embeddings_similarity( $embedding, false ); if ( empty( $embedding_similarity ) ) { @@ -544,6 +544,10 @@ private function get_embeddings_similarity( $embedding, $consider_threshold = tr $calculations = new EmbeddingCalculations(); foreach ( $taxonomies as $tax ) { + if ( is_numeric( $tax ) ) { + continue; + } + $terms = get_terms( [ 'taxonomy' => $tax, @@ -652,7 +656,7 @@ public function generate_embeddings( int $id = 0, $type = 'post' ) { // This check should have already run but if someone were to call // this method directly, we run it again. - if ( $feature->is_enabled() ) { + if ( ! $feature->is_enabled() ) { return new WP_Error( 'not_enabled', esc_html__( 'Classification is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } @@ -674,7 +678,7 @@ public function generate_embeddings( int $id = 0, $type = 'post' ) { return false; } - $request = new APIRequest( $settings['api_key'] ?? '', $this->get_option_name() ); + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $this->get_option_name() ); /** * Filter the request body before sending to OpenAI. diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index f92e0dc3e..721bbb411 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -17,7 +17,7 @@ trait OpenAI { * * @var string */ - protected $completions_url = 'https://api.openai.com/v1/completions'; + protected $model_url = 'https://api.openai.com/v1/models'; /** * Add our OpenAI API settings field. @@ -141,18 +141,7 @@ protected function authenticate_credentials( string $api_key = '' ) { // Make request to ensure credentials work. $request = new APIRequest( $api_key ); - $response = $request->post( - $this->completions_url, - [ - 'body' => wp_json_encode( - [ - 'model' => 'ada', - 'prompt' => 'hi', - 'max_tokens' => 1, - ] - ), - ] - ); + $response = $request->get( $this->model_url ); return ! is_wp_error( $response ) ? true : $response; } @@ -288,7 +277,8 @@ public function get_supported_post_statuses( $feature ) { * @return array */ public function get_supported_taxonomies( $feature ) { - $settings = $feature->get_settings(); + $provider = $feature->get_feature_provider_instance(); + $settings = $feature->get_settings( $provider::ID ); $taxonomies = []; if ( ! empty( $settings ) && isset( $settings['taxonomies'] ) ) { diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index f79e90291..ed896a198 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -174,6 +174,42 @@ function( $args = [] ) { ] ); + add_settings_field( + static::ID . 'classification_mode', + esc_html__( 'Classification mode', 'classifai' ), + [ $this->feature_instance, 'render_radio_group' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'classification_mode', + 'default_value' => $settings['classification_mode'], + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'options' => array( + 'manual_review' => __( 'Manual review', 'classifai' ), + 'automatic_classification' => __( 'Automatic classification', 'classifai' ), + ), + ] + ); + + add_settings_field( + static::ID . 'classification_method', + esc_html__( 'Classification method', 'classifai' ), + [ $this->feature_instance, 'render_radio_group' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'classification_method', + 'default_value' => $settings['classification_method'], + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. + 'options' => array( + 'recommended_terms' => __( 'Recommend terms even if they do not exist on the site', 'classifai' ), + 'existing_terms' => __( 'Only recommend terms that already exist on the site', 'classifai' ), + ), + ] + ); + foreach ( $this->nlu_features as $classify_by => $labels ) { add_settings_field( static::ID . '_' . $classify_by, @@ -199,10 +235,12 @@ function( $args = [] ) { */ public function get_default_provider_settings() { $common_settings = [ - 'endpoint_url' => '', - 'apikey' => '', - 'username' => '', - 'password' => '', + 'endpoint_url' => '', + 'apikey' => '', + 'username' => '', + 'password' => '', + 'classification_mode' => 'automatic_classification', + 'classification_method' => 'existing_terms', ]; switch ( $this->feature_instance::ID ) { @@ -536,6 +574,9 @@ public function sanitize_settings( $new_settings ) { $new_settings[ static::ID ]['authenticated'] = true; } + $new_settings[ static::ID ]['classification_mode'] = sanitize_text_field( $new_settings[ static::ID ]['classification_mode'] ?? $settings[ static::ID ]['classification_mode'] ); + $new_settings[ static::ID ]['classification_method'] = sanitize_text_field( $new_settings[ static::ID ]['classification_method'] ?? $settings[ static::ID ]['classification_method'] ); + $new_settings[ static::ID ]['endpoint_url'] = esc_url_raw( $new_settings[ static::ID ]['endpoint_url'] ?? $settings[ static::ID ]['endpoint_url'] ); $new_settings[ static::ID ]['username'] = sanitize_text_field( $new_settings[ static::ID ]['username'] ?? $settings[ static::ID ]['username'] ); $new_settings[ static::ID ]['password'] = sanitize_text_field( $new_settings[ static::ID ]['password'] ?? $settings[ static::ID ]['password'] ); From 0390a1f931f89a2cc4462a6430aa3d12b6197a02 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Thu, 11 Jan 2024 11:21:01 +0530 Subject: [PATCH 083/127] fix OpenAI Embeddings tests --- tests/cypress/config.config.js | 2 + .../classify-content-ibm-watson.test.js | 15 ++++---- ...lassify-content-openapi-embeddings.test.js | 37 ++++++++++--------- .../resize_content-openapi-chatgpt.test.js | 1 + tests/test-plugin/e2e-test-plugin.php | 2 + tests/test-plugin/models.json | 23 ++++++++++++ 6 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 tests/test-plugin/models.json diff --git a/tests/cypress/config.config.js b/tests/cypress/config.config.js index d9fee0593..f56d48452 100644 --- a/tests/cypress/config.config.js +++ b/tests/cypress/config.config.js @@ -1,6 +1,8 @@ const { defineConfig } = require( 'cypress' ); module.exports = defineConfig( { + viewportWidth: 1280, + viewportHeight: 1280, chromeWebSecurity: false, fixturesFolder: __dirname + '/fixtures', screenshotsFolder: __dirname + '/screenshots', diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index cf6847733..9cd9b0d40 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -4,9 +4,17 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ) + cy.get( '#endpoint_url' ) + .clear() + .type( 'http://e2e-test-nlu-server.test/' ); + cy.get( '#password' ) + .clear() + .type( 'password' ); cy.get( '#classifai_feature_classification_post_types_post' ).check(); cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); cy.get( '#status' ).check(); + cy.get( '#submit' ).click(); cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); cy.wait( 1000 ) cy.get( '#category' ).check(); @@ -27,13 +35,6 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); - cy.get( '#endpoint_url' ) - .clear() - .type( 'http://e2e-test-nlu-server.test/' ); - cy.get( '#password' ) - .clear() - .type( 'password' ); - cy.get( '#status' ).check(); cy.get( '#classifai_feature_classification_post_types_post' ).check(); cy.get( '#classifai_feature_classification_post_types_page' ).check(); diff --git a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js index d1b9c6cc4..cb1c41efb 100644 --- a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js +++ b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js @@ -25,15 +25,30 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.get( '#classifai_feature_classification_post_types_post' ).check(); cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).check(); - cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category_threshold' ).type( 80 ); // "Test" requires 80% confidence. At 81%, it does not apply. + cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category_threshold' ).clear().type( 100 ); // "Test" requires 80% confidence. At 81%, it does not apply. cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); } ); + it( 'Can see the preview on the settings page', () => { + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' + ); + + cy.get( '#submit' ).click(); + + // Click the Preview button. + const closePanelSelector = '#get-classifier-preview-data-btn'; + cy.get( closePanelSelector ).click(); + + // Check the term is received and visible. + cy.get( '.tax-row--Category' ).should( 'exist' ); + } ); + it( 'Can create category and post and category will get auto-assigned', () => { // Remove custom taxonomies so those don't interfere with the test. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' + '/wp-admin/tools.php?page=classifai&tab=language_processing' ); cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).uncheck(); cy.get( '#submit' ).click(); @@ -79,25 +94,13 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { .should( 'be.checked' ); cy.wrap( $panel ) .find( - '.editor-post-taxonomies__hierarchical-terms-list .editor-post-taxonomies__hierarchical-terms-choice:first label' + '.editor-post-taxonomies__hierarchical-terms-list' ) + .children() .contains( 'Test' ); } ); } ); - it( 'Can see the preview on the settings page', () => { - cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' - ); - - // Click the Preview button. - const closePanelSelector = '#get-classifier-preview-data-btn'; - cy.get( closePanelSelector ).click(); - - // Check the term is received and visible. - cy.get( '.tax-row--Category' ).should( 'exist' ); - } ); - it( 'Can create category and post and category will not get auto-assigned if feature turned off', () => { cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' @@ -203,7 +206,7 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can enable/disable content classification feature by role', () => { // Remove custom taxonomies so those don't interfere with the test. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' + '/wp-admin/tools.php?page=classifai&tab=language_processing' ); cy.get( '#submit' ).click(); diff --git a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js index decb348d9..ef78395dc 100644 --- a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js @@ -210,6 +210,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .find( 'a.action__remove_prompt' ) .click( { force: true } ); cy.get( 'div[aria-describedby="js-classifai--delete-prompt-modal"]' ) + .first() .find( '.button-primary' ) .click(); cy.get( diff --git a/tests/test-plugin/e2e-test-plugin.php b/tests/test-plugin/e2e-test-plugin.php index acc213ac3..aeb47bb52 100644 --- a/tests/test-plugin/e2e-test-plugin.php +++ b/tests/test-plugin/e2e-test-plugin.php @@ -19,6 +19,8 @@ function classifai_test_mock_http_requests( $preempt, $parsed_args, $url ) { if ( strpos( $url, 'http://e2e-test-nlu-server.test/v1/analyze' ) !== false ) { $response = file_get_contents( __DIR__ . '/nlu.json' ); + } elseif ( strpos( $url, 'https://api.openai.com/v1/models' ) !== false ) { + $response = file_get_contents( __DIR__ . '/models.json' ); } elseif ( strpos( $url, 'https://api.openai.com/v1/completions' ) !== false ) { $response = file_get_contents( __DIR__ . '/chatgpt.json' ); } elseif ( strpos( $url, 'https://api.openai.com/v1/chat/completions' ) !== false ) { diff --git a/tests/test-plugin/models.json b/tests/test-plugin/models.json new file mode 100644 index 000000000..4b0ef534d --- /dev/null +++ b/tests/test-plugin/models.json @@ -0,0 +1,23 @@ +{ + "object": "list", + "data": [ + { + "id": "model-id-0", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner" + }, + { + "id": "model-id-1", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner" + }, + { + "id": "model-id-2", + "object": "model", + "created": 1686935002, + "owned_by": "openai" + } + ] +} \ No newline at end of file From 594104749746632a2eef7e6268a1528234b85439 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 14 Jan 2024 14:38:46 +0530 Subject: [PATCH 084/127] fix url index --- includes/Classifai/Providers/Azure/SmartCropping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Classifai/Providers/Azure/SmartCropping.php b/includes/Classifai/Providers/Azure/SmartCropping.php index a946a5c9e..ce790af04 100644 --- a/includes/Classifai/Providers/Azure/SmartCropping.php +++ b/includes/Classifai/Providers/Azure/SmartCropping.php @@ -296,7 +296,7 @@ public function get_cropped_thumbnail( $attachment_id, $size_data ) { * @return string */ public function get_api_url() { - return sprintf( '%s%s', trailingslashit( $this->settings['url'] ), static::API_PATH ); + return sprintf( '%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH ); } /** From ae4cb11ef04689b4e4220b410ddbdda4c9564d2b Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 15 Jan 2024 11:07:38 +0530 Subject: [PATCH 085/127] fix eslint issues --- src/js/admin.js | 6 +----- src/js/language-processing.js | 39 +++++++++-------------------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/js/admin.js b/src/js/admin.js index b7b6ceafe..8e8a27f6c 100644 --- a/src/js/admin.js +++ b/src/js/admin.js @@ -46,11 +46,7 @@ document.addEventListener( 'DOMContentLoaded', function () { $userFieldWrapper = $userField.closest( '.classifai-setup-form-field' ); } - if ( - document - .getElementById( 'password' ) - .closest( 'tr' ) - ) { + if ( document.getElementById( 'password' ).closest( 'tr' ) ) { [ $passwordFieldTitle ] = document .getElementById( 'password' ) .closest( 'tr' ) diff --git a/src/js/language-processing.js b/src/js/language-processing.js index 0699e1087..d5a18b393 100644 --- a/src/js/language-processing.js +++ b/src/js/language-processing.js @@ -4,9 +4,7 @@ import '../scss/language-processing.scss'; ( () => { let featureStatuses = {}; - const nonceEl = document.getElementById( - 'classifai-previewer-nonce' - ); + const nonceEl = document.getElementById( 'classifai-previewer-nonce' ); if ( ! nonceEl ) { return; @@ -27,18 +25,10 @@ import '../scss/language-processing.scss'; /** Feature statuses. */ featureStatuses = { - categoriesStatus: document.getElementById( - 'category' - ).checked, - keywordsStatus: document.getElementById( - 'keyword' - ).checked, - entitiesStatus: document.getElementById( - 'entity' - ).checked, - conceptsStatus: document.getElementById( - 'concept' - ).checked, + categoriesStatus: document.getElementById( 'category' ).checked, + keywordsStatus: document.getElementById( 'keyword' ).checked, + entitiesStatus: document.getElementById( 'entity' ).checked, + conceptsStatus: document.getElementById( 'concept' ).checked, }; const plurals = { @@ -49,9 +39,7 @@ import '../scss/language-processing.scss'; }; document - .querySelectorAll( - '#category, #keyword, #entity, #concept' - ) + .querySelectorAll( '#category, #keyword, #entity, #concept' ) .forEach( ( item ) => { item.addEventListener( 'change', ( e ) => { if ( 'category' === e.target.id ) { @@ -96,23 +84,16 @@ import '../scss/language-processing.scss'; function showPreviewWatson( e ) { /** Category thresholds. */ const categoryThreshold = Number( - document.querySelector( - '#category_threshold' - ).value + document.querySelector( '#category_threshold' ).value ); const keywordThreshold = Number( - document.querySelector( - '#keyword_threshold' - ).value + document.querySelector( '#keyword_threshold' ).value ); const entityThreshold = Number( - document.querySelector( '#entity_threshold' ) - .value + document.querySelector( '#entity_threshold' ).value ); const conceptThreshold = Number( - document.querySelector( - '#concept_threshold' - ).value + document.querySelector( '#concept_threshold' ).value ); const postId = document.getElementById( From b2c71cb5b831721ee1e633427722361e6885ce2b Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 12:31:52 -0700 Subject: [PATCH 086/127] Cleanup around the config constants we load, removing some code we don't use and reorganizing a few things --- classifai.php | 22 +++++++--------------- config.php | 15 +++++---------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/classifai.php b/classifai.php index 35d41d977..6ec512ee7 100644 --- a/classifai.php +++ b/classifai.php @@ -59,13 +59,14 @@ function () { } /** - * Small wrapper around PHP's define function. The defined constant is - * ignored if it has already been defined. This allows the - * config.local.php to override any constant in config.php. + * Small wrapper around PHP's define function. * - * @param string $name The constant name - * @param mixed $value The constant value - * @return void + * The defined constant is ignored if it has already + * been defined. This allows these constants to be + * overridden. + * + * @param string $name The constant name. + * @param mixed $value The constant value. */ function classifai_define( $name, $value ) { if ( ! defined( $name ) ) { @@ -73,16 +74,7 @@ function classifai_define( $name, $value ) { } } -if ( file_exists( __DIR__ . '/config.test.php' ) && defined( 'PHPUNIT_RUNNER' ) ) { - require_once __DIR__ . '/config.test.php'; -} - -if ( file_exists( __DIR__ . '/config.local.php' ) ) { - require_once __DIR__ . '/config.local.php'; -} - require_once __DIR__ . '/config.php'; -classifai_define( 'CLASSIFAI_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); /** * Loads the CLASSIFAI PHP autoloader if possible. diff --git a/config.php b/config.php index 4050f7991..8b44d03a6 100644 --- a/config.php +++ b/config.php @@ -1,20 +1,18 @@ Date: Mon, 22 Jan 2024 12:43:01 -0700 Subject: [PATCH 087/127] Cleanup how we autoload things. Rely on composer only instead of having a fallback to our own autoloader. Remove some code that we don't need --- autoload.php | 152 -------------------------------------------------- classifai.php | 42 +++----------- 2 files changed, 7 insertions(+), 187 deletions(-) delete mode 100644 autoload.php diff --git a/autoload.php b/autoload.php deleted file mode 100644 index 5ea902fbf..000000000 --- a/autoload.php +++ /dev/null @@ -1,152 +0,0 @@ -prefixes[ $prefix ] ) === false ) { - $this->prefixes[ $prefix ] = array(); - } - - // retain the base directory for the namespace prefix - if ( $prepend ) { - array_unshift( $this->prefixes[ $prefix ], $base_dir ); - } else { - array_push( $this->prefixes[ $prefix ], $base_dir ); - } - } - - /** - * Loads the class file for a given class name. - * - * @param string $classname The fully-qualified class name. - * @return mixed The mapped file name on success, or boolean false on - * failure. - */ - public function load_class( $classname ) { - // the current namespace prefix - $prefix = $classname; - - // work backwards through the namespace names of the fully-qualified - // class name to find a mapped file name - while ( false !== $pos = strrpos( $prefix, '\\' ) ) { // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition - - // retain the trailing namespace separator in the prefix - $prefix = substr( $classname, 0, $pos + 1 ); - - // the rest is the relative class name - $relative_class = substr( $classname, $pos + 1 ); - - // try to load a mapped file for the prefix and relative class - $mapped_file = $this->load_mapped_file( $prefix, $relative_class ); - if ( $mapped_file ) { - return $mapped_file; - } - - // remove the trailing namespace separator for the next iteration - // of strrpos() - $prefix = rtrim( $prefix, '\\' ); - } - - // never found a mapped file - return false; - } - - /** - * Load the mapped file for a namespace prefix and relative class. - * - * @param string $prefix The namespace prefix. - * @param string $relative_class The relative class name. - * @return mixed Boolean false if no mapped file can be loaded, or the - * name of the mapped file that was loaded. - */ - protected function load_mapped_file( $prefix, $relative_class ) { - // are there any base directories for this namespace prefix? - if ( isset( $this->prefixes[ $prefix ] ) === false ) { - return false; - } - - // look through base directories for this namespace prefix - foreach ( $this->prefixes[ $prefix ] as $base_dir ) { - - // replace the namespace prefix with the base directory, - // replace namespace separators with directory separators - // in the relative class name, append with .php - $file = $base_dir . str_replace( '\\', '/', $relative_class ) . '.php'; - - // if the mapped file exists, require it - if ( $this->require_file( $file ) ) { - // yes, we're done - return $file; - } - } - - // never found it - return false; - } - - /** - * If a file exists, require it from the file system. - * - * @param string $file The file to require. - * @return bool True if the file exists, false if not. - */ - protected function require_file( $file ) { - if ( file_exists( $file ) ) { - require $file; - return true; - } - return false; - } -} - -// instantiate the loader -$classifai_loader = new \Classifai\Psr4AutoloaderClass(); - -// register the autoloader -$classifai_loader->register(); - -// register the base directories for the namespace prefix -$classifai_loader->add_namespace( 'Classifai', __DIR__ . '/includes/Classifai' ); - -require_once __DIR__ . '/includes/Classifai/Helpers.php'; -require_once __DIR__ . '/includes/Classifai/Blocks.php'; diff --git a/classifai.php b/classifai.php index 6ec512ee7..79447d8a6 100644 --- a/classifai.php +++ b/classifai.php @@ -77,27 +77,14 @@ function classifai_define( $name, $value ) { require_once __DIR__ . '/config.php'; /** - * Loads the CLASSIFAI PHP autoloader if possible. + * Loads the autoloader if possible. * * @return bool True or false if autoloading was successful. */ function classifai_autoload() { - if ( classifai_can_autoload() ) { - require_once classifai_autoloader(); - - return true; - } else { - return false; - } -} + if ( file_exists( CLASSIFAI_PLUGIN_DIR . '/vendor/autoload.php' ) ) { + require_once CLASSIFAI_PLUGIN_DIR . '/vendor/autoload.php'; -/** - * In server mode we can autoload if autoloader file exists. For - * test environments we prevent autoloading of the plugin to prevent - * global pollution and for better performance. - */ -function classifai_can_autoload() { - if ( file_exists( classifai_autoloader() ) ) { return true; } else { error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log @@ -108,32 +95,19 @@ function classifai_can_autoload() { } } -/** - * Default is Composer's autoloader - */ -function classifai_autoloader() { - if ( file_exists( CLASSIFAI_PLUGIN_DIR . '/vendor/autoload.php' ) ) { - return CLASSIFAI_PLUGIN_DIR . '/vendor/autoload.php'; - } else { - return CLASSIFAI_PLUGIN_DIR . '/autoload.php'; - } -} - /** * Gets the installation message error. * - * This was put in a function specifically because it's used both in WP-CLI and within an admin notice if not using - * WP-CLI. + * Used both in a WP-CLI context and within an admin notice. * * @return string */ function get_error_install_message() { - return esc_html__( 'Error: Please run $ composer install in the classifai plugin directory.', 'classifai' ); + return esc_html__( 'Error: Please run $ composer install in the ClassifAI plugin directory.', 'classifai' ); } /** - * Plugin code entry point. Singleton instance is used to maintain a common single - * instance of the plugin throughout the current request's lifecycle. + * Plugin code entry point. * * If autoloading failed an admin notice is shown and logged to * the PHP error_log. @@ -159,7 +133,6 @@ function classifai_autorun() { } } - /** * Generate a notice if autoload fails. */ @@ -168,9 +141,8 @@ function classifai_autoload_notice() { error_log( get_error_install_message() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } - /** - * Register an activation hook that we can hook into. + * Run functionality on plugin activation. */ function classifai_activation() { set_transient( 'classifai_activation_notice', 'classifai', HOUR_IN_SECONDS ); From 0c7b439266c6d177082f8b855f443c4a5d0611e2 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 12:48:32 -0700 Subject: [PATCH 088/127] Try updating all of our npm dependencies --- package-lock.json | 1800 +++++++++++++++++++-------------------------- package.json | 13 +- 2 files changed, 755 insertions(+), 1058 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19edbc4c2..0f5a32279 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,22 +9,21 @@ "version": "2.5.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/icons": "^9.26.0", + "@wordpress/icons": "^9.40.0", "choices.js": "^10.2.0", "tippy.js": "^6.3.7" }, "devDependencies": { "@10up/cypress-wp-utils": "^0.2.0", - "@wordpress/env": "^8.13.0", - "@wordpress/scripts": "^26.18.0", - "cypress": "^13.6.1", + "@wordpress/env": "^9.1.0", + "@wordpress/scripts": "^27.0.0", + "cypress": "^13.6.3", "cypress-file-upload": "^5.0.8", - "cypress-mochawesome-reporter": "^3.7.0", + "cypress-mochawesome-reporter": "^3.8.0", "cypress-plugin-tab": "^1.0.5", "husky": "^8.0.3", "jsdoc": "^3.6.11", - "lint-staged": "^13.2.2", - "mochawesome-json-to-md": "^0.7.2", + "lint-staged": "^15.2.0", "node-wp-i18n": "^1.2.7", "svg-react-loader": "^0.4.6", "webpack": "^5.86.0", @@ -336,9 +335,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -1722,16 +1721,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz", - "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", + "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "semver": "^6.3.1" }, "engines": { @@ -2298,9 +2297,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2337,9 +2336,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2361,13 +2360,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2388,9 +2387,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -2941,19 +2940,11 @@ "node": ">= 8" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -2961,94 +2952,14 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@pkgr/utils/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgr/utils/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.1.tgz", + "integrity": "sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==", "dev": true, "peer": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.41.1" }, "bin": { "playwright": "cli.js" @@ -3183,9 +3094,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { "b4a": "^1.6.4", @@ -3818,9 +3729,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -3837,9 +3748,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -4109,9 +4020,9 @@ "dev": true }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { "version": "6.9.10", @@ -4126,9 +4037,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.8.tgz", - "integrity": "sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==", + "version": "18.2.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4136,9 +4047,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", - "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", "dependencies": { "@types/react": "*" } @@ -4159,9 +4070,9 @@ "dev": true }, "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { "version": "7.5.6", @@ -4331,16 +4242,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4399,15 +4310,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4" }, "engines": { @@ -4427,13 +4338,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4444,13 +4355,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -4471,9 +4382,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4484,16 +4395,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -4510,6 +4422,15 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4522,6 +4443,21 @@ "node": ">=10" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -4544,17 +4480,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", "semver": "^7.5.4" }, "engines": { @@ -4602,12 +4538,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.19.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4827,23 +4763,23 @@ } }, "node_modules/@wordpress/api-fetch": { - "version": "6.44.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.44.0.tgz", - "integrity": "sha512-d8ouvBiKDFu67O9Y8MtlUR2YojCAjmLf0LuBKsSOS5r3MOiwte1tQwsLdzFmGYkdCK09mZhT3UVKdOOiAC3kKA==", + "version": "6.46.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.46.0.tgz", + "integrity": "sha512-SimHPw57N8LyZpQB6dK5xq1Kn1WtqP/K27GjGwvxvkb+8xbVv0TI67AF9adsN4sZbOHIZJQwqvCTSGKhNttAvQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.47.0", - "@wordpress/url": "^3.48.0" + "@wordpress/i18n": "^4.49.0", + "@wordpress/url": "^3.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.30.0.tgz", - "integrity": "sha512-UKkyFmEYk1UTO0ZPun6Kw5dNflTEDpDK/6RxAqxbVrsIWUVSkVahwBnqfS0v5LuvVU8y+5vJSR/WjlnKEmS3Sg==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.32.0.tgz", + "integrity": "sha512-ie6p5VpUxTNMPQrHdCYEPddTzmDeFTQjFi3qq17set9WbRAMaOZ8jqQhSxms0NJi8Xa6wZM9TR2ZABAlg+FTeA==", "dev": true, "engines": { "node": ">=14" @@ -4853,9 +4789,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "7.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.31.0.tgz", - "integrity": "sha512-LAiTOlolFvKW6xmL6qRkdbPG09LPwAsmDepz4zWrFXJZHSImDeO2QXHecF1GnFyzLLKr1myHR5MbN3K5MSzpqQ==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.33.0.tgz", + "integrity": "sha512-/OonEa67xJdIn0ADWEd7AJtLhIGlYALKyc17RxTmI2Ojs0zLIQNbgAv1D/cuVguo0UKK9zsMZ9MBkhSKLF9A9Q==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -4864,9 +4800,9 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.30.0", - "@wordpress/browserslist-config": "^5.30.0", - "@wordpress/warning": "^2.47.0", + "@wordpress/babel-plugin-import-jsx-pragma": "^4.32.0", + "@wordpress/browserslist-config": "^5.32.0", + "@wordpress/warning": "^2.49.0", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.2.0" @@ -4876,45 +4812,44 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.38.0.tgz", - "integrity": "sha512-w491MMHfoCHdWibyTAcmGWvXwNMptslFQOU+jQ5DVeDIgDux1KLo/7oZ41CCHwqYayrCf60BC9+JopDXqq1H+g==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.40.0.tgz", + "integrity": "sha512-A+HiyES4YjfbFhJAGrhCLB3QWomgWZR9wkgG7K9l6DD70/9Vd7t+go7jI1HJ1c9qGfBV0rmdQf/qNn89Aai1cg==", "dev": true }, "node_modules/@wordpress/browserslist-config": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.30.0.tgz", - "integrity": "sha512-HFgLCkvvxba+j7/qNjVn1od38tvMm1xVlIJBR+zukkTvvLu/AkdelWKAQpvAoFAXMaZJ7239VxDVBYbVolf6FQ==", + "version": "5.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.32.0.tgz", + "integrity": "sha512-LrL4Zg/abXYfVwwbx1caugz4J1GUL+6WNqVF1MZQVDm6CHdlpTEQOvvr/KEi9mN1UY2YoTlxZtUxzvNRTo2Fsg==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-4.30.0.tgz", - "integrity": "sha512-Z3AcceaoHFvJdRNVp8rf6EI+rxK0gUMGMfcXYZPAoaDhP6Gt0bsbVMP5zQH2EYl7JHsbRZIQmMqd2fG5E/VjSQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.0.0.tgz", + "integrity": "sha512-b3j4yCB5dR04rIbZ73iHN5hMXL4kMUUoApY36Zs8AAREHpgCDTPp5vNqc67zg2bcnpDEhMUZ28DISwrY4z7weg==", "dev": true, "dependencies": { - "json2php": "^0.0.7", - "webpack-sources": "^3.2.2" + "json2php": "^0.0.7" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "webpack": "^4.8.3 || ^5.0.0" + "webpack": "^5.0.0" } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.15.0.tgz", - "integrity": "sha512-ZqCYcxT0Gc59isS42Q7WTQVu3ace8DDEED/RR8loTG+YjqEB1pW5hALFiVXBtM6vSjnnDO0M1NYAldh8l7SCmA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.17.0.tgz", + "integrity": "sha512-WuyorK1PL4r0LtxdhwF8u31s/O7+reuU906dnM3pu6SKSPsyfhXi8O1hgQO4/VASooHygUbsn7PW0GaDdCamOA==", "dev": true, "dependencies": { - "@wordpress/api-fetch": "^6.44.0", - "@wordpress/keycodes": "^3.47.0", - "@wordpress/url": "^3.48.0", + "@wordpress/api-fetch": "^6.46.0", + "@wordpress/keycodes": "^3.49.0", + "@wordpress/url": "^3.50.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", @@ -4944,14 +4879,14 @@ } }, "node_modules/@wordpress/element": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.12.0.tgz", - "integrity": "sha512-W2Gcg8G9Qbzvh/9smHgvisoepe+GWzHXdxXOdRclNtmNXv0GGRkJJRIm2JFeV7emc2rOiI68VM/khnSTc293sQ==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.26.0.tgz", + "integrity": "sha512-pYZ2OsFgDN00amTxPoC7BtlkVtVBeLS/Y1+P1Mlu0CX+gHDP0Il9SUaLVEIAewLnZMN+O3ph3H5nfR0yKkSnAA==", "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.35.0", + "@wordpress/escape-html": "^2.49.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -4962,14 +4897,14 @@ } }, "node_modules/@wordpress/env": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-8.13.0.tgz", - "integrity": "sha512-rtrrBO22DnbLsdBlsGqlMQrjz1dZfbwGnxyKev+gFd1rSfmLs+1F8L89RHOx9vsGPixl5uRwoU/qgYo7Hf1NVQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.1.0.tgz", + "integrity": "sha512-IkPeYPczWmosqyulVHiu/fRQg5Q0PenCimbLieksif7ETFH8hUSwvsiWfvC/Sx//MzIB3/yGaVVodEzZnyJGgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "copy-dir": "^1.3.0", - "docker-compose": "^0.22.2", + "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", "inquirer": "^7.1.0", @@ -4985,9 +4920,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.35.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.35.0.tgz", - "integrity": "sha512-tS/+pHBI3Yqkhy2hQ+dKlxm076ULCVa4hk0bgJFtdu0KejQ9wpC7vh/+i8bkv+OQZJx5B8v86872ccO2dKSciw==", + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.49.0.tgz", + "integrity": "sha512-JmVm6IWr5EhXU5m7LCwMOiSv90qJU1l8Q2xlBCQ+0bIPcWRjsHX9pFKDOJvQ6D55W/CTGO1GQk50uolktTeTtw==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -4996,16 +4931,16 @@ } }, "node_modules/@wordpress/eslint-plugin": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.4.0.tgz", - "integrity": "sha512-CT19Ib1Y0ttVQm/bOtjGP6Ge5eqfEaUSobTqCWreHt1RIoxJXTDmazJ1g0Q5MjqG4dEZ/Q/FI4sdqyiKRySkbQ==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.6.0.tgz", + "integrity": "sha512-piANQS5eaSPmpzPXdNZdXbKcHjAyXbuHeUd9ctVA+6sOMVay70+ICQj7Isu4o61Wv43KtxugQoa2PSBqVtrRKA==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.31.0", - "@wordpress/prettier-config": "^3.4.0", + "@wordpress/babel-preset-default": "^7.33.0", + "@wordpress/prettier-config": "^3.6.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -5039,9 +4974,9 @@ } }, "node_modules/@wordpress/eslint-plugin/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -5066,9 +5001,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.47.0.tgz", - "integrity": "sha512-a0mZ+lSUBrmacJGXDnFTaz1O47sQgTCZi3LrY445WNc7cmiSlscTfeBxrUXaTF0ninzHJnE7evCIeKLbQC3dLQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.49.0.tgz", + "integrity": "sha512-GH546Jg8u/rw9I3fsvAhidwt8rUFNmkdXGByIPGsN3R6y+QwWMXPzsnoYdFmFOmDK9gOGCRDe5bXHikoWnaiKA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -5078,13 +5013,13 @@ } }, "node_modules/@wordpress/i18n": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.47.0.tgz", - "integrity": "sha512-7qOeSChhI8drcnKAbpM2yP2HSWRR0U8xvww3Febd3kGhMKAUp8AMpjyC4rWucak4+Eg1HFfahurCmBt3FxgbYQ==", + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.49.0.tgz", + "integrity": "sha512-8aZmmRfOHzS/3pMWg+4f6QlPci0wK5V+PDllAwtwFFrXgc0pmk8VXu7Quajh1tiVoIQDCZpK6h1sqa+qrCLpZg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.47.0", + "@wordpress/hooks": "^3.49.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -5114,22 +5049,22 @@ "dev": true }, "node_modules/@wordpress/icons": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.26.0.tgz", - "integrity": "sha512-5tS2DqFG++544Sopiz7z5cmNIgtJUxBrnwcElUvyGT8+eorAKCaSPa7O8InOvYvpQOPS5o9vGd9XYfjTX7fufA==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.40.0.tgz", + "integrity": "sha512-NSbhur14Ypr+hbgp848430cmk2AHZ7E2e9zvj8917ZjhrVCD7zYT590hOspswJZEaFxJdY3QSnegGiBSI/MacQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.12.0", - "@wordpress/primitives": "^3.33.0" + "@wordpress/element": "^5.26.0", + "@wordpress/primitives": "^3.47.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/jest-console": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.18.0.tgz", - "integrity": "sha512-OjPGbU1HgjLVNCLW9ROmdkw/qhpFL6Svlfv1aUVBrq5z1nJ7SrjRMeBSq4LJloOhTasSV9z7w4mhHJkMkfolJg==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.20.0.tgz", + "integrity": "sha512-EXexYwBLaJSpSCUwpQeSqjJ9G7KDkzH+oCfiZp4ZYuemmCaJFOn8/HOLwfLU0o7i0bfYFAjt8lSVCr5HiYY0AA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -5143,12 +5078,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "11.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.18.0.tgz", - "integrity": "sha512-qwcDXfKkdBJnnsQAa0qkBsg94usGQCD914pWNeBg997qy/6zmVYVXpPjXoJXaC/lYbEIRAWGfry1RSiM6ZoC9g==", + "version": "11.20.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.20.0.tgz", + "integrity": "sha512-3x2ua/rc0540zfLOrHbfdrEOwS5xWPbX5/f2LUyM2T6zzmhXrnqG2WFdhftFFLAUhC8cbxuy1WNnrzgjUxGeDQ==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^7.18.0", + "@wordpress/jest-console": "^7.20.0", "babel-jest": "^29.6.2" }, "engines": { @@ -5160,23 +5095,22 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.47.0.tgz", - "integrity": "sha512-dmYpqCWUoCM290YA5ApES9nqz/0D1JngIlZtel+BvELf8fj/jctdsT5wDB7dVdvZCuyr5SF+1Od00DYbMbb5oA==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.49.0.tgz", + "integrity": "sha512-Hg+kUTV/ti+CyG4+D3dmRFMmrE45E2QEv7ZKaeIf+t1wlafekLSDwIpdF7e68HxEMmZSzHmLm7bHqQTNjxAoKQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.47.0", - "change-case": "^4.1.2" + "@wordpress/i18n": "^4.49.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.32.0.tgz", - "integrity": "sha512-qyEnU9FoWpaa67pufu9fNmTCikiYhdKc4R01ffO+xX7wyJXMo0Z6EJog6ajU9E2+YL86AmAX+sO1CHuXcsxdbw==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.34.0.tgz", + "integrity": "sha512-mknDw+d5HIfx/1DyrhkbLJNu8XsmUEjc1SsYSgF2XCP20/khpO7YOi0LWn9uQ2QXWZrlhMc7JKSSOcTs0aLphQ==", "dev": true, "engines": { "node": ">=14" @@ -5186,12 +5120,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.31.0.tgz", - "integrity": "sha512-B6bHsCKxt25nkvWfIJH3l7kENKS20mpsiRIl5+CEES6kKfBwg4IPx+JyA/RPLFQcIQNtIYFft22p5bgT4VZcEg==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.33.0.tgz", + "integrity": "sha512-RqKNf8XQTdae0cXO11l6mBw+A3IOEO9dd4sD70g15e4IltrbwuxqwOT5k9muNteUszTCOQKgWgD8gp1KM2/lvQ==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^4.38.0", + "@wordpress/base-styles": "^4.40.0", "autoprefixer": "^10.2.5" }, "engines": { @@ -5202,9 +5136,9 @@ } }, "node_modules/@wordpress/prettier-config": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.4.0.tgz", - "integrity": "sha512-6qawlZqqbe6NDY0txzsPZThRFAXzf0a891wI/A4KNWVKUXQwTluXWMtGZx3xlFtvkX+9ZHdoqXbWysGQztiBOQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.6.0.tgz", + "integrity": "sha512-51GuCeeEGOi4qsMpzGFBmKbqEUKLqWj3eZDIwATymUaHsJPx9oT93dlIP97MqKIaWjxlhxCMt5RjxcCNT7Pckw==", "dev": true, "engines": { "node": ">=14" @@ -5214,12 +5148,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.33.0.tgz", - "integrity": "sha512-tgGkoDaWFELSoVM3FCS8T16DclIHbC7P2i3j8eVcprYsbgsGR+gaob7qWjgGb954A/OtSfayp1UNwl2kKuPh/A==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.47.0.tgz", + "integrity": "sha512-ho4XrOI9PTGmQhgEYHuRBfgnPzPuq2zXJpQa2GCrbhm4fojLmZ7oWVBzrL2cGtFGD6dJhY3dbY+l+rNs97A2TA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.12.0", + "@wordpress/element": "^5.26.0", "classnames": "^2.3.1" }, "engines": { @@ -5227,24 +5161,24 @@ } }, "node_modules/@wordpress/scripts": { - "version": "26.18.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-26.18.0.tgz", - "integrity": "sha512-cL3CKlPbH+JOnkV4MtGFUDys3KNlp6tjwrGBcpXsYOEm55DYtdXNmkRXHIfiM5hxCWiuE0P0dR7o/6F3Nz3TGA==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.0.0.tgz", + "integrity": "sha512-WXZPvgOaFCK1ZBov99lOOWE5Nl/eDMGTnx0sTsE1FcgAOVgKwaKvDCsRWYqYmf1O3aAhud0+YPIJyewbIHOQdQ==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.31.0", - "@wordpress/browserslist-config": "^5.30.0", - "@wordpress/dependency-extraction-webpack-plugin": "^4.30.0", - "@wordpress/e2e-test-utils-playwright": "^0.15.0", - "@wordpress/eslint-plugin": "^17.4.0", - "@wordpress/jest-preset-default": "^11.18.0", - "@wordpress/npm-package-json-lint-config": "^4.32.0", - "@wordpress/postcss-plugins-preset": "^4.31.0", - "@wordpress/prettier-config": "^3.4.0", - "@wordpress/stylelint-config": "^21.30.0", + "@wordpress/babel-preset-default": "^7.33.0", + "@wordpress/browserslist-config": "^5.32.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.0.0", + "@wordpress/e2e-test-utils-playwright": "^0.17.0", + "@wordpress/eslint-plugin": "^17.6.0", + "@wordpress/jest-preset-default": "^11.20.0", + "@wordpress/npm-package-json-lint-config": "^4.34.0", + "@wordpress/postcss-plugins-preset": "^4.33.0", + "@wordpress/prettier-config": "^3.6.0", + "@wordpress/stylelint-config": "^21.32.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", @@ -5295,7 +5229,7 @@ "wp-scripts": "bin/wp-scripts.js" }, "engines": { - "node": ">=14", + "node": ">=18", "npm": ">=6.14.4" }, "peerDependencies": { @@ -5305,9 +5239,9 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "21.30.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.30.0.tgz", - "integrity": "sha512-PlvXzYgjn7OUaVTy2bahSr6oL/eu1OdRWxrZfGVNxF4jRswND/ThqOEHIzxETNGTe0ggZOyY+40St4Swlo1zZQ==", + "version": "21.32.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.32.0.tgz", + "integrity": "sha512-cmrzU55alv+OZu1fXBC2eZGgJIUwyD47TSDDP7l0o9yF6D/w0am7FxC9ungk/S2uK1oatN05nIPsFSTkuHQSzg==", "dev": true, "dependencies": { "stylelint-config-recommended": "^6.0.0", @@ -5321,9 +5255,9 @@ } }, "node_modules/@wordpress/url": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.48.0.tgz", - "integrity": "sha512-12bjIBBGcA5X8RPvUURLJZzpB60O5DI3WxQVIBBKPF4Mv8nUmgT4uemGzf5/ble8lqzJVntyEhEWKPOxEbUbJg==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.50.0.tgz", + "integrity": "sha512-+YQzsPim5Zx55o/y9urtd0CKANUgwqZSdUNjDWYZ/1CWxtLLzPgQJOabtl79hG2yjrKvjDe9PrDPff18bCmG5A==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -5334,9 +5268,9 @@ } }, "node_modules/@wordpress/warning": { - "version": "2.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.47.0.tgz", - "integrity": "sha512-lmpLNI8Si7HrSY0LBBtp7Z6NzAkh1y7yeJI0LZw17EsJ0MM5FSXqXJRrNY7L4tM8G/vv3OacUw1mRAZX7bzBRQ==", + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.49.0.tgz", + "integrity": "sha512-W2Nj9Nj0o2udPLf8jfGijRff3lzQgPOiLZcN4LFUPT6yyb9MxvNIg7ZVTBJL2TB78+KQKGrIH4ERjV5WyDRoEQ==", "dev": true, "engines": { "node": ">=12" @@ -5910,9 +5844,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "funding": [ { @@ -5929,9 +5863,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -6118,13 +6052,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.5.0", "semver": "^6.3.1" }, "peerDependencies": { @@ -6141,25 +6075,41 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", - "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "core-js-compat": "^3.33.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.5.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6231,9 +6181,9 @@ ] }, "node_modules/basic-ftp": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", - "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", + "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", "dev": true, "engines": { "node": ">=10.0.0" @@ -6254,15 +6204,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -6399,18 +6340,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6574,21 +6503,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -6726,9 +6640,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001566", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", - "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", "dev": true, "funding": [ { @@ -6981,9 +6895,9 @@ "dev": true }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -7050,16 +6964,16 @@ } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7077,19 +6991,65 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" }, "engines": { "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -7568,9 +7528,9 @@ } }, "node_modules/core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", + "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", "dev": true, "hasInstallScript": true, "funding": { @@ -7989,9 +7949,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cwd": { "version": "0.10.0", @@ -8007,15 +7967,14 @@ } }, "node_modules/cypress": { - "version": "13.6.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.1.tgz", - "integrity": "sha512-k1Wl5PQcA/4UoTffYKKaxA0FJKwg8yenYNYRzLt11CUR0Kln+h7Udne6mdU1cUIdXBDTVZWtmiUjzqGs7/pEpw==", + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.3.tgz", + "integrity": "sha512-d/pZvgwjAyZsoyJ3FOsJT5lDsqnxQ/clMqnNc++rkHjbkkiF2h9s0JsZSyyH4QXhVFW3zPFg82jD25roFLOdZA==", "dev": true, "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -8077,9 +8036,9 @@ } }, "node_modules/cypress-mochawesome-reporter": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.7.0.tgz", - "integrity": "sha512-aeC5hpYJ/cS0M1PvIBfkyW3+yNIOgrFrI+ijEZZxsovGWqhSankCcias88igjiyzc+6mjFWnIXsd5NuRVF5nwA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.0.tgz", + "integrity": "sha512-awdOlkWmB0xGBsO8iitOAJi30uSTtxvM+S6bq03Xw3DP5nMUdYlbvmLbetG0bkMPFkv1wn5ogQuw58Jv603n2A==", "dev": true, "dependencies": { "commander": "^10.0.1", @@ -8133,15 +8092,6 @@ "ally.js": "^1.4.1" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "18.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", - "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/cypress/node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -8411,41 +8361,19 @@ "node": ">=0.10.0" } }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", "dev": true, "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" + "execa": "^5.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10" } }, - "node_modules/default-browser/node_modules/cross-spawn": { + "node_modules/default-gateway/node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", @@ -8459,214 +8387,30 @@ "node": ">= 8" } }, - "node_modules/default-browser/node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/default-browser/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/default-browser/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-browser/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { + "node_modules/default-gateway/node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", @@ -8972,14 +8716,26 @@ } }, "node_modules/docker-compose": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz", - "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==", + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.3.tgz", + "integrity": "sha512-x3/QN3AIOMe7j2c8f/jcycizMft7dl8MluoB9OGPAYCyKHHiPUFqI9GjCcsU0kYy24vYKMCcfR6+5ZaEyQlrxg==", "dev": true, + "dependencies": { + "yaml": "^2.2.2" + }, "engines": { "node": ">= 6.0.0" } }, + "node_modules/docker-compose/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -9087,12 +8843,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -9428,15 +9178,15 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -9558,9 +9308,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -9579,7 +9329,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -9619,9 +9369,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "27.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.0.tgz", - "integrity": "sha512-MTlusnnDMChbElsszJvrwD1dN3x6nZl//s4JD23BxB6MgR66TZlL064su24xEIS3VACfAoHV1vgyMgPw8nkdng==", + "version": "27.6.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", + "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -9789,9 +9539,9 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "46.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", - "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", + "version": "46.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", + "integrity": "sha512-x8wxIpv00Y50NyweDUpa+58ffgSAI5sqe+zcZh33xphD0AVh+1kqr1ombaTRb7Fhpove1zfUuujlX9DWWBP5ag==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.41.0", @@ -9802,13 +9552,13 @@ "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1" + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": ">=16" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-plugin-jsdoc/node_modules/lru-cache": { @@ -9890,23 +9640,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -10078,9 +9829,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -11110,6 +10861,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -12307,15 +12070,12 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/is-generator-fn": { @@ -12354,39 +12114,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -14250,39 +13977,75 @@ } }, "node_modules/lint-staged": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.2.tgz", - "integrity": "sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz", + "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==", "dev": true, "dependencies": { - "chalk": "5.2.0", - "cli-truncate": "^3.1.0", - "commander": "^10.0.0", - "debug": "^4.3.4", - "execa": "^7.0.0", - "lilconfig": "2.1.0", - "listr2": "^5.0.7", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.3", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.2.2" + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.0", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, + "node_modules/lint-staged/node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -14291,13 +14054,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/lint-staged/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lint-staged/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/lint-staged/node_modules/cross-spawn": { @@ -14314,57 +14092,75 @@ "node": ">= 8" } }, + "node_modules/lint-staged/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/lint-staged/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/lint-staged/node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/lint-staged/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=14.18.0" + "node": ">=16.17.0" } }, "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lint-staged/node_modules/is-stream": { @@ -14379,44 +14175,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/lint-staged/node_modules/listr2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", - "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz", + "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==", "dev": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", "rfdc": "^1.3.0", - "rxjs": "^7.8.0", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^9.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/lint-staged/node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/lint-staged/node_modules/log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", "dev": true, "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14435,9 +14233,9 @@ } }, "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -14476,30 +14274,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/p-map": { + "node_modules/lint-staged/node_modules/restore-cursor": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "dependencies": { - "aggregate-error": "^3.0.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "node_modules/lint-staged/node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lint-staged/node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "tslib": "^2.1.0" + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/lint-staged/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14521,18 +14341,64 @@ "node": ">=8" } }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/lint-staged/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/lint-staged/node_modules/strip-final-newline": { @@ -14541,7 +14407,19 @@ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14562,10 +14440,27 @@ "node": ">= 8" } }, + "node_modules/lint-staged/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lint-staged/node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "dev": true, "engines": { "node": ">= 14" @@ -14614,15 +14509,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/listr2/node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -14799,32 +14685,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -15702,18 +15562,6 @@ "mocha": ">=7" } }, - "node_modules/mochawesome-json-to-md": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/mochawesome-json-to-md/-/mochawesome-json-to-md-0.7.2.tgz", - "integrity": "sha512-dxh+o73bhC6nEph6fNky9wy35R+2oK3ueXwAlJ/COAanlFgu8GuvGzQ00VNO4PPYhYGDsO4vbt4QTcMA3lv25g==", - "dev": true, - "dependencies": { - "yargs": "^17.0.1" - }, - "bin": { - "mochawesome-json-to-md": "index.js" - } - }, "node_modules/mochawesome-merge": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mochawesome-merge/-/mochawesome-merge-4.3.0.tgz", @@ -17157,13 +17005,13 @@ "dev": true }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.1.tgz", + "integrity": "sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==", "dev": true, "peer": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.41.1" }, "bin": { "playwright": "cli.js" @@ -17188,9 +17036,9 @@ } }, "node_modules/playwright/node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.1.tgz", + "integrity": "sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==", "dev": true, "peer": true, "bin": { @@ -18867,115 +18715,6 @@ "node": ">=10.0.0" } }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -19057,13 +18796,13 @@ "dev": true }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -19095,15 +18834,18 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", + "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -19582,33 +19324,22 @@ } }, "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -19811,6 +19542,16 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", @@ -19818,9 +19559,9 @@ "dev": true }, "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", @@ -19959,9 +19700,9 @@ } }, "node_modules/streamx": { - "version": "2.15.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", - "integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==", + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", + "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", "dev": true, "dependencies": { "fast-fifo": "^1.1.0", @@ -20025,15 +19766,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -20482,12 +20214,12 @@ "dev": true }, "node_modules/synckit": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", - "integrity": "sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "dependencies": { - "@pkgr/utils": "^2.4.2", + "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" }, "engines": { @@ -20529,38 +20261,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", @@ -20816,18 +20522,6 @@ "@popperjs/core": "^2.9.0" } }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -20979,9 +20673,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -21232,12 +20926,6 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -21476,6 +21164,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/validate-npm-package-name": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", @@ -21607,9 +21305,9 @@ } }, "node_modules/web-vitals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.0.tgz", - "integrity": "sha512-f5YnCHVG9Y6uLCePD4tY8bO/Ge15NPEQWtvm3tPzDKygloiqtb4SVqRHBcrIAqo2ztqX5XueqDn97zHF0LdT6w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.1.tgz", + "integrity": "sha512-xQ9lvIpfLxUj0eSmT79ZjRoU5wIRfIr7pNukL7ZE4EcWZSmfZQqOlhuAGfkVa3EFmzPHZhWhXfm2i5ys+THVPg==", "dev": true }, "node_modules/webidl-conversions": { diff --git a/package.json b/package.json index a73418c6d..3447e38d7 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,15 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.2.0", - "@wordpress/env": "^8.13.0", - "@wordpress/scripts": "^26.18.0", - "cypress": "^13.6.1", + "@wordpress/env": "^9.1.0", + "@wordpress/scripts": "^27.0.0", + "cypress": "^13.6.3", "cypress-file-upload": "^5.0.8", - "cypress-mochawesome-reporter": "^3.7.0", + "cypress-mochawesome-reporter": "^3.8.0", "cypress-plugin-tab": "^1.0.5", "husky": "^8.0.3", "jsdoc": "^3.6.11", - "lint-staged": "^13.2.2", - "mochawesome-json-to-md": "^0.7.2", + "lint-staged": "^15.2.0", "node-wp-i18n": "^1.2.7", "svg-react-loader": "^0.4.6", "webpack": "^5.86.0", @@ -54,7 +53,7 @@ "wp-hookdoc": "^0.2.0" }, "dependencies": { - "@wordpress/icons": "^9.26.0", + "@wordpress/icons": "^9.40.0", "choices.js": "^10.2.0", "tippy.js": "^6.3.7" } From 0019a908c203712c6e8d27ae943cbd5d39c9fe60 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 12:49:58 -0700 Subject: [PATCH 089/127] Try updating all of our composer dependencies --- composer.lock | 147 ++++++++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/composer.lock b/composer.lock index 62cec493d..fa4caee7d 100644 --- a/composer.lock +++ b/composer.lock @@ -12,12 +12,12 @@ "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "b66d11b7479109ab547f9405b97205640b17d385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", + "reference": "b66d11b7479109ab547f9405b97205640b17d385", "shasum": "" }, "require": { @@ -29,7 +29,7 @@ "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "default-branch": true, "type": "library", @@ -65,7 +65,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.6" + "source": "https://github.com/composer/ca-bundle/tree/1.4.0" }, "funding": [ { @@ -81,7 +81,7 @@ "type": "tidelift" } ], - "time": "2023-06-06T12:02:59+00:00" + "time": "2023-12-18T12:05:55+00:00" }, { "name": "ua-parser/uap-php", @@ -377,25 +377,25 @@ "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "12be2483e1f0e850b353e26869e4e6c038459501" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/12be2483e1f0e850b353e26869e4e6c038459501", + "reference": "12be2483e1f0e850b353e26869e4e6c038459501", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^9 || ^12", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", @@ -423,7 +423,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.x" }, "funding": [ { @@ -439,7 +439,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2023-12-09T14:16:53+00:00" }, { "name": "myclabs/deep-copy", @@ -447,12 +447,12 @@ "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "f6f48cfecf52ab791fe18cc1b11d6345512dc4b8" + "reference": "202aaf6b7c2e1e0a622b0298e9f3f537e4d84018" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/f6f48cfecf52ab791fe18cc1b11d6345512dc4b8", - "reference": "f6f48cfecf52ab791fe18cc1b11d6345512dc4b8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/202aaf6b7c2e1e0a622b0298e9f3f537e4d84018", + "reference": "202aaf6b7c2e1e0a622b0298e9f3f537e4d84018", "shasum": "" }, "require": { @@ -500,29 +500,31 @@ "type": "tidelift" } ], - "time": "2023-07-30T10:01:33+00:00" + "time": "2023-11-01T08:01:43+00:00" }, { "name": "nikic/php-parser", - "version": "4.x-dev", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "ce019e9ad711e31ee87c2c4c72e538b5240970c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce019e9ad711e31ee87c2c4c72e538b5240970c3", + "reference": "ce019e9ad711e31ee87c2c4c72e538b5240970c3", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "default-branch": true, "bin": [ @@ -531,7 +533,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -555,9 +557,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/master" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2024-01-14T09:02:54+00:00" }, { "name": "phar-io/manifest", @@ -937,12 +939,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "7ce595672008088280161470266240d39a4025a3" + "reference": "26dcb893d86fbe90ab2a8abd7b08a3fda3602237" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/7ce595672008088280161470266240d39a4025a3", - "reference": "7ce595672008088280161470266240d39a4025a3", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/26dcb893d86fbe90ab2a8abd7b08a3fda3602237", + "reference": "26dcb893d86fbe90ab2a8abd7b08a3fda3602237", "shasum": "" }, "require": { @@ -1018,7 +1020,7 @@ "type": "open_collective" } ], - "time": "2023-12-11T10:09:05+00:00" + "time": "2024-01-15T05:03:54+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1026,19 +1028,19 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "a74e6341296fe533cb69c8c94193afc7537443ec" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a74e6341296fe533cb69c8c94193afc7537443ec", - "reference": "a74e6341296fe533cb69c8c94193afc7537443ec", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1088,7 +1090,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -1096,7 +1098,7 @@ "type": "github" } ], - "time": "2023-08-21T05:57:35+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1345,12 +1347,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6c24b9dd8390a031a5490a2443fff1958522b49a" + "reference": "4c1997c21fb0e29198b7b83be49d460df2571d79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6c24b9dd8390a031a5490a2443fff1958522b49a", - "reference": "6c24b9dd8390a031a5490a2443fff1958522b49a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c1997c21fb0e29198b7b83be49d460df2571d79", + "reference": "4c1997c21fb0e29198b7b83be49d460df2571d79", "shasum": "" }, "require": { @@ -1365,7 +1367,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -1440,7 +1442,7 @@ "type": "tidelift" } ], - "time": "2023-08-21T05:56:05+00:00" + "time": "2024-01-21T09:34:47+00:00" }, { "name": "sebastian/cli-parser", @@ -1685,20 +1687,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1730,7 +1732,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1738,7 +1740,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -2012,20 +2014,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.x-dev", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -2057,7 +2059,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -2065,7 +2067,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -2471,12 +2473,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "88d3cec355a252c8fb6a338c420960bf1b0d5679" + "reference": "b03d10fc5a68504e3ea28fc84651b92cb0252fd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/88d3cec355a252c8fb6a338c420960bf1b0d5679", - "reference": "88d3cec355a252c8fb6a338c420960bf1b0d5679", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/b03d10fc5a68504e3ea28fc84651b92cb0252fd9", + "reference": "b03d10fc5a68504e3ea28fc84651b92cb0252fd9", "shasum": "" }, "require": { @@ -2486,7 +2488,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "default-branch": true, "bin": [ @@ -2544,20 +2546,20 @@ "type": "open_collective" } ], - "time": "2023-12-14T14:17:01+00:00" + "time": "2024-01-22T02:36:17+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2586,7 +2588,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2594,7 +2596,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "wp-coding-standards/wpcs", @@ -2668,12 +2670,12 @@ "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212" + "reference": "f07cf7b2ea73c3de1f72cf115e3cd446c8ad2713" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/224e4a1329c03d8bad520e3fc4ec980034a4b212", - "reference": "224e4a1329c03d8bad520e3fc4ec980034a4b212", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/f07cf7b2ea73c3de1f72cf115e3cd446c8ad2713", + "reference": "f07cf7b2ea73c3de1f72cf115e3cd446c8ad2713", "shasum": "" }, "require": { @@ -2681,7 +2683,9 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.3.0" + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "yoast/yoastcs": "^3.0.0" }, "type": "library", "extra": { @@ -2718,9 +2722,10 @@ ], "support": { "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", + "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2023-08-19T14:25:08+00:00" + "time": "2023-12-14T21:29:51+00:00" } ], "aliases": [], From 896aa3c293c6ab6545c99ca32913de0c013e2b77 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 13:03:01 -0700 Subject: [PATCH 090/127] Minor cleanup in the main Plugin.php file --- config.php | 2 +- includes/Classifai/Plugin.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config.php b/config.php index 8b44d03a6..2c3c36e0f 100644 --- a/config.php +++ b/config.php @@ -10,7 +10,7 @@ classifai_define( 'CLASSIFAI_PLUGIN', __DIR__ . '/classifai.php' ); classifai_define( 'CLASSIFAI_PLUGIN_VERSION', $plugin_version ); classifai_define( 'CLASSIFAI_PLUGIN_DIR', __DIR__ ); classifai_define( 'CLASSIFAI_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -classifai_define( 'CLASSIFAI_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); +classifai_define( 'CLASSIFAI_PLUGIN_BASENAME', plugin_basename( __DIR__ . '/classifai.php' ) ); // IBM Watson constants diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index 66b72eb7d..e479817dd 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -53,9 +53,10 @@ public function init() { */ do_action( 'before_classifai_init' ); - // Initialize the services, each services handles the providers + // Initialize the services; each service handles their features. $this->init_services(); + // TODO: is there a better place for this? $post_types = get_supported_post_types(); foreach ( $post_types as $post_type ) { register_meta( @@ -69,11 +70,11 @@ public function init() { ); } - // Initialize the classifAI Onboarding. + // Initialize the ClassifAI Onboarding. $onboarding = new Admin\Onboarding(); $onboarding->init(); - // Initialize the classifAI User Profile. + // Initialize the ClassifAI User Profile. $user_profile = new Admin\UserProfile(); $user_profile->init(); @@ -126,7 +127,7 @@ public function init_services() { } /** - * Initiates classes providing admin feature sfor the plugin. + * Initiates classes providing admin features. * * @since 1.4.0 */ @@ -216,10 +217,9 @@ public function enqueue_admin_assets() { * Add the action links to the plugin page. * * @param array $links The Action links for the plugin. - * * @return array */ - public function filter_plugin_action_links( $links ) { + public function filter_plugin_action_links( $links ): array { if ( ! is_array( $links ) ) { return $links; From 505085490eab8f237b75153998499c1dbf019f89 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 13:14:49 -0700 Subject: [PATCH 091/127] Minor cleanup in the Helpers and PostClassifier files --- includes/Classifai/Helpers.php | 83 ++++++++++++++------------- includes/Classifai/PostClassifier.php | 2 + 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 965821f12..6706daf70 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -32,7 +32,7 @@ function get_plugin() { * @param string $provider The provider service name to get settings from, defaults to the first one found. * @return array The array of ClassifAi settings. */ -function get_plugin_settings( $service = '', $provider = '' ) { +function get_plugin_settings( string $service = '', string $provider = '' ): array { $services = Plugin::$instance->services; if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { return []; @@ -88,7 +88,7 @@ function get_plugin_settings( $service = '', $provider = '' ) { * * @param array $settings The settings we're saving. */ -function set_plugin_settings( $settings ) { +function set_plugin_settings( array $settings ) { update_option( 'classifai_settings', $settings ); } @@ -129,6 +129,8 @@ function reset_plugin_settings() { } } +// TODO: would prefer any Watson specific functionality is removed from here. + /** * Returns the currently configured Watson API URL. Lookup order is, * @@ -137,7 +139,7 @@ function reset_plugin_settings() { * * @return string */ -function get_watson_api_url() { +function get_watson_api_url(): string { $settings = ( new Classification() )->get_settings(); $creds = ! empty( $settings[ NLU::ID ] ) ? $settings[ NLU::ID ] : []; @@ -150,7 +152,6 @@ function get_watson_api_url() { } } - /** * Returns the currently configured Watson username. Lookup order is, * @@ -159,7 +160,7 @@ function get_watson_api_url() { * * @return string */ -function get_watson_username() { +function get_watson_username(): string { $settings = ( new Classification() )->get_settings( NLU::ID ); $username = ! empty( $settings['username'] ) ? $settings['username'] : ''; @@ -179,7 +180,7 @@ function get_watson_username() { * * @return string */ -function get_classification_mode() { +function get_classification_mode(): string { $feature = new Classification(); $settings = $feature->get_settings(); $value = $settings[ NLU::ID ]['classification_mode'] ?? ''; @@ -206,7 +207,7 @@ function get_classification_mode() { * * @return string */ -function get_classification_method() { +function get_classification_method(): string { $feature = new Classification(); $settings = $feature->get_settings(); @@ -221,7 +222,7 @@ function get_classification_method() { * * @return string */ -function get_watson_password() { +function get_watson_password(): string { $settings = ( new Classification() )->get_settings( NLU::ID ); $password = ! empty( $settings['password'] ) ? $settings['password'] : ''; @@ -241,7 +242,7 @@ function get_watson_password() { * * @return array */ -function get_post_types_for_language_settings() { +function get_post_types_for_language_settings(): array { $post_types = get_post_types( [ 'public' => true ], 'objects' ); $post_types = array_filter( $post_types, 'is_post_type_viewable' ); @@ -268,7 +269,7 @@ function get_post_types_for_language_settings() { * * @return array */ -function get_post_statuses_for_language_settings() { +function get_post_statuses_for_language_settings(): array { $post_statuses = get_all_post_statuses(); /** @@ -285,12 +286,13 @@ function get_post_statuses_for_language_settings() { } /** - * The list of post types that get the ClassifAI taxonomies. Defaults - * to 'post'. + * The list of post types that get the ClassifAI taxonomies. + * + * Defaults to 'post'. * * return array */ -function get_supported_post_types() { +function get_supported_post_types(): array { $feature = new Classification(); $settings = $feature->get_settings(); $post_types = []; @@ -321,7 +323,7 @@ function get_supported_post_types() { * * @return array Supported Post Types. */ -function get_tts_supported_post_types() { +function get_tts_supported_post_types(): array { $feature = new TextToSpeech( null ); $selected = $feature->get_settings( 'post_types' ); $post_types = []; @@ -336,12 +338,13 @@ function get_tts_supported_post_types() { } /** - * The list of post statuses that get the ClassifAI taxonomies. Defaults - * to 'publish'. + * The list of post statuses that get the ClassifAI taxonomies. + * + * Defaults to 'publish'. * * @return array */ -function get_supported_post_statuses() { +function get_supported_post_statuses(): array { $feature = new Classification(); $settings = $feature->get_settings(); $post_statuses = []; @@ -373,7 +376,7 @@ function get_supported_post_statuses() { * @param string $classify_by category,keyword,entity,concept * @return bool */ -function get_feature_enabled( $classify_by ) { +function get_feature_enabled( string $classify_by ): bool { $feature = new Classification(); $settings = $feature->get_settings( NLU::ID ); @@ -388,9 +391,9 @@ function get_feature_enabled( $classify_by ) { * * @since 1.6.0 * - * @return true + * @return bool */ -function language_processing_features_enabled() { +function language_processing_features_enabled(): bool { $features = [ 'category', 'concept', @@ -417,9 +420,9 @@ function language_processing_features_enabled() { * Any results below the threshold will be ignored. * * @param string $feature The feature whose threshold to lookup - * @return int + * @return float */ -function get_feature_threshold( $feature ) { +function get_feature_threshold( string $feature ): float { $settings = get_plugin_settings( 'language_processing', 'Natural Language Understanding' ); $threshold = 0; @@ -441,6 +444,7 @@ function get_feature_threshold( $feature ) { } $threshold = empty( $threshold ) ? 0.7 : $threshold / 100; + /** * Filter the threshold for a specific feature. Any results below the * threshold will be ignored. @@ -448,22 +452,23 @@ function get_feature_threshold( $feature ) { * @since 1.0.0 * @hook classifai_feature_threshold * - * @param {string} $threshold The threshold to use, expressed as a decimal between 0 and 1 inclusive. + * @param {float} $threshold The threshold to use, expressed as a decimal between 0 and 1 inclusive. * @param {string} $feature The feature in question. * - * @return {string} The filtered threshold. + * @return {float} The filtered threshold. */ return apply_filters( 'classifai_feature_threshold', $threshold, $feature ); } /** - * Returns the Taxonomy for the specified NLU feature. Returns defaults - * in config.php if options have not been configured. + * Returns the Taxonomy for the specified NLU feature. + * + * Returns defaults in config.php if options have not been configured. * * @param string $classify_by NLU feature name * @return string Taxonomy mapped to the feature */ -function get_feature_taxonomy( $classify_by = '' ) { +function get_feature_taxonomy( string $classify_by = '' ): string { $taxonomy = 0; $feature = new Classification(); @@ -502,7 +507,7 @@ function get_feature_taxonomy( $classify_by = '' ) { * * @return int */ -function computer_vision_max_filesize() { +function computer_vision_max_filesize(): int { /** * Filters the Computer Vision maximum allowed filesize. * @@ -525,7 +530,7 @@ function computer_vision_max_filesize() { * @param array $size_2 Associative array containing width and height values. * @return int Returns -1 if $size_1 is larger, 1 if $size_2 is larger, and 0 if they are equal. */ -function sort_images_by_size_cb( $size_1, $size_2 ) { +function sort_images_by_size_cb( array $size_1, array $size_2 ): int { $size_1_total = $size_1['width'] + $size_1['height']; $size_2_total = $size_2['width'] + $size_2['height']; @@ -547,7 +552,7 @@ function sort_images_by_size_cb( $size_1, $size_2 ) { * @param int $max The maximum acceptable size. * @return string|null The image URL, or null if no acceptable image found. */ -function get_largest_acceptable_image_url( $full_image, $full_url, $sizes, $max = MB_IN_BYTES ) { +function get_largest_acceptable_image_url( string $full_image, string $full_url, array $sizes, int $max = MB_IN_BYTES ) { $file_size = @filesize( $full_image ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( $file_size && $max >= $file_size ) { return $full_url; @@ -582,7 +587,7 @@ function get_largest_acceptable_image_url( $full_image, $full_url, $sizes, $max * @param int $max_size The maximum acceptable filesize. Default 1MB. * @return string|null The image URL, or null if no acceptable image found. */ -function get_largest_size_and_dimensions_image_url( $full_image, $full_url, $metadata, $width = [ 0, 4200 ], $height = [ 0, 4200 ], $max_size = MB_IN_BYTES ) { +function get_largest_size_and_dimensions_image_url( string $full_image, string $full_url, array $metadata, array $width = [ 0, 4200 ], array $height = [ 0, 4200 ], int $max_size = MB_IN_BYTES ) { // Check if the full size image meets our filesize and dimension requirements $file_size = @filesize( $full_image ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( @@ -618,10 +623,9 @@ function get_largest_size_and_dimensions_image_url( $full_image, $full_url, $met * Allows returning modified image URL for a given attachment. * * @param int $post_id Post ID. - * * @return mixed */ -function get_modified_image_source_url( $post_id ) { +function get_modified_image_source_url( int $post_id ) { /** * Filter to modify image source URL in order to allow scanning images, * stored on third party storages that cannot be used by @@ -643,9 +647,10 @@ function get_modified_image_source_url( $post_id ) { /** * Check if attachment is PDF document. * - * @param \WP_post $post Post object for the attachment being viewed. + * @param \WP_Post $post Post object for the attachment being viewed. + * @return bool */ -function attachment_is_pdf( $post ) { +function attachment_is_pdf( \WP_Post $post ): bool { $mime_type = get_post_mime_type( $post ); $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); @@ -663,7 +668,7 @@ function attachment_is_pdf( $post ) { * @param string $attribute Optional attribute to get. Can be version or dependencies. * @return string|array */ -function get_asset_info( $slug, $attribute = null ) { +function get_asset_info( string $slug, string $attribute = null ) { if ( file_exists( CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php' ) ) { $asset = require CLASSIFAI_PLUGIN_DIR . '/dist/' . $slug . '.asset.php'; } else { @@ -682,7 +687,7 @@ function get_asset_info( $slug, $attribute = null ) { * * @return array Array of services. */ -function get_services_menu() { +function get_services_menu(): array { $services = Plugin::$instance->services; if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { return []; @@ -704,7 +709,6 @@ function get_services_menu() { * @param string $key $_GET or $_POST array key. * @param boolean $is_get If the request is $_GET. Defaults to false. * @param string $sanitize_callback Sanitize callback. Defaults to `sanitize_text_field` - * * @return string|boolean Sanitized string or `false` as fallback. */ function clean_input( string $key = '', bool $is_get = false, string $sanitize_callback = 'sanitize_text_field' ) { @@ -751,7 +755,7 @@ function find_provider_class( array $provider_classes = [], string $provider_id * * @return array */ -function get_all_post_statuses() { +function get_all_post_statuses(): array { $all_statuses = wp_list_pluck( get_post_stati( [], @@ -835,6 +839,7 @@ function render_disable_feature_link( string $feature ) { $user_profile = new UserProfile(); $allowed_features = $user_profile->get_allowed_features( get_current_user_id() ); $profile_url = get_edit_profile_url( get_current_user_id() ) . '#classifai-profile-features-section'; + if ( ! empty( $allowed_features ) && isset( $allowed_features[ $feature ] ) ) { ?> diff --git a/includes/Classifai/PostClassifier.php b/includes/Classifai/PostClassifier.php index cecde689b..f5c14b3e1 100644 --- a/includes/Classifai/PostClassifier.php +++ b/includes/Classifai/PostClassifier.php @@ -8,6 +8,8 @@ /** * PostClassifier classifies and links WP posts to Taxonomy Terms based * on IBM Watson NLU API output. + * + * TODO: this seems to be specific to IBM Watson so would prefer this lives within that directory. */ class PostClassifier { From 221956dfe818382751f1b975b78441cac12ac100 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 15:25:15 -0700 Subject: [PATCH 092/127] Clean up services --- includes/Classifai/Services/Service.php | 49 ++++++++++++++----- .../Classifai/Services/ServicesManager.php | 35 ++++++------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index fd217248b..24b9f9a47 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -47,7 +47,7 @@ abstract class Service { * @param string $menu_slug Slug for the settings page. * @param array $providers Array of provider classes for this service */ - public function __construct( $display_name, $menu_slug, $providers ) { + public function __construct( string $display_name, string $menu_slug, array $providers ) { $this->menu_slug = $menu_slug; $this->display_name = $display_name; $this->providers = $providers; @@ -78,12 +78,22 @@ public function init() { $this->register_providers(); } + /** + * Filter the list of features for the service. + * + * @since 3.0.0 + * @hook {$this->menu_slug}_features + * + * @param {array} $this->features Array of available features for the service. + * + * @return {array} The filtered available features. + */ $this->features = apply_filters( "{$this->menu_slug}_features", $this->features ); if ( ! empty( $this->features ) && is_array( $this->features ) ) { foreach ( $this->features as $feature ) { if ( class_exists( $feature ) ) { - $feature_instance = new $feature(); + $feature_instance = new $feature(); $this->feature_classes[] = $feature_instance; $feature_instance->setup(); } @@ -109,7 +119,7 @@ public function register_providers() { * * @return string */ - public function get_menu_slug() { + public function get_menu_slug(): string { return $this->menu_slug; } @@ -118,7 +128,7 @@ public function get_menu_slug() { * * @return string */ - public function get_display_name() { + public function get_display_name(): string { return $this->display_name; } @@ -146,8 +156,8 @@ public function render_settings_page() {

    display_name ); ?>

    provider_classes ) ) { - echo '

    ' . esc_html__( 'No providers available for this service.', 'classifai' ) . '

    '; + if ( empty( $this->feature_classes ) ) { + echo '

    ' . esc_html__( 'No features available for this service.', 'classifai' ) . '

    '; echo '
    '; return; } @@ -169,8 +179,21 @@ public function render_settings_page() { submit_button(); ?> - + + + provider_classes ?? [], 'Natural Language Understanding' ); if ( 'openai_embeddings' === $active_tab ) { @@ -260,22 +283,24 @@ public function render_settings_page() { /** * Adds plugin debug information to be printed on the Site Health screen. * + * @since 1.4.0 + * * @param array $debug_information Array of associative arrays corresponding to lines of debug information. * @return array Array with lines added. - * @since 1.4.0 */ - public function add_service_debug_information( $debug_information ) { + public function add_service_debug_information( array $debug_information ): array { return array_merge( $debug_information, $this->get_service_debug_information() ); } /** * Provides debug information for the service. * - * @return array Array of associative arrays representing lines of debug information. * @since 1.4.0 + * + * @return array Array of associative arrays representing lines of debug information. */ - public function get_service_debug_information() { - $make_line = function( $feature ) { + public function get_service_debug_information(): array { + $make_line = function ( $feature ) { return [ 'label' => sprintf( '%s', $feature->get_label() ), 'value' => $feature->get_debug_information(), diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index 7b0ae5d1a..c78aecc8c 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -1,6 +1,6 @@ services = $services; $this->service_classes = []; $this->get_menu_title(); @@ -62,9 +62,9 @@ public function register() { } /** - * Registers providers under the Language Processing Service. + * Registers features under the Language Processing Service. */ - public function register_language_processing_features() { + public function register_language_processing_features(): array { return [ '\Classifai\Features\Classification', '\Classifai\Features\TitleGeneration', @@ -77,9 +77,9 @@ public function register_language_processing_features() { } /** - * Registers providers under the Image Processing Service. + * Registers features under the Image Processing Service. */ - public function register_image_processing_features() { + public function register_image_processing_features(): array { return [ '\Classifai\Features\DescriptiveTextGenerator', '\Classifai\Features\ImageTagsGenerator', @@ -90,9 +90,9 @@ public function register_image_processing_features() { } /** - * Registers providers under the Image Processing Service. + * Registers features under the Recommendation Service. */ - public function register_recommendation_service_features() { + public function register_recommendation_service_features(): array { return [ '\Classifai\Features\RecommendedContent', ]; @@ -102,6 +102,7 @@ public function register_recommendation_service_features() { * Get general ClassifAI settings * * @param string $index Optional specific setting to be retrieved. + * @return mixed */ public function get_settings( $index = false ) { $settings = get_option( 'classifai_settings' ); @@ -124,11 +125,8 @@ public function get_settings( $index = false ) { } } - /** * Create the settings pages. - * - * If there are more than a single service, we'll create a top level admin menu and add subsequent items there. */ public function do_settings() { add_action( 'admin_menu', [ $this, 'register_admin_menu_item' ] ); @@ -138,8 +136,6 @@ public function do_settings() { /** * Register the settings and sanitization callback method. - * - * It's very important that the option group matches the page slug. */ public function register_settings() { register_setting( 'classifai_settings', 'classifai_settings', [ $this, 'sanitize_settings' ] ); @@ -149,14 +145,15 @@ public function register_settings() { * Sanitize settings. * * @param array $settings The settings to be sanitized. - * - * @return mixed + * @return array */ - public function sanitize_settings( $settings ) { + public function sanitize_settings( array $settings ): array { $new_settings = []; + if ( isset( $settings['email'] ) && isset( $settings['license_key'] ) - && $this->check_license_key( $settings['email'], $settings['license_key'] ) ) { + && $this->check_license_key( $settings['email'], $settings['license_key'] ) + ) { $new_settings['valid_license'] = true; $new_settings['email'] = sanitize_text_field( $settings['email'] ); $new_settings['license_key'] = sanitize_text_field( $settings['license_key'] ); @@ -164,6 +161,7 @@ public function sanitize_settings( $settings ) { $new_settings['valid_license'] = false; $new_settings['email'] = isset( $settings['email'] ) ? sanitize_text_field( $settings['email'] ) : ''; $new_settings['license_key'] = isset( $settings['license_key'] ) ? sanitize_text_field( $settings['license_key'] ) : ''; + add_settings_error( 'registration', 'classifai-registration', @@ -239,7 +237,6 @@ protected function register_services() { } } - /** * Helper to return the $menu title */ @@ -311,7 +308,7 @@ public function render_settings_page() { service_classes[0]->render_settings_page(); } } From f6b4e7bd7c39205060b9847106c6c3bae16b184e Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 15:29:51 -0700 Subject: [PATCH 093/127] Clean up services --- .../Classifai/Services/ImageProcessing.php | 21 ++++++++++++---- .../Classifai/Services/LanguageProcessing.php | 13 +++++++++- includes/Classifai/Services/Personalizer.php | 24 +++++++++---------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index af0daef00..c703d9f59 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -1,6 +1,6 @@ register_image_tags_taxonomy(); + add_filter( 'attachment_fields_to_edit', [ $this, 'custom_fields_edit' ] ); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); @@ -41,7 +43,17 @@ public function init() { * * @return array */ - public static function get_service_providers() { + public static function get_service_providers(): array { + /** + * Filter the service providers for Image Processing service. + * + * @since 3.0.0 + * @hook classifai_language_processing_service_providers + * + * @param {array} $providers Array of available providers for the service. + * + * @return {array} The filtered available providers. + */ return apply_filters( 'classifai_language_processing_service_providers', [ @@ -96,10 +108,9 @@ protected function register_image_tags_taxonomy() { * Removes the UI on attachment modals for all taxonomies introduced by this plugin. * * @param array $form_fields The forms fields being rendered on the modal. - * - * @return mixed + * @return array */ - public function custom_fields_edit( $form_fields ) { + public function custom_fields_edit( array $form_fields ): array { unset( $form_fields['classifai-image-tags'] ); unset( $form_fields['watson-category'] ); unset( $form_fields['watson-keyword'] ); diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index efece2af6..b3c4ae39b 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -4,6 +4,7 @@ */ namespace Classifai\Services; + class LanguageProcessing extends Service { /** @@ -30,7 +31,17 @@ public function init() { * * @return array */ - public static function get_service_providers() { + public static function get_service_providers(): array { + /** + * Filter the service providers for Language Processing service. + * + * @since 3.0.0 + * @hook classifai_language_processing_service_providers + * + * @param {array} $providers Array of available providers for the service. + * + * @return {array} The filtered available providers. + */ return apply_filters( 'classifai_language_processing_service_providers', [ diff --git a/includes/Classifai/Services/Personalizer.php b/includes/Classifai/Services/Personalizer.php index 9b3176580..6b71ac307 100644 --- a/includes/Classifai/Services/Personalizer.php +++ b/includes/Classifai/Services/Personalizer.php @@ -5,11 +5,6 @@ namespace Classifai\Services; -use WP_REST_Server; -use WP_REST_Request; -use WP_Error; -use function Classifai\find_provider_class; - class Personalizer extends Service { /** @@ -23,19 +18,22 @@ public function __construct() { ); } - /** - * Register the rest API endpoints - */ - public function init() { - parent::init(); - } - /** * Get service providers for Recommendation service. * * @return array */ - public static function get_service_providers() { + public static function get_service_providers(): array { + /** + * Filter the service providers for Recommendation service. + * + * @since 3.0.0 + * @hook classifai_recommendation_service_providers + * + * @param {array} $providers Array of available providers for the service. + * + * @return {array} The filtered available providers. + */ return apply_filters( 'classifai_recommendation_service_providers', [ From 108501a9c041417255fdb65989039e8ae994a127 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 15:38:32 -0700 Subject: [PATCH 094/127] Clean up taxonomies --- .../Classifai/Services/ImageProcessing.php | 5 ----- .../Classifai/Services/LanguageProcessing.php | 7 ------- .../Classifai/Taxonomy/AbstractTaxonomy.php | 16 +++++++--------- .../Classifai/Taxonomy/CategoryTaxonomy.php | 16 ++++++++++++---- .../Classifai/Taxonomy/ConceptTaxonomy.php | 16 ++++++++++++---- includes/Classifai/Taxonomy/EntityTaxonomy.php | 16 ++++++++++++---- .../Classifai/Taxonomy/ImageTagTaxonomy.php | 18 +++++++++++++----- .../Classifai/Taxonomy/KeywordTaxonomy.php | 16 ++++++++++++---- .../Classifai/Taxonomy/TaxonomyFactory.php | 17 +++++++++-------- 9 files changed, 77 insertions(+), 50 deletions(-) diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index c703d9f59..b7cea27dc 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -90,11 +90,6 @@ public function enqueue_media_scripts() { } } - /** - * Create endpoints for services - */ - public function register_endpoints() {} - /** * Register a common image tag taxonomy */ diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index b3c4ae39b..08d2dfc14 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -54,11 +54,4 @@ public static function get_service_providers(): array { ] ); } - - /** - * Create endpoints for Language Processing. - * - * @since 1.8.0 - */ - public function register_endpoints() {} } diff --git a/includes/Classifai/Taxonomy/AbstractTaxonomy.php b/includes/Classifai/Taxonomy/AbstractTaxonomy.php index a44dcfe34..4395c40b0 100644 --- a/includes/Classifai/Taxonomy/AbstractTaxonomy.php +++ b/includes/Classifai/Taxonomy/AbstractTaxonomy.php @@ -33,21 +33,21 @@ abstract class AbstractTaxonomy { * * @return string */ - abstract public function get_name(); + abstract public function get_name(): string; /** * Get the singular taxonomy label. * * @return string */ - abstract public function get_singular_label(); + abstract public function get_singular_label(): string; /** * Get the plural taxonomy label. * * @return string */ - abstract public function get_plural_label(); + abstract public function get_plural_label(): string; /** * Return true or false based on whether to show this taxonomy. Maps @@ -55,7 +55,7 @@ abstract public function get_plural_label(); * * @return bool */ - abstract public function get_visibility(); + abstract public function get_visibility(): bool; /** * Register hooks and actions. @@ -73,7 +73,7 @@ public function register() { * * @return array */ - public function get_options() { + public function get_options(): array { $visibility = $this->get_visibility(); return array( @@ -93,18 +93,16 @@ public function get_options() { * * @return string */ - public function update_count_callback() { + public function update_count_callback(): string { return ''; } - - /** * Get the labels for the taxonomy. * * @return array */ - public function get_labels() { + public function get_labels(): array { $plural_label = $this->get_plural_label(); $singular_label = $this->get_singular_label(); diff --git a/includes/Classifai/Taxonomy/CategoryTaxonomy.php b/includes/Classifai/Taxonomy/CategoryTaxonomy.php index 4c562213c..0da0853d3 100644 --- a/includes/Classifai/Taxonomy/CategoryTaxonomy.php +++ b/includes/Classifai/Taxonomy/CategoryTaxonomy.php @@ -18,29 +18,37 @@ class CategoryTaxonomy extends AbstractTaxonomy { /** * Get the ClassifAI category taxonomy name. + * + * @return string */ - public function get_name() { + public function get_name(): string { return WATSON_CATEGORY_TAXONOMY; } /** * Get the ClassifAI category taxonomy label. + * + * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Watson Category', 'classifai' ); } /** * Get the ClassifAI category taxonomy plural label. + * + * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Watson Categories', 'classifai' ); } /** * Get the ClassifAI category taxonomy visibility. + * + * @return bool */ - public function get_visibility() { + public function get_visibility(): bool { return \Classifai\get_feature_enabled( 'category' ) && \Classifai\get_feature_taxonomy( 'category' ) === $this->get_name(); } diff --git a/includes/Classifai/Taxonomy/ConceptTaxonomy.php b/includes/Classifai/Taxonomy/ConceptTaxonomy.php index 3363e548b..fc75a6ab2 100644 --- a/includes/Classifai/Taxonomy/ConceptTaxonomy.php +++ b/includes/Classifai/Taxonomy/ConceptTaxonomy.php @@ -18,29 +18,37 @@ class ConceptTaxonomy extends AbstractTaxonomy { /** * Get the ClassifAI concept taxonomy name. + * + * @return string */ - public function get_name() { + public function get_name(): string { return WATSON_CONCEPT_TAXONOMY; } /** * Get the ClassifAI concept taxonomy label. + * + * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Watson Concept', 'classifai' ); } /** * Get the ClassifAI concept taxonomy plural label. + * + * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Watson Concepts', 'classifai' ); } /** * Get the ClassifAI concept taxonomy visibility. + * + * @return bool */ - public function get_visibility() { + public function get_visibility(): bool { return \Classifai\get_feature_enabled( 'concept' ) && \Classifai\get_feature_taxonomy( 'concept' ) === $this->get_name(); } diff --git a/includes/Classifai/Taxonomy/EntityTaxonomy.php b/includes/Classifai/Taxonomy/EntityTaxonomy.php index e91f0e419..75726dd5a 100644 --- a/includes/Classifai/Taxonomy/EntityTaxonomy.php +++ b/includes/Classifai/Taxonomy/EntityTaxonomy.php @@ -18,29 +18,37 @@ class EntityTaxonomy extends AbstractTaxonomy { /** * Get the ClassifAI entity taxonomy name. + * + * @return string */ - public function get_name() { + public function get_name(): string { return WATSON_ENTITY_TAXONOMY; } /** * Get the ClassifAI entity taxonomy label. + * + * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Watson Entity', 'classifai' ); } /** * Get the ClassifAI entity taxonomy plural label. + * + * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Watson Entities', 'classifai' ); } /** * Get the ClassifAI entity taxonomy visibility. + * + * @return bool */ - public function get_visibility() { + public function get_visibility(): bool { return \Classifai\get_feature_enabled( 'entity' ) && \Classifai\get_feature_taxonomy( 'entity' ) === $this->get_name(); } diff --git a/includes/Classifai/Taxonomy/ImageTagTaxonomy.php b/includes/Classifai/Taxonomy/ImageTagTaxonomy.php index 99e32c2a8..b48b8cf60 100644 --- a/includes/Classifai/Taxonomy/ImageTagTaxonomy.php +++ b/includes/Classifai/Taxonomy/ImageTagTaxonomy.php @@ -6,29 +6,37 @@ class ImageTagTaxonomy extends AbstractTaxonomy { /** * Get the ClassifAI category taxonomy name. + * + * @return string */ - public function get_name() { + public function get_name(): string { return 'classifai-image-tags'; } /** * Get the ClassifAI category taxonomy label. + * + * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Image Tag', 'classifai' ); } /** * Get the ClassifAI category taxonomy plural label. + * + * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Image Tags', 'classifai' ); } /** * Get the ClassifAI category taxonomy visibility. + * + * @return bool */ - public function get_visibility() { + public function get_visibility(): bool { return true; } @@ -37,7 +45,7 @@ public function get_visibility() { * * @return string */ - public function update_count_callback() { + public function update_count_callback(): string { return '_update_generic_term_count'; } } diff --git a/includes/Classifai/Taxonomy/KeywordTaxonomy.php b/includes/Classifai/Taxonomy/KeywordTaxonomy.php index 9c0fed4b2..6f510769e 100644 --- a/includes/Classifai/Taxonomy/KeywordTaxonomy.php +++ b/includes/Classifai/Taxonomy/KeywordTaxonomy.php @@ -18,29 +18,37 @@ class KeywordTaxonomy extends AbstractTaxonomy { /** * Get the ClassifAI keyword taxonomy name. + * + * @return string */ - public function get_name() { + public function get_name(): string { return WATSON_KEYWORD_TAXONOMY; } /** * Get the ClassifAI keyword taxonomy label. + * + * @return string */ - public function get_singular_label() { + public function get_singular_label(): string { return esc_html__( 'Watson Keyword', 'classifai' ); } /** * Get the ClassifAI keyword taxonomy plural label. + * + * @return string */ - public function get_plural_label() { + public function get_plural_label(): string { return esc_html__( 'Watson Keywords', 'classifai' ); } /** * Get the ClassifAI keyword taxonomy visibility. + * + * @return bool */ - public function get_visibility() { + public function get_visibility(): bool { return \Classifai\get_feature_enabled( 'keyword' ) && \Classifai\get_feature_taxonomy( 'keyword' ) === $this->get_name(); } diff --git a/includes/Classifai/Taxonomy/TaxonomyFactory.php b/includes/Classifai/Taxonomy/TaxonomyFactory.php index 72cc8ec14..35cad5f9b 100644 --- a/includes/Classifai/Taxonomy/TaxonomyFactory.php +++ b/includes/Classifai/Taxonomy/TaxonomyFactory.php @@ -39,8 +39,10 @@ class TaxonomyFactory { public $taxonomies = []; /** - * Builds all supported taxonomies. This is bound to the 'init' hook - * to allow both frontend and backend to get these taxonomies. + * Builds all supported taxonomies. + * + * This is bound to the 'init' hook to allow both + * frontend and backend to get these taxonomies. */ public function build_all() { $supported_post_types = \Classifai\get_supported_post_types(); @@ -55,10 +57,9 @@ public function build_all() { * * @param string $taxonomy The taxonomy name. * @param array $supported_post_types The supported post types. - * * @return BaseTaxonomy A base taxonomy subclass instance. */ - public function build_if( $taxonomy, $supported_post_types = [] ) { + public function build_if( string $taxonomy, array $supported_post_types = [] ) { if ( ! $this->exists( $taxonomy ) ) { $this->taxonomies[ $taxonomy ] = $this->build( $taxonomy ); $instance = $this->taxonomies[ $taxonomy ]; @@ -76,14 +77,14 @@ public function build_if( $taxonomy, $supported_post_types = [] ) { /** * Instantiates and returns a instance for the specified taxonomy. + * * An exception is thrown if an invalid taxonomy name was specified. * * @param string $taxonomy The taxonomy name - * * @return \Taxonomy\Taxonomy\BaseTaxonomy A base taxonomy subclass instance. * @throws \Exception An exception. */ - public function build( $taxonomy ) { + public function build( string $taxonomy ) { if ( ! empty( $this->mapping[ $taxonomy ] ) ) { $class = $this->mapping[ $taxonomy ]; @@ -106,7 +107,7 @@ public function build( $taxonomy ) { * @param string $taxonomy The taxonomy name * @return bool True if the taxonomy exists else false */ - public function exists( $taxonomy ) { + public function exists( string $taxonomy ): bool { return ! empty( $this->taxonomies[ $taxonomy ] ); } @@ -115,7 +116,7 @@ public function exists( $taxonomy ) { * * @return array List of taxonomy names */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { return array_keys( $this->mapping ); } } From 23aa4d67a28139dc44ed50210f787adaa7427779 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 22 Jan 2024 15:54:22 -0700 Subject: [PATCH 095/127] Clean up admin functionality --- includes/Classifai/Admin/BulkActions.php | 53 ++++++++----------- includes/Classifai/Admin/DebugInfo.php | 14 ++--- includes/Classifai/Admin/Notifications.php | 2 +- includes/Classifai/Admin/Onboarding.php | 38 ++++++------- .../Classifai/Admin/PreviewClassifierData.php | 3 +- includes/Classifai/Admin/SavePostHandler.php | 27 +++++----- includes/Classifai/Admin/Update.php | 4 +- includes/Classifai/Admin/UserProfile.php | 12 ++--- .../recommended-content-block/register.php | 3 +- 9 files changed, 70 insertions(+), 86 deletions(-) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index e48df1f7d..c20678779 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -17,15 +17,6 @@ */ class BulkActions { - /** - * Check to see if we can register this class. - * - * @return bool - */ - public function can_register() { - return is_admin(); - } - /** * Array of language processing features. * @@ -41,9 +32,13 @@ public function can_register() { private $media_processing_features = []; /** - * @var \Classifai\Providers\Watson\NLU + * Check to see if we can register this class. + * + * @return bool */ - private $ibm_watson_nlu; + public function can_register(): bool { + return is_admin(); + } /** * Register the actions needed. @@ -90,13 +85,12 @@ public function register_language_processing_hooks() { } /** - * Register Classifai bulk actions. + * Register Language Processing bulk actions. * * @param array $bulk_actions Current bulk actions. - * * @return array */ - public function register_language_processing_actions( $bulk_actions ) { + public function register_language_processing_actions( array $bulk_actions ): array { foreach ( $this->language_processing_features as $feature ) { if ( ! $feature->is_feature_enabled() ) { continue; @@ -128,12 +122,11 @@ public function register_language_processing_actions( $bulk_actions ) { * @param string $redirect_to Redirect URL after bulk actions. * @param string $doaction Action ID. * @param array $post_ids Post ids to apply bulk actions to. - * * @return string */ - public function language_processing_actions_handler( $redirect_to, $doaction, $post_ids ) { + public function language_processing_actions_handler( string $redirect_to, string $doaction, array $post_ids ): string { $feature_ids = array_map( - function( $feature ) { + function ( $feature ) { return $feature::ID; }, $this->language_processing_features @@ -188,14 +181,13 @@ function ( $feature ) { } /** - * Register Classifai row action. + * Register Language Processing row actions. * * @param array $actions Current row actions. * @param \WP_Post $post Post object. - * * @return array */ - public function register_language_processing_row_action( $actions, $post ) { + public function register_language_processing_row_action( array $actions, \WP_Post $post ): array { foreach ( $this->language_processing_features as $feature ) { if ( ! $feature->is_feature_enabled() ) { continue; @@ -232,7 +224,7 @@ public function register_language_processing_row_action( $actions, $post ) { } /** - * Register bulk actions for the Computer Vision provider. + * Register Image Processing hooks. */ public function register_image_processing_hooks() { $this->media_processing_features = [ @@ -250,13 +242,12 @@ public function register_image_processing_hooks() { } /** - * Register Classifai media bulk actions. + * Register Image Processing bulk actions. * * @param array $bulk_actions Current bulk actions. - * * @return array */ - public function register_media_processing_media_bulk_actions( $bulk_actions ) { + public function register_media_processing_media_bulk_actions( array $bulk_actions ): array { foreach ( $this->media_processing_features as $feature ) { if ( ! $feature->is_feature_enabled() ) { continue; @@ -295,17 +286,16 @@ public function register_media_processing_media_bulk_actions( $bulk_actions ) { } /** - * Handle media bulk actions. + * Handle Image Processing bulk actions. * * @param string $redirect_to Redirect URL after bulk actions. * @param string $doaction Action ID. * @param array $attachment_ids Attachment ids to apply bulk actions to. - * * @return string */ - public function media_processing_bulk_action_handler( $redirect_to, $doaction, $attachment_ids ) { + public function media_processing_bulk_action_handler( string $redirect_to, string $doaction, array $attachment_ids ): string { $feature_ids = array_map( - function( $feature ) { + function ( $feature ) { return $feature::ID; }, $this->media_processing_features @@ -374,13 +364,13 @@ function ( $feature ) { } /** - * Register media row actions. + * Register Image Processing row actions. * * @param array $actions An array of action links for each attachment. * @param \WP_Post $post WP_Post object for the current attachment. * @return array */ - public function register_media_processing_row_action( $actions, $post ) { + public function register_media_processing_row_action( array $actions, \WP_Post $post ): array { if ( attachment_is_pdf( $post ) && ( new PDFTextExtraction() )->is_feature_enabled() ) { $actions[ PDFTextExtraction::ID ] = sprintf( '%s', @@ -442,7 +432,7 @@ public function bulk_action_admin_notice() { $action = ''; $post_type = ! empty( $_GET['post_type'] ) ? sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) : 'post'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $all_feature_ids = array_map( - function( $feature ) { + function ( $feature ) { return $feature::ID; }, array_merge( $this->language_processing_features, $this->media_processing_features ) @@ -503,7 +493,6 @@ function( $feature ) { case Classification::ID: $action_text = __( 'Classification done for', 'classifai' ); break; - } $output = '

    '; diff --git a/includes/Classifai/Admin/DebugInfo.php b/includes/Classifai/Admin/DebugInfo.php index cf9132a60..c023a8ccb 100644 --- a/includes/Classifai/Admin/DebugInfo.php +++ b/includes/Classifai/Admin/DebugInfo.php @@ -18,15 +18,16 @@ class DebugInfo { /** * Checks whether this class's register method should run. * - * @return bool * @since 1.4.0 + * + * @return bool */ - public function can_register() { + public function can_register(): bool { return is_admin(); } /** - * Adds WP hook callbacks. + * Adds hook callbacks. * * @since 1.4.0 */ @@ -38,14 +39,13 @@ public function register() { * Modifies debug information displayed on the WP Site Health screen. * * @see WP_Debug_Data::debug_data - * @filter debug_information + * + * @since 1.4.0 * * @param array $information The full array of site debug information. * @return array Filtered debug information. - * - * @since 1.4.0 */ - public function add_classifai_debug_information( $information ) { + public function add_classifai_debug_information( array $information ): array { $plugin_data = get_plugin_data( CLASSIFAI_PLUGIN ); /** diff --git a/includes/Classifai/Admin/Notifications.php b/includes/Classifai/Admin/Notifications.php index 0e3fb8812..307d06865 100644 --- a/includes/Classifai/Admin/Notifications.php +++ b/includes/Classifai/Admin/Notifications.php @@ -14,7 +14,7 @@ class Notifications { * * @return bool */ - public function can_register() { + public function can_register(): bool { return is_admin(); } diff --git a/includes/Classifai/Admin/Onboarding.php b/includes/Classifai/Admin/Onboarding.php index 6bf3de5df..de6813f17 100644 --- a/includes/Classifai/Admin/Onboarding.php +++ b/includes/Classifai/Admin/Onboarding.php @@ -155,8 +155,6 @@ public function render_setup_page() { /** * Handle the submission of the step of the onboarding wizard. - * - * @return void */ public function handle_step_submission() { if ( ! isset( $_POST['classifai-setup-step-nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai-setup-step-nonce'] ) ), 'classifai-setup-step-action' ) ) { @@ -312,7 +310,9 @@ public function handle_step_submission() { } /** - * Sanitize variables using sanitize_text_field and wp_unslash. Arrays are cleaned recursively. + * Sanitize variables using sanitize_text_field and wp_unslash. + * + * Arrays are cleaned recursively. * Non-scalar values are ignored. * * @param string|array $data Data to sanitize. @@ -327,13 +327,12 @@ public function classifai_sanitize( $data ) { } /** - * Render classifai setup settings with the given fields. + * Render setup settings with the given fields. * * @param string $setting_name The name of the setting. * @param string[] $fields The fields to render. - * @return void */ - public function render_classifai_setup_settings( $setting_name, $fields = array() ) { + public function render_classifai_setup_settings( string $setting_name, array $fields = array() ) { global $wp_settings_sections, $wp_settings_fields; if ( ! isset( $wp_settings_fields[ $setting_name ][ $setting_name ] ) ) { @@ -394,12 +393,11 @@ public function render_classifai_setup_settings( $setting_name, $fields = array( } /** - * Render classifai setup feature settings. + * Render setup feature settings. * * @param string $feature $feature to setup. - * @return void */ - public function render_classifai_setup_feature( $feature ) { + public function render_classifai_setup_feature( string $feature ) { global $wp_settings_fields; $features = $this->get_features( false ); $feature_class = $features[ $feature ] ?? null; @@ -469,7 +467,7 @@ public function render_classifai_setup_feature( $feature ) { * @param bool $nested Whether to return the features in a nested array or not. * @return array The Onboarding features. */ - public function get_features( $nested = true ) { + public function get_features( bool $nested = true ): array { if ( empty( $this->features ) ) { $services = Plugin::$instance->services; if ( empty( $services ) || empty( $services['service_manager'] ) || ! $services['service_manager'] instanceof ServicesManager ) { @@ -517,6 +515,7 @@ public function get_features( $nested = true ) { $features[ $feature_slug ] = $feature; } } + return $features; } @@ -525,7 +524,7 @@ public function get_features( $nested = true ) { * * @return array The onboarding options. */ - public function get_onboarding_options() { + public function get_onboarding_options(): array { return get_option( 'classifai_onboarding_options', array() ); } @@ -534,7 +533,7 @@ public function get_onboarding_options() { * * @return bool True if onboarding is completed, false otherwise. */ - public function is_onboarding_completed() { + public function is_onboarding_completed(): bool { $options = $this->get_onboarding_options(); return isset( $options['status'] ) && 'completed' === $options['status']; } @@ -544,7 +543,7 @@ public function is_onboarding_completed() { * * @param array $options The options to update. */ - public function update_onboarding_options( $options ) { + public function update_onboarding_options( array $options ) { if ( ! is_array( $options ) ) { return; } @@ -586,10 +585,11 @@ public function handle_skip_setup_step() { * * @return array Array of enabled providers. */ - public function get_enabled_features() { + public function get_enabled_features(): array { $features = $this->get_features( false ); $onboarding_options = $this->get_onboarding_options(); $enabled_features = $onboarding_options['enabled_features'] ?? array(); + foreach ( $enabled_features as $feature_key => $value ) { if ( ! isset( $features[ $feature_key ] ) ) { unset( $enabled_features[ $feature_key ] ); @@ -607,10 +607,10 @@ public function get_enabled_features() { * @param string $current_feature Current feature. * @return string|bool Next provider to setup or false if none. */ - public function get_next_feature( $current_feature ) { + public function get_next_feature( string $current_feature ) { $enabled_features = $this->get_enabled_features(); - $keys = array_keys( $enabled_features ); - $index = array_search( $current_feature, $keys, true ); + $keys = array_keys( $enabled_features ); + $index = array_search( $current_feature, $keys, true ); if ( false === $index ) { return false; @@ -633,7 +633,7 @@ public function prevent_direct_step_visits() { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $step = absint( wp_unslash( $_GET['step'] ) ); + $step = absint( wp_unslash( $_GET['step'] ) ); $onboarding_options = $this->get_onboarding_options(); $step_completed = isset( $onboarding_options['step_completed'] ) ? absint( $onboarding_options['step_completed'] ) : 0; @@ -647,7 +647,7 @@ public function prevent_direct_step_visits() { * * @return array */ - public function get_configured_features() { + public function get_configured_features(): array { $features = $this->get_features( false ); $configured_features = array(); diff --git a/includes/Classifai/Admin/PreviewClassifierData.php b/includes/Classifai/Admin/PreviewClassifierData.php index 3d690c319..ad2689200 100644 --- a/includes/Classifai/Admin/PreviewClassifierData.php +++ b/includes/Classifai/Admin/PreviewClassifierData.php @@ -68,8 +68,9 @@ public function get_post_search_results() { * Filter classifier preview based on the feature settings. * * @param array $classified_data The classified data. + * @return array */ - public function filter_classify_preview_data( $classified_data ) { + public function filter_classify_preview_data( array $classified_data ): array { if ( is_wp_error( $classified_data ) ) { return $classified_data; } diff --git a/includes/Classifai/Admin/SavePostHandler.php b/includes/Classifai/Admin/SavePostHandler.php index f6a98973a..cdf4697e0 100644 --- a/includes/Classifai/Admin/SavePostHandler.php +++ b/includes/Classifai/Admin/SavePostHandler.php @@ -2,10 +2,9 @@ namespace Classifai\Admin; -use Classifai\Features\Classification; use Classifai\Features\TextToSpeech; -use \Classifai\Providers\Azure\Speech; -use \Classifai\Watson\Normalizer; +use Classifai\Providers\Azure\Speech; +use Classifai\Watson\Normalizer; use function Classifai\get_classification_mode; /** @@ -31,8 +30,10 @@ public function register() { /** * Check to see if we can register this class. + * + * @return bool */ - public function can_register() { + public function can_register(): bool { $should_register = false; if ( $this->is_configured() && ( is_admin() || $this->is_rest_route() ) ) { @@ -58,7 +59,7 @@ public function can_register() { * * @return bool */ - public function is_configured() { + public function is_configured(): bool { return ! empty( get_option( 'classifai_configured' ) ) && ! empty( get_option( 'classifai_watson_nlu' )['credentials']['watson_url'] ); } @@ -69,10 +70,9 @@ public function is_configured() { * or an array of values. * @param int $object_id Object ID. * @param string $meta_key Meta key. - * * @return mixed */ - public function default_post_metadata( $value, $object_id, $meta_key ) { + public function default_post_metadata( $value, int $object_id, string $meta_key ) { if ( '_classifai_process_content' === $meta_key ) { if ( 'automatic_classification' === get_classification_mode() ) { return 'yes'; @@ -94,7 +94,7 @@ public function default_post_metadata( $value, $object_id, $meta_key ) { * * @param int $post_id The post that was saved */ - public function did_save_post( $post_id ) { + public function did_save_post( int $post_id ) { if ( ! empty( $_GET['classic-editor'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } @@ -135,10 +135,9 @@ public function did_save_post( $post_id ) { * * @param int $post_id the post to classify & link. * @param bool $link_terms Whether to link the terms to the post. - * * @return array */ - public function classify( $post_id, $link_terms = true ) { + public function classify( int $post_id, bool $link_terms = true ): array { /** * Filter whether ClassifAI should classify a post. * @@ -209,7 +208,7 @@ public function classify( $post_id, $link_terms = true ) { * @param int $post_id Post ID. * @return bool|int|WP_Error */ - public function synthesize_speech( $post_id ) { + public function synthesize_speech( int $post_id ) { if ( empty( $post_id ) ) { return new \WP_Error( 'azure_text_to_speech_post_id_missing', @@ -433,7 +432,7 @@ public function show_error_if() { * * @return bool */ - public function is_rest_route() { + public function is_rest_route(): bool { if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return false; @@ -462,8 +461,6 @@ public function is_rest_route() { /** * Classify post manually. - * - * @return void */ public function classifai_classify_post() { if ( ! empty( $_GET['classifai_classify_post_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['classifai_classify_post_nonce'] ) ), 'classifai_classify_post_action' ) ) { @@ -488,7 +485,7 @@ public function classifai_classify_post() { * @param string[] $removable_query_args An array of query variable names to remove from a URL. * @return string[] */ - public function classifai_removable_query_args( $removable_query_args ) { + public function classifai_removable_query_args( array $removable_query_args ): array { $removable_query_args[] = 'classifai_classify'; return $removable_query_args; } diff --git a/includes/Classifai/Admin/Update.php b/includes/Classifai/Admin/Update.php index 04c8e224a..212060f98 100644 --- a/includes/Classifai/Admin/Update.php +++ b/includes/Classifai/Admin/Update.php @@ -2,7 +2,7 @@ /** * ClassifAI Auto Update Integration * - * @package 10up/classifai + * @package classifai */ namespace Classifai\Admin; @@ -33,7 +33,7 @@ class Update { * * @return bool */ - public function can_register() { + public function can_register(): bool { return class_exists( '\YahnisElsts\PluginUpdateChecker\v5\PucFactory' ) && self::license_check(); } diff --git a/includes/Classifai/Admin/UserProfile.php b/includes/Classifai/Admin/UserProfile.php index 2c34f1c84..4982f2862 100644 --- a/includes/Classifai/Admin/UserProfile.php +++ b/includes/Classifai/Admin/UserProfile.php @@ -33,10 +33,9 @@ public function init() { } /** - * Add ClassifAI features opt-out checkboxes to user profile and edit user. + * Add features opt-out checkboxes to user profile and edit user. * * @param \WP_User $user User object. - * @return void */ public function user_settings( \WP_User $user ) { $user_id = $user->ID; @@ -46,7 +45,7 @@ public function user_settings( \WP_User $user ) { return; } - // Bail if user is not allowed to access ClassifAI features. + // Bail if user is not allowed to access features. $features = $this->get_allowed_features( $user->ID ); if ( empty( $features ) ) { return; @@ -88,12 +87,11 @@ public function user_settings( \WP_User $user ) { } /** - * Save ClassifAI features opt-out settings. + * Save features opt-out settings. * * @param int $user_id User ID. - * @return void */ - public function save_user_settings( $user_id ) { + public function save_user_settings( int $user_id ) { if ( ! isset( $_POST['classifai_out_out_features_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_out_out_features_nonce'] ) ), 'classifai_out_out_features' ) @@ -118,7 +116,7 @@ public function save_user_settings( $user_id ) { * @param int $user_id User ID. * @return array List of features. */ - public function get_allowed_features( $user_id ) { + public function get_allowed_features( int $user_id ): array { $user = get_user_by( 'id', $user_id ); if ( ! $user ) { return array(); diff --git a/includes/Classifai/Blocks/recommended-content-block/register.php b/includes/Classifai/Blocks/recommended-content-block/register.php index 0522cac03..c905b2995 100644 --- a/includes/Classifai/Blocks/recommended-content-block/register.php +++ b/includes/Classifai/Blocks/recommended-content-block/register.php @@ -49,10 +49,9 @@ function register() { * Render callback method for the block * * @param array $attributes The blocks attributes. - * * @return string The rendered block markup. */ -function render_block_callback( $attributes ) { +function render_block_callback( array $attributes ): string { // Render block in Gutenberg Editor. if ( defined( 'REST_REQUEST' ) && \REST_REQUEST ) { $personalizer = new Personalizer( false ); From 59a77caf6e26e54d1213513e6cca2d7f4f34bccb Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 13:18:47 -0700 Subject: [PATCH 096/127] Clean up the features --- .../Features/AudioTranscriptsGeneration.php | 34 ++--- .../Classifai/Features/Classification.php | 32 ++-- .../Classifai/Features/ContentResizing.php | 24 +-- .../Features/DescriptiveTextGenerator.php | 28 +--- .../Classifai/Features/ExcerptGeneration.php | 24 +-- includes/Classifai/Features/Feature.php | 137 +++++++++++------- includes/Classifai/Features/ImageCropping.php | 28 +--- .../Classifai/Features/ImageGeneration.php | 30 ++-- .../Classifai/Features/ImageTagsGenerator.php | 28 +--- .../Features/ImageTextExtraction.php | 28 +--- .../Classifai/Features/PDFTextExtraction.php | 28 +--- .../Classifai/Features/RecommendedContent.php | 25 +--- includes/Classifai/Features/TextToSpeech.php | 30 +--- .../Classifai/Features/TitleGeneration.php | 24 +-- .../Classifai/Services/ImageProcessing.php | 1 - .../Classifai/Services/LanguageProcessing.php | 8 - 16 files changed, 183 insertions(+), 326 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 0f83ebd5a..9f32dd1b6 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -3,7 +3,7 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\OpenAI\Whisper; +use Classifai\Providers\OpenAI\Whisper; /** * Class AudioTranscriptsGeneration @@ -20,6 +20,8 @@ class AudioTranscriptsGeneration extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Audio Transcripts Generation', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,24 +30,22 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Audio Transcripts Generation', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { + /** + * Filter the feature providers. + * + * @since 3.0.0 + * @hook classifai_{feature}_providers + * + * @param {array} $providers Feature providers. + * + * @return {array} Filtered providers. + */ return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -118,7 +118,7 @@ public function setup_fields_sections() { * * @return array */ - public function get_default_settings() { + public function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => Whisper::ID, @@ -138,10 +138,9 @@ public function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -162,7 +161,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 8b108166d..a40b41906 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -3,8 +3,8 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\Watson\NLU; -use \Classifai\Providers\OpenAI\Embeddings; +use Classifai\Providers\Watson\NLU; +use Classifai\Providers\OpenAI\Embeddings; use function Classifai\get_post_statuses_for_language_settings; use function Classifai\get_post_types_for_language_settings; @@ -23,6 +23,8 @@ class Classification extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Classification', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -31,24 +33,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Classification', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -154,10 +144,8 @@ public function setup_fields_sections() { * Renders the previewer window for the feature. * * @param string $active_feature The ID of the current feature. - * - * @return void */ - public function render_previewer( $active_feature = '' ) { + public function render_previewer( string $active_feature = '' ) { if ( static::ID !== $active_feature ) { return; } @@ -238,7 +226,7 @@ public function render_previewer( $active_feature = '' ) { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'post_statuses' => [], @@ -260,10 +248,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -286,7 +273,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { @@ -301,7 +287,7 @@ public function run( ...$args ) { [ $provider_instance, 'classify' ], [ ...$args ] ); - } else if ( Embeddings::ID === $provider_instance::ID ) { + } elseif ( Embeddings::ID === $provider_instance::ID ) { /** @var Embeddings $provider_instance */ $result = call_user_func_array( [ $provider_instance, 'generate_embeddings_for_post' ], diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 3de4dff0f..6d88b5e34 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Services\LanguageProcessing; /** @@ -20,6 +20,8 @@ class ContentResizing extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Content Resizing', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Content Resizing', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -118,7 +108,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, @@ -138,10 +128,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -162,7 +151,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 33b0ff6aa..619ee062a 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; /** @@ -20,6 +20,8 @@ class DescriptiveTextGenerator extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Descriptive Text Generator', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,26 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Descriptive Text Generator', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * - * @internal - * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -122,7 +110,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => ComputerVision::ID, @@ -142,12 +130,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * - * @internal - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -168,7 +153,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 3e7d6a839..022920b0f 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -3,7 +3,7 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Providers\OpenAI\ChatGPT; /** * Class ExcerptGeneration @@ -20,6 +20,8 @@ class ExcerptGeneration extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Excerpt Generation', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Excerpt Generation', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -157,7 +147,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'post_types' => [], @@ -179,10 +169,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -218,7 +207,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 13b76b8e3..0423bd338 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -14,6 +14,13 @@ abstract class Feature { */ const ID = ''; + /** + * Feature label. + * + * @var string + */ + public $label = ''; + /** * User role array. * @@ -46,9 +53,9 @@ public function setup_roles() { $this->roles = array_combine( array_keys( $this->roles ), array_column( $this->roles, 'name' ) ); /** - * Filter the allowed WordPress roles for ChatGTP + * Filter the allowed WordPress roles for a feature. * - * @since 2.3.0 + * @since 3.0.0 * @hook classifai_{feature}_roles * * @param {array} $roles Array of arrays containing role information. @@ -64,7 +71,22 @@ public function setup_roles() { * * @return string */ - abstract public function get_label(); + public function get_label(): string { + /** + * Filter the feature label. + * + * @since 3.0.0 + * @hook classifai_{feature}_label + * + * @param {string} $label Feature label. + * + * @return {string} Filtered label. + */ + return apply_filters( + 'classifai_' . static::ID . '_label', + $this->label + ); + } /** * Set up the fields for each section. @@ -79,7 +101,7 @@ abstract protected function setup_fields_sections(); * @internal * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { return [ 'status' => '0', 'role_based_access' => '1', @@ -96,17 +118,16 @@ protected function get_default_settings() { * @internal * @return array */ - abstract protected function get_providers(); + abstract protected function get_providers(): array; /** * Sanitizes the settings before saving. * - * @param array $settings The settings to be sanitized on save. - * * @internal + * @param array $settings The settings to be sanitized on save. * @return array */ - public function sanitize_settings( $settings ) { + public function sanitize_settings( array $settings ): array { $new_settings = $settings; $current_settings = $this->get_settings(); $new_settings['status'] = $settings['status'] ?? $current_settings['status']; @@ -156,7 +177,7 @@ public function sanitize_settings( $settings ) { * * @return bool */ - public function has_access() { + public function has_access(): bool { $access = false; $user_id = get_current_user_id(); $user = get_user_by( 'id', $user_id ); @@ -194,11 +215,11 @@ public function has_access() { /** * Filter to override user access to a ClassifAI feature. * - * @since 2.4.0 + * @since 3.0.0 * @hook classifai_{$feature}_has_access * - * @param {bool} $access Current access value. - * @param {array} $settings Feature settings. + * @param {bool} $access Current access value. + * @param {array} $settings Feature settings. * * @return {bool} Should the user have access? */ @@ -206,15 +227,19 @@ public function has_access() { } /** - * Returns true if the feature meets all the criteria to be enabled. False otherwise. + * Determine if a feature is enabled. + * + * Returns true if the feature meets all the criteria to + * be enabled. False otherwise. + * * Criteria: * - Provider is configured. * - User has access to the feature. * - Feature is turned on. * - * @return boolean|\WP_Error + * @return bool */ - public function is_feature_enabled() { + public function is_feature_enabled(): bool { $is_feature_enabled = false; $settings = $this->get_settings(); @@ -243,6 +268,7 @@ public function is_feature_enabled() { /** * Determine if the feature is turned on. + * * Note: This function does not check if the user has access to the feature. * * - Use `is_feature_enabled()` to check if the user has access to the feature and feature is turned on. @@ -250,7 +276,7 @@ public function is_feature_enabled() { * * @return bool */ - public function is_enabled() { + public function is_enabled(): bool { $settings = $this->get_settings(); // Check if feature is turned on. @@ -261,7 +287,7 @@ public function is_enabled() { /** * Filter to override a specific classifai feature enabled. * - * @since 2.5.0 + * @since 3.0.0 * @hook classifai_{$feature}_is_enabled * * @param {bool} $is_enabled Is the feature enabled? @@ -290,7 +316,7 @@ public function register_setting() { * * @return string */ - public function get_option_name() { + public function get_option_name(): string { return 'classifai_' . static::ID; } @@ -298,7 +324,6 @@ public function get_option_name() { * Returns the settings for the feature. * * @param string $index The index of the setting to return. - * * @return array|string */ public function get_settings( $index = false ) { @@ -318,7 +343,7 @@ public function get_settings( $index = false ) { * * @return array */ - public function get_provider_default_settings() { + public function get_provider_default_settings(): array { $provider_settings = []; foreach ( array_keys( $this->get_providers() ) as $provider_id ) { @@ -334,8 +359,6 @@ public function get_provider_default_settings() { /** * Renders the fields of the provider selected for the feature. - * - * @return void */ public function render_provider_fields() { foreach ( array_keys( $this->get_providers() ) as $provider_id ) { @@ -352,19 +375,16 @@ public function render_provider_fields() { * * @internal * - * @param array $settings Settings data from the database. - * @param array $default Default feature and providers settings data. - * + * @param array $settings Settings data from the database. + * @param array $defaults Default feature and providers settings data. * @return array */ - protected function merge_settings( $settings = [], $default = [] ) { - foreach ( $default as $key => $value ) { + protected function merge_settings( array $settings = [], array $defaults = [] ): array { + foreach ( $defaults as $key => $value ) { if ( ! isset( $settings[ $key ] ) ) { - $settings[ $key ] = $default[ $key ]; - } else { - if ( is_array( $value ) ) { - $settings[ $key ] = $this->merge_settings( $settings[ $key ], $default[ $key ] ); - } + $settings[ $key ] = $defaults[ $key ]; + } elseif ( is_array( $value ) ) { + $settings[ $key ] = $this->merge_settings( $settings[ $key ], $defaults[ $key ] ); } } @@ -379,7 +399,7 @@ protected function merge_settings( $settings = [], $default = [] ) { * @param array $services Array of provider classes. * @return array */ - protected function get_provider_instances( $services ) { + protected function get_provider_instances( array $services ): array { $provider_instances = []; foreach ( $services as $provider_class ) { @@ -393,10 +413,9 @@ protected function get_provider_instances( $services ) { * Returns the instance of the provider set for the feature. * * @param string $provider_id The ID of the provider. - * * @return \Classifai\Providers */ - public function get_feature_provider_instance( $provider_id = '' ) { + public function get_feature_provider_instance( string $provider_id = '' ) { $provider_id = $provider_id ? $provider_id : $this->get_settings( 'provider' ); $provider_instance = find_provider_class( $this->provider_instances ?? [], $provider_id ); @@ -415,7 +434,7 @@ public function get_feature_provider_instance( $provider_id = '' ) { * * @return bool */ - public function is_configured() { + public function is_configured(): bool { $settings = $this->get_settings(); $provider_id = $settings['provider']; $is_configured = false; @@ -429,17 +448,26 @@ public function is_configured() { /** * Can the feature be initialized? + * + * @return bool */ - public function can_register() { + public function can_register(): bool { return $this->is_configured(); } - public static function get_debug_value_text( $setting_value, $type = 0 ) { + /** + * Get the debug value text. + * + * @param mixed $setting_value The value of the setting. + * @param integer $type The type of debug value to return. + * @return string + */ + public static function get_debug_value_text( $setting_value, $type = 0 ): string { $debug_value = ''; - if ( empty ( $setting_value ) ) { + if ( empty( $setting_value ) ) { $boolean = false; - } else if ( 'no' === $setting_value ) { + } elseif ( 'no' === $setting_value ) { $boolean = false; } else { $boolean = true; @@ -462,13 +490,13 @@ public static function get_debug_value_text( $setting_value, $type = 0 ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $feature_settings = $this->get_settings(); $provider = $this->get_feature_provider_instance(); $roles = array_filter( $feature_settings['roles'], - function( $role ) { + function ( $role ) { return '0' !== $role; } ); @@ -491,6 +519,17 @@ function( $role ) { ); } + /** + * Filter to add feature-level debug information. + * + * @since 3.0.0 + * @hook classifai_{feature}_debug_information + * + * @param {array} $all_debug_info Debug information + * @param {object} $this Current feature class. + * + * @return {array} Returns debug information. + */ return apply_filters( 'classifai_' . self::ID . '_debug_information', $all_debug_info, @@ -504,7 +543,7 @@ function( $role ) { * @param array $args The args passed to add_settings_field. * @return string */ - protected function get_data_attribute( $args ) { + protected function get_data_attribute( array $args ): string { $data_attr = $args['data_attr'] ?? []; $data_attr_str = ''; @@ -528,8 +567,6 @@ public function reset_settings() { /** * Add settings fields for Role/User based access. - * - * @return void */ protected function add_access_control_fields() { $settings = $this->get_settings(); @@ -626,7 +663,7 @@ protected function add_access_control_fields() { * * @param array $args The args passed to add_settings_field. */ - public function render_input( $args ) { + public function render_input( array $args ) { $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; $setting_index = $this->get_settings( $option_index ); $type = $args['input_type'] ?? 'text'; @@ -693,7 +730,7 @@ class="" * * @param array $args The args passed to add_settings_field. */ - public function render_prompt_repeater_field( array $args ): void { + public function render_prompt_repeater_field( array $args ) { $option_index = $args['option_index'] ?? false; $setting_index = $this->get_settings( $option_index ); $prompts = $setting_index[ $args['label_for'] ] ?? ''; @@ -795,7 +832,7 @@ class="button-secondary js-classifai-add-prompt-fieldset"> * * @param array $args The args passed to add_settings_field. */ - public function render_select( $args ) { + public function render_select( array $args ) { $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; $setting_index = $this->get_settings( $option_index ); $saved = ( isset( $setting_index[ $args['label_for'] ] ) ) ? $setting_index[ $args['label_for'] ] : ''; @@ -879,9 +916,8 @@ public function render_checkbox_group( array $args = array() ) { * * @param array $args The args passed to add_settings_field. */ - public function render_auto_caption_fields( $args ) { + public function render_auto_caption_fields( array $args ) { $setting_index = $this->get_settings(); - $default_value = ''; if ( isset( $setting_index['enable_image_captions'] ) ) { @@ -943,6 +979,7 @@ public function render_radio_group( array $args = array() ) { $setting_index = $this->get_settings(); $value = $setting_index[ $args['label_for'] ] ?? ''; $options = $args['options'] ?? []; + if ( ! is_array( $options ) ) { return; } diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 905be4ca6..5fbfe1513 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; /** @@ -20,6 +20,8 @@ class ImageCropping extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Image Cropping', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,26 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Image Cropping', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * - * @internal - * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -122,7 +110,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => ComputerVision::ID, @@ -142,12 +130,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * - * @internal - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -168,7 +153,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index 43b794273..f57693518 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -3,7 +3,7 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\OpenAI\DallE; +use Classifai\Providers\OpenAI\DallE; /** * Class TitleGeneration @@ -20,6 +20,8 @@ class ImageGeneration extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Image Generation', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Image Generation', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -64,7 +54,7 @@ public function setup_roles() { $roles = get_editable_roles() ?? []; $roles = array_filter( $roles, - function( $role ) { + function ( $role ) { return isset( $role['capabilities'], $role['capabilities']['upload_files'] ) && $role['capabilities']['upload_files']; } ); @@ -141,9 +131,9 @@ public function setup_fields_sections() { /** * Returns true if the feature meets all the criteria to be enabled. * - * @return boolean + * @return bool */ - public function is_feature_enabled() { + public function is_feature_enabled(): bool { $settings = $this->get_settings(); $is_feature_enabled = parent::is_feature_enabled() && current_user_can( 'upload_files' ); @@ -161,7 +151,7 @@ public function is_feature_enabled() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => DallE::ID, @@ -181,10 +171,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -205,7 +194,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index ce00e4fcd..2eafdfd15 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; /** @@ -20,6 +20,8 @@ class ImageTagsGenerator extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Image Tags Generator', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,26 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Image Tags Generator', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * - * @internal - * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -122,7 +110,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => ComputerVision::ID, @@ -142,12 +130,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * - * @internal - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -168,7 +153,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 8cd12c05e..197fba8bc 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; /** @@ -20,6 +20,8 @@ class ImageTextExtraction extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Image Text Extraction', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,26 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Image Text Extraction', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * - * @internal - * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -122,7 +110,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => ComputerVision::ID, @@ -142,12 +130,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * - * @internal - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -168,7 +153,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index b7431966b..4c30e9a2e 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use \Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; /** @@ -20,6 +20,8 @@ class PDFTextExtraction extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'PDF Text Extraction', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,26 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'PDF Text Extraction', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * - * @internal - * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -122,7 +110,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => ComputerVision::ID, @@ -142,12 +130,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * - * @internal - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -168,7 +153,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index f9f8070c1..6052da9b7 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -3,8 +3,7 @@ namespace Classifai\Features; use Classifai\Services\Personalizer as PersonalizerService; -use \Classifai\Providers\Azure\Personalizer as PersonalizerProvider; -use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Providers\Azure\Personalizer as PersonalizerProvider; /** * Class RecommendedContent @@ -21,6 +20,8 @@ class RecommendedContent extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Recommended Content', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -29,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Recommended Content', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -119,7 +108,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => PersonalizerProvider::ID, @@ -139,10 +128,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -163,7 +151,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 123a1923d..42e8baed2 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -3,9 +3,7 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\Azure\Speech; - -use function Classifai\find_provider_class; +use Classifai\Providers\Azure\Speech; /** * Class TitleGeneration @@ -22,6 +20,8 @@ class TextToSpeech extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Text to Speech', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -30,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Text to speech', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - protected function get_providers() { + protected function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -136,7 +124,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_post_types_select_options() { + protected function get_post_types_select_options(): array { $post_types = \Classifai\get_post_types_for_language_settings(); $options = array(); @@ -157,7 +145,7 @@ protected function get_post_types_select_options() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'post_types' => [], @@ -179,7 +167,7 @@ protected function get_default_settings() { * * @return array Supported Post Types. */ - public function get_tts_supported_post_types() { + public function get_tts_supported_post_types(): array { $selected = $this->get_settings( 'post_types' ); $post_types = []; @@ -196,10 +184,9 @@ public function get_tts_supported_post_types() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -229,7 +216,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index c64243b15..f93d73661 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -3,7 +3,7 @@ namespace Classifai\Features; use Classifai\Services\LanguageProcessing; -use \Classifai\Providers\OpenAI\ChatGPT; +use Classifai\Providers\OpenAI\ChatGPT; /** * Class TitleGeneration @@ -20,6 +20,8 @@ class TitleGeneration extends Feature { * Constructor. */ public function __construct() { + $this->label = __( 'Title Generation', 'classifai' ); + /** * Every feature must set the `provider_instances` variable with the list of provider instances * that are registered to a service. @@ -28,24 +30,12 @@ public function __construct() { $this->provider_instances = $this->get_provider_instances( $service_providers ); } - /** - * Returns the label of the feature. - * - * @return string - */ - public function get_label() { - return apply_filters( - 'classifai_' . static::ID . '_label', - __( 'Title Generation', 'classifai' ) - ); - } - /** * Returns the providers supported by the feature. * * @return array */ - public function get_providers() { + public function get_providers(): array { return apply_filters( 'classifai_' . static::ID . '_providers', [ @@ -118,7 +108,7 @@ public function setup_fields_sections() { * * @return array */ - protected function get_default_settings() { + protected function get_default_settings(): array { $provider_settings = $this->get_provider_default_settings(); $feature_settings = [ 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, @@ -138,10 +128,9 @@ protected function get_default_settings() { * Sanitizes the settings before saving. * * @param array $new_settings The settings to be sanitized on save. - * * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->get_settings(); // Sanitization of the feature-level settings. @@ -162,7 +151,6 @@ public function sanitize_settings( $new_settings ) { * Runs the feature. * * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * * @return mixed */ public function run( ...$args ) { diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index b7cea27dc..6f1e11dd8 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -34,7 +34,6 @@ public function init() { $this->register_image_tags_taxonomy(); add_filter( 'attachment_fields_to_edit', [ $this, 'custom_fields_edit' ] ); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); } diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 08d2dfc14..50365b2ad 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -18,14 +18,6 @@ public function __construct() { ); } - /** - * Init service for Language Processing. - */ - public function init() { - parent::init(); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - } - /** * Get service providers for Language Processing. * From ca3af481db184d67682aa8531df403843c800efe Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 13:40:52 -0700 Subject: [PATCH 097/127] Reduce duplicate code with how a feature sets it's providers --- .../Features/AudioTranscriptsGeneration.php | 35 ++++--------------- .../Classifai/Features/Classification.php | 27 ++++---------- .../Classifai/Features/ContentResizing.php | 25 ++++--------- .../Features/DescriptiveTextGenerator.php | 25 ++++--------- .../Classifai/Features/ExcerptGeneration.php | 25 ++++--------- includes/Classifai/Features/Feature.php | 26 +++++++++++++- includes/Classifai/Features/ImageCropping.php | 25 ++++--------- .../Classifai/Features/ImageGeneration.php | 25 ++++--------- .../Classifai/Features/ImageTagsGenerator.php | 25 ++++--------- .../Features/ImageTextExtraction.php | 25 ++++--------- .../Classifai/Features/PDFTextExtraction.php | 25 ++++--------- .../Classifai/Features/RecommendedContent.php | 25 ++++--------- includes/Classifai/Features/TextToSpeech.php | 13 +++---- .../Classifai/Features/TitleGeneration.php | 25 ++++--------- includes/Classifai/Services/Service.php | 4 +-- 15 files changed, 107 insertions(+), 248 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 9f32dd1b6..cd704e426 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -22,36 +22,13 @@ class AudioTranscriptsGeneration extends Feature { public function __construct() { $this->label = __( 'Audio Transcripts Generation', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - /** - * Filter the feature providers. - * - * @since 3.0.0 - * @hook classifai_{feature}_providers - * - * @param {array} $providers Feature providers. - * - * @return {array} Filtered providers. - */ - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - Whisper::ID => __( 'OpenAI Whisper', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + Whisper::ID => __( 'OpenAI Whisper', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index a40b41906..4183637f2 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -25,27 +25,14 @@ class Classification extends Feature { public function __construct() { $this->label = __( 'Classification', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - NLU::ID => __( 'IBM Watson NLU', 'classifai' ), - Embeddings::ID => __( 'OpenAI Embeddings', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + NLU::ID => __( 'IBM Watson NLU', 'classifai' ), + Embeddings::ID => __( 'OpenAI Embeddings', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 6d88b5e34..52f4af373 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -22,26 +22,13 @@ class ContentResizing extends Feature { public function __construct() { $this->label = __( 'Content Resizing', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 619ee062a..927106ed9 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -22,26 +22,13 @@ class DescriptiveTextGenerator extends Feature { public function __construct() { $this->label = __( 'Descriptive Text Generator', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = ImageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 022920b0f..6e0da4f77 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -22,26 +22,13 @@ class ExcerptGeneration extends Feature { public function __construct() { $this->label = __( 'Excerpt Generation', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 0423bd338..410b1ed1c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -31,10 +31,19 @@ abstract class Feature { /** * Array of provider classes. * + * This contains all the providers that are registered to the service. + * * @var \Classifai\Providers\Provider[] */ public $provider_instances = []; + /** + * Array of providers supported by the feature. + * + * @var \Classifai\Providers\Provider[] + */ + public $supported_providers = []; + /** * Set up necessary hooks. */ @@ -118,7 +127,22 @@ protected function get_default_settings(): array { * @internal * @return array */ - abstract protected function get_providers(): array; + protected function get_providers(): array { + /** + * Filter the feature providers. + * + * @since 3.0.0 + * @hook classifai_{feature}_providers + * + * @param {array} $providers Feature providers. + * + * @return {array} Filtered providers. + */ + return apply_filters( + 'classifai_' . static::ID . '_providers', + $this->supported_providers + ); + } /** * Sanitizes the settings before saving. diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 5fbfe1513..33e2caf0e 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -22,26 +22,13 @@ class ImageCropping extends Feature { public function __construct() { $this->label = __( 'Image Cropping', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = ImageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index f57693518..809efd1e0 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -22,26 +22,13 @@ class ImageGeneration extends Feature { public function __construct() { $this->label = __( 'Image Generation', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - DallE::ID => __( 'OpenAI Dall-E', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + DallE::ID => __( 'OpenAI Dall-E', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 2eafdfd15..0dfecde4e 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -22,26 +22,13 @@ class ImageTagsGenerator extends Feature { public function __construct() { $this->label = __( 'Image Tags Generator', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = ImageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 197fba8bc..dc17a1751 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -22,26 +22,13 @@ class ImageTextExtraction extends Feature { public function __construct() { $this->label = __( 'Image Text Extraction', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = ImageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index 4c30e9a2e..055aa42e9 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -22,26 +22,13 @@ class PDFTextExtraction extends Feature { public function __construct() { $this->label = __( 'PDF Text Extraction', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = ImageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index 6052da9b7..49b45dabf 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -22,26 +22,13 @@ class RecommendedContent extends Feature { public function __construct() { $this->label = __( 'Recommended Content', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = PersonalizerService::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( PersonalizerService::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - PersonalizerProvider::ID => __( 'Microsoft AI Personalizer', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + PersonalizerProvider::ID => __( 'Microsoft AI Personalizer', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 42e8baed2..f10c17e79 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -22,12 +22,13 @@ class TextToSpeech extends Feature { public function __construct() { $this->label = __( 'Text to Speech', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); + + // Contains just the providers this feature supports. + $this->supported_providers = [ + Speech::ID => __( 'Microsoft Azure AI Speech', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index f93d73661..c849cee2d 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -22,26 +22,13 @@ class TitleGeneration extends Feature { public function __construct() { $this->label = __( 'Title Generation', 'classifai' ); - /** - * Every feature must set the `provider_instances` variable with the list of provider instances - * that are registered to a service. - */ - $service_providers = LanguageProcessing::get_service_providers(); - $this->provider_instances = $this->get_provider_instances( $service_providers ); - } + // Contains all providers that are registered to the service. + $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); - /** - * Returns the providers supported by the feature. - * - * @return array - */ - public function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), - ] - ); + // Contains just the providers this feature supports. + $this->supported_providers = [ + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), + ]; } /** diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 24b9f9a47..3f2536069 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -164,8 +164,8 @@ public function render_settings_page() { ?>

    From 6c5471551c07e3c55b77572c84b4d2e9dd24932b Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 15:15:24 -0700 Subject: [PATCH 098/127] Reduce duplicate code with how a feature handles settings --- .../Features/AudioTranscriptsGeneration.php | 97 +-- .../Classifai/Features/Classification.php | 106 +-- .../Classifai/Features/ContentResizing.php | 99 +-- .../Features/DescriptiveTextGenerator.php | 99 +-- .../Classifai/Features/ExcerptGeneration.php | 105 +-- includes/Classifai/Features/Feature.php | 768 ++++++++++-------- includes/Classifai/Features/ImageCropping.php | 99 +-- .../Classifai/Features/ImageGeneration.php | 97 +-- .../Classifai/Features/ImageTagsGenerator.php | 99 +-- .../Features/ImageTextExtraction.php | 99 +-- .../Classifai/Features/PDFTextExtraction.php | 99 +-- .../Classifai/Features/RecommendedContent.php | 97 +-- includes/Classifai/Features/TextToSpeech.php | 127 +-- .../Classifai/Features/TitleGeneration.php | 99 +-- 14 files changed, 588 insertions(+), 1502 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index cd704e426..ae181e626 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -32,106 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable audio transcription', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Enabling this will automatically generate transcripts for supported audio files.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'Enabling this will automatically generate transcripts for supported audio files.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - public function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => Whisper::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 4183637f2..0cdc58fc2 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -36,39 +36,19 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable classification', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Enables the automatic content classification of posts.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); + public function get_enable_description(): string { + return esc_html__( 'Enables the automatic content classification of posts.', 'classifai' ); + } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); $post_statuses = get_post_statuses_for_language_settings(); add_settings_field( @@ -106,24 +86,6 @@ public function setup_fields_sections() { ] ); - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] ); } @@ -146,6 +108,7 @@ public function render_previewer( string $active_feature = '' ) { ?>
    'concepts', ), ); - ?> + ?>

    - + @@ -206,54 +169,29 @@ public function render_previewer( string $active_feature = '' ) { /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'post_statuses' => [], 'post_types' => [], 'provider' => NLU::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); } /** - * Sanitizes the settings before saving. + * Sanitizes the default feature settings. * - * @param array $new_settings The settings to be sanitized on save. + * @param array $new_settings Settings being saved. * @return array */ - public function sanitize_settings( array $new_settings ): array { + public function sanitize_default_feature_settings( array $new_settings ): array { $settings = $this->get_settings(); - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['roles']; - $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['roles']; + $new_settings['post_statuses'] = isset( $new_settings['post_statuses'] ) ? array_map( 'sanitize_text_field', $new_settings['post_statuses'] ) : $settings['post_statuses']; + $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['post_types']; - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); + return $new_settings; } /** diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 52f4af373..548648065 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -32,106 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable content resizing generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( '"Condense this text" and "Expand this text" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( '"Condense this text" and "Expand this text" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + public function get_feature_default_settings(): array { + return [ + 'provider' => ChatGPT::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 927106ed9..4a4888080 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -32,108 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable descriptive text generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Enable this to generate descriptive text for images.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'Enable this to generate descriptive text for images.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @internal - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => ComputerVision::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 6e0da4f77..1b3c5801e 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -32,39 +32,19 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable excerpt generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); + public function get_enable_description(): string { + return esc_html__( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ); + } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); $post_types = \Classifai\get_post_types_for_language_settings(); $post_type_options = array(); @@ -103,70 +83,33 @@ public function setup_fields_sections() { 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this. In testing, ChatGPT tended to exceed this number by 10-15 words.', 'classifai' ), ] ); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'post_types' => [], 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + 'provider' => ChatGPT::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); } /** - * Sanitizes the settings before saving. + * Sanitizes the default feature settings. * - * @param array $new_settings The settings to be sanitized on save. + * @param array $new_settings Settings being saved. * @return array */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); + $post_types = \Classifai\get_post_types_for_language_settings(); - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); - $post_types = \Classifai\get_post_types_for_language_settings(); - foreach ( $post_types as $post_type ) { if ( ! post_type_supports( $post_type->name, 'excerpt' ) ) { continue; @@ -179,15 +122,7 @@ public function sanitize_settings( array $new_settings ): array { } } - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); + return $new_settings; } /** diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 410b1ed1c..8ed30979c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -102,16 +102,67 @@ public function get_label(): string { * * @internal */ - abstract protected function setup_fields_sections(); + public function setup_fields_sections() { + $settings = $this->get_settings(); + + add_settings_section( + $this->get_option_name() . '_section', + esc_html__( 'Feature settings', 'classifai' ), + '__return_empty_string', + $this->get_option_name() + ); + + // Add the enable field. + add_settings_field( + 'status', + esc_html__( 'Enable feature', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'status', + 'input_type' => 'checkbox', + 'default_value' => $settings['status'], + 'description' => $this->get_enable_description(), + ] + ); + + // Add all the needed provider fields. + $this->add_provider_fields(); + + // Add any needed custom fields. + $this->add_custom_settings_fields(); + + // Add user/role-based access fields. + $this->add_access_control_fields(); + } + + /** + * Get the description for the enable field. + * + * @return string + */ + public function get_enable_description(): string { + return ''; + } + + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + } /** * Returns the default settings for the feature. * + * The root-level keys are the setting keys that are independent of the provider. + * Provider specific settings should be nested under the provider key. + * * @internal * @return array */ protected function get_default_settings(): array { - return [ + $shared_defaults = [ 'status' => '0', 'role_based_access' => '1', 'roles' => array_combine( array_keys( $this->roles ), array_keys( $this->roles ) ), @@ -119,28 +170,26 @@ protected function get_default_settings(): array { 'users' => [], 'user_based_opt_out' => 'no', ]; - } + $provider_settings = $this->get_provider_default_settings(); + $feature_settings = $this->get_feature_default_settings(); - /** - * Returns the providers supported by the feature. - * - * @internal - * @return array - */ - protected function get_providers(): array { /** - * Filter the feature providers. + * Filter the default settings for a feature. * * @since 3.0.0 - * @hook classifai_{feature}_providers + * @hook classifai_{feature}_get_default_settings * - * @param {array} $providers Feature providers. + * @param {array} $defaults Default feature settings. * - * @return {array} Filtered providers. + * @return {array} Filtered default feature settings. */ return apply_filters( - 'classifai_' . static::ID . '_providers', - $this->supported_providers + 'classifai_' . static::ID . '_get_default_settings', + array_merge( + $shared_defaults, + $feature_settings, + $provider_settings + ) ); } @@ -152,8 +201,10 @@ protected function get_providers(): array { * @return array */ public function sanitize_settings( array $settings ): array { - $new_settings = $settings; - $current_settings = $this->get_settings(); + $new_settings = $settings; + $current_settings = $this->get_settings(); + + // Sanitize the shared settings. $new_settings['status'] = $settings['status'] ?? $current_settings['status']; $new_settings['provider'] = isset( $settings['provider'] ) ? sanitize_text_field( $settings['provider'] ) : $current_settings['provider']; @@ -193,133 +244,40 @@ public function sanitize_settings( array $settings ): array { } else { $new_settings['user_based_opt_out'] = '1'; } - return $new_settings; - } - - /** - * Determine if the current user has access to the feature - * - * @return bool - */ - public function has_access(): bool { - $access = false; - $user_id = get_current_user_id(); - $user = get_user_by( 'id', $user_id ); - $user_roles = $user->roles ?? []; - $settings = $this->get_settings(); - $feature_roles = $settings['roles'] ?? []; - $feature_users = array_map( 'absint', $settings['users'] ?? [] ); - - $role_based_access_enabled = isset( $settings['role_based_access'] ) && 1 === (int) $settings['role_based_access']; - $user_based_access_enabled = isset( $settings['user_based_access'] ) && 1 === (int) $settings['user_based_access']; - $user_based_opt_out_enabled = isset( $settings['user_based_opt_out'] ) && 1 === (int) $settings['user_based_opt_out']; - - /* - * Checks if Role-based access is enabled and user role has access to the feature. - */ - if ( $role_based_access_enabled ) { - $access = ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ); - } - - /* - * Checks if User-based access is enabled and user has access to the feature. - */ - if ( ! $access && $user_based_access_enabled ) { - $access = ( ! empty( $feature_users ) && ! empty( in_array( $user_id, $feature_users, true ) ) ); - } - - /* - * Checks if User-based opt-out is enabled and user has opted out from the feature. - */ - if ( $access && $user_based_opt_out_enabled ) { - $opted_out_features = (array) get_user_meta( $user_id, 'classifai_opted_out_features', true ); - $access = ( ! in_array( static::ID, $opted_out_features, true ) ); - } - - /** - * Filter to override user access to a ClassifAI feature. - * - * @since 3.0.0 - * @hook classifai_{$feature}_has_access - * - * @param {bool} $access Current access value. - * @param {array} $settings Feature settings. - * - * @return {bool} Should the user have access? - */ - return apply_filters( 'classifai_' . static::ID . '_has_access', $access, $settings ); - } - /** - * Determine if a feature is enabled. - * - * Returns true if the feature meets all the criteria to - * be enabled. False otherwise. - * - * Criteria: - * - Provider is configured. - * - User has access to the feature. - * - Feature is turned on. - * - * @return bool - */ - public function is_feature_enabled(): bool { - $is_feature_enabled = false; - $settings = $this->get_settings(); + // Sanitize the feature specific settings. + $new_settings = $this->sanitize_default_feature_settings( $new_settings ); - // Check if provider is configured, user has access to the feature and the feature is turned on. - if ( - $this->is_configured() && - $this->has_access() && - $this->is_enabled() - ) { - $is_feature_enabled = true; - } + // Sanitize the provider specific settings. + $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); + $new_settings = $provider_instance->sanitize_settings( $new_settings ); /** - * Filter to override permission to a specific classifai feature. + * Filter to change settings before they're saved. * * @since 3.0.0 - * @hook classifai_{$feature}_is_feature_enabled + * @hook classifai_{$feature}_sanitize_settings * - * @param {bool} $is_feature_enabled Is the feature enabled? - * @param {array} $settings Current feature settings. + * @param {array} $new_settings Settings being saved. + * @param {array} $current_settings Existing settings. * - * @return {bool} Returns true if the user has access and the feature is enabled, false otherwise. + * @return {array} Filtered settings. */ - return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $is_feature_enabled, $settings ); + return apply_filters( + 'classifai_' . static::ID . '_sanitize_settings', + $new_settings, + $current_settings + ); } /** - * Determine if the feature is turned on. - * - * Note: This function does not check if the user has access to the feature. - * - * - Use `is_feature_enabled()` to check if the user has access to the feature and feature is turned on. - * - Use `has_access()` to check if the user has access to the feature. + * Sanitize the default feature settings. * - * @return bool + * @param array $settings Settings to sanitize. + * @return array */ - public function is_enabled(): bool { - $settings = $this->get_settings(); - - // Check if feature is turned on. - $feature_status = ( isset( $settings['status'] ) && 1 === (int) $settings['status'] ); - $is_configured = $this->is_configured(); - $is_enabled = $feature_status && $is_configured; - - /** - * Filter to override a specific classifai feature enabled. - * - * @since 3.0.0 - * @hook classifai_{$feature}_is_enabled - * - * @param {bool} $is_enabled Is the feature enabled? - * @param {array} $settings Current feature settings. - * - * @return {bool} Returns true if the feature is enabled, false otherwise. - */ - return apply_filters( 'classifai_' . static::ID . '_is_enabled', $is_enabled, $settings ); + public function sanitize_default_feature_settings( array $settings ): array { + return $settings; } /** @@ -382,9 +340,34 @@ public function get_provider_default_settings(): array { } /** - * Renders the fields of the provider selected for the feature. + * Returns the default settings for the feature. + * + * @return array + */ + abstract public function get_feature_default_settings(): array; + + /** + * Add the provider fields. + * + * Will add a field to choose the provider and any + * fields the selected provider has registered. */ - public function render_provider_fields() { + public function add_provider_fields() { + $settings = $this->get_settings(); + + add_settings_field( + 'provider', + esc_html__( 'Select a provider', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'provider', + 'options' => $this->get_providers(), + 'default_value' => $settings['provider'], + ] + ); + foreach ( array_keys( $this->get_providers() ) as $provider_id ) { $provider = $this->get_feature_provider_instance( $provider_id ); @@ -416,205 +399,61 @@ protected function merge_settings( array $settings = [], array $defaults = [] ): } /** - * Returns array of instances of provider classes registered for the service. + * Returns the providers supported by the feature. * * @internal - * - * @param array $services Array of provider classes. * @return array */ - protected function get_provider_instances( array $services ): array { - $provider_instances = []; - - foreach ( $services as $provider_class ) { - $provider_instances[] = new $provider_class( $this ); - } - - return $provider_instances; + protected function get_providers(): array { + /** + * Filter the feature providers. + * + * @since 3.0.0 + * @hook classifai_{feature}_providers + * + * @param {array} $providers Feature providers. + * + * @return {array} Filtered providers. + */ + return apply_filters( + 'classifai_' . static::ID . '_providers', + $this->supported_providers + ); } /** - * Returns the instance of the provider set for the feature. - * - * @param string $provider_id The ID of the provider. - * @return \Classifai\Providers + * Resets settings for the provider. */ - public function get_feature_provider_instance( string $provider_id = '' ) { - $provider_id = $provider_id ? $provider_id : $this->get_settings( 'provider' ); - $provider_instance = find_provider_class( $this->provider_instances ?? [], $provider_id ); - - if ( is_wp_error( $provider_instance ) ) { - return null; - } - - $provider_class = get_class( $provider_instance ); - $provider_instance = new $provider_class( $this ); - - return $provider_instance; + public function reset_settings() { + update_option( $this->get_option_name(), $this->get_default_settings() ); } /** - * Returns whether the provider is configured or not. - * - * @return bool + * Add settings fields for Role/User based access. */ - public function is_configured(): bool { - $settings = $this->get_settings(); - $provider_id = $settings['provider']; - $is_configured = false; + protected function add_access_control_fields() { + $settings = $this->get_settings(); - if ( ! empty( $settings ) && ! empty( $settings[ $provider_id ]['authenticated'] ) ) { - $is_configured = true; - } + add_settings_field( + 'role_based_access', + esc_html__( 'Enable role-based access', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'role_based_access', + 'input_type' => 'checkbox', + 'default_value' => $settings['role_based_access'], + 'description' => __( 'Enables ability to select which roles can access this feature.', 'classifai' ), + 'class' => 'classifai-role-based-access', + ] + ); - return $is_configured; - } - - /** - * Can the feature be initialized? - * - * @return bool - */ - public function can_register(): bool { - return $this->is_configured(); - } - - /** - * Get the debug value text. - * - * @param mixed $setting_value The value of the setting. - * @param integer $type The type of debug value to return. - * @return string - */ - public static function get_debug_value_text( $setting_value, $type = 0 ): string { - $debug_value = ''; - - if ( empty( $setting_value ) ) { - $boolean = false; - } elseif ( 'no' === $setting_value ) { - $boolean = false; - } else { - $boolean = true; - } - - switch ( $type ) { - case 0: - $debug_value = $boolean ? __( 'Yes', 'classifai' ) : __( 'No', 'classifai' ); - break; - case 1: - $debug_value = $boolean ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); - break; - } - - return $debug_value; - } - - /** - * Returns an array of feature-level debug info. - * - * @return array - */ - public function get_debug_information(): array { - $feature_settings = $this->get_settings(); - $provider = $this->get_feature_provider_instance(); - - $roles = array_filter( - $feature_settings['roles'], - function ( $role ) { - return '0' !== $role; - } - ); - - $common_debug_info = [ - __( 'Authenticated', 'classifai' ) => self::get_debug_value_text( $this->is_configured() ), - __( 'Status', 'classifai' ) => self::get_debug_value_text( $feature_settings['status'], 1 ), - __( 'Role-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['role_based_access'], 1 ), - __( 'Allowed roles (titles)', 'classifai' ) => implode( ', ', $roles ?? [] ), - __( 'User-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_access'], 1 ), - __( 'Allowed users (titles)', 'classifai' ) => implode( ', ', $feature_settings['users'] ?? [] ), - __( 'User based opt-out', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_opt_out'], 1 ), - __( 'Provider', 'classifai' ) => $feature_settings['provider'], - ]; - - if ( method_exists( $provider, 'get_debug_information' ) ) { - $all_debug_info = array_merge( - $common_debug_info, - $provider->get_debug_information() - ); - } - - /** - * Filter to add feature-level debug information. - * - * @since 3.0.0 - * @hook classifai_{feature}_debug_information - * - * @param {array} $all_debug_info Debug information - * @param {object} $this Current feature class. - * - * @return {array} Returns debug information. - */ - return apply_filters( - 'classifai_' . self::ID . '_debug_information', - $all_debug_info, - $this, - ); - } - - /** - * Returns the data attribute string for an input. - * - * @param array $args The args passed to add_settings_field. - * @return string - */ - protected function get_data_attribute( array $args ): string { - $data_attr = $args['data_attr'] ?? []; - $data_attr_str = ''; - - foreach ( $data_attr as $attr_key => $attr_value ) { - if ( is_scalar( $attr_value ) ) { - $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( $attr_value ) . '"'; - } else { - $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( wp_json_encode( $attr_value ) ) . '"'; - } - } - - return $data_attr_str; - } - - /** - * Resets settings for the provider. - */ - public function reset_settings() { - update_option( $this->get_option_name(), $this->get_default_settings() ); - } - - /** - * Add settings fields for Role/User based access. - */ - protected function add_access_control_fields() { - $settings = $this->get_settings(); - - add_settings_field( - 'role_based_access', - esc_html__( 'Enable role-based access', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'role_based_access', - 'input_type' => 'checkbox', - 'default_value' => $settings['role_based_access'], - 'description' => __( 'Enables ability to select which roles can access this feature.', 'classifai' ), - 'class' => 'classifai-role-based-access', - ] - ); - - // Add hidden class if role-based access is disabled. - $class = 'allowed_roles_row'; - if ( ! isset( $settings['role_based_access'] ) || '1' !== $settings['role_based_access'] ) { - $class .= ' hidden'; - } + // Add hidden class if role-based access is disabled. + $class = 'allowed_roles_row'; + if ( ! isset( $settings['role_based_access'] ) || '1' !== $settings['role_based_access'] ) { + $class .= ' hidden'; + } add_settings_field( 'roles', @@ -1059,4 +898,297 @@ class="classifai-search-users" echo '' . wp_kses_post( $args['description'] ) . ''; } } + + /** + * Determine if the current user has access to the feature + * + * @return bool + */ + public function has_access(): bool { + $access = false; + $user_id = get_current_user_id(); + $user = get_user_by( 'id', $user_id ); + $user_roles = $user->roles ?? []; + $settings = $this->get_settings(); + $feature_roles = $settings['roles'] ?? []; + $feature_users = array_map( 'absint', $settings['users'] ?? [] ); + + $role_based_access_enabled = isset( $settings['role_based_access'] ) && 1 === (int) $settings['role_based_access']; + $user_based_access_enabled = isset( $settings['user_based_access'] ) && 1 === (int) $settings['user_based_access']; + $user_based_opt_out_enabled = isset( $settings['user_based_opt_out'] ) && 1 === (int) $settings['user_based_opt_out']; + + /* + * Checks if Role-based access is enabled and user role has access to the feature. + */ + if ( $role_based_access_enabled ) { + $access = ( ! empty( $feature_roles ) && ! empty( array_intersect( $user_roles, $feature_roles ) ) ); + } + + /* + * Checks if User-based access is enabled and user has access to the feature. + */ + if ( ! $access && $user_based_access_enabled ) { + $access = ( ! empty( $feature_users ) && ! empty( in_array( $user_id, $feature_users, true ) ) ); + } + + /* + * Checks if User-based opt-out is enabled and user has opted out from the feature. + */ + if ( $access && $user_based_opt_out_enabled ) { + $opted_out_features = (array) get_user_meta( $user_id, 'classifai_opted_out_features', true ); + $access = ( ! in_array( static::ID, $opted_out_features, true ) ); + } + + /** + * Filter to override user access to a ClassifAI feature. + * + * @since 3.0.0 + * @hook classifai_{$feature}_has_access + * + * @param {bool} $access Current access value. + * @param {array} $settings Feature settings. + * + * @return {bool} Should the user have access? + */ + return apply_filters( 'classifai_' . static::ID . '_has_access', $access, $settings ); + } + + /** + * Determine if a feature is enabled. + * + * Returns true if the feature meets all the criteria to + * be enabled. False otherwise. + * + * Criteria: + * - Provider is configured. + * - User has access to the feature. + * - Feature is turned on. + * + * @return bool + */ + public function is_feature_enabled(): bool { + $is_feature_enabled = false; + $settings = $this->get_settings(); + + // Check if provider is configured, user has access to the feature and the feature is turned on. + if ( + $this->is_configured() && + $this->has_access() && + $this->is_enabled() + ) { + $is_feature_enabled = true; + } + + /** + * Filter to override permission to a specific classifai feature. + * + * @since 3.0.0 + * @hook classifai_{$feature}_is_feature_enabled + * + * @param {bool} $is_feature_enabled Is the feature enabled? + * @param {array} $settings Current feature settings. + * + * @return {bool} Returns true if the user has access and the feature is enabled, false otherwise. + */ + return apply_filters( 'classifai_' . static::ID . '_is_feature_enabled', $is_feature_enabled, $settings ); + } + + /** + * Determine if the feature is turned on. + * + * Note: This function does not check if the user has access to the feature. + * + * - Use `is_feature_enabled()` to check if the user has access to the feature and feature is turned on. + * - Use `has_access()` to check if the user has access to the feature. + * + * @return bool + */ + public function is_enabled(): bool { + $settings = $this->get_settings(); + + // Check if feature is turned on. + $feature_status = ( isset( $settings['status'] ) && 1 === (int) $settings['status'] ); + $is_configured = $this->is_configured(); + $is_enabled = $feature_status && $is_configured; + + /** + * Filter to override a specific classifai feature enabled. + * + * @since 3.0.0 + * @hook classifai_{$feature}_is_enabled + * + * @param {bool} $is_enabled Is the feature enabled? + * @param {array} $settings Current feature settings. + * + * @return {bool} Returns true if the feature is enabled, false otherwise. + */ + return apply_filters( 'classifai_' . static::ID . '_is_enabled', $is_enabled, $settings ); + } + + /** + * Returns array of instances of provider classes registered for the service. + * + * @internal + * + * @param array $services Array of provider classes. + * @return array + */ + protected function get_provider_instances( array $services ): array { + $provider_instances = []; + + foreach ( $services as $provider_class ) { + $provider_instances[] = new $provider_class( $this ); + } + + return $provider_instances; + } + + /** + * Returns the instance of the provider set for the feature. + * + * @param string $provider_id The ID of the provider. + * @return \Classifai\Providers + */ + public function get_feature_provider_instance( string $provider_id = '' ) { + $provider_id = $provider_id ? $provider_id : $this->get_settings( 'provider' ); + $provider_instance = find_provider_class( $this->provider_instances ?? [], $provider_id ); + + if ( is_wp_error( $provider_instance ) ) { + return null; + } + + $provider_class = get_class( $provider_instance ); + $provider_instance = new $provider_class( $this ); + + return $provider_instance; + } + + /** + * Returns whether the provider is configured or not. + * + * @return bool + */ + public function is_configured(): bool { + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $is_configured = false; + + if ( ! empty( $settings ) && ! empty( $settings[ $provider_id ]['authenticated'] ) ) { + $is_configured = true; + } + + return $is_configured; + } + + /** + * Can the feature be initialized? + * + * @return bool + */ + public function can_register(): bool { + return $this->is_configured(); + } + + /** + * Get the debug value text. + * + * @param mixed $setting_value The value of the setting. + * @param integer $type The type of debug value to return. + * @return string + */ + public static function get_debug_value_text( $setting_value, $type = 0 ): string { + $debug_value = ''; + + if ( empty( $setting_value ) ) { + $boolean = false; + } elseif ( 'no' === $setting_value ) { + $boolean = false; + } else { + $boolean = true; + } + + switch ( $type ) { + case 0: + $debug_value = $boolean ? __( 'Yes', 'classifai' ) : __( 'No', 'classifai' ); + break; + case 1: + $debug_value = $boolean ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); + break; + } + + return $debug_value; + } + + /** + * Returns an array of feature-level debug info. + * + * @return array + */ + public function get_debug_information(): array { + $feature_settings = $this->get_settings(); + $provider = $this->get_feature_provider_instance(); + + $roles = array_filter( + $feature_settings['roles'], + function ( $role ) { + return '0' !== $role; + } + ); + + $common_debug_info = [ + __( 'Authenticated', 'classifai' ) => self::get_debug_value_text( $this->is_configured() ), + __( 'Status', 'classifai' ) => self::get_debug_value_text( $feature_settings['status'], 1 ), + __( 'Role-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['role_based_access'], 1 ), + __( 'Allowed roles (titles)', 'classifai' ) => implode( ', ', $roles ?? [] ), + __( 'User-based access', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_access'], 1 ), + __( 'Allowed users (titles)', 'classifai' ) => implode( ', ', $feature_settings['users'] ?? [] ), + __( 'User based opt-out', 'classifai' ) => self::get_debug_value_text( $feature_settings['user_based_opt_out'], 1 ), + __( 'Provider', 'classifai' ) => $feature_settings['provider'], + ]; + + if ( method_exists( $provider, 'get_debug_information' ) ) { + $all_debug_info = array_merge( + $common_debug_info, + $provider->get_debug_information() + ); + } + + /** + * Filter to add feature-level debug information. + * + * @since 3.0.0 + * @hook classifai_{feature}_debug_information + * + * @param {array} $all_debug_info Debug information + * @param {object} $this Current feature class. + * + * @return {array} Returns debug information. + */ + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $all_debug_info, + $this, + ); + } + + /** + * Returns the data attribute string for an input. + * + * @param array $args The args passed to add_settings_field. + * @return string + */ + protected function get_data_attribute( array $args ): string { + $data_attr = $args['data_attr'] ?? []; + $data_attr_str = ''; + + foreach ( $data_attr as $attr_key => $attr_value ) { + if ( is_scalar( $attr_value ) ) { + $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( $attr_value ) . '"'; + } else { + $data_attr_str .= 'data-' . $attr_key . '="' . esc_attr( wp_json_encode( $attr_value ) ) . '"'; + } + } + + return $data_attr_str; + } } diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 33e2caf0e..66db07de6 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -32,108 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable smart cropping', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'AI Vision detects and saves the most visually interesting part of your image (i.e., faces, animals, notable text).', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'AI Vision detects and saves the most visually interesting part of your image (i.e., faces, animals, notable text).', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @internal - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => ComputerVision::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index 809efd1e0..bda68288d 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -62,57 +62,12 @@ function ( $role ) { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable image generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'When enabled, a new Generate images tab will be shown in the media upload flow, allowing you to generate and import images.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'When enabled, a new Generate images tab will be shown in the media upload flow, allowing you to generate and import images.', 'classifai' ); } /** @@ -131,50 +86,12 @@ public function is_feature_enabled(): bool { /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => DallE::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 0dfecde4e..696be8e7f 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -32,108 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable image tag generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Image tags will be added automatically.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'Image tags will be added automatically.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @internal - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => ComputerVision::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index dc17a1751..38646ab01 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -32,108 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable text extraction from images', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'OCR detects text in images (e.g., handwritten notes) and saves that as post content.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'OCR detects text in images (e.g., handwritten notes) and saves that as post content.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @internal - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => ComputerVision::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index 055aa42e9..e4c4568ef 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -32,108 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable text extraction from images', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Extract visible text from multi-pages PDF documents. Store the result as the attachment description.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'Extract visible text from multi-pages PDF documents. Store the result as the attachment description.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @internal - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => ComputerVision::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index 49b45dabf..7b4f405ac 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -32,106 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable Recommended content block.', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'Enables the ability to generate recommended content data for the block.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'Enables the ability to generate recommended content data for the block.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ + public function get_feature_default_settings(): array { + return [ 'provider' => PersonalizerProvider::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index f10c17e79..68d2a4604 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -32,53 +32,19 @@ public function __construct() { } /** - * Returns the providers supported by the feature. + * Get the description for the enable field. * - * @return array + * @return string */ - protected function get_providers(): array { - return apply_filters( - 'classifai_' . static::ID . '_providers', - [ - Speech::ID => __( 'Microsoft Azure AI Speech', 'classifai' ), - ] - ); + public function get_enable_description(): string { + return esc_html__( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ); } /** - * Sets up the fields and sections for the feature. + * Add any needed custom fields. */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable text to speech', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - + public function add_custom_settings_fields() { + $settings = $this->get_settings(); $post_types = \Classifai\get_post_types_for_language_settings(); $post_type_options = array(); @@ -99,25 +65,6 @@ public function setup_fields_sections() { 'description' => __( 'Choose which post types support this feature.', 'classifai' ), ] ); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); } /** @@ -136,33 +83,6 @@ protected function get_post_types_select_options(): array { return $options; } - /** - * Returns the default settings for the feature. - * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * - * @return array - */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ - 'post_types' => [], - 'provider' => Speech::ID, - ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - /** * The list of post types that TTS supports. * @@ -182,17 +102,26 @@ public function get_tts_supported_post_types(): array { } /** - * Sanitizes the settings before saving. + * Returns the default settings for the feature. * - * @param array $new_settings The settings to be sanitized on save. * @return array */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); + public function get_feature_default_settings(): array { + return [ + 'post_types' => [], + 'provider' => Speech::ID, + ]; + } - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - $post_types = \Classifai\get_post_types_for_language_settings(); + /** + * Sanitizes the default feature settings. + * + * @param array $new_settings Settings being saved. + * @return array + */ + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); + $post_types = \Classifai\get_post_types_for_language_settings(); foreach ( $post_types as $post_type ) { if ( ! isset( $new_settings['post_types'][ $post_type->name ] ) ) { @@ -202,15 +131,7 @@ public function sanitize_settings( array $new_settings ): array { } } - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); + return $new_settings; } /** diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index c849cee2d..1c5bad04b 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -32,106 +32,23 @@ public function __construct() { } /** - * Sets up the fields and sections for the feature. + * Get the description for the enable field. + * + * @return string */ - public function setup_fields_sections() { - $settings = $this->get_settings(); - - /* - * These are the feature-level fields that are - * independent of the provider. - */ - add_settings_section( - $this->get_option_name() . '_section', - esc_html__( 'Feature settings', 'classifai' ), - '__return_empty_string', - $this->get_option_name() - ); - - add_settings_field( - 'status', - esc_html__( 'Enable title generation', 'classifai' ), - [ $this, 'render_input' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'status', - 'input_type' => 'checkbox', - 'default_value' => $settings['status'], - 'description' => __( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ), - ] - ); - - // Add user/role-based access fields. - $this->add_access_control_fields(); - - add_settings_field( - 'provider', - esc_html__( 'Select a provider', 'classifai' ), - [ $this, 'render_select' ], - $this->get_option_name(), - $this->get_option_name() . '_section', - [ - 'label_for' => 'provider', - 'options' => $this->get_providers(), - 'default_value' => $settings['provider'], - ] - ); - - /* - * The following renders the fields of all the providers - * that are registered to the feature. - */ - $this->render_provider_fields(); + public function get_enable_description(): string { + return esc_html__( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ); } /** * Returns the default settings for the feature. * - * The root-level keys are the setting keys that are independent of the provider. - * Provider specific settings should be nested under the provider key. - * - * @todo Add a filter hook to allow other plugins to add their own settings. - * * @return array */ - protected function get_default_settings(): array { - $provider_settings = $this->get_provider_default_settings(); - $feature_settings = [ - 'provider' => \Classifai\Providers\OpenAI\ChatGPT::ID, + public function get_feature_default_settings(): array { + return [ + 'provider' => ChatGPT::ID, ]; - - return apply_filters( - 'classifai_' . static::ID . '_get_default_settings', - array_merge( - parent::get_default_settings(), - $feature_settings, - $provider_settings - ) - ); - } - - /** - * Sanitizes the settings before saving. - * - * @param array $new_settings The settings to be sanitized on save. - * @return array - */ - public function sanitize_settings( array $new_settings ): array { - $settings = $this->get_settings(); - - // Sanitization of the feature-level settings. - $new_settings = parent::sanitize_settings( $new_settings ); - - // Sanitization of the provider-level settings. - $provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] ); - $new_settings = $provider_instance->sanitize_settings( $new_settings ); - - return apply_filters( - 'classifai_' . static::ID . '_sanitize_settings', - $new_settings, - $settings - ); } /** From 67db67ae39b4378584e4f2680d99c4dbfd7d2da3 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 15:40:33 -0700 Subject: [PATCH 099/127] Clean up Provider and AccessControl --- .../Classifai/Providers/AccessControl.php | 24 ++++++---- includes/Classifai/Providers/Provider.php | 46 +++++++++---------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/includes/Classifai/Providers/AccessControl.php b/includes/Classifai/Providers/AccessControl.php index 9fd93b0d8..636646c77 100644 --- a/includes/Classifai/Providers/AccessControl.php +++ b/includes/Classifai/Providers/AccessControl.php @@ -88,19 +88,20 @@ public function __construct( Provider $provider, string $feature ) { * * @return array */ - public function get_settings() { + public function get_settings(): array { if ( is_null( $this->settings ) ) { $this->settings = $this->provider->get_settings(); } + return $this->settings; } /** * Determines whether user-based access control is enabled for the current feature. * - * @return boolean + * @return bool */ - public function is_user_based_access_enabled() { + public function is_user_based_access_enabled(): bool { $settings = $this->get_settings(); return isset( $settings[ $this->user_based_access_key ] ) && 1 === (int) $settings[ $this->user_based_access_key ]; } @@ -108,9 +109,9 @@ public function is_user_based_access_enabled() { /** * Determines whether user-based opt-out is enabled for the current feature. * - * @return boolean + * @return bool */ - public function is_user_based_opt_out_enabled() { + public function is_user_based_opt_out_enabled(): bool { $settings = $this->get_settings(); return isset( $settings[ $this->user_based_opt_out_key ] ) && 1 === (int) $settings[ $this->user_based_opt_out_key ]; } @@ -118,9 +119,9 @@ public function is_user_based_opt_out_enabled() { /** * Determines whether role-based access control is enabled for the current feature. * - * @return boolean + * @return bool */ - public function is_role_based_access_enabled() { + public function is_role_based_access_enabled(): bool { $settings = $this->get_settings(); return isset( $settings[ $this->role_based_access_key ] ) && 1 === (int) $settings[ $this->role_based_access_key ]; } @@ -130,9 +131,10 @@ public function is_role_based_access_enabled() { * * @return array */ - public function get_allowed_roles() { + public function get_allowed_roles(): array { $settings = $this->get_settings(); $roles_key = $this->roles_key; + // Backward compatibility for old roles keys. switch ( $this->feature ) { case 'title_generation': @@ -161,9 +163,10 @@ public function get_allowed_roles() { * * @return array */ - public function get_allowed_users() { + public function get_allowed_users(): array { $settings = $this->get_settings(); $users = $settings[ $this->users_key ] ?? []; + return array_map( 'absint', $users ); } @@ -173,10 +176,11 @@ public function get_allowed_users() { * @param int $user_id User ID. * @return bool */ - public function has_access( $user_id = null ) { + public function has_access( $user_id = null ): bool { if ( ! $user_id ) { $user_id = get_current_user_id(); } + $user_id = (int) $user_id; $user = get_user_by( 'id', $user_id ); $user_roles = $user->roles ?? []; diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index 0d319c9e8..edf6ddd7a 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -15,13 +15,12 @@ abstract class Provider { const ID = ''; /** - * @var string The display name for the provider. ie. Azure + * @var string The display name for the provider, i.e. Azure */ public $provider_name; - /** - * @var string $provider_service_name The formal name of the service being provided. i.e Computer Vision, NLU, Rekongnition. + * @var string $provider_service_name Formal name of the provider, i.e AI Vision, NLU, Rekongnition. */ public $provider_service_name; @@ -30,7 +29,6 @@ abstract class Provider { */ protected $option_name; - /** * @var string $service The name of the service this provider belongs to. */ @@ -55,7 +53,7 @@ abstract class Provider { * @param string $provider_service_name The name of the Service. * @param string $option_name Name of the option where the provider settings are stored. */ - public function __construct( $provider_name, $provider_service_name, $option_name ) { + public function __construct( string $provider_name, string $provider_service_name, string $option_name ) { $this->provider_name = $provider_name; $this->provider_service_name = $provider_service_name; $this->option_name = $option_name; @@ -66,15 +64,16 @@ public function __construct( $provider_name, $provider_service_name, $option_nam * * @return string */ - public function get_provider_name() { + public function get_provider_name(): string { return $this->provider_name; } - /** Returns the name of the settings section for this provider + /** + * Returns the name of the settings section for this provider. * * @return string */ - public function get_settings_section() { + public function get_settings_section(): string { return $this->option_name; } @@ -83,7 +82,7 @@ public function get_settings_section() { * * @return string */ - public function get_option_name() { + public function get_option_name(): string { return 'classifai_' . $this->option_name; } @@ -92,14 +91,16 @@ public function get_option_name() { * * @return array */ - public function get_features() { + public function get_features(): array { return $this->features; } /** * Can the Provider be initialized? + * + * @return bool */ - public function can_register() { + public function can_register(): bool { return $this->is_configured(); } @@ -119,7 +120,6 @@ public function register_admin() { * Helper to get the settings and allow for settings default values. * * @param string|bool|mixed $index Optional. Name of the settings option index. - * * @return string|array|mixed */ public function get_settings( $index = false ) { @@ -139,21 +139,19 @@ public function get_settings( $index = false ) { * * @return array */ - public function get_default_settings() { + public function get_default_settings(): array { return []; } /** * Common entry point for all REST endpoints for this provider. - * This is called by the Service. * * @param int $post_id The Post Id we're processing. * @param string $route_to_call The name of the route we're going to be processing. * @param array $args Optional arguments to pass to the route. - * * @return mixed */ - public function rest_endpoint_callback( $post_id, $route_to_call, $args = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function rest_endpoint_callback( int $post_id, string $route_to_call, array $args = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed return null; } @@ -161,10 +159,9 @@ public function rest_endpoint_callback( $post_id, $route_to_call, $args = [] ) { * Format the result of most recent request. * * @param array|WP_Error $data Response data to format. - * * @return string */ - protected function get_formatted_latest_response( $data ) { + protected function get_formatted_latest_response( $data ): string { if ( ! $data ) { return __( 'N/A', 'classifai' ); } @@ -181,7 +178,7 @@ protected function get_formatted_latest_response( $data ) { * * @return bool */ - public function is_configured() { + public function is_configured(): bool { $settings = $this->get_settings(); $is_configured = false; @@ -193,11 +190,11 @@ public function is_configured() { } /** - * Adds an api key field. + * Adds an API key field. * * @param array $args API key field arguments. */ - public function add_api_key_field( $args = [] ) { + public function add_api_key_field( array $args = [] ) { $default_settings = $this->feature_instance->get_settings(); $default_settings = $default_settings[ static::ID ]; $id = $args['id'] ?? 'api_key'; @@ -224,7 +221,7 @@ public function add_api_key_field( $args = [] ) { * @param string $feature Feature to check. * @return bool */ - protected function has_access( string $feature ) { + protected function has_access( string $feature ): bool { $access_control = new AccessControl( $this, $feature ); return $access_control->has_access(); } @@ -235,7 +232,7 @@ protected function has_access( string $feature ) { * @param string $feature Feature to check. * @return bool */ - public function is_feature_enabled( string $feature ) { + public function is_feature_enabled( string $feature ): bool { $is_feature_enabled = false; $settings = $this->get_settings(); @@ -264,6 +261,7 @@ public function is_feature_enabled( string $feature ) { /** * Determine if the feature is turned on. + * * Note: This function does not check if the user has access to the feature. * * - Use `is_feature_enabled()` to check if the user has access to the feature and feature is turned on. @@ -272,7 +270,7 @@ public function is_feature_enabled( string $feature ) { * @param string $feature Feature to check. * @return bool */ - public function is_enabled( string $feature ) { + public function is_enabled( string $feature ): bool { $settings = $this->get_settings(); $enable_key = 'enable_' . $feature; From 8b1293bf2302257b012658786dfbc9174b3280f6 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 15:53:04 -0700 Subject: [PATCH 100/127] Clean up the OpenAI provider code: --- includes/Classifai/Providers/OpenAI/DallE.php | 10 +-- .../Classifai/Providers/OpenAI/Embeddings.php | 75 +++++++++---------- .../Classifai/Providers/OpenAI/OpenAI.php | 23 +++--- .../Classifai/Providers/OpenAI/Tokenizer.php | 8 +- .../Classifai/Providers/OpenAI/Whisper.php | 39 +++++----- .../Providers/OpenAI/Whisper/Whisper.php | 6 +- 6 files changed, 72 insertions(+), 89 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index b9a6c397a..25a6a7b09 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -64,8 +64,6 @@ public function register() { /** * Register settings for the provider. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -140,7 +138,7 @@ public function render_provider_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'api_key' => '', 'authenticated' => false, @@ -188,7 +186,7 @@ public function register_generate_media_page() { * * @param string $hook_suffix The current admin page. */ - public function enqueue_admin_scripts( $hook_suffix = '' ) { + public function enqueue_admin_scripts( string $hook_suffix = '' ) { if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix && 'upload.php' !== $hook_suffix ) { return; } @@ -375,7 +373,7 @@ public function setup_fields_sections() {} * @param array $new_settings Array of settings about to be saved. * @return array The sanitized settings to be saved. */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; @@ -497,8 +495,6 @@ public function generate_image( string $prompt = '', array $args = [] ) { /** * Registers REST endpoints for this provider. - * - * @return void */ public function register_endpoints() { register_rest_route( diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 1f67c772d..8f08103c8 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -11,9 +11,10 @@ use Classifai\Providers\OpenAI\EmbeddingCalculations; use Classifai\Watson\Normalizer; use Classifai\Features\Classification; -use function Classifai\get_asset_info; use WP_Error; +use function Classifai\get_asset_info; + class Embeddings extends Provider { use \Classifai\Providers\OpenAI\OpenAI; @@ -59,8 +60,6 @@ public function __construct( $feature_instance = null ) { /** * Render the provider fields. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -135,7 +134,7 @@ public function render_provider_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'api_key' => '', 'number_of_terms' => 1, @@ -167,16 +166,18 @@ public function register() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); - if ( $feature->is_feature_enabled() && $feature->get_feature_provider_instance()::ID === static::ID ) { - add_action( 'wp_insert_post', [ $this, 'generate_embeddings_for_post' ] ); - add_action( 'created_term', [ $this, 'generate_embeddings_for_term' ] ); - add_action( 'edited_terms', [ $this, 'generate_embeddings_for_term' ] ); - add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ], 9 ); - add_filter( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] ); - add_action( 'add_meta_boxes', [ $this, 'add_metabox' ] ); - add_action( 'save_post', [ $this, 'save_metabox' ] ); - add_action( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) ); + if ( ! $feature->is_feature_enabled() || $feature->get_feature_provider_instance()::ID !== static::ID ) { + return; } + + add_action( 'wp_insert_post', [ $this, 'generate_embeddings_for_post' ] ); + add_action( 'created_term', [ $this, 'generate_embeddings_for_term' ] ); + add_action( 'edited_terms', [ $this, 'generate_embeddings_for_term' ] ); + add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ], 9 ); + add_filter( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] ); + add_action( 'add_meta_boxes', [ $this, 'add_metabox' ] ); + add_action( 'save_post', [ $this, 'save_metabox' ] ); + add_action( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) ); } /** @@ -239,10 +240,9 @@ public function enqueue_editor_assets() { * Sanitization for the options being saved. * * @param array $new_settings Array of settings about to be saved. - * * @return array The sanitized settings to be saved. */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); @@ -268,9 +268,9 @@ public function sanitize_settings( $new_settings ) { /** * The list of supported post types. * - * return array + * @return array */ - public function supported_post_types() { + public function supported_post_types(): array { /** * Filter post types supported for embeddings. * @@ -292,7 +292,7 @@ public function supported_post_types() { * @param string $taxonomy Taxonomy slug. * @return float */ - public function get_threshold( $taxonomy = '' ) { + public function get_threshold( string $taxonomy = '' ): float { $settings = ( new Classification() )->get_settings(); $threshold = 1; @@ -322,7 +322,7 @@ public function get_threshold( $taxonomy = '' ) { * * @return array */ - public function supported_post_statuses() { + public function supported_post_statuses(): array { /** * Filter post statuses supported for embeddings. * @@ -341,7 +341,7 @@ public function supported_post_statuses() { * * @return array */ - public function supported_taxonomies() { + public function supported_taxonomies(): array { /** * Filter taxonomies supported for embeddings. * @@ -362,7 +362,7 @@ public function supported_taxonomies() { * * @return array */ - public function get_post_classifier_embeddings_preview_data() { + public function get_post_classifier_embeddings_preview_data(): array { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : false; if ( ! $nonce || ! wp_verify_nonce( $nonce, 'classifai-previewer-action' ) ) { @@ -381,10 +381,9 @@ public function get_post_classifier_embeddings_preview_data() { * * @param int $post_id ID of post being saved. * @param bool $dryrun Whether to run the process or just return the data. - * * @return array|WP_Error */ - public function generate_embeddings_for_post( $post_id, $dryrun = false ) { + public function generate_embeddings_for_post( int $post_id, bool $dryrun = false ) { // Don't run on autosaves. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; @@ -467,7 +466,6 @@ private function set_terms( int $post_id = 0, array $embedding = [] ) { * Get the terms of a post based on embeddings. * * @param array $embedding Embedding data. - * * @return array|WP_Error */ private function get_terms( array $embedding = [] ) { @@ -535,10 +533,9 @@ private function get_terms( array $embedding = [] ) { * * @param array $embedding Embedding data. * @param bool $consider_threshold Whether to consider the threshold setting. - * * @return array */ - private function get_embeddings_similarity( $embedding, $consider_threshold = true ) { + private function get_embeddings_similarity( array $embedding, bool $consider_threshold = true ): array { $embedding_similarity = []; $taxonomies = $this->supported_taxonomies(); $calculations = new EmbeddingCalculations(); @@ -617,7 +614,7 @@ private function trigger_taxonomy_update( string $taxonomy = '' ) { * * @param int $term_id ID of term being saved. */ - public function generate_embeddings_for_term( $term_id ) { + public function generate_embeddings_for_term( int $term_id ) { // Ensure the user has permissions to edit. if ( ! current_user_can( 'edit_term', $term_id ) ) { return; @@ -741,7 +738,7 @@ public function generate_embeddings( int $id = 0, $type = 'post' ) { * @param string $type Type of content. Default 'post'. * @return string */ - public function get_content( int $id = 0, string $type = 'post' ) { + public function get_content( int $id = 0, string $type = 'post' ): string { $tokenizer = new Tokenizer( $this->max_tokens ); $normalizer = new Normalizer(); @@ -810,7 +807,7 @@ public function add_process_content_meta_to_rest_api() { * * @param string $post_type Post type name. */ - public function add_metabox( $post_type ) { + public function add_metabox( string $post_type ) { if ( ! in_array( $post_type, $this->get_supported_post_types( new Classification() ), true ) ) { return; } @@ -831,10 +828,9 @@ public function add_metabox( $post_type ) { /** * Render metabox. * - * @param WP_POST $post A WordPress post instance. - * @return void + * @param \WP_Post $post A WordPress post instance. */ - public function render_metabox( $post ) { + public function render_metabox( \WP_Post $post ) { $classifai_process_content = get_post_meta( $post->ID, '_classifai_process_content', true ); $checked = 'no' === $classifai_process_content ? '' : 'checked="checked"'; @@ -858,9 +854,8 @@ public function render_metabox( $post ) { * Handles saving the metabox. * * @param int $post_id Current post ID. - * @return void */ - public function save_metabox( $post_id ) { + public function save_metabox( int $post_id ) { if ( empty( $_POST['classifai_language_processing_meta'] ) ) { return; @@ -966,13 +961,11 @@ public function render_checkbox_group( array $args = array() ) { * * @since 2.5.0 * - * @param array $args The args passed to add_settings_field - * @param string $option_value The option value. - * @param string $value The value. - * - * @return void + * @param array $args The args passed to add_settings_field + * @param string $option_value The option value. + * @param string $value The value. */ - public function render_threshold_field( $args, $option_value, $value ) { + public function render_threshold_field( array $args, string $option_value, string $value ) { printf( '

    @@ -993,7 +986,7 @@ public function render_threshold_field( $args, $option_value, $value ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php index b1bf3c164..feeb90aed 100644 --- a/includes/Classifai/Providers/OpenAI/OpenAI.php +++ b/includes/Classifai/Providers/OpenAI/OpenAI.php @@ -95,7 +95,7 @@ function () { * @param array $settings Current settings, if any. * @return array */ - public function sanitize_api_key_settings( array $new_settings = [], $settings ) { + public function sanitize_api_key_settings( array $new_settings = [], array $settings = [] ): array { $authenticated = $this->authenticate_credentials( $new_settings[ static::ID ]['api_key'] ?? '' ); $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; @@ -151,7 +151,7 @@ protected function authenticate_credentials( string $api_key = '' ) { * * @return array */ - public function get_post_types_for_settings() { + public function get_post_types_for_settings(): array { $post_types = []; $post_type_objs = get_post_types( [], 'objects' ); $post_type_objs = array_filter( $post_type_objs, 'is_post_type_viewable' ); @@ -180,7 +180,7 @@ public function get_post_types_for_settings() { * * @return array */ - public function get_post_statuses_for_settings() { + public function get_post_statuses_for_settings(): array { $post_statuses = get_all_post_statuses(); /** @@ -202,7 +202,7 @@ public function get_post_statuses_for_settings() { * * @return array */ - public function get_taxonomies_for_settings() { + public function get_taxonomies_for_settings(): array { $taxonomies = get_taxonomies( [], 'objects' ); $taxonomies = array_filter( $taxonomies, 'is_taxonomy_viewable' ); $supported = []; @@ -228,11 +228,10 @@ public function get_taxonomies_for_settings() { /** * The list of supported post types. * - * @param \Classifai\Features\Feature $feature - * + * @param \Classifai\Features\Feature $feature Feature to check. * @return array */ - public function get_supported_post_types( $feature ) { + public function get_supported_post_types( \Classifai\Features\Feature $feature ): array { $settings = $feature->get_settings(); $post_types = []; @@ -250,11 +249,10 @@ public function get_supported_post_types( $feature ) { /** * The list of supported post statuses. * - * @param \Classifai\Features\Feature $feature - * + * @param \Classifai\Features\Feature $feature Feature to check * @return array */ - public function get_supported_post_statuses( $feature ) { + public function get_supported_post_statuses( \Classifai\Features\Feature $feature ): array { $settings = $feature->get_settings(); $post_statuses = []; @@ -272,11 +270,10 @@ public function get_supported_post_statuses( $feature ) { /** * The list of supported taxonomies. * - * @param \Classifai\Features\Feature $feature - * + * @param \Classifai\Features\Feature $feature Feature to check. * @return array */ - public function get_supported_taxonomies( $feature ) { + public function get_supported_taxonomies( \Classifai\Features\Feature $feature ): array { $provider = $feature->get_feature_provider_instance(); $settings = $feature->get_settings( $provider::ID ); $taxonomies = []; diff --git a/includes/Classifai/Providers/OpenAI/Tokenizer.php b/includes/Classifai/Providers/OpenAI/Tokenizer.php index 58314af4b..b3087e352 100644 --- a/includes/Classifai/Providers/OpenAI/Tokenizer.php +++ b/includes/Classifai/Providers/OpenAI/Tokenizer.php @@ -33,7 +33,7 @@ class Tokenizer { * * @param int $max_tokens Maximum tokens the model supports. */ - public function __construct( $max_tokens ) { + public function __construct( int $max_tokens ) { $this->max_tokens = $max_tokens; /** @@ -69,7 +69,7 @@ public function __construct( $max_tokens ) { * @param string $content Content to analyze. * @return int */ - public function tokens_in_content( string $content = '' ) { + public function tokens_in_content( string $content = '' ): int { $tokens = ceil( mb_strlen( $content ) / $this->characters_in_token ); return (int) $tokens; @@ -81,7 +81,7 @@ public function tokens_in_content( string $content = '' ) { * @param int $words Number of words we want. * @return int */ - public function tokens_in_words( int $words = 1 ) { + public function tokens_in_words( int $words = 1 ): int { $tokens = ceil( $this->tokens_per_word * absint( $words ) ); return (int) $tokens; @@ -94,7 +94,7 @@ public function tokens_in_words( int $words = 1 ) { * @param int $max_tokens Maximum tokens our content can have. * @return string */ - public function trim_content( string $content = '', int $max_tokens = 0 ) { + public function trim_content( string $content = '', int $max_tokens = 0 ): string { // Remove linebreaks that may have been added. $content = str_replace( "\n\n", ' ', $content ); diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index e4c1662e7..5517e32ff 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -8,12 +8,13 @@ use Classifai\Features\AudioTranscriptsGeneration; use Classifai\Providers\Provider; use Classifai\Providers\OpenAI\Whisper\Transcribe; -use function Classifai\clean_input; -use function Classifai\get_asset_info; use WP_REST_Server; use WP_REST_Request; use WP_Error; +use function Classifai\clean_input; +use function Classifai\get_asset_info; + class Whisper extends Provider { use \Classifai\Providers\OpenAI\OpenAI; @@ -48,19 +49,19 @@ public function __construct( $feature_instance = null ) { * This only fires if can_register returns true. */ public function register() { - if ( ( new AudioTranscriptsGeneration() )->is_feature_enabled() ) { - add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); - add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); - add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); - add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); + if ( ! ( new AudioTranscriptsGeneration() )->is_feature_enabled() ) { + return; } + + add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); + add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); } /** * Register settings for this provider. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -101,7 +102,7 @@ public function render_provider_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'api_key' => '', 'authenticated' => false, @@ -121,7 +122,7 @@ public function get_default_provider_settings() { * @param array $new_settings New settings. * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; @@ -132,8 +133,6 @@ public function sanitize_settings( $new_settings ) { /** * Enqueue assets. - * - * @return void */ public function enqueue_media_scripts() { wp_enqueue_script( @@ -151,7 +150,7 @@ public function enqueue_media_scripts() { * @param int $attachment_id Attachment ID to process. * @return WP_Error|bool */ - public function transcribe_audio( $attachment_id = 0 ) { + public function transcribe_audio( int $attachment_id = 0 ) { if ( $attachment_id && ! current_user_can( 'edit_post', $attachment_id ) ) { return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to edit this attachment.', 'classifai' ) ); } @@ -176,7 +175,7 @@ public function transcribe_audio( $attachment_id = 0 ) { * @param \WP_Post $attachment Attachment object. * @return array */ - public function add_buttons_to_media_modal( $form_fields, $attachment ) { + public function add_buttons_to_media_modal( array $form_fields, \WP_Post $attachment ): array { $feature = new AudioTranscriptsGeneration(); $settings = $feature->get_settings(); $transcribe = new Transcribe( $attachment->ID, $settings[ static::ID ] ); @@ -202,7 +201,7 @@ public function add_buttons_to_media_modal( $form_fields, $attachment ) { * * @param \WP_Post $post Post object. */ - public function setup_attachment_meta_box( $post ) { + public function setup_attachment_meta_box( \WP_Post $post ) { $feature = new AudioTranscriptsGeneration(); $settings = $feature->get_settings(); $transcribe = new Transcribe( $post->ID, $settings[ static::ID ] ); @@ -226,7 +225,7 @@ public function setup_attachment_meta_box( $post ) { * * @param \WP_Post $post Post object. */ - public function attachment_meta_box( $post ) { + public function attachment_meta_box( \WP_Post $post ) { $text = empty( get_the_content( null, false, $post ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); wp_nonce_field( 'classifai_openai_whisper_meta_action', 'classifai_openai_whisper_meta' ); @@ -249,7 +248,7 @@ public function attachment_meta_box( $post ) { * * @param int $attachment_id Attachment ID. */ - public function maybe_transcribe_audio( $attachment_id ) { + public function maybe_transcribe_audio( int $attachment_id ) { if ( $attachment_id && ! current_user_can( 'edit_post', $attachment_id ) ) { return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to edit this attachment.', 'classifai' ) ); } @@ -278,8 +277,6 @@ public function maybe_transcribe_audio( $attachment_id ) { /** * Register REST endpoints for this provider. - * - * @return void */ public function register_endpoints() { register_rest_route( diff --git a/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php index 5a11a11ea..b36edd52e 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php @@ -49,7 +49,7 @@ trait Whisper { * @param string $path Path to append to API URL. * @return string */ - public function get_api_url( $path = '' ) { + public function get_api_url( string $path = '' ): string { return sprintf( '%s%s', trailingslashit( $this->whisper_url ), $path ); } @@ -59,9 +59,9 @@ public function get_api_url( $path = '' ) { * Ensure the file is a supported format and is under the maximum file size. * * @param int $attachment_id Attachment ID to process. - * @return boolean + * @return bool */ - public function should_process( int $attachment_id ) { + public function should_process( int $attachment_id ): bool { $mime_type = get_post_mime_type( $attachment_id ); $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); $process = false; From 9a28c1976452cce3efa8e8e37a79e40f222a04df Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 16:00:56 -0700 Subject: [PATCH 101/127] Clean up the Watson provider code --- includes/Classifai/Providers/Watson/NLU.php | 76 +++++++++------------ 1 file changed, 32 insertions(+), 44 deletions(-) diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index ed896a198..f2981ae73 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -11,12 +11,13 @@ use Classifai\Taxonomy\TaxonomyFactory; use Classifai\Features\Classification; use Classifai\Features\Feature; -use function Classifai\get_asset_info; -use function Classifai\check_term_permissions; use WP_Error; use WP_REST_Request; use WP_REST_Server; +use function Classifai\get_asset_info; +use function Classifai\check_term_permissions; + class NLU extends Provider { const ID = 'ibm_watson_nlu'; @@ -86,8 +87,6 @@ public function __construct( $feature = null ) { /** * Renders settings fields for this provider. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -119,7 +118,6 @@ public function render_provider_fields() { 'label_for' => 'username', 'default_value' => $settings['username'], 'input_type' => 'text', - 'default_value' => 'apikey', 'large' => true, 'class' => 'classifai-provider-field ' . ( $this->use_username_password() ? 'hidden' : '' ) . ' provider-scope-' . static::ID, // Important to add this. ] @@ -158,10 +156,10 @@ public function render_provider_fields() { add_settings_field( static::ID . '_toggle', '', - function( $args = [] ) { + function ( $args = [] ) { printf( '%s', - $args['class'] ?? '', + $args['class'] ? esc_attr( $args['class'] ) : '', $this->use_username_password() ? esc_html__( 'Use a username/password instead?', 'classifai' ) : esc_html__( 'Use an API Key instead?', 'classifai' ) @@ -233,7 +231,7 @@ function( $args = [] ) { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'endpoint_url' => '', 'apikey' => '', @@ -417,7 +415,7 @@ function () { * * @return bool */ - protected function use_username_password() { + protected function use_username_password(): bool { $feature = new Classification(); $settings = $feature->get_settings( static::ID ); @@ -432,10 +430,8 @@ protected function use_username_password() { * Render the NLU features settings. * * @param array $args Settings for the inputs - * - * @return void */ - public function render_nlu_feature_settings( $args ) { + public function render_nlu_feature_settings( array $args ) { $feature = $args['feature']; $labels = $args['labels']; $option_index = $args['option_index']; @@ -493,7 +489,7 @@ public function render_nlu_feature_settings( $args ) { * * @return array */ - public function get_supported_taxonomies() { + public function get_supported_taxonomies(): array { $taxonomies = \get_taxonomies( [], 'objects' ); $supported = []; @@ -508,10 +504,9 @@ public function get_supported_taxonomies() { * Helper to ensure the authentication works. * * @param array $settings The list of settings to be saved - * * @return bool|WP_Error */ - protected function nlu_authentication_check( $settings ) { + protected function nlu_authentication_check( array $settings ) { // Check that we have credentials before hitting the API. if ( empty( $settings[ static::ID ]['username'] ) || empty( $settings[ static::ID ]['password'] ) @@ -555,10 +550,9 @@ protected function nlu_authentication_check( $settings ) { * Sanitization for the options being saved. * * @param array $new_settings Array of settings about to be saved. - * * @return array The sanitized settings to be saved. */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $authenticated = $this->nlu_authentication_check( $new_settings ); @@ -604,10 +598,9 @@ public function sanitize_settings( $new_settings ) { * Format the result of most recent request. * * @param array|WP_Error $data Response data to format. - * * @return string */ - protected function get_formatted_latest_response( $data ) { + protected function get_formatted_latest_response( $data ): string { if ( ! $data ) { return __( 'N/A', 'classifai' ); } @@ -634,12 +627,12 @@ protected function get_formatted_latest_response( $data ) { /** * Add metabox to enable/disable language processing on post/post types. * - * @param string $post_type Post Type. - * @param WP_Post $post WP_Post object. - * * @since 1.8.0 + * + * @param string $post_type Post Type. + * @param \WP_Post $post WP_Post object. */ - public function add_classifai_meta_box( $post_type, $post ) { + public function add_classifai_meta_box( string $post_type, \WP_Post $post ) { $supported_post_types = \Classifai\get_supported_post_types(); $post_statuses = \Classifai\get_supported_post_statuses(); $post_status = get_post_status( $post ); @@ -659,11 +652,11 @@ public function add_classifai_meta_box( $post_type, $post ) { /** * Render metabox content. * - * @param WP_Post $post WP_Post object. - * * @since 1.8.0 + * + * @param \WP_Post $post WP_Post object. */ - public function render_classifai_meta_box( $post ) { + public function render_classifai_meta_box( \WP_Post $post ) { wp_nonce_field( 'classifai_language_processing_meta_action', 'classifai_language_processing_meta' ); $classifai_process_content = get_post_meta( $post->ID, '_classifai_process_content', true ); $classifai_process_content = ( 'no' === $classifai_process_content ) ? 'no' : 'yes'; @@ -694,11 +687,11 @@ public function render_classifai_meta_box( $post ) { /** * Save language processing meta data on post/post types. * - * @param int $post_id Post ID. - * * @since 1.8.0 + * + * @param int $post_id Post ID. */ - public function classifai_save_post_metadata( $post_id ) { + public function classifai_save_post_metadata( int $post_id ) { if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) { return; } @@ -755,7 +748,7 @@ public function add_process_content_meta_to_rest_api() { * * @return bool */ - public function is_configured() { + public function is_configured(): bool { $is_configured = parent::is_configured(); if ( ! $is_configured ) { @@ -767,8 +760,6 @@ public function is_configured() { /** * Register REST endpoints. - * - * @return void */ public function register_endpoints() { register_rest_route( @@ -799,7 +790,6 @@ public function register_endpoints() { * Handle request to generate tags for given post ID. * * @param WP_REST_Request $request The full request object. - * * @return array|bool|string|WP_Error */ public function generate_post_tags( WP_REST_Request $request ) { @@ -867,7 +857,7 @@ public function get_all_feature_taxonomies() { * @param array $args Optional arguments to pass to the route. * @return string|WP_Error */ - public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args = [] ) { + public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { $route_to_call = strtolower( $route_to_call ); if ( ! $post_id || ! get_post( $post_id ) ) { @@ -890,10 +880,9 @@ public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args * Handle request to generate tags for given post ID. * * @param int $post_id The Post Id we're processing. - * * @return mixed */ - public function classify_post( $post_id ) { + public function classify_post( int $post_id ) { try { if ( empty( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to classify post.', 'classifai' ) ); @@ -978,12 +967,11 @@ public function generate_post_tags_permissions_check( WP_REST_Request $request ) * Classifies the post specified with the PostClassifier object. * Existing terms relationships are removed before classification. * - * @param int $post_id the post to classify & link + * @param int $post_id the post to classify & link * @param bool $link_terms Whether to link the terms to the post. - * - * @return array + * @return array|bool */ - public function classify( $post_id, $link_terms = true ) { + public function classify( int $post_id, bool $link_terms = true ) { /** * Filter whether ClassifAI should classify a post. * @@ -1009,15 +997,15 @@ public function classify( $post_id, $link_terms = true ) { if ( \Classifai\get_feature_enabled( 'category' ) ) { wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'category' ) ); } - + if ( \Classifai\get_feature_enabled( 'keyword' ) ) { wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'keyword' ) ); } - + if ( \Classifai\get_feature_enabled( 'concept' ) ) { wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'concept' ) ); } - + if ( \Classifai\get_feature_enabled( 'entity' ) ) { wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'entity' ) ); } @@ -1049,7 +1037,7 @@ public function classify( $post_id, $link_terms = true ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; From 6224a3987d4c942a64e1559549ab14c42cc5b48e Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 16:17:25 -0700 Subject: [PATCH 102/127] Clean up the Azure provider code --- .../Providers/Azure/ComputerVision.php | 75 +++++++++---------- includes/Classifai/Providers/Azure/OCR.php | 6 +- .../Providers/Azure/Personalizer.php | 40 +++++----- includes/Classifai/Providers/Azure/Read.php | 26 +++---- .../Providers/Azure/SmartCropping.php | 14 ++-- includes/Classifai/Providers/Azure/Speech.php | 47 ++++++------ 6 files changed, 95 insertions(+), 113 deletions(-) diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index ba3cfa90b..6c98b0ca4 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -206,7 +206,7 @@ public function add_image_tags_generation_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'endpoint_url' => '', 'api_key' => '', @@ -251,10 +251,9 @@ public function get_default_provider_settings() { * Sanitization * * @param array $new_settings The settings being saved. - * * @return array|mixed */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ) { $settings = $this->feature_instance->get_settings(); if ( ! empty( $new_settings[ static::ID ]['endpoint_url'] ) && ! empty( $new_settings[ static::ID ]['api_key'] ) ) { @@ -301,7 +300,7 @@ public function sanitize_settings( $new_settings ) { * * @return array */ - public function get_alt_text_settings() { + public function get_alt_text_settings(): array { $alt_generator = new DescriptiveTextGenerator(); $settings = $alt_generator->get_settings( static::ID ); $enabled_fields = array(); @@ -394,10 +393,9 @@ public function enqueue_editor_assets() { * Filter the post content to inject aria-describedby attribute. * * @param string $content Post content. - * * @return string */ - public function add_ocr_aria_describedby( $content ) { + public function add_ocr_aria_describedby( string $content ): string { $modified = false; if ( ! is_singular() || empty( $content ) ) { @@ -451,7 +449,7 @@ public function add_ocr_aria_describedby( $content ) { * * @param \WP_Post $post The post object. */ - public function setup_attachment_meta_box( $post ) { + public function setup_attachment_meta_box( \WP_Post $post ) { if ( wp_attachment_is_image( $post ) && ( @@ -488,7 +486,7 @@ public function setup_attachment_meta_box( $post ) { * * @param \WP_Post $post The post object. */ - public function attachment_data_meta_box( $post ) { + public function attachment_data_meta_box( \WP_Post $post ) { $alt_generator = new DescriptiveTextGenerator(); $image_tagging = new ImageTagsGenerator(); $image_to_text = new ImageTextExtraction(); @@ -545,7 +543,7 @@ public function attachment_data_meta_box( $post ) { * * @param \WP_Post $post The post object. */ - public function attachment_pdf_data_meta_box( $post ) { + public function attachment_pdf_data_meta_box( \WP_Post $post ) { $status = self::get_read_status( $post->ID ); $read = (bool) $status['read'] ? __( 'Rescan PDF for text', 'classifai' ) : __( 'Scan PDF for text', 'classifai' ); $running = (bool) $status['running']; @@ -570,7 +568,7 @@ public function attachment_pdf_data_meta_box( $post ) { * @param array $form_fields Array of fields * @param \WP_post $post Post object for the attachment being viewed. */ - public function add_rescan_button_to_media_modal( $form_fields, $post ) { + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ) { $pdf_to_text = new PDFTextExtraction(); $alt_generator = new DescriptiveTextGenerator(); $image_to_text = new ImageTextExtraction(); @@ -681,7 +679,7 @@ public static function get_read_status_ajax() { * Callback to get the status of the PDF read. * * @param int $attachment_id The attachment ID. - * @return array Read and running status. + * @return array|null Read and running status. */ public static function get_read_status( $attachment_id = null ) { if ( empty( $attachment_id ) || ! is_numeric( $attachment_id ) ) { @@ -704,10 +702,11 @@ public static function get_read_status( $attachment_id = null ) { } /** + * Determine if we need to rescan the image. * * @param int $attachment_id Post id for the attachment */ - public function maybe_rescan_image( $attachment_id ) { + public function maybe_rescan_image( int $attachment_id ) { if ( clean_input( 'rescan-pdf' ) ) { $this->read_pdf( $attachment_id ); return; // We can exit early, if this is a call for PDF scanning - everything else relates to images. @@ -759,7 +758,7 @@ public function maybe_rescan_image( $attachment_id ) { * @param int $attachment_id Attachment ID. * @return array Filtered attachment metadata. */ - public function smart_crop_image( $metadata, $attachment_id ) { + public function smart_crop_image( array $metadata, int $attachment_id ): array { $feature = new ImageCropping(); $settings = $feature->get_settings( static::ID ); @@ -805,10 +804,9 @@ public function smart_crop_image( $metadata, $attachment_id ) { * * @param array $metadata The metadata for the image. * @param int $attachment_id Post ID for the attachment. - * * @return mixed */ - public function generate_image_alt_tags( $metadata, $attachment_id ) { + public function generate_image_alt_tags( array $metadata, int $attachment_id ) { $feature = new ImageTagsGenerator(); if ( $feature->is_feature_enabled() ) { @@ -853,7 +851,7 @@ public function generate_image_alt_tags( $metadata, $attachment_id ) { * @param boolean $force Whether to force processing or not. Default false. * @return array Filtered attachment metadata. */ - public function ocr_processing( array $metadata = [], int $attachment_id = 0, bool $force = false ) { + public function ocr_processing( array $metadata = [], int $attachment_id = 0, bool $force = false ): array { $feature = new ImageTextExtraction(); $settings = $feature->get_settings( static::ID ); @@ -899,10 +897,9 @@ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bo * * @param string $image_url Path to the uploaded image. * @param \Classifai\Features\Feature $feature Feature instance - * * @return bool|object|WP_Error */ - protected function scan_image( $image_url, $feature = null ) { + protected function scan_image( string $image_url, \Classifai\Features\Feature $feature = null ) { $settings = $feature->get_settings( static::ID ); // Check if valid authentication is in place. @@ -950,10 +947,9 @@ protected function scan_image( $image_url, $feature = null ) { * Build and return the API endpoint based on settings. * * @param \Classifai\Features\Feature $feature Feature instance - * * @return string */ - protected function prep_api_url( $feature = null ) { + protected function prep_api_url( \Classifai\Features\Feature $feature = null ): string { $settings = $feature->get_settings( static::ID ); $api_features = []; @@ -974,10 +970,9 @@ protected function prep_api_url( $feature = null ) { * Generate the alt tags for the image being uploaded. * * @param int $attachment_id Post ID for the attachment. - * * @return string|WP_Error */ - public function generate_alt_tags( $attachment_id ) { + public function generate_alt_tags( int $attachment_id ) { $rtn = ''; $enabled_fields = $this->get_alt_text_settings(); @@ -1064,7 +1059,7 @@ public function generate_alt_tags( $attachment_id ) { * * @param int $attachment_id Attachment ID. */ - public function read_pdf( $attachment_id ) { + public function read_pdf( int $attachment_id ) { $feature = new PDFTextExtraction(); $settings = $feature->get_settings( static::ID ); $should_read_pdf = $feature->is_feature_enabled(); @@ -1095,7 +1090,7 @@ public function read_pdf( $attachment_id ) { * @param string $operation_url Operation URL for checking the read status. * @param int $attachment_id Attachment ID. */ - public function do_read_cron( $operation_url, $attachment_id ) { + public function do_read_cron( string $operation_url, int $attachment_id ) { $settings = ( new PDFTextExtraction() )->get_settings( static::ID ); ( new Read( $settings, intval( $attachment_id ) ) )->check_read_result( $operation_url ); @@ -1105,10 +1100,9 @@ public function do_read_cron( $operation_url, $attachment_id ) { * Generate the image tags for the image being uploaded. * * @param int $attachment_id Post ID for the attachment. - * * @return string|array|WP_Error */ - public function generate_image_tags( $attachment_id ) { + public function generate_image_tags( int $attachment_id ) { $rtn = ''; $feature = new ImageTagsGenerator(); $settings = $feature->get_settings( static::ID ); @@ -1183,10 +1177,9 @@ public function setup_fields_sections() {} * * @param string $url Endpoint URL. * @param string $api_key Api Key. - * * @return bool|WP_Error */ - protected function authenticate_credentials( $url, $api_key ) { + protected function authenticate_credentials( string $url, string $api_key ) { $rtn = false; $request = wp_remote_post( trailingslashit( $url ) . $this->analyze_url, @@ -1317,7 +1310,7 @@ public function register_endpoints() { * @param WP_REST_Request $request Request object. * @return WP_Error|WP_REST_Response */ - public function computer_vision_endpoint_callback( $request ) { + public function computer_vision_endpoint_callback( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $custom_atts = $request->get_attributes(); $route_to_call = empty( $custom_atts['args']['route'] ) ? false : strtolower( $custom_atts['args']['route'][0] ); @@ -1346,7 +1339,7 @@ public function computer_vision_endpoint_callback( $request ) { * @param array $args Optional arguments to pass to the route. * @return array|string|WP_Error */ - public function rest_endpoint_callback( $post_id, $route_to_call = [], $args = [] ) { + public function rest_endpoint_callback( int $post_id, string $route_to_call = '', array $args = [] ) { // Check to be sure the post both exists and is an attachment. if ( ! get_post( $post_id ) || 'attachment' !== get_post_type( $post_id ) ) { /* translators: %1$s: the attachment ID */ @@ -1400,7 +1393,7 @@ public function rest_endpoint_callback( $post_id, $route_to_call = [], $args = [ * @param WP_REST_Request $request Request object. * @return bool|WP_Error */ - public function descriptive_text_generator_permissions_check( $request ) { + public function descriptive_text_generator_permissions_check( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $post_type = get_post_type_object( 'attachment' ); @@ -1427,7 +1420,7 @@ public function descriptive_text_generator_permissions_check( $request ) { * @param WP_REST_Request $request Request object. * @return bool|WP_Error */ - public function image_tags_generator_permissions_check( $request ) { + public function image_tags_generator_permissions_check( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $post_type = get_post_type_object( 'attachment' ); $image_tags_feature = new ImageTagsGenerator(); @@ -1466,7 +1459,7 @@ public function image_tags_generator_permissions_check( $request ) { * @param WP_REST_Request $request Request object. * @return bool|WP_Error */ - public function smart_crop_permissions_check( $request ) { + public function smart_crop_permissions_check( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $post_type = get_post_type_object( 'attachment' ); @@ -1493,7 +1486,7 @@ public function smart_crop_permissions_check( $request ) { * @param WP_REST_Request $request Request object. * @return bool|WP_Error */ - public function pdf_read_permissions_check( $request ) { + public function pdf_read_permissions_check( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $post_type = get_post_type_object( 'attachment' ); @@ -1520,7 +1513,7 @@ public function pdf_read_permissions_check( $request ) { * @param WP_REST_Request $request Request object. * @return bool|WP_Error */ - public function image_text_extractor_permissions_check( $request ) { + public function image_text_extractor_permissions_check( WP_REST_Request $request ) { $attachment_id = $request->get_param( 'id' ); $post_type = get_post_type_object( 'attachment' ); @@ -1548,7 +1541,7 @@ public function image_text_extractor_permissions_check( $request ) { * DISTINCT, fields (SELECT), and LIMITS clauses. * @return array The modified clauses. */ - public function filter_attachment_query_keywords( $clauses ) { + public function filter_attachment_query_keywords( array $clauses ): array { global $wpdb; remove_filter( 'posts_clauses', __FUNCTION__ ); @@ -1575,28 +1568,28 @@ public function filter_attachment_query_keywords( $clauses ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { - $descriptive_text = array_filter( + $descriptive_text = array_filter( $provider_settings['descriptive_text_fields'], - function( $type ) { + function ( $type ) { return '0' !== $type; } ); $debug_info[ __( 'Generate descriptive text', 'classifai' ) ] = implode( ', ', $descriptive_text ); $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['descriptive_confidence_threshold']; - $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response' ) ); + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response' ) ); } if ( $this->feature_instance instanceof ImageTagsGenerator ) { $debug_info[ __( 'Tag taxonomy', 'classifai' ) ] = $provider_settings['tag_taxonomy']; $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['tag_confidence_threshold']; - $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_tags_latest_response' ) ); + $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_tags_latest_response' ) ); } if ( $this->feature_instance instanceof ImageCropping ) { diff --git a/includes/Classifai/Providers/Azure/OCR.php b/includes/Classifai/Providers/Azure/OCR.php index f0ce19163..ffd4fe78f 100644 --- a/includes/Classifai/Providers/Azure/OCR.php +++ b/includes/Classifai/Providers/Azure/OCR.php @@ -94,7 +94,7 @@ public function __construct( array $settings, $scan, bool $force ) { * * @return string */ - public function get_api_url() { + public function get_api_url(): string { return sprintf( '%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH ); } @@ -104,9 +104,9 @@ public function get_api_url() { * @since 1.6.0 * * @param int $attachment_id Attachment ID. - * @return boolean + * @return bool */ - public function should_process( int $attachment_id ) { + public function should_process( int $attachment_id ): bool { // Bypass check if this is a force request if ( $this->force ) { return true; diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index bb49bb993..514f084f1 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -63,8 +63,6 @@ public function register() { /** * Render the provider fields. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -118,7 +116,7 @@ public function render_provider_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'endpoint_url' => '', 'api_key' => '', @@ -139,7 +137,7 @@ public function get_default_provider_settings() { * @param array $new_settings The settings array. * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $new_settings['endpoint_url'] = esc_url_raw( $new_settings['endpoint_url'] ?? $settings['endpoint_url'] ); @@ -187,7 +185,7 @@ public function sanitize_settings( $new_settings ) { * @param array $attributes The block attributes. * @return array recent actions based on block attributes. */ - protected function get_recent_actions( $attributes ) { + protected function get_recent_actions( array $attributes ): array { $post_type = $attributes['contentPostType']; $key_attributes = array( 'terms' => isset( $attributes['taxQuery'] ) ? $attributes['taxQuery'] : array(), @@ -265,9 +263,9 @@ protected function get_recent_actions( $attributes ) { * Get Recommended content Id from Azure personalizer. * * @param array $attributes The block attributes. - * @return Object + * @return mixed */ - public function get_recommended_content( $attributes ) { + public function get_recommended_content( array $attributes ) { $actions = $this->get_recent_actions( $attributes ); if ( empty( $actions ) ) { @@ -337,10 +335,9 @@ function ( $ele ) { * Renders the `classifai/recommended-content-block` block on server. * * @param array $attributes The block attributes. - * * @return string Returns the post content with recommended content added. */ - public function render_recommended_content( $attributes ) { + public function render_recommended_content( array $attributes ): string { /** * Filter the recommended content block attributes * @@ -553,7 +550,7 @@ function ( $ele ) use ( $rewarded_post ) { * @param int $post_id Post Object. * @return array */ - protected function get_post_features( $post_id ) { + protected function get_post_features( int $post_id ): array { $features = array( 'title' => $this->get_string_words( get_the_title( $post_id ) ), 'excerpt' => $this->get_string_words( get_the_excerpt( $post_id ) ), @@ -604,7 +601,7 @@ protected function get_user_agent_features() { * @param string $text String of text to get words from. * @return array */ - protected function get_string_words( $text ) { + protected function get_string_words( string $text ): array { $str_array = preg_split( '/\s+/', $text ); $words = array(); foreach ( $str_array as $str ) { @@ -620,7 +617,7 @@ protected function get_string_words( $text ) { * @param array $rank_request Prepared Request data. * @return object|string */ - protected function personalizer_get_ranked_action( $rank_request ) { + protected function personalizer_get_ranked_action( array $rank_request ) { $feature = new RecommendedContent(); $settings = $feature->get_settings( static::ID ); $result = wp_remote_post( @@ -641,6 +638,7 @@ protected function personalizer_get_ranked_action( $rank_request ) { } return $response; } + return $result; } @@ -649,10 +647,9 @@ protected function personalizer_get_ranked_action( $rank_request ) { * * @param string $event_id Personalizer event ID. * @param int $reward Reward value to send. - * * @return object|string */ - public function personalizer_send_reward( $event_id, $reward ) { + public function personalizer_send_reward( string $event_id, int $reward ) { $feature = new RecommendedContent(); $settings = $feature->get_settings( static::ID ); @@ -685,10 +682,9 @@ public function personalizer_send_reward( $event_id, $reward ) { * * @param string $url Endpoint URL. * @param string $api_key Api Key. - * * @return bool|WP_Error */ - protected function authenticate_credentials( $url, $api_key ) { + protected function authenticate_credentials( string $url, string $api_key ) { $rtn = false; // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get $result = wp_remote_get( @@ -713,6 +709,9 @@ protected function authenticate_credentials( $url, $api_key ) { return $rtn; } + /** + * Register the REST API endpoints. + */ public function register_endpoints() { register_rest_route( 'classifai/v1', @@ -772,8 +771,6 @@ public function reward_permissions_check() { /** * Render Recommended Content over AJAX. - * - * @return void */ public function ajax_render_recommended_content() { check_ajax_referer( 'classifai-recommended-block', 'security' ); @@ -812,9 +809,8 @@ public function ajax_render_recommended_content() { * Maybe clear transients for recent actions. * * @param int $post_id Post Id. - * @return void */ - public function maybe_clear_transient( $post_id ) { + public function maybe_clear_transient( int $post_id ) { global $wpdb; $post_type = get_post_type( $post_id ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching @@ -832,13 +828,13 @@ public function maybe_clear_transient( $post_id ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; if ( $this->feature_instance instanceof RecommendedContent ) { - $debug_info[ __( 'API URL', 'classifai' ) ] = $provider_settings['endpoint_url']; + $debug_info[ __( 'API URL', 'classifai' ) ] = $provider_settings['endpoint_url']; $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_personalizer_status_response' ) ); } diff --git a/includes/Classifai/Providers/Azure/Read.php b/includes/Classifai/Providers/Azure/Read.php index 2b7ace184..6b34453c8 100644 --- a/includes/Classifai/Providers/Azure/Read.php +++ b/includes/Classifai/Providers/Azure/Read.php @@ -55,7 +55,7 @@ class Read { * @param int $attachment_id Attachment ID to process. * @param boolean $force Whether to force processing or not. */ - public function __construct( array $settings, $attachment_id, bool $force = false ) { + public function __construct( array $settings, int $attachment_id, bool $force = false ) { $this->settings = $settings; $this->attachment_id = $attachment_id; $this->force = $force; @@ -65,19 +65,18 @@ public function __construct( array $settings, $attachment_id, bool $force = fals * Builds the API url. * * @param string $path Path to append to API URL. - * * @return string */ - public function get_api_url( $path = '' ) { + public function get_api_url( string $path = '' ): string { return sprintf( '%s%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH, $path ); } /** * Returns whether Read processing should be applied to the attachment * - * @return boolean + * @return bool */ - public function should_process() { + public function should_process(): bool { // Bypass check if this is a force request if ( $this->force ) { return true; @@ -220,10 +219,9 @@ public function read_document() { * Use WP Cron to periodically check the status of the read operation. * * @param string $operation_url Operation URL for checking the read status. - * * @return WP_Error|null|array */ - public function check_read_result( $operation_url ) { + public function check_read_result( string $operation_url ) { if ( function_exists( 'vip_safe_wp_remote_get' ) ) { $response = vip_safe_wp_remote_get( $operation_url ); } else { @@ -281,11 +279,10 @@ public function check_read_result( $operation_url ) { /** * Update document description using text received from Read API. * - * @param array $data Read result. - * + * @param array $data Read result. * @return WP_Error|array */ - public function update_document_description( $data ) { + public function update_document_description( array $data ) { if ( empty( $data['analyzeResult'] ) || empty( $data['analyzeResult']['readResults'] ) ) { return $this->log_error( new WP_Error( 'invalid_read_result', esc_html__( 'The Read result is invalid.', 'classifai' ) ) ); } @@ -343,7 +340,7 @@ public function update_document_description( $data ) { * * @param WP_Error $error WP_Error object. */ - private function log_error( $error ) { + private function log_error( WP_Error $error ) { update_post_meta( $this->attachment_id, '_classifai_azure_read_error', $error->get_error_message() ); return $error; @@ -352,11 +349,12 @@ private function log_error( $error ) { /** * Log the status of read process to database. * - * @param array $data Response body of the read result. - * * @see https://centraluseuap.dev.cognitive.microsoft.com/docs/services/computer-vision-v3-2/operations/5d9869604be85dee480c8750 + * + * @param array $data Response body of the read result. + * @return array */ - private function update_status( $data ) { + private function update_status( array $data ): array { update_post_meta( $this->attachment_id, '_classifai_azure_read_status', $data ); return $data; diff --git a/includes/Classifai/Providers/Azure/SmartCropping.php b/includes/Classifai/Providers/Azure/SmartCropping.php index ce790af04..a3d385bc0 100644 --- a/includes/Classifai/Providers/Azure/SmartCropping.php +++ b/includes/Classifai/Providers/Azure/SmartCropping.php @@ -96,7 +96,7 @@ public function get_wp_filesystem() { * * @return int */ - public function get_max_pixel_dimension() { + public function get_max_pixel_dimension(): int { /** * Filters the maximum allowable width or height of an image to be cropped. Default 1024. * @@ -116,9 +116,9 @@ public function get_max_pixel_dimension() { * @since 1.5.0 * * @param string $size An image size. - * @return boolean + * @return bool */ - public function should_crop( $size ) { + public function should_crop( string $size ): bool { if ( 'thumbnail' === $size ) { return boolval( get_option( 'thumbnail_crop', false ) ); } @@ -166,7 +166,7 @@ public function should_crop( $size ) { * @param int $attachment_id Attachment ID. * @return array Filtered image attachment metadata. */ - public function generate_attachment_metadata( $metadata, $attachment_id ) { + public function generate_attachment_metadata( array $metadata, int $attachment_id ): array { if ( ! isset( $metadata['sizes'] ) || empty( $metadata['sizes'] ) ) { return $metadata; } @@ -200,7 +200,7 @@ public function generate_attachment_metadata( $metadata, $attachment_id ) { * @param array $size_data Attachment metadata size data. * @return string|\WP_Error The thumbnail file name or WP_Error on failure. */ - public function get_cropped_thumbnail( $attachment_id, $size_data ) { + public function get_cropped_thumbnail( int $attachment_id, array $size_data ) { /** * Filters the image URL to send to Computer Vision for smart cropping. A non-null value will override default * plugin behavior. @@ -295,7 +295,7 @@ public function get_cropped_thumbnail( $attachment_id, $size_data ) { * * @return string */ - public function get_api_url() { + public function get_api_url(): string { return sprintf( '%s%s', trailingslashit( $this->settings['endpoint_url'] ), static::API_PATH ); } @@ -307,7 +307,7 @@ public function get_api_url() { * @param array $data Data for an attachment image size. * @return string|\WP_Error */ - public function request_cropped_thumbnail( $data ) { + public function request_cropped_thumbnail( array $data ) { $url = add_query_arg( [ 'height' => $data['height'], diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index 7b52ff3a0..d48c1c6c4 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -5,10 +5,9 @@ namespace Classifai\Providers\Azure; -use \Classifai\Admin\SavePostHandler; -use \Classifai\Providers\Provider; -use \Classifai\Watson\Normalizer; -use \Classifai\Features\TextToSpeech; +use Classifai\Providers\Provider; +use Classifai\Watson\Normalizer; +use Classifai\Features\TextToSpeech; use stdClass; use WP_Http; use WP_REST_Server; @@ -138,8 +137,6 @@ public function register() { /** * Render the provider fields. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -202,7 +199,7 @@ public function render_provider_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'api_key' => '', 'endpoint_url' => '', @@ -225,7 +222,7 @@ public function get_default_provider_settings() { * @param array $new_settings The settings being saved. * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $is_credentials_changed = false; @@ -291,7 +288,7 @@ public function sanitize_settings( $new_settings ) { * @param array $args Overridable args. * @return array */ - public function connect_to_service( array $args = array() ) { + public function connect_to_service( array $args = array() ): array { $settings = $this->feature_instance->get_settings( static::ID ); $default = array( @@ -387,7 +384,7 @@ public function connect_to_service( array $args = array() ) { * * @return array */ - public function get_voices_select_options() { + public function get_voices_select_options(): array { $settings = $this->feature_instance->get_settings( static::ID ); $voices = $settings['voices']; $options = array(); @@ -425,7 +422,7 @@ public function get_voices_select_options() { * points to a non-existent post returns `null`. Defaults to global $post. * @return bool The initial state of audio generation. Default true. */ - public function get_audio_generation_initial_state( $post = null ) { + public function get_audio_generation_initial_state( $post = null ): bool { /** * Initial state of the audio generation toggle when no audio already exists for the post. * @@ -450,7 +447,7 @@ public function get_audio_generation_initial_state( $post = null ) { * points to a non-existent post returns `null`. Defaults to global $post. * @return bool The subsequent state of audio generation. Default false. */ - public function get_audio_generation_subsequent_state( $post = null ) { + public function get_audio_generation_subsequent_state( $post = null ): bool { /** * Subsequent state of the audio generation toggle when audio exists for the post. * @@ -540,7 +537,7 @@ public function add_synthesize_speech_meta_to_rest_api() { * @param int $post_id Post ID. * @return bool|int|WP_Error */ - public function synthesize_speech( $post_id ) { + public function synthesize_speech( int $post_id ) { if ( empty( $post_id ) ) { return new \WP_Error( 'azure_text_to_speech_post_id_missing', @@ -690,10 +687,10 @@ public function synthesize_speech( $post_id ) { /** * Handles audio generation on rest updates / inserts. * - * @param WP_Post $post Inserted or updated post object. - * @param WP_REST_Request $request Request object. + * @param \WP_Post $post Inserted or updated post object. + * @param \WP_REST_Request $request Request object. */ - public function rest_handle_audio( $post, $request ) { + public function rest_handle_audio( \WP_Post $post, \WP_REST_Request $request ) { $audio_id = get_post_meta( $request->get_param( 'id' ), self::AUDIO_ID_KEY, true ); // Since we have dynamic generation option agnostic to meta saves we need a flag to differentiate audio generation accurately @@ -719,7 +716,7 @@ public function rest_handle_audio( $post, $request ) { * * @param string $post_type Post type. */ - public function add_meta_box( $post_type ) { + public function add_meta_box( string $post_type ) { if ( ! in_array( $post_type, get_tts_supported_post_types(), true ) ) { return; } @@ -740,7 +737,7 @@ public function add_meta_box( $post_type ) { * * @param \WP_Post $post WP_Post object. */ - public function render_meta_box( $post ) { + public function render_meta_box( \WP_Post $post ) { wp_nonce_field( 'classifai_text_to_speech_meta_action', 'classifai_text_to_speech_meta' ); $source_url = false; @@ -818,7 +815,7 @@ public function render_meta_box( $post ) { * * @param int $post_id Post ID. */ - public function save_post_metadata( $post_id ) { + public function save_post_metadata( int $post_id ) { if ( ! in_array( get_post_type( $post_id ), get_tts_supported_post_types(), true ) ) { return; @@ -849,7 +846,7 @@ public function save_post_metadata( $post_id ) { * @param string $content Post content. * @return string */ - public function render_post_audio_controls( $content ) { + public function render_post_audio_controls( string $content ): string { $_post = get_post(); @@ -992,7 +989,7 @@ public function render_post_audio_controls( $content ) { * * @return array */ - protected function get_post_types_select_options() { + protected function get_post_types_select_options(): array { $post_types = get_post_types_for_language_settings(); $options = array(); @@ -1005,8 +1002,6 @@ protected function get_post_types_select_options() { /** * Registers REST endpoints for this provider. - * - * @return void */ public function register_endpoints() { register_rest_route( @@ -1085,15 +1080,15 @@ public function speech_synthesis_permissions_check( WP_REST_Request $request ) { * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; if ( $this->feature_instance instanceof TextToSpeech ) { - $post_types = array_filter( + $post_types = array_filter( $settings['post_types'], - function( $value ) { + function ( $value ) { return '0' !== $value; } ); From 84c217aa448b1d70d307656a97a6fbe7ca041db8 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 23 Jan 2024 16:26:49 -0700 Subject: [PATCH 103/127] Fix all remaining PHPCS issues --- includes/Classifai/Command/ClassifaiCommand.php | 2 +- .../Classifai/Providers/Azure/ComputerVision.php | 2 +- .../Classifai/Providers/Azure/Personalizer.php | 2 +- includes/Classifai/Providers/OpenAI/ChatGPT.php | 14 +++++++------- includes/Classifai/Providers/OpenAI/Embeddings.php | 2 +- includes/Classifai/Providers/Watson/NLU.php | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index 169a42bb7..db62ba9f9 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -880,7 +880,7 @@ public function crop( $args = [], $opts = [] ) { $smart_cropping = new SmartCropping( $settings ); if ( ! $smart_cropping->should_crop( $size ) ) { - continue; + break; } $data = [ diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 6c98b0ca4..75c81f23e 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -566,7 +566,7 @@ public function attachment_pdf_data_meta_box( \WP_Post $post ) { * Adds the rescan buttons to the media modal. * * @param array $form_fields Array of fields - * @param \WP_post $post Post object for the attachment being viewed. + * @param \WP_Post $post Post object for the attachment being viewed. */ public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ) { $pdf_to_text = new PDFTextExtraction(); diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 514f084f1..e0b63354b 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -800,7 +800,7 @@ public function ajax_render_recommended_content() { } } - echo $this->render_recommended_content( $attributes ); + echo $this->render_recommended_content( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit(); } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 5b8ea0b98..0039dee11 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -1416,18 +1416,18 @@ public function get_debug_information() { $debug_info = []; if ( $this->feature_instance instanceof TitleGeneration ) { - $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles']; + $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles']; $debug_info[ __( 'Generate title prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_title_prompt'] ); - $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_title_generation_latest_response' ) ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_title_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ExcerptGeneration ) { - $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length']; + $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length']; $debug_info[ __( 'Generate excerpt prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_excerpt_prompt'] ); - $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_excerpt_generation_latest_response' ) ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_excerpt_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ContentResizing ) { - $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions']; - $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ); + $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions']; + $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ); $debug_info[ __( 'Condense text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['condense_text_prompt'] ); - $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_content_resizing_latest_response' ) ); + $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_content_resizing_latest_response' ) ); } return apply_filters( diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 8f08103c8..c0ea8d48c 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -973,7 +973,7 @@ public function render_threshold_field( array $args, string $option_value, strin

    ', esc_attr( $this->feature_instance->get_option_name() ), - $args['option_index'], + esc_attr( $args['option_index'] ), esc_attr( $args['label_for'] ?? '' ), esc_attr( $option_value ), esc_html__( 'Threshold (%)', 'classifai' ), diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index f2981ae73..0114e1b96 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -769,7 +769,7 @@ public function register_endpoints() { 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'generate_post_tags' ], 'args' => array( - 'id' => array( + 'id' => array( 'required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint', From 46ea2391cc04a6a4e39b1c965605b39bcec06d5e Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 09:04:08 -0700 Subject: [PATCH 104/127] Start fixing unit tests --- tests/Classifai/Azure/ComputerVisionTest.php | 6 +++--- tests/Classifai/Providers/Azure/SmartCroppingTest.php | 2 +- tests/Classifai/Watson/NLUSettingsTest.php | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Classifai/Azure/ComputerVisionTest.php b/tests/Classifai/Azure/ComputerVisionTest.php index 14b74d326..7c603cfa4 100644 --- a/tests/Classifai/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Azure/ComputerVisionTest.php @@ -29,7 +29,7 @@ function set_up() { /** * Tests the function providing debug information. */ - public function test_get_provider_debug_information() { + public function test_get_debug_information() { $this->assertEquals( [ 'Authenticated', @@ -39,7 +39,7 @@ public function test_get_provider_debug_information() { 'Latest response - Smart Cropping', 'Latest response - OCR', ], - array_keys( $this->provider->get_provider_debug_information() ) + array_keys( $this->provider->test_get_debug_information() ) ); $this->assertEquals( @@ -51,7 +51,7 @@ public function test_get_provider_debug_information() { 'Latest response - Smart Cropping' => 'N/A', 'Latest response - OCR' => 'N/A', ], - $this->provider->get_provider_debug_information( + $this->provider->test_get_debug_information( [ 'url' => 'my-azure-url.com', 'caption_threshold' => 77, diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index 23d241581..98b861525 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -36,7 +36,7 @@ public function tear_down() { * @return SmartCropping */ public function get_smart_cropping( - array $args = [ 'url' => 'my-api-url.com', 'api_key' => 'my-key' ] + array $args = [ 'endpoint_url' => 'my-api-url.com', 'api_key' => 'my-key' ] ) : SmartCropping { return new SmartCropping( $args ); } diff --git a/tests/Classifai/Watson/NLUSettingsTest.php b/tests/Classifai/Watson/NLUSettingsTest.php index 783cb013a..86fccb904 100644 --- a/tests/Classifai/Watson/NLUSettingsTest.php +++ b/tests/Classifai/Watson/NLUSettingsTest.php @@ -55,7 +55,7 @@ public function test_retrieving_options() { /** * Tests the function providing debug information. */ - public function test_get_provider_debug_information() { + public function test_get_debug_information() { $this->assertEquals( [ 'Configured', @@ -65,7 +65,7 @@ public function test_get_provider_debug_information() { 'Features', 'Latest response', ], - array_keys( $this->provider->get_provider_debug_information() ) + array_keys( $this->provider->get_debug_information() ) ); $this->assertEquals( @@ -77,7 +77,7 @@ public function test_get_provider_debug_information() { 'Features' => '{"feature":true}', 'Latest response' => 'N/A', ], - $this->provider->get_provider_debug_information( + $this->provider->get_debug_information( [ 'credentials' => [ 'watson_url' => 'my-watson-url.com', From 53d789f8d581fb1710c73f3a996c0cdd644e7e03 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 09:09:36 -0700 Subject: [PATCH 105/127] A couple more PHPCS and test fixes --- includes/Classifai/Providers/Watson/NLU.php | 6 +++--- tests/Classifai/Taxonomy/AbstractTaxonomyTest.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 0114e1b96..75a51ff0a 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -159,10 +159,10 @@ public function render_provider_fields() { function ( $args = [] ) { printf( '%s', - $args['class'] ? esc_attr( $args['class'] ) : '', - $this->use_username_password() + $args['class'] ? esc_attr( $args['class'] ) : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->use_username_password() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ? esc_html__( 'Use a username/password instead?', 'classifai' ) - : esc_html__( 'Use an API Key instead?', 'classifai' ) + : esc_html__( 'Use an API Key instead?', 'classifai' ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); }, $this->feature_instance->get_option_name(), diff --git a/tests/Classifai/Taxonomy/AbstractTaxonomyTest.php b/tests/Classifai/Taxonomy/AbstractTaxonomyTest.php index 571b0f00b..1fff4f472 100644 --- a/tests/Classifai/Taxonomy/AbstractTaxonomyTest.php +++ b/tests/Classifai/Taxonomy/AbstractTaxonomyTest.php @@ -54,19 +54,19 @@ class ThingTaxonomy extends AbstractTaxonomy { public $name = 'thing'; - public function get_name() { + public function get_name(): string { return $this->name; } - public function get_singular_label() { + public function get_singular_label(): string { return $this->name; } - public function get_plural_label() { + public function get_plural_label(): string { return $this->name . 's'; } - public function get_visibility() { + public function get_visibility(): bool { return true; } From 63c4144f751209aecfe0765ec9ec5fc8ef088dcc Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 09:52:08 -0700 Subject: [PATCH 106/127] Clean up a few more test failures --- includes/Classifai/Helpers.php | 2 +- .../Providers/Azure/ComputerVision.php | 2 +- includes/Classifai/Providers/Watson/NLU.php | 1 + .../Classifai/Services/ServicesManager.php | 18 +++++----- tests/Classifai/Azure/ComputerVisionTest.php | 2 +- .../Providers/Azure/ComputerVisionTest.php | 34 ++++++++++--------- .../Providers/Azure/SmartCroppingTest.php | 8 ++--- tests/Classifai/Watson/APIRequestTest.php | 4 +-- tests/Classifai/Watson/NLUSettingsTest.php | 4 +-- 9 files changed, 40 insertions(+), 35 deletions(-) diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 6706daf70..374df784d 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -290,7 +290,7 @@ function get_post_statuses_for_language_settings(): array { * * Defaults to 'post'. * - * return array + * @return array */ function get_supported_post_types(): array { $feature = new Classification(); diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 75c81f23e..6659d3e39 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -758,7 +758,7 @@ public function maybe_rescan_image( int $attachment_id ) { * @param int $attachment_id Attachment ID. * @return array Filtered attachment metadata. */ - public function smart_crop_image( array $metadata, int $attachment_id ): array { + public function smart_crop_image( $metadata, int $attachment_id ): array { $feature = new ImageCropping(); $settings = $feature->get_settings( static::ID ); diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 75a51ff0a..9e5ad98be 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -80,6 +80,7 @@ public function __construct( $feature = null ) { ], ]; + // TODO: if no feature is passed in, seems like this might break $this->feature_instance = $feature; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index c78aecc8c..b5882e432 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -144,10 +144,10 @@ public function register_settings() { /** * Sanitize settings. * - * @param array $settings The settings to be sanitized. + * @param mixed $settings The settings to be sanitized. * @return array */ - public function sanitize_settings( array $settings ): array { + public function sanitize_settings( $settings ): array { $new_settings = []; if ( isset( $settings['email'] ) @@ -258,7 +258,7 @@ protected function get_menu_title() { * * @return array */ - public function get_services() { + public function get_services(): array { return $this->services; } @@ -316,12 +316,13 @@ public function render_settings_page() { /** * Hit license API to see if key/email is valid * - * @param string $email Email address. - * @param string $license_key License key. * @since 1.2 + * + * @param string $email Email address. + * @param string $license_key License key. * @return bool */ - public function check_license_key( $email, $license_key ) { + public function check_license_key( string $email, string $license_key ): bool { $request = wp_remote_post( 'https://classifaiplugin.com/wp-json/classifai-theme/v1/validate-license', @@ -348,12 +349,13 @@ public function check_license_key( $email, $license_key ) { /** * Adds debug information to the ClassifAI Site Health screen. * + * @since 1.4.0 + * * @param array $debug_information Array of lines representing debug information. * @param array|null $settings Settings array. If empty, will be fetched. * @return array Array with lines added. - * @since 1.4.0 */ - public function add_debug_information( $debug_information, $settings = null ) { + public function add_debug_information( array $debug_information, $settings = null ): array { if ( is_null( $settings ) ) { $settings = $this->sanitize_settings( $this->get_settings() ); } diff --git a/tests/Classifai/Azure/ComputerVisionTest.php b/tests/Classifai/Azure/ComputerVisionTest.php index 7c603cfa4..74e6cc354 100644 --- a/tests/Classifai/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Azure/ComputerVisionTest.php @@ -39,7 +39,7 @@ public function test_get_debug_information() { 'Latest response - Smart Cropping', 'Latest response - OCR', ], - array_keys( $this->provider->test_get_debug_information() ) + array_keys( $this->provider->get_debug_information() ) ); $this->assertEquals( diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index f8ff6ca9a..3b592f11a 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -140,24 +140,26 @@ public function test_set_image_meta_data() { } public function test_alt_text_option_reformatting() { - add_option( 'classifai_computer_vision', array() ); + add_option( 'classifai_feature_descriptive_text_generator', array() ); $options = array( - 'valid' => false, - 'url' => '', - 'api_key' => '', - 'enable_image_captions' => '1', - 'enable_image_tagging' => '1', - 'enable_smart_cropping' => 'no', - 'enable_ocr' => 'no', - 'enable_read_pdf' => 'no', - 'caption_threshold' => 75, - 'tag_threshold' => 70, - 'image_tag_taxonomy' => 'classifai-image-tags', + 'status' => '1', + 'provider' => 'ms_computer_vision', + 'ms_computer_vision' => array( + 'endpoint_url' => '', + 'api_key' => '', + 'descriptive_text_fields' => array( + 'alt' => 'alt', + 'caption' => '0', + 'description' => '0', + ), + 'descriptive_confidence_threshold' => '75', + 'authenticated' => true, + ), ); - // Test with `enable_image_captions` set to `1`. - add_filter( 'pre_option_classifai_computer_vision', function() use( $options ) { + // Test with `descriptive_text_fields` set to `alt`. + add_filter( 'pre_option_classifai_feature_descriptive_text_generator', function() use( $options ) { return $options; } ); @@ -172,8 +174,8 @@ public function test_alt_text_option_reformatting() { ); // Test with `enable_image_captions` set to `no`. - $options['enable_image_captions'] = 'no'; - add_filter( 'pre_option_classifai_computer_vision', function() use( $options ) { + $options['ms_computer_vision']['descriptive_text_fields']['alt'] = '0'; + add_filter( 'pre_option_classifai_feature_descriptive_text_generator', function() use( $options ) { return $options; } ); diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index 98b861525..0406dc57a 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -144,8 +144,8 @@ public function test_get_cropped_thumbnail() { $this->assertWPError( $this->get_smart_cropping( [ - 'url' => 'my-bad-url.com', - 'api_key' => 'my-key', + 'endpoint_url' => 'my-bad-url.com', + 'api_key' => 'my-key', ] )->get_cropped_thumbnail( $attachment, @@ -214,8 +214,8 @@ public function test_request_cropped_thumbnail() { $this->assertWPError( $this->get_smart_cropping( [ - 'url' => 'my-bad-url.com', - 'api_key' => 'my-key', + 'endpoint_url' => 'my-bad-url.com', + 'api_key' => 'my-key', ] )->request_cropped_thumbnail( [ diff --git a/tests/Classifai/Watson/APIRequestTest.php b/tests/Classifai/Watson/APIRequestTest.php index 85b1f6eb7..44521c2d3 100644 --- a/tests/Classifai/Watson/APIRequestTest.php +++ b/tests/Classifai/Watson/APIRequestTest.php @@ -28,7 +28,7 @@ function test_it_uses_constant_username_if_present() { } function test_it_uses_option_username_if_present() { - update_option( 'classifai_watson_nlu', [ 'credentials' => [ 'watson_username' => 'foo-option' ] ] ); + update_option( 'classifai_feature_classification', [ 'ibm_watson_nlu' => [ 'username' => 'foo-option' ] ] ); $actual = $this->request->get_username(); $this->assertEquals( 'foo-option', $actual ); } @@ -49,7 +49,7 @@ function test_it_constant_password_if_present() { } function test_it_uses_option_password_if_present() { - update_option( 'classifai_watson_nlu', [ 'credentials' => [ 'watson_password' => 'foo-option' ] ] ); + update_option( 'classifai_feature_classification', [ 'ibm_watson_nlu' => [ 'password' => 'foo-option' ] ] ); $actual = $this->request->get_password(); $this->assertEquals( 'foo-option', $actual ); } diff --git a/tests/Classifai/Watson/NLUSettingsTest.php b/tests/Classifai/Watson/NLUSettingsTest.php index 86fccb904..8439dd1d4 100644 --- a/tests/Classifai/Watson/NLUSettingsTest.php +++ b/tests/Classifai/Watson/NLUSettingsTest.php @@ -6,7 +6,7 @@ use \WP_UnitTestCase; use \Classifai\Providers\Watson\NLU; - +use Classifai\Features\Classification; /** * Class NLUSettingsTest @@ -33,7 +33,7 @@ function set_up() { // Add the settings update_option( 'classifai_watson_nlu', $this->settings ); - $this->provider = new NLU( 'service_name' ); + $this->provider = new NLU( new Classification() ); } /** From dff258a311eae2583521cc7e1cd5c038f5c5a1c6 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 14:14:02 -0700 Subject: [PATCH 107/127] Migrate all Excerpt Generation specific items from the Provider class to the Feature class --- includes/Classifai/Admin/BulkActions.php | 2 +- .../Classifai/Command/ClassifaiCommand.php | 4 +- .../Classifai/Features/ExcerptGeneration.php | 287 +++++++++++++-- includes/Classifai/Features/Feature.php | 60 +++- includes/Classifai/Helpers.php | 85 +++++ .../Classifai/Providers/OpenAI/ChatGPT.php | 332 ++---------------- src/js/post-excerpt/panel.js | 2 +- 7 files changed, 424 insertions(+), 348 deletions(-) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index c20678779..e5b1b20bd 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -147,7 +147,7 @@ function ( $feature ) { break; case ExcerptGeneration::ID: - $excerpt = ( new ExcerptGeneration() )->run( $post_id ); + $excerpt = ( new ExcerptGeneration() )->run( $post_id, 'excerpt' ); $action = $doaction; if ( ! is_wp_error( $excerpt ) ) { diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index db62ba9f9..6a4fc5f5f 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -616,7 +616,7 @@ public function generate_excerpt( $args = [], $opts = [] ) { continue; } - $result = ( new ExcerptGeneration() )->run( (int) $post->ID, [] ); + $result = ( new ExcerptGeneration() )->run( $post->ID, 'excerpt' ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %d: %s', $post->ID, $result->get_error_message() ), false ); @@ -669,7 +669,7 @@ public function generate_excerpt( $args = [], $opts = [] ) { continue; } - $result = ( new ExcerptGeneration() )->run( (int) $post->ID, [] ); + $result = ( new ExcerptGeneration() )->run( $post_id, 'excerpt' ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %d: %s', $post_id, $result->get_error_message() ), false ); diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 1b3c5801e..6ae11eb36 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -4,6 +4,12 @@ use Classifai\Services\LanguageProcessing; use Classifai\Providers\OpenAI\ChatGPT; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\get_asset_info; +use function Classifai\sanitize_prompts; /** * Class ExcerptGeneration @@ -16,6 +22,13 @@ class ExcerptGeneration extends Feature { */ const ID = 'feature_excerpt_generation'; + /** + * Prompt for generating excerpts + * + * @var string + */ + protected $prompt = 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.'; + /** * Constructor. */ @@ -31,6 +44,221 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + */ + public function setup() { + parent::setup(); + + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'generate-excerpt(?:/(?P\d+))?', + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Post ID to generate excerpt for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_excerpt_permissions_check' ], + ], + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'content' => [ + 'required' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Content to summarize into an excerpt.', 'classifai' ), + ], + 'title' => [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Title of content we want a summary for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_excerpt_permissions_check' ], + ], + ] + ); + } + + /** + * Check if a given request has access to generate an excerpt. + * + * This check ensures we have a proper post ID, the current user + * making the request has access to that post, that we are + * properly authenticated with OpenAI and that excerpt generation + * is turned on. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function generate_excerpt_permissions_check( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + + // Ensure we have a logged in user that can edit the item. + if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { + return false; + } + + $post_type = get_post_type( $post_id ); + $post_type_obj = get_post_type_object( $post_type ); + + // Ensure the post type is allowed in REST endpoints. + if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { + return false; + } + + // Ensure the feature is enabled. Also runs a user check. + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( '/classifai/v1/generate-excerpt' === $route ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'id' ), + 'excerpt', + [ + 'content' => $request->get_param( 'content' ), + 'title' => $request->get_param( 'title' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Enqueue the editor scripts. + */ + public function enqueue_editor_assets() { + global $post; + + if ( empty( $post ) ) { + return; + } + + // This script removes the core excerpt panel and replaces it with our own. + wp_enqueue_script( + 'classifai-post-excerpt', + CLASSIFAI_PLUGIN_URL . 'dist/post-excerpt.js', + array_merge( get_asset_info( 'post-excerpt', 'dependencies' ), [ 'lodash' ] ), + get_asset_info( 'post-excerpt', 'version' ), + true + ); + } + + /** + * Enqueue the admin scripts. + * + * @param string $hook_suffix The current admin page. + */ + public function enqueue_admin_assets( string $hook_suffix ) { + // Load jQuery UI Dialog for prompt deletion. + if ( + ( + 'tools_page_classifai' === $hook_suffix + && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_excerpt_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) || + 'admin_page_classifai_setup' === $hook_suffix + ) { + wp_enqueue_script( 'jquery-ui-dialog' ); + wp_enqueue_style( 'wp-jquery-ui-dialog' ); + + add_action( + 'admin_footer', + static function () { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + ); + } + + // Load asset in new post and edit post screens. + if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { + $screen = get_current_screen(); + + // Load the assets for the classic editor. + if ( $screen && ! $screen->is_block_editor() ) { + if ( post_type_supports( $screen->post_type, 'excerpt' ) ) { + wp_enqueue_style( + 'classifai-generate-title-classic-css', + CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.css', + [], + get_asset_info( 'generate-title-classic', 'version' ), + 'all' + ); + + wp_enqueue_script( + 'classifai-generate-excerpt-classic-js', + CLASSIFAI_PLUGIN_URL . 'dist/generate-excerpt-classic.js', + array_merge( get_asset_info( 'generate-excerpt-classic', 'dependencies' ), array( 'wp-api' ) ), + get_asset_info( 'generate-excerpt-classic', 'version' ), + true + ); + + wp_add_inline_script( + 'classifai-generate-excerpt-classic-js', + sprintf( + 'var classifaiGenerateExcerpt = %s;', + wp_json_encode( + [ + 'path' => '/classifai/v1/generate-excerpt/', + 'buttonText' => __( 'Generate excerpt', 'classifai' ), + 'regenerateText' => __( 'Re-generate excerpt', 'classifai' ), + ] + ) + ), + 'before' + ); + } + } + + wp_enqueue_style( + 'classifai-language-processing-style', + CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css', + [], + get_asset_info( 'language-processing', 'version' ), + ); + } + } + /** * Get the description for the enable field. * @@ -54,6 +282,21 @@ public function add_custom_settings_fields() { } } + add_settings_field( + 'generate_excerpt_prompt', + esc_html__( 'Prompt', 'classifai' ), + [ $this, 'render_prompt_repeater_field' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'generate_excerpt_prompt', + 'placeholder' => $this->prompt, + 'default_value' => $settings['generate_excerpt_prompt'], + 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), + ] + ); + add_settings_field( 'post_types', esc_html__( 'Allowed post types', 'classifai' ), @@ -92,9 +335,16 @@ public function add_custom_settings_fields() { */ public function get_feature_default_settings(): array { return [ - 'post_types' => [], - 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), - 'provider' => ChatGPT::ID, + 'generate_excerpt_prompt' => [ + [ + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => $this->prompt, + 'original' => 1, + ], + ], + 'post_types' => [], + 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), + 'provider' => ChatGPT::ID, ]; } @@ -108,6 +358,8 @@ public function sanitize_default_feature_settings( array $new_settings ): array $settings = $this->get_settings(); $post_types = \Classifai\get_post_types_for_language_settings(); + $new_settings['generate_excerpt_prompt'] = sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); + $new_settings['length'] = absint( $settings['length'] ?? $new_settings['length'] ); foreach ( $post_types as $post_type ) { @@ -124,33 +376,4 @@ public function sanitize_default_feature_settings( array $new_settings ): array return $new_settings; } - - /** - * Runs the feature. - * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ChatGPT::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ChatGPT::ID === $provider_instance::ID ) { - /** @var ChatGPT $provider_instance */ - return call_user_func_array( - [ $provider_instance, 'generate_excerpt' ], - [ ...$args ] - ); - } - - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); - } } diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 8ed30979c..cfca11279 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -2,6 +2,8 @@ namespace Classifai\Features; +use WP_REST_Request; +use WP_Error; use function Classifai\find_provider_class; abstract class Feature { @@ -596,7 +598,7 @@ class="" public function render_prompt_repeater_field( array $args ) { $option_index = $args['option_index'] ?? false; $setting_index = $this->get_settings( $option_index ); - $prompts = $setting_index[ $args['label_for'] ] ?? ''; + $prompts = $setting_index[ $args['label_for'] ] ?? []; $class = $args['class'] ?? 'large-text'; $placeholder = $args['placeholder'] ?? ''; $field_name_prefix = sprintf( @@ -614,8 +616,8 @@ public function render_prompt_repeater_field( array $args ) {
    @@ -1191,4 +1193,56 @@ protected function get_data_attribute( array $args ): string { return $data_attr_str; } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() {} + + /** + * Generic callback that can be used for all custom endpoints. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response|WP_Error + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + return rest_ensure_response( new WP_Error( 'invalid_route', esc_html__( 'Invalid route.', 'classifai' ) ) ); + } + + /** + * Runs the feature. + * + * @param mixed ...$args Arguments required by the feature depending on the provider selected. + * @return mixed + */ + public function run( ...$args ) { + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $provider_instance = $this->get_feature_provider_instance( $provider_id ); + + if ( ! is_callable( [ $provider_instance, 'rest_endpoint_callback' ] ) ) { + return new WP_Error( 'invalid_route', esc_html__( 'The selected provider does not have a valid callback in place.', 'classifai' ) ); + } + + /** + * Filter the results of running the feature. + * + * @since 3.0.0 + * @hook classifai_{feature}_run + * + * @param {mixed} $result Result of running the feature. + * @param {Classifai\Providers} $provider_instance Provider used. + * @param {mixed} $args Arguments used by the feature. + * @param {Feature} $this Current feature class. + * + * @return {mixed} Results. + */ + return apply_filters( + 'classifai_' . static::ID . '_run', + $provider_instance->rest_endpoint_callback( ...$args ), + $provider_instance, + $args, + $this + ); + } } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 374df784d..d4326f31e 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -848,3 +848,88 @@ function render_disable_feature_link( string $feature ) { sanitize_text_field( $prompt['title'] ), + 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), + 'default' => absint( $default ), + 'original' => absint( $prompt['original'] ), + ); + }, + $prompts + ); + + // If there is no default, use the first prompt. + if ( false === $has_default && ! empty( $prompts ) ) { + $prompts[0]['default'] = 1; + } + + return $prompts; + } + + return array(); +} + +/** + * Get the default prompt for use. + * + * @since 2.4.0 + * + * @param array $prompts Prompt data. + * @return string|null Default prompt. + */ +function get_default_prompt( array $prompts ): ?string { + $default_prompt = null; + + if ( ! empty( $prompts ) ) { + $prompt_data = array_filter( + $prompts, + function ( $prompt ) { + return $prompt['default'] && ! $prompt['original']; + } + ); + + if ( ! empty( $prompt_data ) ) { + $default_prompt = current( $prompt_data )['prompt']; + } elseif ( ! empty( $prompts[0]['prompt'] ) && ! $prompts[0]['original'] ) { + // If there is no default, use the first prompt, unless it's the original prompt. + $default_prompt = $prompts[0]['prompt']; + } + } + + return $default_prompt; +} diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 0039dee11..8291da3a3 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -14,6 +14,8 @@ use WP_REST_Request; use WP_Error; use function Classifai\get_asset_info; +use function Classifai\sanitize_prompts; +use function Classifai\get_default_prompt; class ChatGPT extends Provider { @@ -89,8 +91,6 @@ public function __construct( $feature_instance = null ) { /** * Render the provider fields. - * - * @return void */ public function render_provider_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -128,10 +128,6 @@ public function render_provider_fields() { $this->add_title_generation_fields(); break; - case ExcerptGeneration::ID: - $this->add_excerpt_generation_fields(); - break; - case ContentResizing::ID: $this->add_content_resizing_fields(); break; @@ -141,9 +137,7 @@ public function render_provider_fields() { } /** - * Renders te fields for Title generation feature. - * - * @return void + * Renders fields for the Title generation feature. */ private function add_title_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -184,34 +178,7 @@ private function add_title_generation_fields() { } /** - * Renders te fields for Excerpt generation feature. - * - * @return void - */ - private function add_excerpt_generation_fields() { - $settings = $this->feature_instance->get_settings( static::ID ); - - add_settings_field( - static::ID . '_generate_excerpt_prompt', - $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), - [ $this->feature_instance, 'render_prompt_repeater_field' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'generate_excerpt_prompt', - 'placeholder' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), - 'default_value' => $settings['generate_excerpt_prompt'], - 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - } - - /** - * Renders te fields for Content resizing feature. - * - * @return void + * Renders fields for the Content resizing feature. */ private function add_content_resizing_fields() { $settings = $this->feature_instance->get_settings( static::ID ); @@ -272,7 +239,7 @@ private function add_content_resizing_fields() { * * @return array */ - public function get_default_provider_settings() { + public function get_default_provider_settings(): array { $common_settings = [ 'api_key' => '', 'authenticated' => false, @@ -294,20 +261,6 @@ public function get_default_provider_settings() { ] ); - case ExcerptGeneration::ID: - return array_merge( - $common_settings, - [ - 'generate_excerpt_prompt' => array( - array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.', 'classifai' ), - 'original' => 1, - ), - ), - ] - ); - case ContentResizing::ID: return array_merge( $common_settings, @@ -340,7 +293,7 @@ public function get_default_provider_settings() { * @param array $new_settings The settings array. * @return array */ - public function sanitize_settings( $new_settings ) { + public function sanitize_settings( array $new_settings ): array { $settings = $this->feature_instance->get_settings(); $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; @@ -348,17 +301,13 @@ public function sanitize_settings( $new_settings ) { if ( $this->feature_instance instanceof TitleGeneration ) { $new_settings[ static::ID ]['number_of_titles'] = $this->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); - $new_settings[ static::ID ]['generate_title_prompt'] = $this->sanitize_prompts( 'generate_title_prompt', $new_settings ); - } - - if ( $this->feature_instance instanceof ExcerptGeneration ) { - $new_settings[ static::ID ]['generate_excerpt_prompt'] = $this->sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); + $new_settings[ static::ID ]['generate_title_prompt'] = sanitize_prompts( 'generate_title_prompt', $new_settings ); } if ( $this->feature_instance instanceof ContentResizing ) { $new_settings[ static::ID ]['number_of_suggestions'] = $this->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); - $new_settings[ static::ID ]['condense_text_prompt'] = $this->sanitize_prompts( 'condense_text_prompt', $new_settings ); - $new_settings[ static::ID ]['expand_text_prompt'] = $this->sanitize_prompts( 'expand_text_prompt', $new_settings ); + $new_settings[ static::ID ]['condense_text_prompt'] = sanitize_prompts( 'condense_text_prompt', $new_settings ); + $new_settings[ static::ID ]['expand_text_prompt'] = sanitize_prompts( 'expand_text_prompt', $new_settings ); } return $new_settings; @@ -368,10 +317,9 @@ public function sanitize_settings( $new_settings ) { * Sanitisation callback for api key. * * @param array $new_settings The settings array. - * * @return string */ - public function sanitize_api_key( $new_settings ) { + public function sanitize_api_key( array $new_settings ): string { $settings = $this->feature_instance->get_settings(); return sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ?? '' ); } @@ -382,10 +330,9 @@ public function sanitize_api_key( $new_settings ) { * @param string $key The key of the value we are sanitizing. * @param array $new_settings The settings array. * @param array $settings Current array. - * - * @return integer + * @return int */ - public function sanitize_number_of_responses_field( $key, $new_settings, $settings ) { + public function sanitize_number_of_responses_field( string $key, array $new_settings, array $settings ): int { return absint( $new_settings[ static::ID ][ $key ] ?? $settings[ static::ID ][ $key ] ?? '' ); } @@ -430,17 +377,6 @@ public function enqueue_editor_assets() { return; } - if ( ( new ExcerptGeneration() )->is_feature_enabled() ) { - // This script removes the core excerpt panel and replaces it with our own. - wp_enqueue_script( - 'classifai-post-excerpt', - CLASSIFAI_PLUGIN_URL . 'dist/post-excerpt.js', - array_merge( get_asset_info( 'post-excerpt', 'dependencies' ), [ 'lodash' ] ), - get_asset_info( 'post-excerpt', 'version' ), - true - ); - } - if ( ( new TitleGeneration() )->is_feature_enabled() ) { wp_enqueue_script( 'classifai-post-status-info', @@ -487,7 +423,6 @@ public function enqueue_editor_assets() { public function enqueue_admin_assets( string $hook_suffix ) { $prompt_features = array( 'feature_title_generation', - 'feature_excerpt_generation', 'feature_content_resizing', ); @@ -550,42 +485,6 @@ static function () { 'before' ); } - - if ( - post_type_supports( $screen->post_type, 'excerpt' ) && - ( new ExcerptGeneration() )->is_feature_enabled() - ) { - wp_enqueue_style( - 'classifai-generate-title-classic-css', - CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.css', - [], - get_asset_info( 'generate-title-classic', 'version' ), - 'all' - ); - - wp_enqueue_script( - 'classifai-generate-excerpt-classic-js', - CLASSIFAI_PLUGIN_URL . 'dist/generate-excerpt-classic.js', - array_merge( get_asset_info( 'generate-excerpt-classic', 'dependencies' ), array( 'wp-api' ) ), - get_asset_info( 'generate-excerpt-classic', 'version' ), - true - ); - - wp_add_inline_script( - 'classifai-generate-excerpt-classic-js', - sprintf( - 'var classifaiGenerateExcerpt = %s;', - wp_json_encode( - [ - 'path' => '/classifai/v1/openai/generate-excerpt/', - 'buttonText' => __( 'Generate excerpt', 'classifai' ), - 'regenerateText' => __( 'Re-generate excerpt', 'classifai' ), - ] - ) - ), - 'before' - ); - } } wp_enqueue_style( @@ -624,17 +523,17 @@ public function register_generated_titles_template() { * @return string|WP_Error */ public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args = [] ) { - $route_to_call = strtolower( $route_to_call ); if ( ! $post_id || ! get_post( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) ); } - $return = ''; + $route_to_call = strtolower( $route_to_call ); + $return = ''; // Handle all of our routes. switch ( $route_to_call ) { case 'excerpt': - $return = ( new ExcerptGeneration() )->run( $post_id, $args ); + $return = $this->generate_excerpt( $post_id, $args ); break; case 'title': $return = ( new TitleGeneration() )->run( $post_id, $args ); @@ -679,7 +578,7 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); - $excerpt_prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_excerpt_prompt'] ) ?? $this->generate_excerpt_prompt ); + $excerpt_prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['generate_excerpt_prompt'] ) ?? $this->feature_instance->prompt ); // Replace our variables in the prompt. $prompt_search = array( '{{WORDS}}', '{{TITLE}}' ); @@ -783,7 +682,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); - $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['generate_title_prompt'] ) ?? $this->generate_title_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['generate_title_prompt'] ) ?? $this->generate_title_prompt ); /** * Filter the prompt we will send to ChatGPT. @@ -885,9 +784,9 @@ public function resize_content( int $post_id, array $args = array() ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); if ( 'shrink' === $args['resize_type'] ) { - $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['condense_text_prompt'] ) ?? $this->condense_text_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['condense_text_prompt'] ) ?? $this->condense_text_prompt ); } else { - $prompt = esc_textarea( $this->get_default_prompt( $settings[ static::ID ]['expand_text_prompt'] ) ?? $this->expand_text_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['expand_text_prompt'] ) ?? $this->expand_text_prompt ); } /** @@ -975,7 +874,7 @@ public function resize_content( int $post_id, array $args = array() ) { * @param string $post_content The post content. * @return string */ - public function get_content( int $post_id = 0, int $return_length = 0, bool $use_title = true, string $post_content = '' ) { + public function get_content( int $post_id = 0, int $return_length = 0, bool $use_title = true, string $post_content = '' ): string { $tokenizer = new Tokenizer( $this->max_tokens ); $normalizer = new Normalizer(); @@ -1028,93 +927,6 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use return apply_filters( 'classifai_chatgpt_content', $content, $post_id ); } - /** - * Sanitize the prompt data. - * This is used for the repeater field. - * - * @since 2.4.0 - * - * @param array $prompt_key Prompt key. - * @param array $new_settings Settings data. - * - * @return array Sanitized prompt data. - */ - public function sanitize_prompts( $prompt_key = '', array $new_settings ): array { - if ( isset( $new_settings[ self::ID ][ $prompt_key ] ) && is_array( $new_settings[ self::ID ][ $prompt_key ] ) ) { - - $prompts = $new_settings[ self::ID ][ $prompt_key ]; - - // Remove any prompts that don't have a title and prompt. - $prompts = array_filter( - $prompts, - function ( $prompt ) { - return ! empty( $prompt['title'] ) && ! empty( $prompt['prompt'] ); - } - ); - - // Sanitize the prompts and make sure only one prompt is marked as default. - $has_default = false; - - $prompts = array_map( - function ( $prompt ) use ( &$has_default ) { - $default = isset( $prompt['default'] ) && $prompt['default'] && ! $has_default; - - if ( $default ) { - $has_default = true; - } - - return array( - 'title' => sanitize_text_field( $prompt['title'] ), - 'prompt' => sanitize_textarea_field( $prompt['prompt'] ), - 'default' => absint( $default ), - 'original' => absint( $prompt['original'] ), - ); - }, - $prompts - ); - - // If there is no default, use the first prompt. - if ( false === $has_default && ! empty( $prompts ) ) { - $prompts[0]['default'] = 1; - } - - return $prompts; - } - - return array(); - } - - /** - * Get the default prompt for use. - * - * @since 2.4.0 - * - * @param array $prompts Prompt data. - * - * @return string|null Default prompt. - */ - public function get_default_prompt( array $prompts ): ?string { - $default_prompt = null; - - if ( ! empty( $prompts ) ) { - $prompt_data = array_filter( - $prompts, - function ( $prompt ) { - return $prompt['default'] && ! $prompt['original']; - } - ); - - if ( ! empty( $prompt_data ) ) { - $default_prompt = current( $prompt_data )['prompt']; - } elseif ( ! empty( $prompts[0]['prompt'] ) && ! $prompts[0]['original'] ) { - // If there is no default, use the first prompt, unless it's the original prompt. - $default_prompt = $prompts[0]['prompt']; - } - } - - return $default_prompt; - } - /** * Registers REST endpoints for this provider. * @@ -1163,46 +975,6 @@ public function register_endpoints() { ] ); - register_rest_route( - 'classifai/v1/openai', - 'generate-excerpt(?:/(?P\d+))?', - [ - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'generate_post_excerpt' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Post ID to generate excerpt for.', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_post_excerpt_permissions_check' ], - ], - [ - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'generate_post_excerpt' ], - 'args' => [ - 'content' => [ - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Content to summarize into an excerpt.', 'classifai' ), - ], - 'title' => [ - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Title of content we want a summary for.', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_post_excerpt_permissions_check' ], - ], - ] - ); - register_rest_route( 'classifai/v1/openai', 'resize-content', @@ -1292,66 +1064,6 @@ public function generate_post_title_permissions_check( WP_REST_Request $request return true; } - /** - * Handle request to generate excerpt for given post ID. - * - * @param WP_REST_Request $request The full request object. - * @return \WP_REST_Response|WP_Error - */ - public function generate_post_excerpt( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $content = $request->get_param( 'content' ); - $title = $request->get_param( 'title' ); - - return rest_ensure_response( - $this->rest_endpoint_callback( - $post_id, - 'excerpt', - [ - 'content' => $content, - 'title' => $title, - ] - ) - ); - } - - /** - * Check if a given request has access to generate an excerpt. - * - * This check ensures we have a proper post ID, the current user - * making the request has access to that post, that we are - * properly authenticated with OpenAI and that excerpt generation - * is turned on. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function generate_post_excerpt_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - // Ensure we have a logged in user that can edit the item. - if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { - return false; - } - - $post_type = get_post_type( $post_id ); - $post_type_obj = get_post_type_object( $post_type ); - - // Ensure the post type is allowed in REST endpoints. - if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { - return false; - } - - $feature = new ExcerptGeneration(); - - // Ensure the feature is enabled. Also runs a user check. - if ( ! $feature->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation not currently enabled.', 'classifai' ) ); - } - - return true; - } - /** * Handle request to resize content. * @@ -1408,9 +1120,11 @@ public function resize_post_content_permissions_check( WP_REST_Request $request /** * Returns the debug information for the provider settings. * + * TODO: this should be in feature. + * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; diff --git a/src/js/post-excerpt/panel.js b/src/js/post-excerpt/panel.js index 61b38c6f4..e4ba898c3 100644 --- a/src/js/post-excerpt/panel.js +++ b/src/js/post-excerpt/panel.js @@ -83,7 +83,7 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { disabled={ isLoading } data-id={ postId } onClick={ () => - buttonClick( '/classifai/v1/openai/generate-excerpt/' ) + buttonClick( '/classifai/v1/generate-excerpt/' ) } > { buttonText } From 957b28a950c1efec88bb5d7e57acc66c6c744b7a Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 15:23:48 -0700 Subject: [PATCH 108/127] Migrate all Title Generation specific items from the Provider class to the Feature class --- .../Classifai/Features/Classification.php | 2 +- .../Features/DescriptiveTextGenerator.php | 2 +- .../Classifai/Features/ExcerptGeneration.php | 6 +- includes/Classifai/Features/ImageCropping.php | 2 +- .../Classifai/Features/ImageGeneration.php | 2 +- .../Classifai/Features/ImageTagsGenerator.php | 2 +- .../Features/ImageTextExtraction.php | 2 +- .../Classifai/Features/PDFTextExtraction.php | 2 +- includes/Classifai/Features/TextToSpeech.php | 2 +- .../Classifai/Features/TitleGeneration.php | 352 ++++++++++++++++-- includes/Classifai/Helpers.php | 12 + .../Classifai/Providers/OpenAI/ChatGPT.php | 290 +-------------- 12 files changed, 354 insertions(+), 322 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 0cdc58fc2..ff7591cbe 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -9,7 +9,7 @@ use function Classifai\get_post_types_for_language_settings; /** - * Class TitleGeneration + * Class Classification */ class Classification extends Feature { /** diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 4a4888080..072d4b9fd 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -6,7 +6,7 @@ use Classifai\Services\ImageProcessing; /** - * Class TitleGeneration + * Class DescriptiveTextGenerator */ class DescriptiveTextGenerator extends Feature { /** diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 6ae11eb36..bd83298a1 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -23,11 +23,11 @@ class ExcerptGeneration extends Feature { const ID = 'feature_excerpt_generation'; /** - * Prompt for generating excerpts + * Prompt for generating excerpts. * * @var string */ - protected $prompt = 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.'; + public $prompt = 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.'; /** * Constructor. @@ -293,7 +293,7 @@ public function add_custom_settings_fields() { 'label_for' => 'generate_excerpt_prompt', 'placeholder' => $this->prompt, 'default_value' => $settings['generate_excerpt_prompt'], - 'description' => esc_html__( "Enter your custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), + 'description' => esc_html__( "Add a custom prompt. Note the following variables that can be used in the prompt and will be replaced with content: {{WORDS}} will be replaced with the desired excerpt length setting. {{TITLE}} will be replaced with the item's title.", 'classifai' ), ] ); diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 66db07de6..acd0d70b1 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -6,7 +6,7 @@ use Classifai\Services\ImageProcessing; /** - * Class TitleGeneration + * Class ImageCropping */ class ImageCropping extends Feature { /** diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index bda68288d..3ce3be25f 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -6,7 +6,7 @@ use Classifai\Providers\OpenAI\DallE; /** - * Class TitleGeneration + * Class ImageGeneration */ class ImageGeneration extends Feature { /** diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 696be8e7f..894286d34 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -6,7 +6,7 @@ use Classifai\Services\ImageProcessing; /** - * Class TitleGeneration + * Class ImageTagsGenerator */ class ImageTagsGenerator extends Feature { /** diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 38646ab01..5dbfbaf43 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -6,7 +6,7 @@ use Classifai\Services\ImageProcessing; /** - * Class TitleGeneration + * Class ImageTextExtraction */ class ImageTextExtraction extends Feature { /** diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index e4c4568ef..e0055c6e9 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -6,7 +6,7 @@ use Classifai\Services\ImageProcessing; /** - * Class TitleGeneration + * Class PDFTextExtraction */ class PDFTextExtraction extends Feature { /** diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 68d2a4604..7f0eea9ac 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -6,7 +6,7 @@ use Classifai\Providers\Azure\Speech; /** - * Class TitleGeneration + * Class TextToSpeech */ class TextToSpeech extends Feature { /** diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 1c5bad04b..424a89418 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -4,6 +4,13 @@ use Classifai\Services\LanguageProcessing; use Classifai\Providers\OpenAI\ChatGPT; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\sanitize_prompts; +use function Classifai\sanitize_number_of_responses_field; +use function Classifai\get_asset_info; /** * Class TitleGeneration @@ -16,6 +23,13 @@ class TitleGeneration extends Feature { */ const ID = 'feature_title_generation'; + /** + * Prompt for generating titles. + * + * @var string + */ + public $prompt = 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.'; + /** * Constructor. */ @@ -31,6 +45,263 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + */ + public function setup() { + parent::setup(); + + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); + add_action( 'edit_form_before_permalink', [ $this, 'register_generated_titles_template' ] ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'generate-title(?:/(?P\d+))?', + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Post ID to generate title for.', 'classifai' ), + ], + 'n' => [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 10, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Number of titles to generate', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_title_permissions_check' ], + ], + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'content' => [ + 'required' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Content to generate a title for', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_title_permissions_check' ], + ], + ] + ); + } + + /** + * Check if a given request has access to generate a title. + * + * This check ensures we have a proper post ID, the current user + * making the request has access to that post, that we are + * properly authenticated with OpenAI and that title generation + * is turned on. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function generate_title_permissions_check( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + + // Ensure we have a logged in user that can edit the item. + if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { + return false; + } + + $post_type = get_post_type( $post_id ); + $post_type_obj = get_post_type_object( $post_type ); + + // Ensure the post type is allowed in REST endpoints. + if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { + return false; + } + + // Ensure the feature is enabled. Also runs a user check. + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Title generation not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( '/classifai/v1/generate-title' === $route ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'id' ), + 'title', + [ + 'num' => $request->get_param( 'n' ), + 'content' => $request->get_param( 'content' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Enqueue the editor scripts. + */ + public function enqueue_editor_assets() { + global $post; + + if ( empty( $post ) ) { + return; + } + + wp_enqueue_script( + 'classifai-post-status-info', + CLASSIFAI_PLUGIN_URL . 'dist/post-status-info.js', + get_asset_info( 'post-status-info', 'dependencies' ), + get_asset_info( 'post-status-info', 'version' ), + true + ); + + wp_add_inline_script( + 'classifai-post-status-info', + sprintf( + 'var classifaiChatGPTData = %s;', + wp_json_encode( $this->get_localised_vars() ) + ), + 'before' + ); + } + + /** + * Enqueue the admin scripts. + * + * @param string $hook_suffix The current admin page. + */ + public function enqueue_admin_assets( string $hook_suffix ) { + // Load jQuery UI Dialog for prompt deletion. + if ( + ( + 'tools_page_classifai' === $hook_suffix + && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_title_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) || + 'admin_page_classifai_setup' === $hook_suffix + ) { + wp_enqueue_script( 'jquery-ui-dialog' ); + wp_enqueue_style( 'wp-jquery-ui-dialog' ); + + add_action( + 'admin_footer', + static function () { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + ); + } + + // Load asset in new post and edit post screens. + if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { + $screen = get_current_screen(); + + // Load the assets for the classic editor. + if ( $screen && ! $screen->is_block_editor() ) { + if ( post_type_supports( $screen->post_type, 'title' ) ) { + wp_enqueue_style( + 'classifai-generate-title-classic-css', + CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.css', + [], + get_asset_info( 'generate-title-classic', 'version' ), + 'all' + ); + + wp_enqueue_script( + 'classifai-generate-title-classic-js', + CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.js', + array_merge( get_asset_info( 'generate-title-classic', 'dependencies' ), array( 'wp-api' ) ), + get_asset_info( 'generate-title-classic', 'version' ), + true + ); + + wp_add_inline_script( + 'classifai-generate-title-classic-js', + sprintf( + 'var classifaiChatGPTData = %s;', + wp_json_encode( $this->get_localised_vars() ) + ), + 'before' + ); + } + } + + wp_enqueue_style( + 'classifai-language-processing-style', + CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css', + [], + get_asset_info( 'language-processing', 'version' ), + ); + } + } + + /** + * HTML template for title generation result popup. + */ + public function register_generated_titles_template() { + ?> + + [ + 0 => [ + 'feature' => 'title', + 'path' => '/classifai/v1/generate-title/', + 'buttonText' => __( 'Generate titles', 'classifai' ), + 'modalTitle' => __( 'Select a title', 'classifai' ), + 'selectBtnText' => __( 'Select', 'classifai' ), + ], + ], + 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ), + ]; + } + /** * Get the description for the enable field. * @@ -40,6 +311,45 @@ public function get_enable_description(): string { return esc_html__( 'A button will be added to the status panel that can be used to generate titles.', 'classifai' ); } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); + + add_settings_field( + 'number_of_titles', + esc_html__( 'Number of titles', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'number_of_titles', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $settings['number_of_titles'], + 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), + ] + ); + + add_settings_field( + 'generate_title_prompt', + esc_html__( 'Prompt', 'classifai' ), + [ $this, 'render_prompt_repeater_field' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'generate_title_prompt', + 'placeholder' => $this->prompt, + 'default_value' => $settings['generate_title_prompt'], + 'description' => esc_html__( 'Add a custom prompt, if desired.', 'classifai' ), + ] + ); + } + /** * Returns the default settings for the feature. * @@ -47,36 +357,30 @@ public function get_enable_description(): string { */ public function get_feature_default_settings(): array { return [ - 'provider' => ChatGPT::ID, + 'number_of_titles' => 1, + 'generate_title_prompt' => [ + [ + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => $this->prompt, + 'original' => 1, + ], + ], + 'provider' => ChatGPT::ID, ]; } /** - * Runs the feature. + * Sanitizes the default feature settings. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed + * @param array $new_settings Settings being saved. + * @return array */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ChatGPT::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ChatGPT::ID === $provider_instance::ID ) { - /** @var ChatGPT $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'generate_titles' ], - [ ...$args ] - ); - } + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + $new_settings['number_of_titles'] = sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); + $new_settings['generate_title_prompt'] = sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); + + return $new_settings; } } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index d4326f31e..e53276b5a 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -933,3 +933,15 @@ function ( $prompt ) { return $default_prompt; } + +/** + * Sanitisation callback for number of responses. + * + * @param string $key The key of the value we are sanitizing. + * @param array $new_settings The settings array. + * @param array $settings Current array. + * @return int + */ +function sanitize_number_of_responses_field( string $key, array $new_settings, array $settings ): int { + return absint( $new_settings[ $key ] ?? $settings[ $key ] ?? '' ); +} diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 8291da3a3..1fddba374 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -44,20 +44,6 @@ class ChatGPT extends Provider { */ protected $max_tokens = 16385; - /** - * Prompt for generating excerpts - * - * @var string - */ - protected $generate_excerpt_prompt = 'Summarize the following message using a maximum of {{WORDS}} words. Ensure this summary pairs well with the following text: {{TITLE}}.'; - - /** - * Prompt for generating titles - * - * @var string - */ - protected $generate_title_prompt = 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.'; - /** * Prompt for shrinking content * @@ -124,10 +110,6 @@ public function render_provider_fields() { ); switch ( $this->feature_instance::ID ) { - case TitleGeneration::ID: - $this->add_title_generation_fields(); - break; - case ContentResizing::ID: $this->add_content_resizing_fields(); break; @@ -136,47 +118,6 @@ public function render_provider_fields() { do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } - /** - * Renders fields for the Title generation feature. - */ - private function add_title_generation_fields() { - $settings = $this->feature_instance->get_settings( static::ID ); - - add_settings_field( - static::ID . '_number_of_titles', - esc_html__( 'Number of titles', 'classifai' ), - [ $this->feature_instance, 'render_input' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'number_of_titles', - 'input_type' => 'number', - 'min' => 1, - 'step' => 1, - 'default_value' => $settings['number_of_titles'], - 'description' => esc_html__( 'Number of titles that will be generated in one request.', 'classifai' ), - 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - - add_settings_field( - static::ID . '_generate_title_prompt', - $args['label'] ?? esc_html__( 'Prompt', 'classifai' ), - [ $this->feature_instance, 'render_prompt_repeater_field' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'generate_title_prompt', - 'placeholder' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), - 'default_value' => $settings['generate_title_prompt'], - 'description' => esc_html__( 'Enter a custom prompt, if desired.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - } - /** * Renders fields for the Content resizing feature. */ @@ -246,21 +187,6 @@ public function get_default_provider_settings(): array { ]; switch ( $this->feature_instance::ID ) { - case TitleGeneration::ID: - return array_merge( - $common_settings, - [ - 'number_of_titles' => 1, - 'generate_title_prompt' => array( - array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Write an SEO-friendly title for the following content that will encourage readers to clickthrough, staying within a range of 40 to 60 characters.', 'classifai' ), - 'original' => 1, - ), - ), - ] - ); - case ContentResizing::ID: return array_merge( $common_settings, @@ -299,11 +225,6 @@ public function sanitize_settings( array $new_settings ): array { $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; $new_settings[ static::ID ]['authenticated'] = $api_key_settings[ static::ID ]['authenticated']; - if ( $this->feature_instance instanceof TitleGeneration ) { - $new_settings[ static::ID ]['number_of_titles'] = $this->sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); - $new_settings[ static::ID ]['generate_title_prompt'] = sanitize_prompts( 'generate_title_prompt', $new_settings ); - } - if ( $this->feature_instance instanceof ContentResizing ) { $new_settings[ static::ID ]['number_of_suggestions'] = $this->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); $new_settings[ static::ID ]['condense_text_prompt'] = sanitize_prompts( 'condense_text_prompt', $new_settings ); @@ -324,18 +245,6 @@ public function sanitize_api_key( array $new_settings ): string { return sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ?? '' ); } - /** - * Sanitisation callback for number of responses. - * - * @param string $key The key of the value we are sanitizing. - * @param array $new_settings The settings array. - * @param array $settings Current array. - * @return int - */ - public function sanitize_number_of_responses_field( string $key, array $new_settings, array $settings ): int { - return absint( $new_settings[ static::ID ][ $key ] ?? $settings[ static::ID ][ $key ] ?? '' ); - } - /** * Register what we need for the plugin. * @@ -344,27 +253,6 @@ public function sanitize_number_of_responses_field( string $key, array $new_sett public function register() { add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); - add_action( 'edit_form_before_permalink', [ $this, 'register_generated_titles_template' ] ); - } - - /** - * Returns localised data for title generation. - */ - public function get_localised_vars() { - global $post; - - return [ - 'enabledFeatures' => [ - 0 => [ - 'feature' => 'title', - 'path' => '/classifai/v1/openai/generate-title/', - 'buttonText' => __( 'Generate titles', 'classifai' ), - 'modalTitle' => __( 'Select a title', 'classifai' ), - 'selectBtnText' => __( 'Select', 'classifai' ), - ], - ], - 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ), - ]; } /** @@ -377,25 +265,6 @@ public function enqueue_editor_assets() { return; } - if ( ( new TitleGeneration() )->is_feature_enabled() ) { - wp_enqueue_script( - 'classifai-post-status-info', - CLASSIFAI_PLUGIN_URL . 'dist/post-status-info.js', - get_asset_info( 'post-status-info', 'dependencies' ), - get_asset_info( 'post-status-info', 'version' ), - true - ); - - wp_add_inline_script( - 'classifai-post-status-info', - sprintf( - 'var classifaiChatGPTData = %s;', - wp_json_encode( $this->get_localised_vars() ) - ), - 'before' - ); - } - if ( ( new ContentResizing() )->is_feature_enabled() ) { wp_enqueue_script( 'classifai-content-resizing-plugin-js', @@ -422,7 +291,6 @@ public function enqueue_editor_assets() { */ public function enqueue_admin_assets( string $hook_suffix ) { $prompt_features = array( - 'feature_title_generation', 'feature_content_resizing', ); @@ -452,41 +320,6 @@ static function () { // Load asset in new post and edit post screens. if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { - $screen = get_current_screen(); - - // Load the assets for the classic editor. - if ( $screen && ! $screen->is_block_editor() ) { - if ( - post_type_supports( $screen->post_type, 'title' ) && - ( new TitleGeneration() )->is_feature_enabled() - ) { - wp_enqueue_style( - 'classifai-generate-title-classic-css', - CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.css', - [], - get_asset_info( 'generate-title-classic', 'version' ), - 'all' - ); - - wp_enqueue_script( - 'classifai-generate-title-classic-js', - CLASSIFAI_PLUGIN_URL . 'dist/generate-title-classic.js', - array_merge( get_asset_info( 'generate-title-classic', 'dependencies' ), array( 'wp-api' ) ), - get_asset_info( 'generate-title-classic', 'version' ), - true - ); - - wp_add_inline_script( - 'classifai-generate-title-classic-js', - sprintf( - 'var classifaiChatGPTData = %s;', - wp_json_encode( $this->get_localised_vars() ) - ), - 'before' - ); - } - } - wp_enqueue_style( 'classifai-language-processing-style', CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css', @@ -496,23 +329,6 @@ static function () { } } - /** - * HTML template for title generation result popup. - */ - public function register_generated_titles_template() { - ?> - - generate_excerpt( $post_id, $args ); break; case 'title': - $return = ( new TitleGeneration() )->run( $post_id, $args ); + $return = $this->generate_titles( $post_id, $args ); break; case 'resize_content': $return = ( new ContentResizing() )->run( $post_id, $args ); @@ -578,7 +394,7 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); - $excerpt_prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['generate_excerpt_prompt'] ) ?? $this->feature_instance->prompt ); + $excerpt_prompt = esc_textarea( get_default_prompt( $settings['generate_excerpt_prompt'] ) ?? $feature->prompt ); // Replace our variables in the prompt. $prompt_search = array( '{{WORDS}}', '{{TITLE}}' ); @@ -682,7 +498,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); - $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['generate_title_prompt'] ) ?? $this->generate_title_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings['generate_title_prompt'] ) ?? $feature->prompt ); /** * Filter the prompt we will send to ChatGPT. @@ -933,48 +749,6 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use * @return void */ public function register_endpoints() { - register_rest_route( - 'classifai/v1/openai', - 'generate-title(?:/(?P\d+))?', - [ - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'generate_post_title' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Post ID to generate title for.', 'classifai' ), - ], - 'n' => [ - 'type' => 'integer', - 'minimum' => 1, - 'maximum' => 10, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Number of titles to generate', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_post_title_permissions_check' ], - ], - [ - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'generate_post_title' ], - 'args' => [ - 'content' => [ - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Content to generate a title for', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_post_title_permissions_check' ], - ], - ] - ); - register_rest_route( 'classifai/v1/openai', 'resize-content', @@ -1006,64 +780,6 @@ public function register_endpoints() { ); } - /** - * Handle request to generate title for given post ID. - * - * @param WP_REST_Request $request The full request object. - * @return \WP_REST_Response|WP_Error - */ - public function generate_post_title( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - return rest_ensure_response( - $this->rest_endpoint_callback( - $post_id, - 'title', - [ - 'num' => $request->get_param( 'n' ), - 'content' => $request->get_param( 'content' ), - ] - ) - ); - } - - /** - * Check if a given request has access to generate a title. - * - * This check ensures we have a proper post ID, the current user - * making the request has access to that post, that we are - * properly authenticated with OpenAI and that title generation - * is turned on. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function generate_post_title_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - // Ensure we have a logged in user that can edit the item. - if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { - return false; - } - - $post_type = get_post_type( $post_id ); - $post_type_obj = get_post_type_object( $post_type ); - - // Ensure the post type is allowed in REST endpoints. - if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { - return false; - } - - $feature = new TitleGeneration(); - - // Ensure the feature is enabled. Also runs a user check. - if ( ! $feature->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Title generation not currently enabled.', 'classifai' ) ); - } - - return true; - } - /** * Handle request to resize content. * From 5550d4087009766c9bb13ecaf992a9f888ad978c Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 24 Jan 2024 15:52:50 -0700 Subject: [PATCH 109/127] Migrate all Content Resizing specific items from the Provider class to the Feature class --- .../Classifai/Features/ContentResizing.php | 291 ++++++++++++++-- .../Classifai/Features/ExcerptGeneration.php | 1 - includes/Classifai/Features/Feature.php | 3 +- .../Classifai/Features/TitleGeneration.php | 2 - .../Classifai/Providers/OpenAI/ChatGPT.php | 310 +----------------- .../content-resizing-plugin.js | 2 +- 6 files changed, 285 insertions(+), 324 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 548648065..02f6c64cb 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -4,6 +4,13 @@ use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Services\LanguageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\sanitize_prompts; +use function Classifai\sanitize_number_of_responses_field; +use function Classifai\get_asset_info; /** * Class ContentResizing @@ -16,6 +23,20 @@ class ContentResizing extends Feature { */ const ID = 'feature_content_resizing'; + /** + * Prompt for shrinking content. + * + * @var string + */ + public $condense_prompt = 'Decrease the content length no more than 2 to 4 sentences.'; + + /** + * Prompt for growing content. + * + * @var string + */ + public $expand_prompt = 'Increase the content length no more than 2 to 4 sentences.'; + /** * Constructor. */ @@ -31,6 +52,175 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + */ + public function setup() { + parent::setup(); + + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'resize-content', + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'permission_callback' => [ $this, 'resize_content_permissions_check' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Post ID to resize the content for.', 'classifai' ), + ], + 'content' => [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'The content to resize.', 'classifai' ), + ], + 'resize_type' => [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'The type of resize operation. "expand" or "condense".', 'classifai' ), + ], + ], + ] + ); + } + + /** + * Check if a given request has access to resize content. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function resize_content_permissions_check( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + + // Ensure we have a logged in user that can edit the item. + if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { + return false; + } + + $post_type = get_post_type( $post_id ); + $post_type_obj = get_post_type_object( $post_type ); + + // Ensure the post type is allowed in REST endpoints. + if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { + return false; + } + + // Ensure the feature is enabled. Also runs a user check. + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Content resizing is not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( '/classifai/v1/resize-content' === $route ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'id' ), + 'resize_content', + [ + 'content' => $request->get_param( 'content' ), + 'resize_type' => $request->get_param( 'resize_type' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Enqueue the editor scripts. + */ + public function enqueue_editor_assets() { + global $post; + + if ( empty( $post ) ) { + return; + } + + wp_enqueue_script( + 'classifai-content-resizing-plugin-js', + CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.js', + get_asset_info( 'content-resizing-plugin', 'dependencies' ), + get_asset_info( 'content-resizing-plugin', 'version' ), + true + ); + + wp_enqueue_style( + 'classifai-content-resizing-plugin-css', + CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.css', + [], + get_asset_info( 'content-resizing-plugin', 'version' ), + 'all' + ); + } + + /** + * Enqueue the admin scripts. + * + * @param string $hook_suffix The current admin page. + */ + public function enqueue_admin_assets( string $hook_suffix ) { + // Load jQuery UI Dialog for prompt deletion. + if ( + ( + 'tools_page_classifai' === $hook_suffix + && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_content_resizing' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) || + 'admin_page_classifai_setup' === $hook_suffix + ) { + wp_enqueue_script( 'jquery-ui-dialog' ); + wp_enqueue_style( 'wp-jquery-ui-dialog' ); + + add_action( + 'admin_footer', + static function () { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + ); + } + + // Load asset in new post and edit post screens. + if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { + wp_enqueue_style( + 'classifai-language-processing-style', + CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css', + [], + get_asset_info( 'language-processing', 'version' ), + ); + } + } + /** * Get the description for the enable field. * @@ -40,6 +230,57 @@ public function get_enable_description(): string { return esc_html__( '"Condense this text" and "Expand this text" menu items will be added to the paragraph block\'s toolbar menu.', 'classifai' ); } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); + + add_settings_field( + 'number_of_suggestions', + esc_html__( 'Number of suggestions', 'classifai' ), + [ $this, 'render_input' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'number_of_suggestions', + 'input_type' => 'number', + 'min' => 1, + 'step' => 1, + 'default_value' => $settings['number_of_suggestions'], + 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), + ] + ); + + add_settings_field( + 'condense_text_prompt', + esc_html__( 'Condense text prompt', 'classifai' ), + [ $this, 'render_prompt_repeater_field' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'condense_text_prompt', + 'placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), + 'default_value' => $settings['condense_text_prompt'], + 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), + ] + ); + + add_settings_field( + 'expand_text_prompt', + esc_html__( 'Expand text prompt', 'classifai' ), + [ $this, 'render_prompt_repeater_field' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'expand_text_prompt', + 'placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), + 'default_value' => $settings['expand_text_prompt'], + 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), + ] + ); + } + /** * Returns the default settings for the feature. * @@ -47,36 +288,38 @@ public function get_enable_description(): string { */ public function get_feature_default_settings(): array { return [ - 'provider' => ChatGPT::ID, + 'number_of_suggestions' => 1, + 'condense_text_prompt' => [ + [ + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => $this->condense_prompt, + 'original' => 1, + ], + ], + 'expand_text_prompt' => [ + [ + 'title' => esc_html__( 'ClassifAI default', 'classifai' ), + 'prompt' => $this->expand_prompt, + 'original' => 1, + ], + ], + 'provider' => ChatGPT::ID, ]; } /** - * Runs the feature. + * Sanitizes the default feature settings. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed + * @param array $new_settings Settings being saved. + * @return array */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ChatGPT::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ChatGPT::ID === $provider_instance::ID ) { - /** @var ChatGPT $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'resize_content' ], - [ ...$args ] - ); - } + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + $new_settings['number_of_suggestions'] = sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); + $new_settings['condense_text_prompt'] = sanitize_prompts( 'condense_text_prompt', $new_settings ); + $new_settings['expand_text_prompt'] = sanitize_prompts( 'expand_text_prompt', $new_settings ); + + return $new_settings; } } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index bd83298a1..4ce27c717 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -289,7 +289,6 @@ public function add_custom_settings_fields() { $this->get_option_name(), $this->get_option_name() . '_section', [ - 'option_index' => static::ID, 'label_for' => 'generate_excerpt_prompt', 'placeholder' => $this->prompt, 'default_value' => $settings['generate_excerpt_prompt'], diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index cfca11279..3763f050c 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -573,8 +573,8 @@ public function render_input( array $args ) { + get_data_attribute( $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> /> + ' . wp_kses_post( $args['description'] ) . ''; diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 424a89418..6195d7862 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -324,7 +324,6 @@ public function add_custom_settings_fields() { $this->get_option_name(), $this->get_option_name() . '_section', [ - 'option_index' => static::ID, 'label_for' => 'number_of_titles', 'input_type' => 'number', 'min' => 1, @@ -341,7 +340,6 @@ public function add_custom_settings_fields() { $this->get_option_name(), $this->get_option_name() . '_section', [ - 'option_index' => static::ID, 'label_for' => 'generate_title_prompt', 'placeholder' => $this->prompt, 'default_value' => $settings['generate_title_prompt'], diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 1fddba374..6941a8a37 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -10,11 +10,7 @@ use Classifai\Features\TitleGeneration; use Classifai\Providers\Provider; use Classifai\Watson\Normalizer; -use WP_REST_Server; -use WP_REST_Request; use WP_Error; -use function Classifai\get_asset_info; -use function Classifai\sanitize_prompts; use function Classifai\get_default_prompt; class ChatGPT extends Provider { @@ -44,20 +40,6 @@ class ChatGPT extends Provider { */ protected $max_tokens = 16385; - /** - * Prompt for shrinking content - * - * @var string - */ - protected $condense_text_prompt = 'Decrease the content length no more than 2 to 4 sentences.'; - - /** - * Prompt for growing content - * - * @var string - */ - protected $expand_text_prompt = 'Increase the content length no more than 2 to 4 sentences.'; - /** * OpenAI ChatGPT constructor. * @@ -71,8 +53,12 @@ public function __construct( $feature_instance = null ) { ); $this->feature_instance = $feature_instance; + } - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + /** + * Register any needed hooks. + */ + public function register() { } /** @@ -109,72 +95,9 @@ public function render_provider_fields() { ] ); - switch ( $this->feature_instance::ID ) { - case ContentResizing::ID: - $this->add_content_resizing_fields(); - break; - } - do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } - /** - * Renders fields for the Content resizing feature. - */ - private function add_content_resizing_fields() { - $settings = $this->feature_instance->get_settings( static::ID ); - - add_settings_field( - static::ID . '_number_of_suggestions', - esc_html__( 'Number of suggestions', 'classifai' ), - [ $this->feature_instance, 'render_input' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'number_of_suggestions', - 'input_type' => 'number', - 'min' => 1, - 'step' => 1, - 'default_value' => $settings['number_of_suggestions'], - 'description' => esc_html__( 'Number of suggestions that will be generated in one request.', 'classifai' ), - 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - - add_settings_field( - static::ID . '_condense_text_prompt', - $args['label'] ?? esc_html__( 'Condense text prompt', 'classifai' ), - [ $this->feature_instance, 'render_prompt_repeater_field' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'condense_text_prompt', - 'placeholder' => esc_html__( 'Decrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'default_value' => $settings['condense_text_prompt'], - 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - - add_settings_field( - static::ID . '_expand_text_prompt', - $args['label'] ?? esc_html__( 'Expand text prompt', 'classifai' ), - [ $this->feature_instance, 'render_prompt_repeater_field' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'expand_text_prompt', - 'placeholder' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'default_value' => $settings['expand_text_prompt'], - 'description' => esc_html__( 'Enter your custom prompt.', 'classifai' ), - 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - } - /** * Returns the default settings for this provider. * @@ -186,30 +109,6 @@ public function get_default_provider_settings(): array { 'authenticated' => false, ]; - switch ( $this->feature_instance::ID ) { - case ContentResizing::ID: - return array_merge( - $common_settings, - [ - 'number_of_suggestions' => 1, - 'condense_text_prompt' => array( - array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Descrease the content length no more than 2 to 4 sentences.', 'classifai' ), - 'original' => 1, - ), - ), - 'expand_text_prompt' => array( - array( - 'title' => esc_html__( 'ClassifAI default', 'classifai' ), - 'prompt' => esc_html__( 'Increase the content length no more than 2 to 4 sentences.', 'classifai' ), - 'original' => 1, - ), - ), - ] - ); - } - return $common_settings; } @@ -220,22 +119,17 @@ public function get_default_provider_settings(): array { * @return array */ public function sanitize_settings( array $new_settings ): array { - $settings = $this->feature_instance->get_settings(); - $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); + $settings = $this->feature_instance->get_settings(); + $api_key_settings = $this->sanitize_api_key_settings( $new_settings, $settings ); + $new_settings[ static::ID ]['api_key'] = $api_key_settings[ static::ID ]['api_key']; $new_settings[ static::ID ]['authenticated'] = $api_key_settings[ static::ID ]['authenticated']; - if ( $this->feature_instance instanceof ContentResizing ) { - $new_settings[ static::ID ]['number_of_suggestions'] = $this->sanitize_number_of_responses_field( 'number_of_suggestions', $new_settings, $settings ); - $new_settings[ static::ID ]['condense_text_prompt'] = sanitize_prompts( 'condense_text_prompt', $new_settings ); - $new_settings[ static::ID ]['expand_text_prompt'] = sanitize_prompts( 'expand_text_prompt', $new_settings ); - } - return $new_settings; } /** - * Sanitisation callback for api key. + * Sanitize the API key. * * @param array $new_settings The settings array. * @return string @@ -245,90 +139,6 @@ public function sanitize_api_key( array $new_settings ): string { return sanitize_text_field( $new_settings[ static::ID ]['api_key'] ?? $settings[ static::ID ]['api_key'] ?? '' ); } - /** - * Register what we need for the plugin. - * - * This only fires if can_register returns true. - */ - public function register() { - add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); - } - - /** - * Enqueue the editor scripts. - */ - public function enqueue_editor_assets() { - global $post; - - if ( empty( $post ) ) { - return; - } - - if ( ( new ContentResizing() )->is_feature_enabled() ) { - wp_enqueue_script( - 'classifai-content-resizing-plugin-js', - CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.js', - get_asset_info( 'content-resizing-plugin', 'dependencies' ), - get_asset_info( 'content-resizing-plugin', 'version' ), - true - ); - - wp_enqueue_style( - 'classifai-content-resizing-plugin-css', - CLASSIFAI_PLUGIN_URL . 'dist/content-resizing-plugin.css', - [], - get_asset_info( 'content-resizing-plugin', 'version' ), - 'all' - ); - } - } - - /** - * Enqueue the admin scripts. - * - * @param string $hook_suffix The current admin page. - */ - public function enqueue_admin_assets( string $hook_suffix ) { - $prompt_features = array( - 'feature_content_resizing', - ); - - // Load jQuery UI Dialog for prompt deletion. - if ( - ( - 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && in_array( $_GET['feature'], $prompt_features, true ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ) || - 'admin_page_classifai_setup' === $hook_suffix - ) { - wp_enqueue_script( 'jquery-ui-dialog' ); - wp_enqueue_style( 'wp-jquery-ui-dialog' ); - - add_action( - 'admin_footer', - static function () { - printf( - '', - esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), - ); - } - ); - } - - // Load asset in new post and edit post screens. - if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { - wp_enqueue_style( - 'classifai-language-processing-style', - CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css', - [], - get_asset_info( 'language-processing', 'version' ), - ); - } - } - /** * Common entry point for all REST endpoints for this provider. * This is called by the Service. @@ -355,7 +165,7 @@ public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args $return = $this->generate_titles( $post_id, $args ); break; case 'resize_content': - $return = ( new ContentResizing() )->run( $post_id, $args ); + $return = $this->resize_content( $post_id, $args ); break; } @@ -485,7 +295,7 @@ public function generate_titles( int $post_id = 0, array $args = [] ) { $args = wp_parse_args( array_filter( $args ), [ - 'num' => $settings[ static::ID ]['number_of_titles'] ?? 1, + 'num' => $settings['number_of_titles'] ?? 1, 'content' => '', ] ); @@ -593,16 +403,16 @@ public function resize_content( int $post_id, array $args = array() ) { $args = wp_parse_args( array_filter( $args ), [ - 'num' => $settings[ static::ID ]['number_of_suggestions'] ?? 1, + 'num' => $settings['number_of_suggestions'] ?? 1, ] ); $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); if ( 'shrink' === $args['resize_type'] ) { - $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['condense_text_prompt'] ) ?? $this->condense_text_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings['condense_text_prompt'] ) ?? $feature->condense_prompt ); } else { - $prompt = esc_textarea( get_default_prompt( $settings[ static::ID ]['expand_text_prompt'] ) ?? $this->expand_text_prompt ); + $prompt = esc_textarea( get_default_prompt( $settings['expand_text_prompt'] ) ?? $feature->expand_prompt ); } /** @@ -743,100 +553,10 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use return apply_filters( 'classifai_chatgpt_content', $content, $post_id ); } - /** - * Registers REST endpoints for this provider. - * - * @return void - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1/openai', - 'resize-content', - [ - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'resize_post_content' ], - 'permission_callback' => [ $this, 'resize_post_content_permissions_check' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Post ID to resize the content for.', 'classifai' ), - ], - 'content' => [ - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'The content to resize.', 'classifai' ), - ], - 'resize_type' => [ - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'The type of resize operation. "expand" or "condense".', 'classifai' ), - ], - ], - ] - ); - } - - /** - * Handle request to resize content. - * - * @param WP_REST_Request $request The full request object. - * @return \WP_REST_Response|WP_Error - */ - public function resize_post_content( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - return rest_ensure_response( - $this->rest_endpoint_callback( - $post_id, - 'resize_content', - [ - 'content' => $request->get_param( 'content' ), - 'resize_type' => $request->get_param( 'resize_type' ), - ] - ) - ); - } - - /** - * Check if a given request has access to resize content. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function resize_post_content_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - // Ensure we have a logged in user that can edit the item. - if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { - return false; - } - - $post_type = get_post_type( $post_id ); - $post_type_obj = get_post_type_object( $post_type ); - - // Ensure the post type is allowed in REST endpoints. - if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { - return false; - } - - $feature = new ContentResizing(); - - // Ensure the feature is enabled. Also runs a user check. - if ( ! $feature->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Content resizing is not currently enabled.', 'classifai' ) ); - } - - return true; - } - /** * Returns the debug information for the provider settings. * - * TODO: this should be in feature. + * TODO: this should be in feature. Or at least any settings moved to the feature should be * * @return array */ diff --git a/src/js/gutenberg-plugins/content-resizing-plugin.js b/src/js/gutenberg-plugins/content-resizing-plugin.js index edf1d7e4b..af6d8e5d4 100644 --- a/src/js/gutenberg-plugins/content-resizing-plugin.js +++ b/src/js/gutenberg-plugins/content-resizing-plugin.js @@ -185,7 +185,7 @@ const ContentResizingPlugin = () => { */ async function getResizedContent() { let __textArray = []; - const apiUrl = `${ wpApiSettings.root }classifai/v1/openai/resize-content`; + const apiUrl = `${ wpApiSettings.root }classifai/v1/resize-content`; const postId = select( editorStore ).getCurrentPostId(); const formData = new FormData(); From ce661511f80f07034ee43abe016a6f9ac92d150f Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 25 Jan 2024 11:03:46 -0700 Subject: [PATCH 110/127] Migrate all Audio Transcription specific items from the Provider class to the Feature class --- includes/Classifai/Admin/BulkActions.php | 2 +- .../Classifai/Command/ClassifaiCommand.php | 40 +-- .../Features/AudioTranscriptsGeneration.php | 297 +++++++++++++++-- .../Classifai/Features/ExcerptGeneration.php | 4 +- includes/Classifai/Features/Feature.php | 16 +- .../Classifai/Features/ImageGeneration.php | 4 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 5 +- .../Classifai/Providers/OpenAI/Whisper.php | 315 ++++++------------ .../Providers/OpenAI/Whisper/Transcribe.php | 144 -------- .../Providers/OpenAI/Whisper/Whisper.php | 85 ----- .../Classifai/Services/ImageProcessing.php | 1 + .../Classifai/Services/LanguageProcessing.php | 1 - .../Classifai/Services/ServicesManager.php | 2 +- src/js/media.js | 2 +- 14 files changed, 430 insertions(+), 488 deletions(-) delete mode 100644 includes/Classifai/Providers/OpenAI/Whisper/Transcribe.php delete mode 100644 includes/Classifai/Providers/OpenAI/Whisper/Whisper.php diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index e5b1b20bd..69d5faeaf 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -344,7 +344,7 @@ function ( $feature ) { case AudioTranscriptsGeneration::ID: if ( wp_attachment_is( 'audio', $attachment_id ) ) { - ( new AudioTranscriptsGeneration() )->run( $attachment_id ); + ( new AudioTranscriptsGeneration() )->run( $attachment_id, 'transcript' ); } break; } diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index 6a4fc5f5f..eb9f0fa66 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -13,10 +13,7 @@ use Classifai\PostClassifier; use Classifai\Providers\Azure\ComputerVision; use Classifai\Providers\Azure\SmartCropping; -use Classifai\Providers\OpenAI\Whisper; -use Classifai\Providers\OpenAI\Whisper\Transcribe; use Classifai\Providers\OpenAI\Embeddings; -use WP_Error; /** * ClassifaiCommand is the command line interface of the ClassifAI plugin. @@ -404,6 +401,7 @@ public function transcribe_audio( $args = [], $opts = [] ) { $audio_transcription = new AudioTranscriptsGeneration(); $feature_settings = $audio_transcription->get_settings(); + $provider_instance = $audio_transcription->get_feature_provider_instance( $feature_settings['provider'] ); // Determine if this is a dry run or not. if ( isset( $opts['dry-run'] ) ) { @@ -430,19 +428,20 @@ public function transcribe_audio( $args = [], $opts = [] ) { foreach ( $attachment_ids as $attachment_id ) { $attachment = get_post( $attachment_id ); - $transcribe = new Transcribe( $attachment_id, $feature_settings[ Whisper::ID ] ); - if ( ! $this->should_transcribe_attachment( $attachment, $attachment_id, $transcribe, (bool) $opts['force'] ) ) { + if ( ! $this->should_transcribe_attachment( $attachment, $attachment_id, $audio_transcription, (bool) $opts['force'] ) ) { ++$errors; continue; } if ( ! $dry_run ) { - $result = $transcribe->process(); + $result = $audio_transcription->run( $attachment_id, 'transcript' ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %s: %s', $attachment_id, $result->get_error_message() ), false ); ++$errors; + } else { + $result = $audio_transcription->add_transcription( $result, $attachment_id ); } } @@ -456,12 +455,11 @@ public function transcribe_audio( $args = [], $opts = [] ) { $paged = 1; $mime_types = []; - $transcribe = new Transcribe( 1, [] ); // Get all the mime types for the file formats we support. foreach ( wp_get_mime_types() as $extensions => $mime ) { foreach ( explode( '|', $extensions ) as $ext ) { - if ( in_array( $ext, $transcribe->file_formats, true ) ) { + if ( in_array( $ext, $provider_instance->file_formats ?? [ 'mp3' ], true ) ) { $mime_types[] = $mime; } } @@ -482,19 +480,20 @@ public function transcribe_audio( $args = [], $opts = [] ) { foreach ( $attachments as $attachment_id ) { $attachment = get_post( $attachment_id ); - $transcribe = new Transcribe( $attachment_id, $feature_settings[ Whisper::ID ] ); - if ( ! $this->should_transcribe_attachment( $attachment, (int) $attachment_id, $transcribe, (bool) $opts['force'] ) ) { + if ( ! $this->should_transcribe_attachment( $attachment, (int) $attachment_id, $audio_transcription, (bool) $opts['force'] ) ) { ++$errors; continue; } if ( ! $dry_run ) { - $result = $transcribe->process(); + $result = $audio_transcription->run( $attachment_id, 'transcript' ); if ( is_wp_error( $result ) ) { \WP_CLI::error( sprintf( 'Error while processing item ID %s: %s', $attachment_id, $result->get_error_message() ), false ); ++$errors; + } else { + $result = $audio_transcription->add_transcription( $result, $attachment_id ); } } @@ -709,13 +708,13 @@ public function generate_excerpt( $args = [], $opts = [] ) { /** * Determine if an attachment should be transcribed. * - * @param \WP_Post|null $attachment Attachment we are processing. - * @param int $attachment_id Attachment ID. - * @param Transcribe $transcribe Transcribe instance. - * @param boolean $force Whether to force processing. - * @return boolean + * @param \WP_Post|null $attachment Attachment we are processing. + * @param int $attachment_id Attachment ID. + * @param AudioTranscriptsGeneration $audio_transcription AudioTranscriptsGeneration instance. + * @param bool $force Whether to force processing. + * @return bool */ - private function should_transcribe_attachment( $attachment, int $attachment_id, Transcribe $transcribe, bool $force = false ) { + private function should_transcribe_attachment( $attachment, int $attachment_id, AudioTranscriptsGeneration $audio_transcription, bool $force = false ) { // Ensure we have a valid ID. if ( ! $attachment ) { \WP_CLI::error( sprintf( 'Item ID %d does not exist', $attachment_id ), false ); @@ -729,8 +728,11 @@ private function should_transcribe_attachment( $attachment, int $attachment_id, } // Ensure the attachment meets the requirements for processing. - if ( ! $transcribe->should_process( $attachment_id ) ) { - \WP_CLI::error( sprintf( 'Item ID %d does not meet processing requirements. Ensure the file type is one of %s and file size is under %d bytes.', $attachment_id, implode( ', ', $transcribe->file_formats ), $transcribe->max_file_size ), false ); + if ( ! $audio_transcription->should_process( $attachment_id ) ) { + $feature_settings = $audio_transcription->get_settings(); + $provider_instance = $audio_transcription->get_feature_provider_instance( $feature_settings['provider'] ); + + \WP_CLI::error( sprintf( 'Item ID %d does not meet processing requirements. Ensure the file type is one of %s and file size is under %d bytes.', $attachment_id, implode( ', ', $provider_instance->file_formats ?? [ 'mp3' ] ), $provider_instance->max_file_size ?? 25 * MB_IN_BYTES ), false ); return false; } diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index ae181e626..1c38e3705 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -4,6 +4,12 @@ use Classifai\Services\LanguageProcessing; use Classifai\Providers\OpenAI\Whisper; +use WP_Error; +use WP_REST_Server; +use WP_REST_Request; + +use function Classifai\get_asset_info; +use function Classifai\clean_input; /** * Class AudioTranscriptsGeneration @@ -31,6 +37,214 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); + add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); + + add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'generate-transcript/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Attachment ID to generate transcript for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_audio_transcript_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate a transcript. + * + * This check ensures we have a valid user with proper capabilities + * making the request, that we are properly authenticated with OpenAI + * and that transcription is turned on. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function generate_audio_transcript_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Audio transciption is not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/generate-transcript' ) === 0 ) { + $result = $this->run( $request->get_param( 'id' ), 'transcript' ); + + if ( ! is_wp_error( $result ) ) { + $result = $this->add_transcription( $result, $request->get_param( 'id' ) ); + } + + return rest_ensure_response( $result ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Enqueue assets. + */ + public function enqueue_admin_assets() { + wp_enqueue_script( + 'classifai-media-script', + CLASSIFAI_PLUGIN_URL . 'dist/media.js', + array_merge( get_asset_info( 'media', 'dependencies' ), array( 'jquery', 'media-editor', 'lodash' ) ), + get_asset_info( 'media', 'version' ), + true + ); + } + + /** + * Add new buttons to the media modal. + * + * @param array $form_fields Existing form fields. + * @param \WP_Post $attachment Attachment object. + * @return array + */ + public function add_buttons_to_media_modal( array $form_fields, \WP_Post $attachment ): array { + if ( ! $this->should_process( $attachment->ID ) ) { + return $form_fields; + } + + $text = empty( get_the_content( null, false, $attachment ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); + + $form_fields['retranscribe'] = [ + 'label' => __( 'Transcribe audio', 'classifai' ), + 'input' => 'html', + 'html' => '', + 'show_in_edit' => false, + ]; + + return $form_fields; + } + + /** + * Add metabox on single attachment view to allow for transcription. + * + * @param \WP_Post $post Post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + if ( ! $this->should_process( $post->ID ) ) { + return; + } + + add_meta_box( + 'attachment_meta_box', + __( 'ClassifAI Audio Processing', 'classifai' ), + [ $this, 'attachment_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Display the attachment meta box. + * + * @param \WP_Post $post Post object. + */ + public function attachment_meta_box( \WP_Post $post ) { + $text = empty( get_the_content( null, false, $post ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); + + wp_nonce_field( 'classifai_audio_transcript_meta_action', 'classifai_audio_transcript_meta' ); + ?> + +
    +
    + +
    +
    + + run( $attachment_id, 'transcript' ); + + if ( ! is_wp_error( $result ) ) { + $result = $this->add_transcription( $result, $attachment_id ); + } + + return $result; + } + + /** + * Transcribe audio on attachment save, if option is selected. + * + * @param int $attachment_id Attachment ID. + * @return WP_Error|string|null + */ + public function maybe_transcribe_audio( int $attachment_id ) { + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $attachment_id ) ) { + return; + } + + if ( empty( $_POST['classifai_audio_transcript_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_audio_transcript_meta'] ) ), 'classifai_audio_transcript_meta_action' ) ) { + return; + } + + if ( clean_input( 'retranscribe' ) ) { + // Remove to avoid infinite loop. + remove_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); + + return $this->transcribe_audio( $attachment_id ); + } + } + /** * Get the description for the enable field. * @@ -52,31 +266,76 @@ public function get_feature_default_settings(): array { } /** - * Runs the feature. + * Should this attachment be processed. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed + * Ensure the file is a supported format and is under the maximum file size. + * + * @param int $attachment_id Attachment ID to process. + * @return bool */ - public function run( ...$args ) { + public function should_process( int $attachment_id ): bool { $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? Whisper::ID; + $provider_id = $settings['provider']; $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( Whisper::ID === $provider_instance::ID ) { - /** @var Whisper $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'transcribe_audio' ], - [ ...$args ] - ); + + $mime_type = get_post_mime_type( $attachment_id ); + $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); + $process = false; + + foreach ( $matched_extensions as $ext ) { + if ( in_array( $ext, $provider_instance->file_formats ?? [ 'mp3' ], true ) ) { + $process = true; + } } - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this + // If we have a proper file format, check the file size. + if ( $process ) { + $filesize = filesize( get_attached_file( $attachment_id ) ); + if ( ! $filesize || $filesize > $provider_instance->max_file_size ?? 25 * MB_IN_BYTES ) { + $process = false; + } + } + + return $process; + } + + /** + * Add the transcribed text to the attachment. + * + * @param string $text Transcription result. + * @param int $attachment_id Attachment ID. + * @return string|WP_Error + */ + public function add_transcription( string $text = '', int $attachment_id = 0 ) { + if ( empty( $text ) ) { + return new WP_Error( 'invalid_result', esc_html__( 'The transcription result is invalid.', 'classifai' ) ); + } + + /** + * Filter the text result returned from Whisper API. + * + * @since 2.2.0 + * @hook classifai_whisper_transcribe_result + * + * @param {string} $text Text extracted from the response. + * @param {int} $attachment_id The attachment ID. + * + * @return {string} + */ + $text = apply_filters( 'classifai_whisper_transcribe_result', $text, $attachment_id ); + + $update = wp_update_post( + [ + 'ID' => (int) $attachment_id, + 'post_content' => wp_kses_post( $text ), + ], + true ); + + if ( is_wp_error( $update ) ) { + return $update; + } else { + return $text; + } } } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 4ce27c717..a90ac0d86 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -47,9 +47,7 @@ public function __construct() { /** * Set up necessary hooks. */ - public function setup() { - parent::setup(); - + public function feature_setup() { add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 3763f050c..334cb6320 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -53,6 +53,18 @@ public function setup() { add_action( 'admin_init', [ $this, 'setup_roles' ] ); add_action( 'admin_init', [ $this, 'register_setting' ] ); add_action( 'admin_init', [ $this, 'setup_fields_sections' ] ); + + if ( $this->is_feature_enabled() ) { + $this->feature_setup(); + } + } + + /** + * Setup any hooks the feature needs. + * + * TODO: make this abstract once implemented in all features. + */ + public function feature_setup() { } /** @@ -333,7 +345,7 @@ public function get_provider_default_settings(): array { foreach ( array_keys( $this->get_providers() ) as $provider_id ) { $provider = $this->get_feature_provider_instance( $provider_id ); - if ( method_exists( $provider, 'get_default_provider_settings' ) ) { + if ( $provider && method_exists( $provider, 'get_default_provider_settings' ) ) { $provider_settings[ $provider_id ] = $provider->get_default_provider_settings(); } } @@ -373,7 +385,7 @@ public function add_provider_fields() { foreach ( array_keys( $this->get_providers() ) as $provider_id ) { $provider = $this->get_feature_provider_instance( $provider_id ); - if ( method_exists( $provider, 'render_provider_fields' ) ) { + if ( $provider && method_exists( $provider, 'render_provider_fields' ) ) { $provider->render_provider_fields(); } } diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index 3ce3be25f..234f3c24d 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -2,7 +2,7 @@ namespace Classifai\Features; -use Classifai\Services\LanguageProcessing; +use Classifai\Services\ImageProcessing; use Classifai\Providers\OpenAI\DallE; /** @@ -23,7 +23,7 @@ public function __construct() { $this->label = __( 'Image Generation', 'classifai' ); // Contains all providers that are registered to the service. - $this->provider_instances = $this->get_provider_instances( LanguageProcessing::get_service_providers() ); + $this->provider_instances = $this->get_provider_instances( ImageProcessing::get_service_providers() ); // Contains just the providers this feature supports. $this->supported_providers = [ diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 6941a8a37..60e854638 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -141,14 +141,13 @@ public function sanitize_api_key( array $new_settings ): string { /** * Common entry point for all REST endpoints for this provider. - * This is called by the Service. * - * @param int $post_id The Post Id we're processing. + * @param int $post_id The Post ID we're processing. * @param string $route_to_call The route we are processing. * @param array $args Optional arguments to pass to the route. * @return string|WP_Error */ - public function rest_endpoint_callback( $post_id = 0, $route_to_call = '', $args = [] ) { + public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { if ( ! $post_id || ! get_post( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) ); } diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index 5517e32ff..fdb5cc875 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -7,14 +7,8 @@ use Classifai\Features\AudioTranscriptsGeneration; use Classifai\Providers\Provider; -use Classifai\Providers\OpenAI\Whisper\Transcribe; -use WP_REST_Server; -use WP_REST_Request; use WP_Error; -use function Classifai\clean_input; -use function Classifai\get_asset_info; - class Whisper extends Provider { use \Classifai\Providers\OpenAI\OpenAI; @@ -26,6 +20,42 @@ class Whisper extends Provider { */ const ID = 'openai_whisper'; + /** + * OpenAI Whisper URL + * + * @var string + */ + protected $whisper_url = 'https://api.openai.com/v1/audio/'; + + /** + * OpenAI Whisper model + * + * @var string + */ + protected $whisper_model = 'whisper-1'; + + /** + * Supported file formats + * + * @var array + */ + public $file_formats = [ + 'mp3', + 'mp4', + 'mpeg', + 'mpga', + 'm4a', + 'wav', + 'webm', + ]; + + /** + * Maximum file size our model supports + * + * @var int + */ + public $max_file_size = 25 * MB_IN_BYTES; + /** * OpenAI Whisper constructor. * @@ -39,25 +69,22 @@ public function __construct( $feature_instance = null ) { ); $this->feature_instance = $feature_instance; - - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } /** - * Register what we need for the plugin. - * - * This only fires if can_register returns true. + * Register any needed hooks. */ public function register() { - if ( ! ( new AudioTranscriptsGeneration() )->is_feature_enabled() ) { - return; - } + } - add_action( 'add_attachment', [ $this, 'transcribe_audio' ] ); - add_filter( 'attachment_fields_to_edit', [ $this, 'add_buttons_to_media_modal' ], 10, 2 ); - add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); - add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_media_scripts' ] ); + /** + * Builds the API url. + * + * @param string $path Path to append to API URL. + * @return string + */ + public function get_api_url( string $path = '' ): string { + return sprintf( '%s%s', trailingslashit( $this->whisper_url ), $path ); } /** @@ -108,12 +135,7 @@ public function get_default_provider_settings(): array { 'authenticated' => false, ]; - switch ( $this->feature_instance::ID ) { - case AudioTranscriptsGeneration::ID: - return $common_settings; - } - - return []; + return $common_settings; } /** @@ -132,213 +154,93 @@ public function sanitize_settings( array $new_settings ): array { } /** - * Enqueue assets. - */ - public function enqueue_media_scripts() { - wp_enqueue_script( - 'classifai-media-script', - CLASSIFAI_PLUGIN_URL . 'dist/media.js', - array_merge( get_asset_info( 'media', 'dependencies' ), array( 'jquery', 'media-editor', 'lodash' ) ), - get_asset_info( 'media', 'version' ), - true - ); - } - - /** - * Start the audio transcription process. + * Common entry point for all REST endpoints for this provider. * - * @param int $attachment_id Attachment ID to process. - * @return WP_Error|bool + * @param int $post_id The Post ID we're processing. + * @param string $route_to_call The route we are processing. + * @param array $args Optional arguments to pass to the route. + * @return string|WP_Error */ - public function transcribe_audio( int $attachment_id = 0 ) { - if ( $attachment_id && ! current_user_can( 'edit_post', $attachment_id ) ) { - return new \WP_Error( 'no_permission', esc_html__( 'User does not have permission to edit this attachment.', 'classifai' ) ); + public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { + if ( ! $post_id || ! get_post( $post_id ) ) { + return new WP_Error( 'post_id_required', esc_html__( 'A valid attachment ID is required to generate a transcript.', 'classifai' ) ); } - $feature = new AudioTranscriptsGeneration(); - $enabled = $feature->is_feature_enabled(); + $route_to_call = strtolower( $route_to_call ); + $return = ''; - if ( is_wp_error( $enabled ) ) { - return $enabled; + // Handle all of our routes. + switch ( $route_to_call ) { + case 'transcript': + $return = $this->transcribe_audio( $post_id, $args ); + break; } - $settings = $feature->get_settings( static::ID ); - $transcribe = new Transcribe( intval( $attachment_id ), $settings ); - - return $transcribe->process(); + return $return; } /** - * Add new buttons to the media modal. - * - * @param array $form_fields Existing form fields. - * @param \WP_Post $attachment Attachment object. - * @return array - */ - public function add_buttons_to_media_modal( array $form_fields, \WP_Post $attachment ): array { - $feature = new AudioTranscriptsGeneration(); - $settings = $feature->get_settings(); - $transcribe = new Transcribe( $attachment->ID, $settings[ static::ID ] ); - - if ( ! $transcribe->should_process( $attachment->ID ) ) { - return $form_fields; - } - - $text = empty( get_the_content( null, false, $attachment ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); - - $form_fields['retranscribe'] = [ - 'label' => __( 'Transcribe audio', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - - return $form_fields; - } - - /** - * Add metabox on single attachment view to allow for transcription. - * - * @param \WP_Post $post Post object. - */ - public function setup_attachment_meta_box( \WP_Post $post ) { - $feature = new AudioTranscriptsGeneration(); - $settings = $feature->get_settings(); - $transcribe = new Transcribe( $post->ID, $settings[ static::ID ] ); - - if ( ! $transcribe->should_process( $post->ID ) ) { - return; - } - - add_meta_box( - 'attachment_meta_box', - __( 'ClassifAI Audio Processing', 'classifai' ), - [ $this, 'attachment_meta_box' ], - 'attachment', - 'side', - 'high' - ); - } - - /** - * Display the attachment meta box. - * - * @param \WP_Post $post Post object. - */ - public function attachment_meta_box( \WP_Post $post ) { - $text = empty( get_the_content( null, false, $post ) ) ? __( 'Transcribe', 'classifai' ) : __( 'Re-transcribe', 'classifai' ); - - wp_nonce_field( 'classifai_openai_whisper_meta_action', 'classifai_openai_whisper_meta' ); - ?> - -
    -
    - -
    -
    - - is_feature_enabled(); - - if ( is_wp_error( $enabled ) ) { - return; - } - if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $attachment_id ) ) { - return; + if ( ! $feature->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Transcript generation is disabled. Please check your settings.', 'classifai' ) ); } - if ( empty( $_POST['classifai_openai_whisper_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_openai_whisper_meta'] ) ), 'classifai_openai_whisper_meta_action' ) ) { - return; + if ( ! $feature->should_process( $attachment_id ) ) { + return new WP_Error( 'process_error', esc_html__( 'Attachment does not meet processing requirements. Ensure the file type and size meet requirements.', 'classifai' ) ); } - if ( clean_input( 'retranscribe' ) ) { - // Remove to avoid infinite loop. - remove_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); - $this->transcribe_audio( $attachment_id ); - } - } - - /** - * Register REST endpoints for this provider. - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1/openai', - 'generate-transcript/(?P\d+)', + $settings = $feature->get_settings(); + + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); + + /** + * Filter the request body before sending to Whisper. + * + * @since 2.2.0 + * @hook classifai_whisper_transcribe_request_body + * + * @param {array} $body Request body that will be sent to Whisper. + * @param {int} $attachment_id ID of attachment we are transcribing. + * + * @return {array} Request body. + */ + $body = apply_filters( + 'classifai_whisper_transcribe_request_body', [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'generate_audio_transcript' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Attachment ID to generate transcript for.', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_audio_transcript_permissions_check' ], - ] + 'file' => get_attached_file( $attachment_id ) ?? '', + 'model' => $this->whisper_model, + 'response_format' => 'json', + 'temperature' => 0, + ], + $attachment_id ); - } - - /** - * Handle request to generate a transcript for given attachment ID. - * - * @param WP_REST_Request $request The full request object. - * @return \WP_REST_Response|WP_Error - */ - public function generate_audio_transcript( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - return rest_ensure_response( ( new AudioTranscriptsGeneration() )->run( $attachment_id ) ); - } - - /** - * Check if a given request has access to generate a transcript. - * - * This check ensures we have a valid user with proper capabilities - * making the request, that we are properly authenticated with OpenAI - * and that transcription is turned on. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function generate_audio_transcript_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } + // Make our API request. + $response = $request->post_form( + $this->get_api_url( 'transcriptions' ), + $body + ); - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } + set_transient( 'classifai_openai_whisper_latest_response', $response, DAY_IN_SECONDS * 30 ); - if ( ! ( new AudioTranscriptsGeneration() )->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Audio transciption is not currently enabled.', 'classifai' ) ); + // Extract out the text response, if it exists. + if ( ! is_wp_error( $response ) && isset( $response['text'] ) ) { + $response = $response['text']; } - return true; + return $response; } /** @@ -346,10 +248,9 @@ public function generate_audio_transcript_permissions_check( WP_REST_Request $re * * @return array */ - public function get_debug_information() { - $settings = $this->feature_instance->get_settings(); - $provider_settings = $settings[ static::ID ]; - $debug_info = []; + public function get_debug_information(): array { + $settings = $this->feature_instance->get_settings(); + $debug_info = []; if ( $this->feature_instance instanceof AudioTranscriptsGeneration ) { $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_whisper_latest_response' ) ); diff --git a/includes/Classifai/Providers/OpenAI/Whisper/Transcribe.php b/includes/Classifai/Providers/OpenAI/Whisper/Transcribe.php deleted file mode 100644 index 33cf185b2..000000000 --- a/includes/Classifai/Providers/OpenAI/Whisper/Transcribe.php +++ /dev/null @@ -1,144 +0,0 @@ -attachment_id = $attachment_id; - $this->settings = $settings; - } - - /** - * Transcribe the audio file. - * - * @return string|WP_Error - */ - public function process() { - if ( ! $this->should_process( $this->attachment_id ) ) { - return new WP_Error( 'process_error', esc_html__( 'Attachment does not meet processing requirements. Ensure the file type and size meet requirements.', 'classifai' ) ); - } - - $request = new APIRequest( $this->settings['api_key'] ?? '', 'whisper' ); - - /** - * Filter the request body before sending to Whisper. - * - * @since 2.2.0 - * @hook classifai_whisper_transcribe_request_body - * - * @param {array} $body Request body that will be sent to Whisper. - * @param {int} $attachment_id ID of attachment we are transcribing. - * - * @return {array} Request body. - */ - $body = apply_filters( - 'classifai_whisper_transcribe_request_body', - [ - 'file' => get_attached_file( $this->attachment_id ) ?? '', - 'model' => $this->whisper_model, - 'response_format' => 'json', - 'temperature' => 0, - ], - $this->attachment_id - ); - - // Make our API request. - $response = $request->post_form( - $this->get_api_url( $this->path ), - $body - ); - - set_transient( 'classifai_openai_whisper_latest_response', $response, DAY_IN_SECONDS * 30 ); - - // Extract out the text response, if it exists. - if ( ! is_wp_error( $response ) && isset( $response['text'] ) ) { - $response = $this->add_transcription( $response['text'] ); - } - - return $response; - } - - /** - * Add the transcribed text to the attachment. - * - * @param string $text Transcription result. - * @return string|WP_Error - */ - public function add_transcription( string $text = '' ) { - if ( empty( $text ) ) { - return new WP_Error( 'invalid_result', esc_html__( 'The transcription result is invalid.', 'classifai' ) ); - } - - /** - * Filter the text result returned from Whisper API. - * - * @since 2.2.0 - * @hook classifai_whisper_transcribe_result - * - * @param {string} $text Text extracted from the response. - * @param {int} $attachment_id The attachment ID. - * - * @return {string} - */ - $text = apply_filters( 'classifai_whisper_transcribe_result', $text, $this->attachment_id ); - - $update = wp_update_post( - [ - 'ID' => (int) $this->attachment_id, - 'post_content' => wp_kses_post( $text ), - ], - true - ); - - if ( is_wp_error( $update ) ) { - return $update; - } else { - return $text; - } - } -} diff --git a/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php deleted file mode 100644 index b36edd52e..000000000 --- a/includes/Classifai/Providers/OpenAI/Whisper/Whisper.php +++ /dev/null @@ -1,85 +0,0 @@ -whisper_url ), $path ); - } - - /** - * Should this attachment be processed. - * - * Ensure the file is a supported format and is under the maximum file size. - * - * @param int $attachment_id Attachment ID to process. - * @return bool - */ - public function should_process( int $attachment_id ): bool { - $mime_type = get_post_mime_type( $attachment_id ); - $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); - $process = false; - - foreach ( $matched_extensions as $ext ) { - if ( in_array( $ext, $this->file_formats, true ) ) { - $process = true; - } - } - - // If we have a proper file format, check the file size. - if ( $process ) { - $filesize = filesize( get_attached_file( $attachment_id ) ); - if ( ! $filesize || $filesize > $this->max_file_size ) { - $process = false; - } - } - - return $process; - } -} diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index 6f1e11dd8..d14efb8b6 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -57,6 +57,7 @@ public static function get_service_providers(): array { 'classifai_language_processing_service_providers', [ 'Classifai\Providers\Azure\ComputerVision', + 'Classifai\Providers\OpenAI\DallE', ] ); } diff --git a/includes/Classifai/Services/LanguageProcessing.php b/includes/Classifai/Services/LanguageProcessing.php index 50365b2ad..86e8afe8f 100644 --- a/includes/Classifai/Services/LanguageProcessing.php +++ b/includes/Classifai/Services/LanguageProcessing.php @@ -41,7 +41,6 @@ public static function get_service_providers(): array { 'Classifai\Providers\OpenAI\ChatGPT', 'Classifai\Providers\OpenAI\Embeddings', 'Classifai\Providers\OpenAI\Whisper', - 'Classifai\Providers\OpenAI\DallE', 'Classifai\Providers\Azure\Speech', ] ); diff --git a/includes/Classifai/Services/ServicesManager.php b/includes/Classifai/Services/ServicesManager.php index b5882e432..29092fe7f 100644 --- a/includes/Classifai/Services/ServicesManager.php +++ b/includes/Classifai/Services/ServicesManager.php @@ -72,7 +72,6 @@ public function register_language_processing_features(): array { '\Classifai\Features\ContentResizing', '\Classifai\Features\TextToSpeech', '\Classifai\Features\AudioTranscriptsGeneration', - '\Classifai\Features\ImageGeneration', ]; } @@ -85,6 +84,7 @@ public function register_image_processing_features(): array { '\Classifai\Features\ImageTagsGenerator', '\Classifai\Features\ImageCropping', '\Classifai\Features\ImageTextExtraction', + '\Classifai\Features\ImageGeneration', '\Classifai\Features\PDFTextExtraction', ]; } diff --git a/src/js/media.js b/src/js/media.js index 5dcfb0dc4..d45c25733 100644 --- a/src/js/media.js +++ b/src/js/media.js @@ -136,7 +136,7 @@ import { __ } from '@wordpress/i18n'; transcribeButton.addEventListener( 'click', ( e ) => handleClick( { button: e.target, - endpoint: '/classifai/v1/openai/generate-transcript/', + endpoint: '/classifai/v1/generate-transcript/', callback: ( resp ) => { if ( resp ) { const textField = From 2adbebf1f175e197b9298d27d72969cae5bf484b Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 25 Jan 2024 13:11:59 -0700 Subject: [PATCH 111/127] Migrate all Image Generation specific items from the Provider class to the Feature class --- .../Features/AudioTranscriptsGeneration.php | 11 +- .../Classifai/Features/ContentResizing.php | 9 +- .../Classifai/Features/ExcerptGeneration.php | 11 +- .../Classifai/Features/ImageGeneration.php | 325 +++++++++++++-- .../Classifai/Features/TitleGeneration.php | 9 +- .../Providers/Azure/ComputerVision.php | 2 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 2 +- includes/Classifai/Providers/OpenAI/DallE.php | 391 ++++-------------- .../Classifai/Providers/OpenAI/Whisper.php | 6 +- includes/Classifai/Providers/Provider.php | 6 +- includes/Classifai/Providers/Watson/NLU.php | 2 +- 11 files changed, 421 insertions(+), 353 deletions(-) diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 1c38e3705..a37e723e0 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -39,9 +39,18 @@ public function __construct() { /** * Set up necessary hooks. + * + * We utilize this so we can register the REST route. */ - public function feature_setup() { + public function setup() { + parent::setup(); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); add_action( 'edit_attachment', [ $this, 'maybe_transcribe_audio' ] ); diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 02f6c64cb..c7bfdc286 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -54,11 +54,18 @@ public function __construct() { /** * Set up necessary hooks. + * + * We utilize this so we can register the REST route. */ public function setup() { parent::setup(); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index a90ac0d86..1b6079110 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -46,9 +46,18 @@ public function __construct() { /** * Set up necessary hooks. + * + * We utilize this so we can register the REST route. */ - public function feature_setup() { + public function setup() { + parent::setup(); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); } diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index 234f3c24d..42e808467 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -4,6 +4,12 @@ use Classifai\Services\ImageProcessing; use Classifai\Providers\OpenAI\DallE; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\get_asset_info; +use function Classifai\render_disable_feature_link; /** * Class ImageGeneration @@ -31,6 +37,294 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'admin_menu', [ $this, 'register_generate_media_page' ], 0 ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] ); + add_action( 'print_media_templates', [ $this, 'print_media_templates' ] ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'generate-image', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'prompt' => [ + 'required' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Prompt used to generate an image', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_image_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate an image. + * + * This check ensures we have a valid user with proper capabilities + * making the request, that we are properly authenticated with OpenAI + * and that image generation is turned on. + * + * @return WP_Error|bool + */ + public function generate_image_permissions_check() { + // Ensure the feature is enabled. Also runs a user check. + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image generation not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( '/classifai/v1/generate-image' === $route ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'prompt' ), + 'image_gen', + $request->get_params(), + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Registers a Media > Generate Image submenu. + */ + public function register_generate_media_page() { + if ( ! $this->is_feature_enabled() ) { + return; + } + + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $number_of_images = absint( $settings[ $provider_id ]['number_of_images'] ); + + add_submenu_page( + 'upload.php', + $number_of_images > 1 ? esc_html__( 'Generate Images', 'classifai' ) : esc_html__( 'Generate Image', 'classifai' ), + $number_of_images > 1 ? esc_html__( 'Generate Images', 'classifai' ) : esc_html__( 'Generate Image', 'classifai' ), + 'upload_files', + esc_url( admin_url( 'upload.php?action=classifai-generate-image' ) ), + '' + ); + } + + /** + * Enqueue the admin scripts. + * + * @since 2.4.0 Use get_asset_info to get the asset version and dependencies. + * + * @param string $hook_suffix The current admin page. + */ + public function enqueue_admin_scripts( string $hook_suffix = '' ) { + if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix && 'upload.php' !== $hook_suffix ) { + return; + } + + if ( ! $this->is_feature_enabled() ) { + return; + } + + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $number_of_images = absint( $settings[ $provider_id ]['number_of_images'] ); + + wp_enqueue_media(); + + wp_enqueue_style( + 'classifai-image-processing-style', + CLASSIFAI_PLUGIN_URL . 'dist/media-modal.css', + [], + get_asset_info( 'media-modal', 'version' ), + 'all' + ); + + wp_enqueue_script( + 'classifai-generate-images', + CLASSIFAI_PLUGIN_URL . 'dist/media-modal.js', + array_merge( get_asset_info( 'media-modal', 'dependencies' ), array( 'jquery', 'wp-api' ) ), + get_asset_info( 'media-modal', 'version' ), + true + ); + + wp_enqueue_script( + 'classifai-inserter-media-category', + CLASSIFAI_PLUGIN_URL . 'dist/inserter-media-category.js', + get_asset_info( 'inserter-media-category', 'dependencies' ), + get_asset_info( 'inserter-media-category', 'version' ), + true + ); + + /** + * Filter the default attribution added to generated images. + * + * @since 2.1.0 + * @hook classifai_dalle_caption + * + * @param {string} $caption Attribution to be added as a caption to the image. + * + * @return {string} Caption. + */ + $caption = apply_filters( + 'classifai_dalle_caption', + sprintf( + /* translators: %1$s is replaced with the OpenAI DALLĀ·E URL */ + esc_html__( 'Image generated by OpenAI\'s DALLĀ·E', 'classifai' ), + 'https://openai.com/research/dall-e' + ) + ); + + wp_localize_script( + 'classifai-generate-images', + 'classifaiDalleData', + [ + 'endpoint' => 'classifai/v1/generate-image', + 'tabText' => $number_of_images > 1 ? esc_html__( 'Generate images', 'classifai' ) : esc_html__( 'Generate image', 'classifai' ), + 'errorText' => esc_html__( 'Something went wrong. No results found', 'classifai' ), + 'buttonText' => esc_html__( 'Select image', 'classifai' ), + 'caption' => $caption, + ] + ); + + if ( 'upload.php' === $hook_suffix ) { + $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( 'classifai-generate-image' === $action ) { + wp_enqueue_script( + 'classifai-generate-images-media-upload', + CLASSIFAI_PLUGIN_URL . 'dist/generate-image-media-upload.js', + array_merge( get_asset_info( 'generate-image-media-upload', 'dependencies' ), array( 'jquery' ) ), + get_asset_info( 'classifai-generate-images-media-upload', 'version' ), + true + ); + + wp_localize_script( + 'classifai-generate-images-media-upload', + 'classifaiGenerateImages', + [ + 'upload_url' => esc_url( admin_url( 'upload.php' ) ), + ] + ); + } + } + } + + /** + * Print the templates we need for our media modal integration. + */ + public function print_media_templates() { + if ( ! $this->is_feature_enabled() ) { + return; + } + + $settings = $this->get_settings(); + $provider_id = $settings['provider']; + $number_of_images = absint( $settings[ $provider_id ]['number_of_images'] ); + $provider_instance = $this->get_feature_provider_instance( $provider_id ); + ?> + + + + + + + DallE::ID, ]; } - - /** - * Runs the feature. - * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? DallE::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( DallE::ID === $provider_instance::ID ) { - /** @var DallE $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'generate_image' ], - [ ...$args ] - ); - } - - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); - } } diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 6195d7862..dff6d3050 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -47,11 +47,18 @@ public function __construct() { /** * Set up necessary hooks. + * + * We utilize this so we can register the REST route. */ public function setup() { parent::setup(); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_action( 'edit_form_before_permalink', [ $this, 'register_generated_titles_template' ] ); diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 6659d3e39..7261bab30 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -1339,7 +1339,7 @@ public function computer_vision_endpoint_callback( WP_REST_Request $request ) { * @param array $args Optional arguments to pass to the route. * @return array|string|WP_Error */ - public function rest_endpoint_callback( int $post_id, string $route_to_call = '', array $args = [] ) { + public function rest_endpoint_callback( $post_id, string $route_to_call = '', array $args = [] ) { // Check to be sure the post both exists and is an attachment. if ( ! get_post( $post_id ) || 'attachment' !== get_post_type( $post_id ) ) { /* translators: %1$s: the attachment ID */ diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 60e854638..66a239b20 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -147,7 +147,7 @@ public function sanitize_api_key( array $new_settings ): string { * @param array $args Optional arguments to pass to the route. * @return string|WP_Error */ - public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { + public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) { if ( ! $post_id || ! get_post( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) ); } diff --git a/includes/Classifai/Providers/OpenAI/DallE.php b/includes/Classifai/Providers/OpenAI/DallE.php index 25a6a7b09..556247056 100644 --- a/includes/Classifai/Providers/OpenAI/DallE.php +++ b/includes/Classifai/Providers/OpenAI/DallE.php @@ -9,10 +9,7 @@ use Classifai\Providers\Provider; use Classifai\Providers\OpenAI\APIRequest; use WP_Error; -use WP_REST_Request; use WP_REST_Server; -use function Classifai\get_asset_info; -use function Classifai\render_disable_feature_link; class DallE extends Provider { @@ -47,8 +44,6 @@ public function __construct( $feature_instance = null ) { ); $this->feature_instance = $feature_instance; - - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); } /** @@ -57,9 +52,64 @@ public function __construct( $feature_instance = null ) { * This only fires if can_register returns true. */ public function register() { - add_action( 'admin_menu', [ $this, 'register_generate_media_page' ], 0 ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts' ] ); - add_action( 'print_media_templates', [ $this, 'print_media_templates' ] ); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Register any needed endpoints. + * + * This endpoint is registered in the feature class + * but we need to add additional arguments to it + * that this provider supports. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'generate-image', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this->feature_instance, 'rest_endpoint_callback' ], + 'args' => [ + 'prompt' => [ + 'required' => true, + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Prompt used to generate an image', 'classifai' ), + ], + 'n' => [ + 'type' => 'integer', + 'minimum' => 1, + 'maximum' => 10, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Number of images to generate', 'classifai' ), + ], + 'size' => [ + 'type' => 'string', + 'enum' => [ + '256x256', + '512x512', + '1024x1024', + ], + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Size of generated image', 'classifai' ), + ], + 'format' => [ + 'type' => 'string', + 'enum' => [ + 'url', + 'b64_json', + ], + 'sanitize_callback' => 'sanitize_text_field', + 'validate_callback' => 'rest_validate_request_arg', + 'description' => esc_html__( 'Format of generated image', 'classifai' ), + ], + ], + 'permission_callback' => [ $this->feature_instance, 'generate_image_permissions_check' ], + ] + ); } /** @@ -158,215 +208,6 @@ public function get_default_provider_settings(): array { return $common_settings; } - /** - * Registers a Media > Generate Image submenu - */ - public function register_generate_media_page() { - $image_generation = new ImageGeneration(); - - if ( $image_generation->is_feature_enabled() ) { - $settings = $image_generation->get_settings( static::ID ); - $number_of_images = absint( $settings['number_of_images'] ); - - add_submenu_page( - 'upload.php', - $number_of_images > 1 ? esc_html__( 'Generate Images', 'classifai' ) : esc_html__( 'Generate Image', 'classifai' ), - $number_of_images > 1 ? esc_html__( 'Generate Images', 'classifai' ) : esc_html__( 'Generate Image', 'classifai' ), - 'upload_files', - esc_url( admin_url( 'upload.php?action=classifai-generate-image' ) ), - '' - ); - } - } - - /** - * Enqueue the admin scripts. - * - * @since 2.4.0 Use get_asset_info to get the asset version and dependencies. - * - * @param string $hook_suffix The current admin page. - */ - public function enqueue_admin_scripts( string $hook_suffix = '' ) { - if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix && 'upload.php' !== $hook_suffix ) { - return; - } - - $image_generation = new ImageGeneration(); - - if ( $image_generation->is_feature_enabled() ) { - $settings = $image_generation->get_settings( static::ID ); - $number_of_images = absint( $settings['number_of_images'] ); - - wp_enqueue_media(); - - wp_enqueue_style( - 'classifai-image-processing-style', - CLASSIFAI_PLUGIN_URL . 'dist/media-modal.css', - [], - get_asset_info( 'media-modal', 'version' ), - 'all' - ); - - wp_enqueue_script( - 'classifai-generate-images', - CLASSIFAI_PLUGIN_URL . 'dist/media-modal.js', - array_merge( get_asset_info( 'media-modal', 'dependencies' ), array( 'jquery', 'wp-api' ) ), - get_asset_info( 'media-modal', 'version' ), - true - ); - - wp_enqueue_script( - 'classifai-inserter-media-category', - CLASSIFAI_PLUGIN_URL . 'dist/inserter-media-category.js', - get_asset_info( 'inserter-media-category', 'dependencies' ), - get_asset_info( 'inserter-media-category', 'version' ), - true - ); - - /** - * Filter the default attribution added to generated images. - * - * @since 2.1.0 - * @hook classifai_dalle_caption - * - * @param {string} $caption Attribution to be added as a caption to the image. - * - * @return {string} Caption. - */ - $caption = apply_filters( - 'classifai_dalle_caption', - sprintf( - /* translators: %1$s is replaced with the OpenAI DALLĀ·E URL */ - esc_html__( 'Image generated by OpenAI\'s DALLĀ·E', 'classifai' ), - 'https://openai.com/research/dall-e' - ) - ); - - wp_localize_script( - 'classifai-generate-images', - 'classifaiDalleData', - [ - 'endpoint' => 'classifai/v1/openai/generate-image', - 'tabText' => $number_of_images > 1 ? esc_html__( 'Generate images', 'classifai' ) : esc_html__( 'Generate image', 'classifai' ), - 'errorText' => esc_html__( 'Something went wrong. No results found', 'classifai' ), - 'buttonText' => esc_html__( 'Select image', 'classifai' ), - 'caption' => $caption, - ] - ); - - if ( 'upload.php' === $hook_suffix ) { - $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - - if ( 'classifai-generate-image' === $action ) { - wp_enqueue_script( - 'classifai-generate-images-media-upload', - CLASSIFAI_PLUGIN_URL . 'dist/generate-image-media-upload.js', - array_merge( get_asset_info( 'generate-image-media-upload', 'dependencies' ), array( 'jquery' ) ), - get_asset_info( 'classifai-generate-images-media-upload', 'version' ), - true - ); - - wp_localize_script( - 'classifai-generate-images-media-upload', - 'classifaiGenerateImages', - [ - 'upload_url' => esc_url( admin_url( 'upload.php' ) ), - ] - ); - } - } - } - } - - /** - * Print the templates we need for our media modal integration. - */ - public function print_media_templates() { - $image_generation = new ImageGeneration(); - - if ( $image_generation->is_feature_enabled() ) : - $settings = $image_generation->get_settings( static::ID ); - $number_of_images = absint( $settings['number_of_images'] ); - ?> - - - - - - - - - get_option_name(), $this->get_default_settings() ); } + /** + * Common entry point for all REST endpoints for this provider. + * + * @param string $prompt The prompt used to generate an image. + * @param string $route_to_call The route we are processing. + * @param array $args Optional arguments to pass to the route. + * @return string|WP_Error + */ + public function rest_endpoint_callback( $prompt = '', string $route_to_call = '', array $args = [] ) { + $route_to_call = strtolower( $route_to_call ); + $return = ''; + + // Handle all of our routes. + switch ( $route_to_call ) { + case 'image_gen': + $return = $this->generate_image( $prompt, $args ); + break; + } + + return $return; + } + /** * Entry point for the generate-image REST endpoint. * @@ -493,104 +356,12 @@ public function generate_image( string $prompt = '', array $args = [] ) { return $response; } - /** - * Registers REST endpoints for this provider. - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1/openai', - 'generate-image', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'generate_image_endpoint_callback' ], - 'args' => [ - 'prompt' => [ - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Prompt used to generate an image', 'classifai' ), - ], - 'n' => [ - 'type' => 'integer', - 'minimum' => 1, - 'maximum' => 10, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Number of images to generate', 'classifai' ), - ], - 'size' => [ - 'type' => 'string', - 'enum' => [ - '256x256', - '512x512', - '1024x1024', - ], - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Size of generated image', 'classifai' ), - ], - 'format' => [ - 'type' => 'string', - 'enum' => [ - 'url', - 'b64_json', - ], - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Format of generated image', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'generate_image_permissions_check' ], - ] - ); - } - - /** - * Handle request to generate an image for a given prompt. - * - * @param WP_REST_Request $request The full request object. - * @return \WP_REST_Response|WP_Error - */ - public function generate_image_endpoint_callback( WP_REST_Request $request ) { - return rest_ensure_response( - ( new ImageGeneration() )->run( - $request->get_param( 'prompt' ), - [ - 'num' => $request->get_param( 'n' ), - 'size' => $request->get_param( 'size' ), - 'format' => $request->get_param( 'format' ), - ] - ) - ); - } - - /** - * Check if a given request has access to generate an image. - * - * This check ensures we have a valid user with proper capabilities - * making the request, that we are properly authenticated with OpenAI - * and that image generation is turned on. - * - * @return WP_Error|bool - */ - public function generate_image_permissions_check() { - $image_generation = new ImageGeneration(); - - // Ensure the feature is enabled. Also runs a user check. - if ( ! $image_generation->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Image generation not currently enabled.', 'classifai' ) ); - } - - return true; - } - /** * Returns the debug information for the provider settings. * * @return array */ - public function get_debug_information() { + public function get_debug_information(): array { $settings = $this->feature_instance->get_settings(); $provider_settings = $settings[ static::ID ]; $debug_info = []; diff --git a/includes/Classifai/Providers/OpenAI/Whisper.php b/includes/Classifai/Providers/OpenAI/Whisper.php index fdb5cc875..3e513bbba 100644 --- a/includes/Classifai/Providers/OpenAI/Whisper.php +++ b/includes/Classifai/Providers/OpenAI/Whisper.php @@ -161,7 +161,7 @@ public function sanitize_settings( array $new_settings ): array { * @param array $args Optional arguments to pass to the route. * @return string|WP_Error */ - public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { + public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) { if ( ! $post_id || ! get_post( $post_id ) ) { return new WP_Error( 'post_id_required', esc_html__( 'A valid attachment ID is required to generate a transcript.', 'classifai' ) ); } @@ -201,9 +201,9 @@ public function transcribe_audio( int $attachment_id = 0, array $args = [] ) { return new WP_Error( 'process_error', esc_html__( 'Attachment does not meet processing requirements. Ensure the file type and size meet requirements.', 'classifai' ) ); } - $settings = $feature->get_settings(); + $settings = $feature->get_settings( static::ID ); - $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); + $request = new APIRequest( $settings['api_key'] ?? '', $feature->get_option_name() ); /** * Filter the request body before sending to Whisper. diff --git a/includes/Classifai/Providers/Provider.php b/includes/Classifai/Providers/Provider.php index edf6ddd7a..5eb26664c 100644 --- a/includes/Classifai/Providers/Provider.php +++ b/includes/Classifai/Providers/Provider.php @@ -146,12 +146,12 @@ public function get_default_settings(): array { /** * Common entry point for all REST endpoints for this provider. * - * @param int $post_id The Post Id we're processing. + * @param mixed $item The item we're processing. * @param string $route_to_call The name of the route we're going to be processing. - * @param array $args Optional arguments to pass to the route. + * @param array $args Optional arguments to pass to the route. * @return mixed */ - public function rest_endpoint_callback( int $post_id, string $route_to_call, array $args = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function rest_endpoint_callback( $item, string $route_to_call, array $args = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed return null; } diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 9e5ad98be..9f2f4b5d8 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -858,7 +858,7 @@ public function get_all_feature_taxonomies() { * @param array $args Optional arguments to pass to the route. * @return string|WP_Error */ - public function rest_endpoint_callback( int $post_id = 0, string $route_to_call = '', array $args = [] ) { + public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) { $route_to_call = strtolower( $route_to_call ); if ( ! $post_id || ! get_post( $post_id ) ) { From 2a53c86bdfade362d9818191c3e8a10c56a7dd32 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 25 Jan 2024 16:59:32 -0700 Subject: [PATCH 112/127] Initial work done on getting the Azure AI Vision things moved from the Provider class to the Feature classes. Also fixed some bugs I found --- .../Classifai/Features/ContentResizing.php | 2 +- .../Features/DescriptiveTextGenerator.php | 361 ++++++++- .../Classifai/Features/ExcerptGeneration.php | 2 +- includes/Classifai/Features/ImageCropping.php | 220 ++++++ .../Classifai/Features/ImageGeneration.php | 2 +- .../Classifai/Features/ImageTagsGenerator.php | 328 +++++++- .../Features/ImageTextExtraction.php | 312 ++++++++ .../Classifai/Features/TitleGeneration.php | 2 +- .../Providers/Azure/ComputerVision.php | 742 ++---------------- .../Classifai/Services/ImageProcessing.php | 24 +- 10 files changed, 1251 insertions(+), 744 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index c7bfdc286..1aea08a94 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -144,7 +144,7 @@ public function resize_content_permissions_check( WP_REST_Request $request ) { public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( '/classifai/v1/resize-content' === $route ) { + if ( strpos( $route, '/classifai/v1/resize-content' ) === 0 ) { return rest_ensure_response( $this->run( $request->get_param( 'id' ), diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index 072d4b9fd..dd88dd633 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -4,6 +4,11 @@ use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\clean_input; /** * Class DescriptiveTextGenerator @@ -31,6 +36,296 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + + add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_alt_tags' ], 8, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'alt-tags/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'descriptive_text_generator_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate descriptive text. + * + * @param WP_REST_Request $request Request object. + * @return bool|WP_Error + */ + public function descriptive_text_generator_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image descriptive text is disabled. Please check your settings.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/alt-tags' ) === 0 ) { + $result = $this->run( $request->get_param( 'id' ), 'descriptive_text' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $request->get_param( 'id' ) ); + } + + return rest_ensure_response( $result ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Generate the alt tags for the image being uploaded. + * + * @param array $metadata The metadata for the image. + * @param int $attachment_id Post ID for the attachment. + * @return array + */ + public function generate_image_alt_tags( array $metadata, int $attachment_id ): array { + if ( ! $this->is_feature_enabled() ) { + return $metadata; + } + + $result = $this->run( $attachment_id, 'descriptive_text' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } + + return $metadata; + } + + /** + * Save the returned result based on our settings. + * + * @param string $result The result to save. + * @param int $attachment_id The attachment ID. + */ + public function save( string $result, int $attachment_id ) { + $enabled_fields = $this->get_alt_text_settings(); + + if ( in_array( 'alt', $enabled_fields, true ) ) { + update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $result ) ); + } + + $excerpt = get_the_excerpt( $attachment_id ); + + if ( in_array( 'caption', $enabled_fields, true ) && $excerpt !== $result ) { + wp_update_post( + array( + 'ID' => $attachment_id, + 'post_excerpt' => sanitize_text_field( $result ), + ) + ); + } + + $content = get_the_content( null, false, $attachment_id ); + + if ( in_array( 'description', $enabled_fields, true ) && $content !== $result ) { + wp_update_post( + array( + 'ID' => $attachment_id, + 'post_content' => sanitize_text_field( $result ), + ) + ); + } + } + + /** + * Adds a meta box for rescanning options if the settings are configured. + * + * @param \WP_Post $post The post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + if ( ! wp_attachment_is_image( $post ) || ! $this->is_feature_enabled() ) { + return; + } + + // Add our content to the metabox. + add_action( 'classifai_render_attachment_metabox', [ $this, 'attachment_data_meta_box_content' ] ); + + // If the metabox was already registered, don't add it again. + if ( isset( $wp_meta_boxes['attachment']['side']['high']['classifai_image_processing'] ) ) { + return; + } + + // Register the metabox if needed. + add_meta_box( + 'classifai_image_processing', + __( 'ClassifAI Image Processing', 'classifai' ), + [ $this, 'attachment_data_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Render the meta box. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box( \WP_Post $post ) { + /** + * Allows more fields to be rendered in attachment metabox. + * + * @since 3.0.0 + * @hook classifai_render_attachment_metabox + * + * @param {WP_Post} $post The post object. + * @param {object} $this The Provider object. + */ + do_action( 'classifai_render_attachment_metabox', $post, $this ); + } + + /** + * Display meta data. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box_content( \WP_Post $post ) { + $captions = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ? __( 'No descriptive text? Rescan image', 'classifai' ) : __( 'Generate descriptive text', 'classifai' ); + ?> + + is_feature_enabled() && ! empty( $this->get_alt_text_settings() ) ) : ?> +
    + +
    + run( $attachment_id, 'descriptive_text' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } + } + } + + /** + * Adds the rescan buttons to the media modal. + * + * @param array $form_fields Array of fields + * @param \WP_Post $post Post object for the attachment being viewed. + * @return array + */ + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ): array { + if ( + ! $this->is_feature_enabled() || + ! wp_attachment_is_image( $post ) || + empty( $this->get_alt_text_settings() ) + ) { + return $form_fields; + } + + $alt_tags_text = empty( get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); + + $form_fields['rescan_alt_tags'] = [ + 'label' => __( 'Descriptive text', 'classifai' ), + 'input' => 'html', + 'show_in_edit' => false, + 'html' => '', + ]; + + return $form_fields; + } + + /** + * Returns an array of fields enabled to be set to store image captions. + * + * @return array + */ + public function get_alt_text_settings(): array { + $settings = $this->get_settings(); + $enabled_fields = array(); + + if ( ! isset( $settings['descriptive_text_fields'] ) ) { + return array(); + } + + if ( ! is_array( $settings['descriptive_text_fields'] ) ) { + return array( + 'alt' => 'no' === $settings['descriptive_text_fields']['caption'] ? 0 : 'alt', + 'caption' => 0, + 'description' => 0, + ); + } + + foreach ( $settings['descriptive_text_fields'] as $key => $value ) { + if ( 0 !== $value && '0' !== $value ) { + $enabled_fields[] = $key; + } + } + + return $enabled_fields; + } + /** * Get the description for the enable field. * @@ -40,6 +335,32 @@ public function get_enable_description(): string { return esc_html__( 'Enable this to generate descriptive text for images.', 'classifai' ); } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); + $checkbox_options = array( + 'alt' => esc_html__( 'Alt text', 'classifai' ), + 'caption' => esc_html__( 'Image caption', 'classifai' ), + 'description' => esc_html__( 'Image description', 'classifai' ), + ); + + add_settings_field( + 'descriptive_text_fields', + esc_html__( 'Descriptive text fields', 'classifai' ), + [ $this, 'render_checkbox_group' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'descriptive_text_fields', + 'options' => $checkbox_options, + 'default_values' => $settings['descriptive_text_fields'], + 'description' => __( 'Choose image fields where the generated text should be applied.', 'classifai' ), + ] + ); + } + /** * Returns the default settings for the feature. * @@ -47,36 +368,26 @@ public function get_enable_description(): string { */ public function get_feature_default_settings(): array { return [ - 'provider' => ComputerVision::ID, + 'descriptive_text_fields' => [ + 'alt' => 0, + 'caption' => 0, + 'description' => 0, + ], + 'provider' => ComputerVision::ID, ]; } /** - * Runs the feature. + * Sanitizes the default feature settings. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ComputerVision::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ComputerVision::ID === $provider_instance::ID ) { - /** @var ComputerVision $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'generate_alt_tags' ], - [ ...$args ] - ); - } + * @param array $new_settings Settings being saved. + * @return array + */ + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + $new_settings['descriptive_text_fields'] = array_map( 'sanitize_text_field', $new_settings['descriptive_text_fields'] ?? $settings['descriptive_text_fields'] ); + + return $new_settings; } } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 1b6079110..badacb04d 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -151,7 +151,7 @@ public function generate_excerpt_permissions_check( WP_REST_Request $request ) { public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( '/classifai/v1/generate-excerpt' === $route ) { + if ( strpos( $route, '/classifai/v1/generate-excerpt' ) === 0 ) { return rest_ensure_response( $this->run( $request->get_param( 'id' ), diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index acd0d70b1..57d78d35f 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -4,6 +4,14 @@ use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\computer_vision_max_filesize; +use function Classifai\get_largest_acceptable_image_url; +use function Classifai\get_modified_image_source_url; +use function Classifai\clean_input; /** * Class ImageCropping @@ -31,6 +39,218 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + + add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'smart-crop/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate smart crop.', 'classifai' ), + ], + 'route' => [ 'smart-crop' ], + ], + 'permission_callback' => [ $this, 'smart_crop_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate smart crops. + * + * @param WP_REST_Request $request Request object. + * @return bool|WP_Error + */ + public function smart_crop_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Smart cropping is disabled. Please check your settings.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/alt-tags' ) === 0 ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'id' ), + 'excerpt', + [ + 'content' => $request->get_param( 'content' ), + 'title' => $request->get_param( 'title' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Adds a meta box for rescanning options if the settings are configured. + * + * @param \WP_Post $post The post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + if ( ! wp_attachment_is_image( $post ) || ! $this->is_feature_enabled() ) { + return; + } + + // Add our content to the metabox. + add_action( 'classifai_render_attachment_metabox', [ $this, 'attachment_data_meta_box_content' ] ); + + // If the metabox was already registered, don't add it again. + if ( isset( $wp_meta_boxes['attachment']['side']['high']['classifai_image_processing'] ) ) { + return; + } + + // Register the metabox if needed. + add_meta_box( + 'classifai_image_processing', + __( 'ClassifAI Image Processing', 'classifai' ), + [ $this, 'attachment_data_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Render the meta box. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box( \WP_Post $post ) { + /** + * Allows more fields to be rendered in attachment metabox. + * + * @since 3.0.0 + * @hook classifai_render_attachment_metabox + * + * @param {WP_Post} $post The post object. + * @param {object} $this The Provider object. + */ + do_action( 'classifai_render_attachment_metabox', $post, $this ); + } + + /** + * Display meta data. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box_content( \WP_Post $post ) { + $smart_crop = get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ? __( 'Regenerate smart thumbnail', 'classifai' ) : __( 'Create smart thumbnail', 'classifai' ); + ?> + + is_feature_enabled() ) : ?> +
    + +
    + run( $attachment_id, 'crop', $metadata ); + } + } + + /** + * Adds the rescan buttons to the media modal. + * + * @param array $form_fields Array of fields + * @param \WP_Post $post Post object for the attachment being viewed. + * @return array + */ + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ): array { + if ( ! $this->is_feature_enabled() || ! wp_attachment_is_image( $post ) ) { + return $form_fields; + } + + $smart_crop_text = empty( get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ) ? __( 'Generate', 'classifai' ) : __( 'Regenerate', 'classifai' ); + + $form_fields['rescan_smart_crop'] = [ + 'label' => __( 'Smart thumbnail', 'classifai' ), + 'input' => 'html', + 'show_in_edit' => false, + 'html' => '', + ]; + + return $form_fields; + } + /** * Get the description for the enable field. * diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index 42e808467..a55f39870 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -107,7 +107,7 @@ public function generate_image_permissions_check() { public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( '/classifai/v1/generate-image' === $route ) { + if ( strpos( $route, '/classifai/v1/generate-image' ) === 0 ) { return rest_ensure_response( $this->run( $request->get_param( 'prompt' ), diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 894286d34..a9220c8fa 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -4,6 +4,15 @@ use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\computer_vision_max_filesize; +use function Classifai\get_largest_acceptable_image_url; +use function Classifai\get_modified_image_source_url; +use function Classifai\clean_input; +use function Classifai\check_term_permissions; /** * Class ImageTagsGenerator @@ -31,6 +40,256 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + + add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_tags' ], 8, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'image-tags/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate tags for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'image_tags_generator_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate image tags. + * + * @param WP_REST_Request $request Request object. + * @return bool|WP_Error + */ + public function image_tags_generator_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image tagging is disabled. Please check your settings.', 'classifai' ) ); + } + + $settings = $this->get_settings(); + if ( ! empty( $settings ) && isset( $settings['tag_taxonomy'] ) ) { + $permission = check_term_permissions( $settings['tag_taxonomy'] ); + + if ( is_wp_error( $permission ) ) { + return $permission; + } + } else { + return new WP_Error( 'invalid_settings', esc_html__( 'Ensure the service settings have been saved.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/image-tags' ) === 0 ) { + $result = $this->run( $request->get_param( 'id' ), 'tags' ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $this->save( $result, $request->get_param( 'id' ) ); + } + + return rest_ensure_response( $result ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Generate the tags for the image being uploaded. + * + * @param array $metadata The metadata for the image. + * @param int $attachment_id Post ID for the attachment. + * @return array + */ + public function generate_image_tags( array $metadata, int $attachment_id ): array { + if ( ! $this->is_feature_enabled() ) { + return $metadata; + } + + $result = $this->run( $attachment_id, 'tags' ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } + + return $metadata; + } + + /** + * Save the returned result based on our settings. + * + * @param array $result The results to save. + * @param int $attachment_id The attachment ID. + */ + public function save( array $result, int $attachment_id ) { + $settings = $this->get_settings(); + $taxonomy = $settings['tag_taxonomy']; + + foreach ( $result as $tag ) { + wp_add_object_terms( $attachment_id, $tag, $taxonomy ); + } + + wp_update_term_count_now( $result, $taxonomy ); + } + + /** + * Adds a meta box for rescanning options if the settings are configured. + * + * @param \WP_Post $post The post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + global $wp_meta_boxes; + + if ( ! wp_attachment_is_image( $post ) || ! $this->is_feature_enabled() ) { + return; + } + + // Add our content to the metabox. + add_action( 'classifai_render_attachment_metabox', [ $this, 'attachment_data_meta_box_content' ] ); + + // If the metabox was already registered, don't add it again. + if ( isset( $wp_meta_boxes['attachment']['side']['high']['classifai_image_processing'] ) ) { + return; + } + + // Register the metabox if needed. + add_meta_box( + 'classifai_image_processing', + __( 'ClassifAI Image Processing', 'classifai' ), + [ $this, 'attachment_data_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Render the meta box. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box( \WP_Post $post ) { + /** + * Allows more fields to be rendered in attachment metabox. + * + * @since 3.0.0 + * @hook classifai_render_attachment_metabox + * + * @param {WP_Post} $post The post object. + * @param {object} $this The Provider object. + */ + do_action( 'classifai_render_attachment_metabox', $post, $this ); + } + + /** + * Display meta data + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box_content( \WP_Post $post ) { + $tags = ! empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Rescan image for new tags', 'classifai' ) : __( 'Generate image tags', 'classifai' ); + ?> + + is_feature_enabled() ) : ?> +
    + +
    + run( $attachment_id, 'tags' ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } + } + } + + /** + * Adds the rescan buttons to the media modal. + * + * @param array $form_fields Array of fields + * @param \WP_Post $post Post object for the attachment being viewed. + * @return array + */ + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ): array { + if ( ! $this->is_feature_enabled() || ! wp_attachment_is_image( $post ) ) { + return $form_fields; + } + + $image_tags_text = empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); + + $form_fields['rescan_captions'] = [ + 'label' => __( 'Image tags', 'classifai' ), + 'input' => 'html', + 'show_in_edit' => false, + 'html' => '', + ]; + + return $form_fields; + } + /** * Get the description for the enable field. * @@ -40,43 +299,62 @@ public function get_enable_description(): string { return esc_html__( 'Image tags will be added automatically.', 'classifai' ); } + /** + * Add any needed custom fields. + */ + public function add_custom_settings_fields() { + $settings = $this->get_settings(); + $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); + $options = []; + + foreach ( $attachment_taxonomies as $name => $taxonomy ) { + $options[ $name ] = $taxonomy->label; + } + + add_settings_field( + 'tag_taxonomy', + esc_html__( 'Tag taxonomy', 'classifai' ), + [ $this, 'render_select' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'tag_taxonomy', + 'options' => $options, + 'default_value' => $settings['tag_taxonomy'], + ] + ); + } + /** * Returns the default settings for the feature. * * @return array */ public function get_feature_default_settings(): array { + $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); + $options = []; + + foreach ( $attachment_taxonomies as $name => $taxonomy ) { + $options[ $name ] = $taxonomy->label; + } + return [ - 'provider' => ComputerVision::ID, + 'tag_taxonomy' => array_key_first( $options ), + 'provider' => ComputerVision::ID, ]; } /** - * Runs the feature. + * Sanitizes the default feature settings. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ComputerVision::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ComputerVision::ID === $provider_instance::ID ) { - /** @var ComputerVision $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'generate_image_tags' ], - [ ...$args ] - ); - } + * @param array $new_settings Settings being saved. + * @return array + */ + public function sanitize_default_feature_settings( array $new_settings ): array { + $settings = $this->get_settings(); - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + $new_settings['tag_taxonomy'] = $new_settings['tag_taxonomy'] ?? $settings['tag_taxonomy']; + + return $new_settings; } } diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 5dbfbaf43..376cc7d2b 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -4,6 +4,16 @@ use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; +use DOMDocument; + +use function Classifai\get_asset_info; +use function Classifai\computer_vision_max_filesize; +use function Classifai\get_largest_acceptable_image_url; +use function Classifai\get_modified_image_source_url; +use function Classifai\clean_input; /** * Class ImageTextExtraction @@ -31,6 +41,308 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); + add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + + add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); + add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'ocr/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to read text from.', 'classifai' ), + ], + 'route' => [ 'ocr' ], + ], + 'permission_callback' => [ $this, 'image_text_extractor_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate OCR. + * + * @param WP_REST_Request $request Request object. + * @return bool|WP_Error + */ + public function image_text_extractor_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Scan image for text is disabled. Please check your settings.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/alt-tags' ) === 0 ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'id' ), + 'excerpt', + [ + 'content' => $request->get_param( 'content' ), + 'title' => $request->get_param( 'title' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Include classifai_computer_vision_ocr in API response. + */ + public function add_ocr_data_to_api_response() { + register_rest_field( + 'attachment', + 'classifai_has_ocr', + [ + 'get_callback' => function ( $params ) { + return ! empty( get_post_meta( $params['id'], 'classifai_computer_vision_ocr', true ) ); + }, + 'schema' => [ + 'type' => 'boolean', + 'context' => [ 'view' ], + ], + ] + ); + } + + /** + * Enqueue the editor scripts. + */ + public function enqueue_editor_assets() { + wp_enqueue_script( + 'editor-ocr', + CLASSIFAI_PLUGIN_URL . 'dist/editor-ocr.js', + array_merge( get_asset_info( 'editor-ocr', 'dependencies' ), array( 'lodash' ) ), + get_asset_info( 'editor-ocr', 'version' ), + true + ); + } + + /** + * Adds a meta box for rescanning options if the settings are configured. + * + * @param \WP_Post $post The post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + if ( ! wp_attachment_is_image( $post ) || ! $this->is_feature_enabled() ) { + return; + } + + // Add our content to the metabox. + add_action( 'classifai_render_attachment_metabox', [ $this, 'attachment_data_meta_box_content' ] ); + + // If the metabox was already registered, don't add it again. + if ( isset( $wp_meta_boxes['attachment']['side']['high']['classifai_image_processing'] ) ) { + return; + } + + // Register the metabox if needed. + add_meta_box( + 'classifai_image_processing', + __( 'ClassifAI Image Processing', 'classifai' ), + [ $this, 'attachment_data_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Render the meta box. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box( \WP_Post $post ) { + /** + * Allows more fields to be rendered in attachment metabox. + * + * @since 3.0.0 + * @hook classifai_render_attachment_metabox + * + * @param {WP_Post} $post The post object. + * @param {object} $this The Provider object. + */ + do_action( 'classifai_render_attachment_metabox', $post, $this ); + } + + /** + * Display meta data. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box_content( \WP_Post $post ) { + $ocr = get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ? __( 'Rescan for text', 'classifai' ) : __( 'Scan image for text', 'classifai' ); + ?> + + is_feature_enabled() ) : ?> +
    + +
    + run( $attachment_id, 'ocr', $metadata ); + } + } + + /** + * Filter the post content to inject aria-describedby attribute. + * + * @param string $content Post content. + * @return string + */ + public function add_ocr_aria_describedby( string $content ): string { + $modified = false; + + if ( ! is_singular() || empty( $content ) ) { + return $content; + } + + $dom = new DOMDocument(); + + // Suppress warnings generated by loadHTML. + $errors = libxml_use_internal_errors( true ); + $dom->loadHTML( + sprintf( + '%s', + esc_attr( get_bloginfo( 'charset' ) ), + $content + ) + ); + libxml_use_internal_errors( $errors ); + + foreach ( $dom->getElementsByTagName( 'img' ) as $image ) { + foreach ( $image->attributes as $attribute ) { + if ( 'aria-describedby' === $attribute->name ) { + break; + } + + if ( 'class' !== $attribute->name ) { + continue; + } + + $image_id = preg_match( '~wp-image-\K\d+~', $image->getAttribute( 'class' ), $out ) ? $out[0] : 0; + $ocr_scanned_text_id = "classifai-ocr-$image_id"; + $ocr_scanned_text = $dom->getElementById( $ocr_scanned_text_id ); + + if ( ! empty( $ocr_scanned_text ) ) { + $image->setAttribute( 'aria-describedby', $ocr_scanned_text_id ); + $modified = true; + } + } + } + + if ( $modified ) { + $body = $dom->getElementsByTagName( 'body' )->item( 0 ); + return trim( $dom->saveHTML( $body ) ); + } + + return $content; + } + + /** + * Adds the rescan buttons to the media modal. + * + * @param array $form_fields Array of fields + * @param \WP_Post $post Post object for the attachment being viewed. + * @return array + */ + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ): array { + if ( ! $this->is_feature_enabled() || ! wp_attachment_is_image( $post ) ) { + return $form_fields; + } + + $ocr_text = empty( get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); + + $form_fields['rescan_ocr'] = [ + 'label' => __( 'Scan image for text', 'classifai' ), + 'input' => 'html', + 'show_in_edit' => false, + 'html' => '', + ]; + + return $form_fields; + } + /** * Get the description for the enable field. * diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index dff6d3050..52e5246f8 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -155,7 +155,7 @@ public function generate_title_permissions_check( WP_REST_Request $request ) { public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( '/classifai/v1/generate-title' === $route ) { + if ( strpos( $route, '/classifai/v1/generate-title' ) === 0 ) { return rest_ensure_response( $this->run( $request->get_param( 'id' ), diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 7261bab30..72e919323 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -11,7 +11,6 @@ use Classifai\Features\PDFTextExtraction; use Classifai\Features\ImageCropping; use Classifai\Providers\Provider; -use DOMDocument; use WP_Error; use WP_REST_Server; use WP_REST_Request; @@ -21,8 +20,6 @@ use function Classifai\get_largest_acceptable_image_url; use function Classifai\get_modified_image_source_url; use function Classifai\attachment_is_pdf; -use function Classifai\check_term_permissions; -use function Classifai\get_asset_info; use function Classifai\clean_input; class ComputerVision extends Provider { @@ -103,6 +100,14 @@ public function render_provider_fields() { break; } + /** + * Allows more Provider specific settings to be rendered. + * + * @since 3.0.0 + * @hook classifai_ms_computer_vision_render_provider_fields + * + * @param {object} $this The Provider object. + */ do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); } @@ -112,28 +117,6 @@ public function render_provider_fields() { public function add_descriptive_text_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); - $checkbox_options = array( - 'alt' => esc_html__( 'Alt text', 'classifai' ), - 'caption' => esc_html__( 'Image caption', 'classifai' ), - 'description' => esc_html__( 'Image description', 'classifai' ), - ); - - add_settings_field( - static::ID . '_descriptive_text_fields', - esc_html__( 'Generate descriptive text', 'classifai' ), - [ $this->feature_instance, 'render_checkbox_group' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'descriptive_text_fields', - 'options' => $checkbox_options, - 'default_values' => $settings['descriptive_text_fields'], - 'description' => __( 'Choose image fields where the generated captions should be applied.', 'classifai' ), - 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - add_settings_field( static::ID . '_descriptive_confidence_threshold', esc_html__( 'Confidence threshold', 'classifai' ), @@ -147,7 +130,7 @@ public function add_descriptive_text_generation_fields() { 'min' => 1, 'step' => 1, 'default_value' => $settings['descriptive_confidence_threshold'], - 'description' => esc_html__( 'Minimum confidence score for automatically added alt text, numeric value from 0-100. Recommended to be set to at least 75.', 'classifai' ), + 'description' => esc_html__( 'Minimum confidence score for automatically added generated text, numeric value from 0-100. Recommended to be set to at least 75.', 'classifai' ), 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. ] ); @@ -159,28 +142,6 @@ public function add_descriptive_text_generation_fields() { public function add_image_tags_generation_fields() { $settings = $this->feature_instance->get_settings( static::ID ); - $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); - $options = []; - - foreach ( $attachment_taxonomies as $name => $taxonomy ) { - $options[ $name ] = $taxonomy->label; - } - - add_settings_field( - static::ID . '_tag_taxonomy', - esc_html__( 'Tag taxonomy', 'classifai' ), - [ $this->feature_instance, 'render_select' ], - $this->feature_instance->get_option_name(), - $this->feature_instance->get_option_name() . '_section', - [ - 'option_index' => static::ID, - 'label_for' => 'tag_taxonomy', - 'options' => $options, - 'default_value' => $settings['tag_taxonomy'], - 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this. - ] - ); - add_settings_field( static::ID . '_tag_confidence_threshold', esc_html__( 'Confidence threshold', 'classifai' ), @@ -218,28 +179,15 @@ public function get_default_provider_settings(): array { return array_merge( $common_settings, [ - 'descriptive_text_fields' => [ - 'alt' => 0, - 'caption' => 0, - 'description' => 0, - ], 'descriptive_confidence_threshold' => 75, ] ); case ImageTagsGenerator::ID: - $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); - $options = []; - - foreach ( $attachment_taxonomies as $name => $taxonomy ) { - $options[ $name ] = $taxonomy->label; - } - return array_merge( $common_settings, [ 'tag_confidence_threshold' => 70, - 'tag_taxonomy' => array_key_first( $options ), ] ); } @@ -284,48 +232,15 @@ public function sanitize_settings( array $new_settings ) { if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { $new_settings[ static::ID ]['descriptive_confidence_threshold'] = absint( $new_settings[ static::ID ]['descriptive_confidence_threshold'] ?? $settings[ static::ID ]['descriptive_confidence_threshold'] ); - $new_settings[ static::ID ]['descriptive_text_fields'] = array_map( 'sanitize_text_field', $new_settings[ static::ID ]['descriptive_text_fields'] ?? $settings[ static::ID ]['descriptive_text_fields'] ); } if ( $this->feature_instance instanceof ImageTagsGenerator ) { $new_settings[ static::ID ]['tag_confidence_threshold'] = absint( $new_settings[ static::ID ]['tag_confidence_threshold'] ?? $settings[ static::ID ]['tag_confidence_threshold'] ); - $new_settings[ static::ID ]['tag_taxonomy'] = $new_settings[ static::ID ]['tag_taxonomy'] ?? $settings[ static::ID ]['tag_taxonomy']; } return $new_settings; } - /** - * Returns an array of fields enabled to be set to store image captions. - * - * @return array - */ - public function get_alt_text_settings(): array { - $alt_generator = new DescriptiveTextGenerator(); - $settings = $alt_generator->get_settings( static::ID ); - $enabled_fields = array(); - - if ( ! isset( $settings['descriptive_text_fields'] ) ) { - return array(); - } - - if ( ! is_array( $settings['descriptive_text_fields'] ) ) { - return array( - 'alt' => 'no' === $settings['descriptive_text_fields']['caption'] ? 0 : 'alt', - 'caption' => 0, - 'description' => 0, - ); - } - - foreach ( $settings['descriptive_text_fields'] as $key => $value ) { - if ( 0 !== $value && '0' !== $value ) { - $enabled_fields[] = $key; - } - } - - return $enabled_fields; - } - /** * Register the functionality. */ @@ -335,113 +250,16 @@ public function register() { add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); add_filter( 'posts_clauses', [ $this, 'filter_attachment_query_keywords' ], 10, 1 ); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); if ( ( new ImageCropping() )->is_feature_enabled() ) { add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); } - if ( ( new DescriptiveTextGenerator() )->is_feature_enabled() ) { - add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_image_alt_tags' ], 8, 2 ); - } - if ( ( new PDFTextExtraction() )->is_feature_enabled() ) { add_action( 'add_attachment', [ $this, 'read_pdf' ] ); add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); add_action( 'wp_ajax_classifai_get_read_status', [ $this, 'get_read_status_ajax' ] ); } - - if ( ( new ImageTextExtraction() )->is_feature_enabled() ) { - add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); - add_filter( 'rest_api_init', [ $this, 'add_ocr_data_to_api_response' ] ); - } - } - - /** - * Include classifai_computer_vision_ocr in API response. - */ - public function add_ocr_data_to_api_response() { - register_rest_field( - 'attachment', - 'classifai_has_ocr', - [ - 'get_callback' => function ( $params ) { - return ! empty( get_post_meta( $params['id'], 'classifai_computer_vision_ocr', true ) ); - }, - 'schema' => [ - 'type' => 'boolean', - 'context' => [ 'view' ], - ], - ] - ); - } - - /** - * Enqueue the editor scripts. - */ - public function enqueue_editor_assets() { - wp_enqueue_script( - 'editor-ocr', - CLASSIFAI_PLUGIN_URL . 'dist/editor-ocr.js', - array_merge( get_asset_info( 'editor-ocr', 'dependencies' ), array( 'lodash' ) ), - get_asset_info( 'editor-ocr', 'version' ), - true - ); - } - - /** - * Filter the post content to inject aria-describedby attribute. - * - * @param string $content Post content. - * @return string - */ - public function add_ocr_aria_describedby( string $content ): string { - $modified = false; - - if ( ! is_singular() || empty( $content ) ) { - return $content; - } - - $dom = new DOMDocument(); - - // Suppress warnings generated by loadHTML. - $errors = libxml_use_internal_errors( true ); - $dom->loadHTML( - sprintf( - '%s', - esc_attr( get_bloginfo( 'charset' ) ), - $content - ) - ); - libxml_use_internal_errors( $errors ); - - foreach ( $dom->getElementsByTagName( 'img' ) as $image ) { - foreach ( $image->attributes as $attribute ) { - if ( 'aria-describedby' === $attribute->name ) { - break; - } - - if ( 'class' !== $attribute->name ) { - continue; - } - - $image_id = preg_match( '~wp-image-\K\d+~', $image->getAttribute( 'class' ), $out ) ? $out[0] : 0; - $ocr_scanned_text_id = "classifai-ocr-$image_id"; - $ocr_scanned_text = $dom->getElementById( $ocr_scanned_text_id ); - - if ( ! empty( $ocr_scanned_text ) ) { - $image->setAttribute( 'aria-describedby', $ocr_scanned_text_id ); - $modified = true; - } - } - } - - if ( $modified ) { - $body = $dom->getElementsByTagName( 'body' )->item( 0 ); - return trim( $dom->saveHTML( $body ) ); - } - - return $content; } /** @@ -450,25 +268,6 @@ public function add_ocr_aria_describedby( string $content ): string { * @param \WP_Post $post The post object. */ public function setup_attachment_meta_box( \WP_Post $post ) { - if ( - wp_attachment_is_image( $post ) && - ( - ( new DescriptiveTextGenerator() )->is_feature_enabled() || - ( new ImageTagsGenerator() )->is_feature_enabled() || - ( new ImageTextExtraction() )->is_feature_enabled() || - ( new ImageCropping() )->is_feature_enabled() - ) - ) { - add_meta_box( - 'attachment_meta_box', - __( 'ClassifAI Image Processing', 'classifai' ), - [ $this, 'attachment_data_meta_box' ], - 'attachment', - 'side', - 'high' - ); - } - if ( ( new PDFTextExtraction() )->is_feature_enabled() && attachment_is_pdf( $post ) ) { add_meta_box( 'attachment_meta_box', @@ -481,63 +280,6 @@ public function setup_attachment_meta_box( \WP_Post $post ) { } } - /** - * Display meta data - * - * @param \WP_Post $post The post object. - */ - public function attachment_data_meta_box( \WP_Post $post ) { - $alt_generator = new DescriptiveTextGenerator(); - $image_tagging = new ImageTagsGenerator(); - $image_to_text = new ImageTextExtraction(); - $crop_generator = new ImageCropping(); - - $captions = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ? __( 'No descriptive text? Rescan image', 'classifai' ) : __( 'Generate descriptive text', 'classifai' ); - $tags = ! empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Rescan image for new tags', 'classifai' ) : __( 'Generate image tags', 'classifai' ); - $ocr = get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ? __( 'Rescan for text', 'classifai' ) : __( 'Scan image for text', 'classifai' ); - $smart_crop = get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ? __( 'Regenerate smart thumbnail', 'classifai' ) : __( 'Create smart thumbnail', 'classifai' ); - ?> - -
    - is_feature_enabled() && ! empty( $this->get_alt_text_settings() ) ) : ?> -
    - -
    - - - is_feature_enabled() ) : ?> -
    - -
    - - - is_feature_enabled() ) : ?> -
    - -
    - - - is_feature_enabled() ) : ?> -
    - -
    - -
    - is_feature_enabled() && attachment_is_pdf( $post ) ) { @@ -593,52 +332,6 @@ public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $ ]; } - // Description generator. - if ( $alt_generator->is_feature_enabled() && wp_attachment_is_image( $post ) ) { - if ( ! empty( $this->get_alt_text_settings() ) ) { - $alt_tags_text = empty( get_post_meta( $post->ID, '_wp_attachment_image_alt', true ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); - $form_fields['rescan_alt_tags'] = [ - 'label' => __( 'Descriptive text', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } - } - - // Image tagging. - if ( $image_tagging->is_feature_enabled() && wp_attachment_is_image( $post ) ) { - $image_tags_text = empty( wp_get_object_terms( $post->ID, 'classifai-image-tags' ) ) ? __( 'Generate', 'classifai' ) : __( 'Rescan', 'classifai' ); - $form_fields['rescan_captions'] = [ - 'label' => __( 'Image tags', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } - - // Smart crop. - if ( $smart_crop->is_feature_enabled() && wp_attachment_is_image( $post ) ) { - $smart_crop_text = empty( get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ) ? __( 'Generate', 'classifai' ) : __( 'Regenerate', 'classifai' ); - $form_fields['rescan_smart_crop'] = [ - 'label' => __( 'Smart thumbnail', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } - - // Image to text. - if ( $image_to_text->is_feature_enabled() && wp_attachment_is_image( $post ) ) { - $ocr_text = empty( get_post_meta( $post->ID, 'classifai_computer_vision_ocr', true ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); - $form_fields['rescan_ocr'] = [ - 'label' => __( 'Scan image for text', 'classifai' ), - 'input' => 'html', - 'html' => '', - 'show_in_edit' => false, - ]; - } - return $form_fields; } @@ -709,42 +402,7 @@ public static function get_read_status( $attachment_id = null ) { public function maybe_rescan_image( int $attachment_id ) { if ( clean_input( 'rescan-pdf' ) ) { $this->read_pdf( $attachment_id ); - return; // We can exit early, if this is a call for PDF scanning - everything else relates to images. - } - - $metadata = wp_get_attachment_metadata( $attachment_id ); - - // Allow rescanning image that are not stored in local storage. - $image_url = get_modified_image_source_url( $attachment_id ); - - if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - $image_url = get_largest_acceptable_image_url( - get_attached_file( $attachment_id ), - wp_get_attachment_url( $attachment_id ), - $metadata['sizes'] ?? [], - computer_vision_max_filesize() - ); - } - - if ( clean_input( 'rescan-smart-crop' ) ) { - $feature = new ImageCropping(); - $feature->run( $metadata, $attachment_id ); - } - - if ( clean_input( 'rescan-tags' ) ) { - $feature = new ImageTagsGenerator(); - $feature->run( $attachment_id ); - } - - if ( clean_input( 'rescan-captions' ) ) { - $feature = new DescriptiveTextGenerator(); - $feature->run( $attachment_id ); - } - - // Are we updating the OCR text? - if ( clean_input( 'rescan-ocr' ) ) { - $feature = new ImageTextExtraction(); - $this->ocr_processing( wp_get_attachment_metadata( $attachment_id ), $attachment_id, true ); + return; } } @@ -799,46 +457,6 @@ public function smart_crop_image( $metadata, int $attachment_id ): array { return $smart_cropping->generate_attachment_metadata( $metadata, intval( $attachment_id ) ); } - /** - * Generate the alt tags for the image being uploaded. - * - * @param array $metadata The metadata for the image. - * @param int $attachment_id Post ID for the attachment. - * @return mixed - */ - public function generate_image_alt_tags( array $metadata, int $attachment_id ) { - $feature = new ImageTagsGenerator(); - - if ( $feature->is_feature_enabled() ) { - - // Allow scanning image that are not stored in local storage. - $image_url = get_modified_image_source_url( $attachment_id ); - - if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { - $image_url = get_largest_acceptable_image_url( - get_attached_file( $attachment_id ), - wp_get_attachment_url( $attachment_id ), - $metadata['sizes'], - computer_vision_max_filesize() - ); - } else { - $image_url = wp_get_attachment_url( $attachment_id ); - } - } - - $feature->run( $attachment_id ); - } - - $feature = new DescriptiveTextGenerator(); - - if ( $feature->is_feature_enabled() && ! empty( $this->get_alt_text_settings() ) ) { - $feature->run( $attachment_id ); - } - - return $metadata; - } - /** * Runs text recognition on the attachment. * @@ -893,7 +511,7 @@ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bo } /** - * Scan the image and return the captions. + * Scan the image and return the results. * * @param string $image_url Path to the uploaded image. * @param \Classifai\Features\Feature $feature Feature instance @@ -910,7 +528,7 @@ protected function scan_image( string $image_url, \Classifai\Features\Feature $f $endpoint_url = $this->prep_api_url( $feature ); /* - * MS Computer Vision requires full image URL. So, if the file URL is relative, + * Azure AI Vision requires full image URL. So, if the file URL is relative, * then we transform it into a full URL. */ if ( '/' === substr( $image_url, 0, 1 ) ) { @@ -953,7 +571,7 @@ protected function prep_api_url( \Classifai\Features\Feature $feature = null ): $settings = $feature->get_settings( static::ID ); $api_features = []; - if ( $feature instanceof DescriptiveTextGenerator && $feature->is_feature_enabled() && ! empty( $this->get_alt_text_settings() ) ) { + if ( $feature instanceof DescriptiveTextGenerator && $feature->is_feature_enabled() && ! empty( $feature->get_alt_text_settings() ) ) { $api_features[] = 'Description'; } @@ -967,27 +585,21 @@ protected function prep_api_url( \Classifai\Features\Feature $feature = null ): } /** - * Generate the alt tags for the image being uploaded. + * Generate alt tags for an image. * - * @param int $attachment_id Post ID for the attachment. - * @return string|WP_Error + * @param string $image_url URL of image to process. + * @param int $attachment_id Post ID for the attachment. + * @return string */ - public function generate_alt_tags( int $attachment_id ) { - $rtn = ''; + public function generate_alt_tags( string $image_url, int $attachment_id ): string { + $feature = new DescriptiveTextGenerator(); + $rtn = ''; - $enabled_fields = $this->get_alt_text_settings(); - $feature = new DescriptiveTextGenerator(); - $image_url = wp_get_attachment_url( $attachment_id ); - $details = $this->scan_image( $image_url, $feature ); - $captions = isset( $details->description->captions ) ? $details->description->captions : []; + $details = $this->scan_image( $image_url, $feature ); + $captions = $details->description->captions ?? []; set_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response', $details, DAY_IN_SECONDS * 30 ); - // Don't save tags if feature is disabled or user don't have access to use it. - if ( ! $this->is_feature_enabled( 'image_captions' ) ) { - return new WP_Error( 'invalid_settings', esc_html__( 'Image descriptive text feature is disabled.', 'classifai' ) ); - } - /** * Filter the captions returned from the API. * @@ -1000,38 +612,13 @@ public function generate_alt_tags( int $attachment_id ) { */ $captions = apply_filters( 'classifai_computer_vision_captions', $captions ); - // If $captions isn't an array, don't save them. + // Process the returned captions to see if they pass the threshold. if ( is_array( $captions ) && ! empty( $captions ) ) { $settings = $feature->get_settings( static::ID ); $threshold = $settings['descriptive_confidence_threshold']; - // Save the first caption as the alt text if it passes the threshold. + // Check the first caption to see if it passes the threshold. if ( $captions[0]->confidence * 100 > $threshold ) { - if ( in_array( 'alt', $enabled_fields, true ) ) { - update_post_meta( $attachment_id, '_wp_attachment_image_alt', $captions[0]->text ); - } - - $excerpt = get_the_excerpt( $attachment_id ); - - if ( in_array( 'caption', $enabled_fields, true ) && $excerpt !== $captions[0]->text ) { - wp_update_post( - array( - 'ID' => $attachment_id, - 'post_excerpt' => $captions[0]->text, - ) - ); - } - - $content = get_the_content( null, false, $attachment_id ); - - if ( in_array( 'description', $enabled_fields, true ) && $content !== $captions[0]->text ) { - wp_update_post( - array( - 'ID' => $attachment_id, - 'post_content' => $captions[0]->text, - ) - ); - } $rtn = $captions[0]->text; } else { /** @@ -1047,10 +634,11 @@ public function generate_alt_tags( int $attachment_id ) { } // Save all the results for later. + // TODO: do we need this? update_post_meta( $attachment_id, 'classifai_computer_vision_captions', $captions ); } - // return the caption or empty string + $rtn = 'This is darin'; return $rtn; } @@ -1097,24 +685,19 @@ public function do_read_cron( string $operation_url, int $attachment_id ) { } /** - * Generate the image tags for the image being uploaded. + * Generate the image tags for the passed in image. * - * @param int $attachment_id Post ID for the attachment. - * @return string|array|WP_Error + * @param string $image_url URL of image to process. + * @param int $attachment_id Post ID for the attachment. + * @return array */ - public function generate_image_tags( int $attachment_id ) { - $rtn = ''; + public function generate_image_tags( string $image_url, int $attachment_id ): array { + $rtn = []; $feature = new ImageTagsGenerator(); $settings = $feature->get_settings( static::ID ); - // Don't save tags if the setting is disabled. - if ( ! $feature->is_feature_enabled() ) { - return new WP_Error( 'invalid_settings', esc_html__( 'Image tagging is disabled.', 'classifai' ) ); - } - - $image_url = wp_get_attachment_url( $attachment_id ); - $details = $this->scan_image( $image_url, $feature ); - $tags = isset( $details->tags ) ? $details->tags : []; + $details = $this->scan_image( $image_url, $feature ); + $tags = $details->tags ?? []; set_transient( 'classifai_azure_computer_vision_image_tags_latest_response', $details, DAY_IN_SECONDS * 30 ); @@ -1130,22 +713,19 @@ public function generate_image_tags( int $attachment_id ) { */ $tags = apply_filters( 'classifai_computer_vision_image_tags', $tags ); - // If $tags isn't an array, don't save them. + // Process the returned tags to see if they pass the threshold. if ( is_array( $tags ) && ! empty( $tags ) ) { $threshold = $settings['tag_confidence_threshold']; - $taxonomy = $settings['tag_taxonomy']; $custom_tags = []; - // Save the first caption as the alt text if it passes the threshold. + // Save each tag if it passes the threshold. foreach ( $tags as $tag ) { if ( $tag->confidence * 100 > $threshold ) { $custom_tags[] = $tag->name; - wp_add_object_terms( $attachment_id, $tag->name, $taxonomy ); } } if ( ! empty( $custom_tags ) ) { - wp_update_term_count_now( $custom_tags, $taxonomy ); $rtn = $custom_tags; } else { /** @@ -1160,7 +740,8 @@ public function generate_image_tags( int $attachment_id ) { do_action( 'classifai_computer_vision_image_tag_failed', $tags, $threshold ); } - // Save the tags for later + // Save all the tags for later. + // TODO: do we need this? update_post_meta( $attachment_id, 'classifai_computer_vision_image_tags', $tags ); } @@ -1208,63 +789,6 @@ protected function authenticate_credentials( string $url, string $api_key ) { * Register the REST API endpoints for this provider. */ public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'alt-tags/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'alt-tags' ], - ], - 'permission_callback' => [ $this, 'descriptive_text_generator_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'image-tags/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'image-tags' ], - ], - 'permission_callback' => [ $this, 'image_tags_generator_permissions_check' ], - ] - ); - - register_rest_route( - 'classifai/v1', - 'smart-crop/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate smart crop.', 'classifai' ), - ], - 'route' => [ 'smart-crop' ], - ], - 'permission_callback' => [ $this, 'smart_crop_permissions_check' ], - ] - ); - register_rest_route( 'classifai/v1', 'read-pdf/(?P\d+)', @@ -1283,25 +807,6 @@ public function register_endpoints() { 'permission_callback' => [ $this, 'pdf_read_permissions_check' ], ] ); - - register_rest_route( - 'classifai/v1', - 'ocr/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate text from.', 'classifai' ), - ], - 'route' => [ 'ocr' ], - ], - 'permission_callback' => [ $this, 'image_text_extractor_permissions_check' ], - ] - ); } /** @@ -1332,40 +837,43 @@ public function computer_vision_endpoint_callback( WP_REST_Request $request ) { /** * Common entry point for all REST endpoints for this provider. - * This is called by the Service. * - * @param int $post_id The Post Id we're processing. + * @param int $attachment_id The attachment ID we're processing. * @param string $route_to_call The name of the route we're going to be processing. * @param array $args Optional arguments to pass to the route. * @return array|string|WP_Error */ - public function rest_endpoint_callback( $post_id, string $route_to_call = '', array $args = [] ) { + public function rest_endpoint_callback( $attachment_id, string $route_to_call = '', array $args = [] ) { // Check to be sure the post both exists and is an attachment. - if ( ! get_post( $post_id ) || 'attachment' !== get_post_type( $post_id ) ) { + if ( ! get_post( $attachment_id ) || 'attachment' !== get_post_type( $attachment_id ) ) { /* translators: %1$s: the attachment ID */ - return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $post_id ), [ 'status' => 404 ] ); + return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $attachment_id ), [ 'status' => 404 ] ); } - $metadata = wp_get_attachment_metadata( $post_id ); - $image_url = get_modified_image_source_url( $post_id ); + $metadata = wp_get_attachment_metadata( $attachment_id ); + $image_url = get_modified_image_source_url( $attachment_id ); if ( 'ocr' === $route_to_call ) { $feature = new ImageTextExtraction(); - return $feature->run( $metadata, $post_id, true ); + return $feature->run( $metadata, $attachment_id, true ); } if ( 'read-pdf' === $route_to_call ) { $feature = new PDFTextExtraction(); - return $feature->run( $post_id ); + return $feature->run( $attachment_id ); } if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - $image_url = get_largest_acceptable_image_url( - get_attached_file( $post_id ), - wp_get_attachment_url( $post_id ), - $metadata['sizes'], - computer_vision_max_filesize() - ); + if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { + $image_url = get_largest_acceptable_image_url( + get_attached_file( $attachment_id ), + wp_get_attachment_url( $attachment_id ), + $metadata['sizes'], + computer_vision_max_filesize() + ); + } else { + $image_url = wp_get_attachment_url( $attachment_id ); + } } if ( empty( $image_url ) ) { @@ -1373,113 +881,18 @@ public function rest_endpoint_callback( $post_id, string $route_to_call = '', ar } switch ( $route_to_call ) { - case 'alt-tags': - $feature = new DescriptiveTextGenerator(); - return $feature->run( $post_id ); + case 'descriptive_text': + return $this->generate_alt_tags( $image_url, $attachment_id ); - case 'image-tags': - $feature = new ImageTagsGenerator(); - return $feature->run( $post_id ); + case 'tags': + return $this->generate_image_tags( $image_url, $attachment_id ); case 'smart-crop': $feature = new ImageCropping(); - return $feature->run( $metadata, $post_id ); + return $feature->run( $metadata, $attachment_id ); } } - /** - * REST request permissions check for DescriptiveTextGenerator feature. - * - * @param WP_REST_Request $request Request object. - * @return bool|WP_Error - */ - public function descriptive_text_generator_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - if ( ! ( new DescriptiveTextGenerator() )->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Image descriptive text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - return true; - } - - /** - * REST request permissions check for ImageTagsGenerator feature. - * - * @param WP_REST_Request $request Request object. - * @return bool|WP_Error - */ - public function image_tags_generator_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - $image_tags_feature = new ImageTagsGenerator(); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - if ( ! $image_tags_feature->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Image tagging is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - $settings = $image_tags_feature->get_settings(); - if ( ! empty( $settings ) && isset( $settings[ static::ID ]['tag_taxonomy'] ) ) { - $permission = check_term_permissions( $settings[ static::ID ]['tag_taxonomy'] ); - - if ( is_wp_error( $permission ) ) { - return $permission; - } - } else { - return new WP_Error( 'invalid_settings', esc_html__( 'Ensure the service settings have been saved.', 'classifai' ) ); - } - - return true; - } - - /** - * REST request permissions check for ImageCropping feature. - * - * @param WP_REST_Request $request Request object. - * @return bool|WP_Error - */ - public function smart_crop_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - if ( ! ( new ImageCropping() )->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Smart cropping is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - return true; - } - /** * REST request permissions check for PDFTextExtraction feature. * @@ -1507,33 +920,6 @@ public function pdf_read_permissions_check( WP_REST_Request $request ) { return true; } - /** - * REST request permissions check for ImageTextExtraction feature. - * - * @param WP_REST_Request $request Request object. - * @return bool|WP_Error - */ - public function image_text_extractor_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - if ( ! ( new ImageTextExtraction() )->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'Scan image for text is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - return true; - } - /** * Filter the SQL clauses of an attachment query to include tags and alt text. * diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index d14efb8b6..5345f9d15 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -5,8 +5,10 @@ namespace Classifai\Services; +use Classifai\Features\DescriptiveTextGenerator; use Classifai\Providers\Azure\ComputerVision; use Classifai\Taxonomy\ImageTagTaxonomy; + use function Classifai\get_asset_info; use function Classifai\find_provider_class; @@ -76,18 +78,16 @@ public function enqueue_media_scripts() { true ); - $provider = find_provider_class( $this->provider_classes ?? [], ComputerVision::ID ); - if ( ! is_wp_error( $provider ) ) { - wp_add_inline_script( - 'classifai-media-script', - 'const classifaiMediaVars = ' . wp_json_encode( - array( - 'enabledAltTextFields' => $provider->get_alt_text_settings() ? $provider->get_alt_text_settings() : array(), - ) - ), - 'before' - ); - } + $feature = new DescriptiveTextGenerator(); + wp_add_inline_script( + 'classifai-media-script', + 'const classifaiMediaVars = ' . wp_json_encode( + array( + 'enabledAltTextFields' => $feature->get_alt_text_settings() ? $feature->get_alt_text_settings() : array(), + ) + ), + 'before' + ); } /** From 099824fa6e3995ec1e294f78412c50cff7624b91 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Fri, 26 Jan 2024 12:00:54 -0700 Subject: [PATCH 113/127] Migrate all Image Cropping specific items from the Provider class to the Feature class --- includes/Classifai/Features/ImageCropping.php | 190 +++++++++++++----- .../Providers/Azure/ComputerVision.php | 29 ++- .../Providers/Azure/SmartCropping.php | 126 +++--------- 3 files changed, 170 insertions(+), 175 deletions(-) diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 57d78d35f..1c7bc53f2 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -8,9 +8,6 @@ use WP_REST_Request; use WP_Error; -use function Classifai\computer_vision_max_filesize; -use function Classifai\get_largest_acceptable_image_url; -use function Classifai\get_modified_image_source_url; use function Classifai\clean_input; /** @@ -24,6 +21,15 @@ class ImageCropping extends Feature { */ const ID = 'feature_image_cropping'; + /** + * WP_Filesystem_Base instance. + * + * @since 1.5.0 + * + * @var WP_Filesystem_Base + */ + private $wp_filesystem; + /** * Constructor. */ @@ -54,9 +60,10 @@ public function setup() { */ public function feature_setup() { add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); - add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_crop_image' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); } /** @@ -119,22 +126,105 @@ public function smart_crop_permissions_check( WP_REST_Request $request ) { public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( strpos( $route, '/classifai/v1/alt-tags' ) === 0 ) { - return rest_ensure_response( - $this->run( - $request->get_param( 'id' ), - 'excerpt', - [ - 'content' => $request->get_param( 'content' ), - 'title' => $request->get_param( 'title' ), - ] - ) - ); + if ( strpos( $route, '/classifai/v1/smart-crop' ) === 0 ) { + $result = $this->run( $request->get_param( 'id' ), 'crop' ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $meta = $this->save( $result, $request->get_param( 'id' ) ); + wp_update_attachment_metadata( $request->get_param( 'id' ), $meta ); + } + + return rest_ensure_response( $result ); } return parent::rest_endpoint_callback( $request ); } + /** + * Generate smart cropped thumbnails for the image being uploaded. + * + * @param array $metadata The metadata for the image. + * @param int $attachment_id Post ID for the attachment. + * @return array + */ + public function smart_crop_image( array $metadata, int $attachment_id ): array { + if ( ! $this->is_feature_enabled() ) { + return $metadata; + } + + $result = $this->run( $attachment_id, 'crop', $metadata ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $metadata = $this->save( $result, $attachment_id ); + } + + return $metadata; + } + + /** + * Save the cropped images. + * + * @param array $result The results to save. + * @param int $attachment_id The attachment ID. + * @return array + */ + public function save( array $result, int $attachment_id ): array { + $metadata = wp_get_attachment_metadata( $attachment_id ); + + foreach ( $result as $size => $image ) { + if ( is_wp_error( $image['data'] ) || empty( $image['data'] ) ) { + continue; + } + + $attached_file = get_attached_file( $attachment_id ); + $file_path_info = pathinfo( $attached_file ); + $new_thumb_file_name = str_replace( + $file_path_info['filename'], + sprintf( + '%s-%dx%d', + $file_path_info['filename'], + $image['width'], + $image['height'] + ), + $attached_file + ); + + /** + * Filters the file name of the smart-cropped image. + * + * By default, the filename mirrors what is generated by + * core -- e.g., my-thumb-150x150.jpg -- so will override the + * core-generated image. Apply this filter to keep the original + * file in the file system. + * + * @since 1.5.0 + * @hook classifai_smart_cropping_thumb_file_name + * + * @param {string} Default file name. + * @param {int} The ID of the attachment being processed. + * @param {array} Width and height data for the image. + * + * @return {string} Filtered file name. + */ + $new_thumb_file_name = apply_filters( + 'classifai_smart_cropping_thumb_file_name', + $new_thumb_file_name, + $attachment_id, + [ + 'width' => $image['width'], + 'height' => $image['height'], + ] + ); + + $filesystem = $this->get_wp_filesystem(); + if ( $filesystem && $filesystem->put_contents( $new_thumb_file_name, $image['data'] ) ) { + $metadata['sizes'][ $size ]['file'] = basename( $new_thumb_file_name ); + } + } + + return $metadata; + } + /** * Adds a meta box for rescanning options if the settings are configured. * @@ -203,27 +293,20 @@ public function attachment_data_meta_box_content( \WP_Post $post ) { } /** - * Determine if we need to rescan the image. + * Determine if we need to crop the image. * * @param int $attachment_id Attachment ID. */ - public function maybe_rescan_image( int $attachment_id ) { + public function maybe_crop_image( int $attachment_id ) { $metadata = wp_get_attachment_metadata( $attachment_id ); - // Allow rescanning images that are not stored in local storage. - $image_url = get_modified_image_source_url( $attachment_id ); - - if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - $image_url = get_largest_acceptable_image_url( - get_attached_file( $attachment_id ), - wp_get_attachment_url( $attachment_id ), - $metadata['sizes'] ?? [], - computer_vision_max_filesize() - ); - } - if ( clean_input( 'rescan-smart-crop' ) ) { - $this->run( $attachment_id, 'crop', $metadata ); + $result = $this->run( $attachment_id, 'crop', $metadata ); + + if ( ! empty( $result ) && ! is_wp_error( $result ) ) { + $meta = $this->save( $result, $attachment_id ); + wp_update_attachment_metadata( $attachment_id, $meta ); + } } } @@ -272,34 +355,31 @@ public function get_feature_default_settings(): array { } /** - * Runs the feature. + * Provides the global WP_Filesystem_Base class instance. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed + * @return WP_Filesystem_Base */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ComputerVision::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ComputerVision::ID === $provider_instance::ID ) { - /** @var ComputerVision $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'smart_crop_image' ], - [ - ...$args, - $this, - ] - ); + public function get_wp_filesystem() { + global $wp_filesystem; + + if ( is_null( $this->wp_filesystem ) ) { + if ( ! $wp_filesystem ) { + WP_Filesystem(); // Initiates the global. + } + + $this->wp_filesystem = $wp_filesystem; } - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + /** + * Filters the filesystem class instance used to save image files. + * + * @since 1.5.0 + * @hook classifai_smart_crop_wp_filesystem + * + * @param {WP_Filesystem_Base} $this->wp_filesystem Filesystem class for saving images. + * + * @return {WP_Filesystem_Base} Filtered Filesystem class. + */ + return apply_filters( 'classifai_smart_crop_wp_filesystem', $this->wp_filesystem ); } } diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 72e919323..18c9c5fdc 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -1,6 +1,6 @@ is_feature_enabled() ) { - add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); - } - if ( ( new PDFTextExtraction() )->is_feature_enabled() ) { add_action( 'add_attachment', [ $this, 'read_pdf' ] ); add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); @@ -407,21 +404,20 @@ public function maybe_rescan_image( int $attachment_id ) { } /** - * Adds smart-cropped image thumbnails to the attachment metadata. + * Generate smart-cropped image thumbnails. * * @since 1.5.0 - * @filter wp_generate_attachment_metadata * * @param array $metadata Attachment metadata. * @param int $attachment_id Attachment ID. - * @return array Filtered attachment metadata. + * @return array|WP_Error */ - public function smart_crop_image( $metadata, int $attachment_id ): array { + public function smart_crop_image( array $metadata, int $attachment_id ) { $feature = new ImageCropping(); $settings = $feature->get_settings( static::ID ); if ( ! is_array( $metadata ) || ! is_array( $settings ) ) { - return $metadata; + return new WP_Error( 'invalid', esc_html__( 'Invalid data found. Please check your settings and try again.', 'classifai' ) ); } $should_smart_crop = $feature->is_feature_enabled(); @@ -439,7 +435,7 @@ public function smart_crop_image( $metadata, int $attachment_id ): array { * @return {bool} Whether to apply smart cropping. */ if ( ! apply_filters( 'classifai_should_smart_crop_image', $should_smart_crop, $metadata, $attachment_id ) ) { - return $metadata; + return []; } // Direct file system access is required for the current implementation of this feature. @@ -449,12 +445,12 @@ public function smart_crop_image( $metadata, int $attachment_id ): array { $access_type = get_filesystem_method(); if ( 'direct' !== $access_type || ! WP_Filesystem() ) { - return $metadata; + return new WP_Error( 'access', esc_html__( 'Access to the filesystem is required for this feature to work.', 'classifai' ) ); } - $smart_cropping = new \Classifai\Providers\Azure\SmartCropping( $settings ); + $smart_cropping = new SmartCropping( $settings ); - return $smart_cropping->generate_attachment_metadata( $metadata, intval( $attachment_id ) ); + return $smart_cropping->generate_cropped_images( $metadata, intval( $attachment_id ) ); } /** @@ -887,9 +883,8 @@ public function rest_endpoint_callback( $attachment_id, string $route_to_call = case 'tags': return $this->generate_image_tags( $image_url, $attachment_id ); - case 'smart-crop': - $feature = new ImageCropping(); - return $feature->run( $metadata, $attachment_id ); + case 'crop': + return $this->smart_crop_image( $metadata, $attachment_id ); } } diff --git a/includes/Classifai/Providers/Azure/SmartCropping.php b/includes/Classifai/Providers/Azure/SmartCropping.php index a3d385bc0..616c89c65 100644 --- a/includes/Classifai/Providers/Azure/SmartCropping.php +++ b/includes/Classifai/Providers/Azure/SmartCropping.php @@ -37,15 +37,6 @@ class SmartCropping { */ private $settings; - /** - * WP_Filesystem_Base instance. - * - * @since 1.5.0 - * - * @var WP_Filesystem_Base - */ - private $wp_filesystem; - /** * SmartCropping constructor * @@ -57,37 +48,6 @@ public function __construct( array $settings ) { $this->settings = $settings; } - /** - * Provides the global WP_Filesystem_Base class instance. - * - * @since 1.5.0 - * - * @return WP_Filesystem_Base - */ - public function get_wp_filesystem() { - global $wp_filesystem; - - if ( is_null( $this->wp_filesystem ) ) { - if ( ! $wp_filesystem ) { - WP_Filesystem(); // Initiates the global. - } - - $this->wp_filesystem = $wp_filesystem; - } - - /** - * Filters the filesystem class instance used to save image files. - * - * @since 1.5.0 - * @hook classifai_smart_crop_wp_filesystem - * - * @param {WP_Filesystem_Base} $this->wp_filesystem Filesystem class for saving images. - * - * @return {WP_Filesystem_Base} Filtered Filesystem class. - */ - return apply_filters( 'classifai_smart_crop_wp_filesystem', $this->wp_filesystem ); - } - /** * Provides the maximum allowable width or height in pixels accepted by the generateThumbnail endpoint. * @@ -158,17 +118,17 @@ public function should_crop( string $size ): bool { } /** - * Filters attachment meta data - * - * @since 1.5.0 + * Generate cropped image sizes. * * @param array $metadata Image attachment metadata. - * @param int $attachment_id Attachment ID. - * @return array Filtered image attachment metadata. + * @param int $attachment_id Attachment ID + * @return array|\WP_Error */ - public function generate_attachment_metadata( array $metadata, int $attachment_id ): array { + public function generate_cropped_images( array $metadata, int $attachment_id ) { + $cropped_images = []; + if ( ! isset( $metadata['sizes'] ) || empty( $metadata['sizes'] ) ) { - return $metadata; + return $cropped_images; } foreach ( $metadata['sizes'] as $size => $size_data ) { @@ -181,14 +141,20 @@ public function generate_attachment_metadata( array $metadata, int $attachment_i 'height' => $size_data['height'], ]; - $better_thumb_filename = $this->get_cropped_thumbnail( $attachment_id, $data ); + $data = $this->get_cropped_thumbnail( $attachment_id, $size_data ); - if ( ! is_wp_error( $better_thumb_filename ) ) { - $metadata['sizes'][ $size ]['file'] = basename( $better_thumb_filename ); + if ( is_wp_error( $data ) ) { + return $data; } + + $cropped_images[ $size ] = [ + 'width' => $size_data['width'], + 'height' => $size_data['height'], + 'data' => $data, + ]; } - return $metadata; + return $cropped_images; } /** @@ -198,12 +164,13 @@ public function generate_attachment_metadata( array $metadata, int $attachment_i * * @param int $attachment_id Attachment ID. * @param array $size_data Attachment metadata size data. - * @return string|\WP_Error The thumbnail file name or WP_Error on failure. + * @return string|\WP_Error */ public function get_cropped_thumbnail( int $attachment_id, array $size_data ) { /** - * Filters the image URL to send to Computer Vision for smart cropping. A non-null value will override default - * plugin behavior. + * Filters the image URL to send to AI Vision for smart cropping. + * + * A non-null value will override default plugin behavior. * * @since 1.5.0 * @hook classifai_smart_cropping_source_url @@ -214,7 +181,7 @@ public function get_cropped_thumbnail( int $attachment_id, array $size_data ) { * @return {null|string} URL to be sent to Computer Vision for smart cropping. */ $url = apply_filters( 'classifai_smart_cropping_source_url', null, $attachment_id ); - +$url = 'https://recipes.darinkotter.com/wp-content/uploads/sites/3/2023/11/test.png'; if ( empty( $url ) ) { $url = get_largest_acceptable_image_url( get_attached_file( $attachment_id ), @@ -238,54 +205,7 @@ public function get_cropped_thumbnail( int $attachment_id, array $size_data ) { set_transient( 'classifai_azure_computer_vision_image_cropping_latest_response', $new_thumb_image, DAY_IN_SECONDS * 30 ); - if ( is_wp_error( $new_thumb_image ) ) { - return $new_thumb_image; - } - - if ( empty( $new_thumb_image ) ) { - return new \WP_Error( 'classifai_smart_cropping_empty_image', 'Empty cropped image.' ); - } - - $attached_file = get_attached_file( $attachment_id ); - $file_path_info = pathinfo( $attached_file ); - $new_thumb_file_name = str_replace( - $file_path_info['filename'], - sprintf( - '%s-%dx%d', - $file_path_info['filename'], - $size_data['width'], - $size_data['height'] - ), - $attached_file - ); - - /** - * Filters the file name of the smart-cropped image. By default, the filename mirrors what is generated by - * core -- e.g., my-thumb-150x150.jpg -- so will override the core-generated image. Apply this filter to keep - * the original file in the file system. - * - * @since 1.5.0 - * @hook classifai_smart_cropping_thumb_file_name - * - * @param {string} Default file name. - * @param {int} The ID of the attachment being processed. - * @param {array} Width and height data for the image. - * - * @return {string} Filtered file name. - */ - $new_thumb_file_name = apply_filters( - 'classifai_smart_cropping_thumb_file_name', - $new_thumb_file_name, - $attachment_id, - $size_data - ); - - $filesystem = $this->get_wp_filesystem(); - if ( $filesystem && $filesystem->put_contents( $new_thumb_file_name, $new_thumb_image ) ) { - return $new_thumb_file_name; - } - - return new \WP_Error( 'classifai_smart_cropping_filesystem_error', 'Filesystem error. Can not write cropped thumbnail file.' ); + return $new_thumb_image; } /** From 9747ccabd4b3932ac29ae9e1bc283f58afc68d7a Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Fri, 26 Jan 2024 15:26:54 -0700 Subject: [PATCH 114/127] Migrate all PDF Text Extraction specific items from the Provider class to the Feature class --- includes/Classifai/Admin/BulkActions.php | 29 +- includes/Classifai/Features/ImageCropping.php | 7 +- .../Classifai/Features/ImageTagsGenerator.php | 3 - .../Features/ImageTextExtraction.php | 133 +++--- .../Classifai/Features/PDFTextExtraction.php | 248 ++++++++-- .../Providers/Azure/ComputerVision.php | 435 ++++++------------ includes/Classifai/Providers/Azure/OCR.php | 68 +-- includes/Classifai/Providers/Azure/Read.php | 40 +- .../Providers/Azure/SmartCropping.php | 2 +- .../Classifai/Services/ImageProcessing.php | 2 - 10 files changed, 478 insertions(+), 489 deletions(-) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index 69d5faeaf..78b7e0454 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -314,31 +314,50 @@ function ( $feature ) { switch ( $doaction ) { case DescriptiveTextGenerator::ID: if ( wp_attachment_is_image( $attachment_id ) ) { - ( new DescriptiveTextGenerator() )->run( $attachment_id ); + $desc_text = new DescriptiveTextGenerator(); + $desc_text_result = $desc_text->run( $attachment_id, 'descriptive_text' ); + + if ( $desc_text_result && ! is_wp_error( $desc_text_result ) ) { + $desc_text->save( $desc_text_result, $attachment_id ); + } } break; case ImageTagsGenerator::ID: if ( wp_attachment_is_image( $attachment_id ) ) { - ( new ImageTagsGenerator() )->run( $attachment_id ); + $image_tags = new ImageTagsGenerator(); + $tags_result = $image_tags->run( $attachment_id, 'tags' ); + + if ( ! empty( $tags_result ) && ! is_wp_error( $tags_result ) ) { + $image_tags->save( $tags_result, $attachment_id ); + } } break; case ImageCropping::ID: if ( wp_attachment_is_image( $attachment_id ) ) { - ( new ImageCropping() )->run( $current_meta, $attachment_id ); + $crop = new ImageCropping(); + $crop_result = $crop->run( $attachment_id, 'crop', $current_meta ); + if ( ! empty( $crop_result ) && ! is_wp_error( $crop_result ) ) { + $ocr_meta = $crop->save( $crop_result, $attachment_id ); + wp_update_attachment_metadata( $attachment_id, $ocr_meta ); + } } break; case ImageTextExtraction::ID: if ( wp_attachment_is_image( $attachment_id ) ) { - ( new ImageTextExtraction() )->run( $current_meta, $attachment_id, true ); + $ocr = new ImageTextExtraction(); + $ocr_result = $ocr->run( $attachment_id, 'ocr' ); + if ( $ocr_result && ! is_wp_error( $ocr_result ) ) { + $ocr->save( $ocr_result, $attachment_id ); + } } break; case PDFTextExtraction::ID: if ( attachment_is_pdf( $attachment_id ) ) { - ( new PDFTextExtraction() )->run( $attachment_id ); + ( new PDFTextExtraction() )->run( $attachment_id, 'read_pdf' ); } break; diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index 1c7bc53f2..e90e0e028 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -63,7 +63,7 @@ public function feature_setup() { add_action( 'edit_attachment', [ $this, 'maybe_crop_image' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); - add_filter( 'wp_generate_attachment_metadata', [ $this, 'smart_crop_image' ], 7, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_smart_crops' ], 7, 2 ); } /** @@ -77,13 +77,12 @@ public function register_endpoints() { 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'rest_endpoint_callback' ], 'args' => [ - 'id' => [ + 'id' => [ 'required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint', 'description' => esc_html__( 'Image ID to generate smart crop.', 'classifai' ), ], - 'route' => [ 'smart-crop' ], ], 'permission_callback' => [ $this, 'smart_crop_permissions_check' ], ] @@ -147,7 +146,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { * @param int $attachment_id Post ID for the attachment. * @return array */ - public function smart_crop_image( array $metadata, int $attachment_id ): array { + public function generate_smart_crops( array $metadata, int $attachment_id ): array { if ( ! $this->is_feature_enabled() ) { return $metadata; } diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index a9220c8fa..6a6e02ea0 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -8,9 +8,6 @@ use WP_REST_Request; use WP_Error; -use function Classifai\computer_vision_max_filesize; -use function Classifai\get_largest_acceptable_image_url; -use function Classifai\get_modified_image_source_url; use function Classifai\clean_input; use function Classifai\check_term_permissions; diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 376cc7d2b..9e5040d4f 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -10,9 +10,6 @@ use DOMDocument; use function Classifai\get_asset_info; -use function Classifai\computer_vision_max_filesize; -use function Classifai\get_largest_acceptable_image_url; -use function Classifai\get_modified_image_source_url; use function Classifai\clean_input; /** @@ -62,6 +59,7 @@ public function feature_setup() { add_filter( 'the_content', [ $this, 'add_ocr_aria_describedby' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'generate_ocr_text' ], 9, 2 ); } /** @@ -75,13 +73,12 @@ public function register_endpoints() { 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'rest_endpoint_callback' ], 'args' => [ - 'id' => [ + 'id' => [ 'required' => true, 'type' => 'integer', 'sanitize_callback' => 'absint', 'description' => esc_html__( 'Image ID to read text from.', 'classifai' ), ], - 'route' => [ 'ocr' ], ], 'permission_callback' => [ $this, 'image_text_extractor_permissions_check' ], ] @@ -124,22 +121,77 @@ public function image_text_extractor_permissions_check( WP_REST_Request $request public function rest_endpoint_callback( WP_REST_Request $request ) { $route = $request->get_route(); - if ( strpos( $route, '/classifai/v1/alt-tags' ) === 0 ) { - return rest_ensure_response( - $this->run( - $request->get_param( 'id' ), - 'excerpt', - [ - 'content' => $request->get_param( 'content' ), - 'title' => $request->get_param( 'title' ), - ] - ) - ); + if ( strpos( $route, '/classifai/v1/ocr' ) === 0 ) { + $result = $this->run( $request->get_param( 'id' ), 'ocr' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $request->get_param( 'id' ) ); + } + + return rest_ensure_response( $result ); } return parent::rest_endpoint_callback( $request ); } + /** + * Generate the tags for the image being uploaded. + * + * @param array $metadata The metadata for the image. + * @param int $attachment_id Post ID for the attachment. + * @return array + */ + public function generate_ocr_text( array $metadata, int $attachment_id ): array { + if ( ! $this->is_feature_enabled() ) { + return $metadata; + } + + $result = $this->run( $attachment_id, 'ocr' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } + + return $metadata; + } + + /** + * Save the OCR text. + * + * @param string $result The result to save. + * @param int $attachment_id The attachment ID. + */ + public function save( string $result, int $attachment_id ) { + $content = get_the_content( null, false, $attachment_id ); + + $post_args = [ + 'ID' => $attachment_id, + 'post_content' => sanitize_text_field( $result ), + ]; + + /** + * Filter the post arguments before saving the text to post_content. + * + * This enables text to be stored in a different post or post meta field, + * or do other post data setting based on scan results. + * + * @since 1.6.0 + * @hook classifai_ocr_text_post_args + * + * @param {string} $post_args Array of post data for the attachment post update. Defaults to `ID` and `post_content`. + * @param {int} $attachment_id ID of the attachment post. + * @param {object} $scan The full scan results from the API. + * @param {string} $text The text data to be saved. + * + * @return {string} The filtered text data. + */ + $post_args = apply_filters( 'classifai_ocr_text_post_args', $post_args, $attachment_id, $result ); + + if ( $content !== $result ) { + wp_update_post( $post_args ); + } + } + /** * Include classifai_computer_vision_ocr in API response. */ @@ -245,22 +297,12 @@ public function attachment_data_meta_box_content( \WP_Post $post ) { * @param int $attachment_id Attachment ID. */ public function maybe_rescan_image( int $attachment_id ) { - $metadata = wp_get_attachment_metadata( $attachment_id ); - - // Allow rescanning images that are not stored in local storage. - $image_url = get_modified_image_source_url( $attachment_id ); - - if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - $image_url = get_largest_acceptable_image_url( - get_attached_file( $attachment_id ), - wp_get_attachment_url( $attachment_id ), - $metadata['sizes'] ?? [], - computer_vision_max_filesize() - ); - } - if ( clean_input( 'rescan-ocr' ) ) { - $this->run( $attachment_id, 'ocr', $metadata ); + $result = $this->run( $attachment_id, 'ocr' ); + + if ( $result && ! is_wp_error( $result ) ) { + $this->save( $result, $attachment_id ); + } } } @@ -362,33 +404,4 @@ public function get_feature_default_settings(): array { 'provider' => ComputerVision::ID, ]; } - - /** - * Runs the feature. - * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ComputerVision::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ComputerVision::ID === $provider_instance::ID ) { - /** @var ComputerVision $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'ocr_processing' ], - [ ...$args ] - ); - } - - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); - } } diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index e0055c6e9..3baa6bccb 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -4,6 +4,12 @@ use Classifai\Providers\Azure\ComputerVision; use Classifai\Services\ImageProcessing; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\attachment_is_pdf; +use function Classifai\clean_input; /** * Class PDFTextExtraction @@ -31,6 +37,219 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); + add_action( 'add_attachment', [ $this, 'read_pdf' ] ); + add_action( 'edit_attachment', [ $this, 'maybe_rescan_pdf' ] ); + + add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'read-pdf/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'read_pdf_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to read a PDF. + * + * @param WP_REST_Request $request Request object. + * @return bool|WP_Error + */ + public function read_pdf_permissions_check( WP_REST_Request $request ) { + $attachment_id = $request->get_param( 'id' ); + $post_type = get_post_type_object( 'attachment' ); + + // Ensure attachments are allowed in REST endpoints. + if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { + return false; + } + + // Ensure we have a logged in user that can upload and change files. + if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { + return false; + } + + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'PDF Text Extraction is disabled. Please check your settings.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/read-pdf' ) === 0 ) { + return rest_ensure_response( + $this->run( $request->get_param( 'id' ), 'read_pdf' ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Adds a meta box for rescanning options if the settings are configured. + * + * @param \WP_Post $post The post object. + */ + public function setup_attachment_meta_box( \WP_Post $post ) { + if ( ! attachment_is_pdf( $post ) || ! $this->is_feature_enabled() ) { + return; + } + + add_meta_box( + 'classifai_pdf_processing', + __( 'ClassifAI PDF Processing', 'classifai' ), + [ $this, 'attachment_data_meta_box' ], + 'attachment', + 'side', + 'high' + ); + } + + /** + * Render the meta box. + * + * @param \WP_Post $post The post object. + */ + public function attachment_data_meta_box( \WP_Post $post ) { + /** + * Filter the status of the PDF read operation. + * + * @since 3.0.0 + * @hook classifai_feature_pdf_to_text_generation_read_status + * + * @param {array} $status Status of the PDF read operation. + * @param {int} $post_id ID of attachment. + * + * @return {array} Status. + */ + $status = apply_filters( 'classifai_' . static::ID . '_read_status', [], $post->ID ); + + $read = ! empty( $status['read'] ) && (bool) $status['read'] ? __( 'Rescan PDF for text', 'classifai' ) : __( 'Scan PDF for text', 'classifai' ); + $running = ! empty( $status['running'] ) && (bool) $status['running']; + ?> + +
    +
    + +
    +
    + + run( $attachment_id, 'read_pdf' ); + } + + /** + * Determine if we need to rescan the PDF. + * + * @param int $attachment_id Attachment ID. + */ + public function maybe_rescan_pdf( int $attachment_id ) { + if ( clean_input( 'rescan-pdf' ) ) { + $this->run( $attachment_id, 'read_pdf' ); + } + } + + /** + * Save the returned result. + * + * @param string $result The result to save. + * @param int $attachment_id The attachment ID. + */ + public function save( string $result, int $attachment_id ) { + return wp_update_post( + [ + 'ID' => $attachment_id, + 'post_content' => $result, + ] + ); + } + + /** + * Adds the rescan buttons to the media modal. + * + * @param array $form_fields Array of fields + * @param \WP_Post $post Post object for the attachment being viewed. + * @return array + */ + public function add_rescan_button_to_media_modal( array $form_fields, \WP_Post $post ): array { + if ( ! $this->is_feature_enabled() || ! attachment_is_pdf( $post ) ) { + return $form_fields; + } + + $read_text = empty( get_the_content( null, false, $post ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); + $status = apply_filters( 'classifai_' . static::ID . '_read_status', [], $post->ID ); + + if ( ! empty( $status['running'] ) && (bool) $status['running'] ) { + $html = ''; + } else { + $html = ''; + } + + $form_fields['rescan_pdf'] = [ + 'label' => __( 'Scan PDF for text', 'classifai' ), + 'input' => 'html', + 'html' => $html, + 'show_in_edit' => false, + ]; + + return $form_fields; + } + /** * Get the description for the enable field. * @@ -50,33 +269,4 @@ public function get_feature_default_settings(): array { 'provider' => ComputerVision::ID, ]; } - - /** - * Runs the feature. - * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? ComputerVision::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( ComputerVision::ID === $provider_instance::ID ) { - /** @var ComputerVision $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'read_pdf' ], - [ ...$args ] - ); - } - - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); - } } diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 18c9c5fdc..7af816a87 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -13,15 +13,10 @@ use Classifai\Providers\Azure\SmartCropping; use Classifai\Providers\Provider; use WP_Error; -use WP_REST_Server; -use WP_REST_Request; -use WP_REST_Response; use function Classifai\computer_vision_max_filesize; use function Classifai\get_largest_acceptable_image_url; use function Classifai\get_modified_image_source_url; -use function Classifai\attachment_is_pdf; -use function Classifai\clean_input; class ComputerVision extends Provider { @@ -246,90 +241,10 @@ public function sanitize_settings( array $new_settings ) { * Register the functionality. */ public function register() { - add_action( 'add_meta_boxes_attachment', [ $this, 'setup_attachment_meta_box' ] ); - add_filter( 'attachment_fields_to_edit', [ $this, 'add_rescan_button_to_media_modal' ], 10, 2 ); - add_action( 'edit_attachment', [ $this, 'maybe_rescan_image' ] ); + add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); + add_action( 'wp_ajax_classifai_get_read_status', [ $this, 'get_read_status_ajax' ] ); + add_filter( 'classifai_feature_pdf_to_text_generation_read_status', [ $this, 'get_read_status' ], 10, 2 ); add_filter( 'posts_clauses', [ $this, 'filter_attachment_query_keywords' ], 10, 1 ); - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); - - if ( ( new PDFTextExtraction() )->is_feature_enabled() ) { - add_action( 'add_attachment', [ $this, 'read_pdf' ] ); - add_action( 'classifai_retry_get_read_result', [ $this, 'do_read_cron' ], 10, 2 ); - add_action( 'wp_ajax_classifai_get_read_status', [ $this, 'get_read_status_ajax' ] ); - } - } - - /** - * Adds a meta box for rescanning options if the settings are configured - * - * @param \WP_Post $post The post object. - */ - public function setup_attachment_meta_box( \WP_Post $post ) { - if ( ( new PDFTextExtraction() )->is_feature_enabled() && attachment_is_pdf( $post ) ) { - add_meta_box( - 'attachment_meta_box', - __( 'ClassifAI PDF Processing', 'classifai' ), - [ $this, 'attachment_pdf_data_meta_box' ], - 'attachment', - 'side', - 'high' - ); - } - } - - /** - * Display PDF scanning actions. - * - * @param \WP_Post $post The post object. - */ - public function attachment_pdf_data_meta_box( \WP_Post $post ) { - $status = self::get_read_status( $post->ID ); - $read = (bool) $status['read'] ? __( 'Rescan PDF for text', 'classifai' ) : __( 'Scan PDF for text', 'classifai' ); - $running = (bool) $status['running']; - ?> -
    -
    - -
    -
    - is_feature_enabled() && attachment_is_pdf( $post ) ) { - $read_text = empty( get_the_content( null, false, $post ) ) ? __( 'Scan', 'classifai' ) : __( 'Rescan', 'classifai' ); - $status = get_post_meta( $post->ID, '_classifai_azure_read_status', true ); - if ( ! empty( $status['status'] ) && 'running' === $status['status'] ) { - $html = ''; - } else { - $html = ''; - } - - $form_fields['rescan_pdf'] = [ - 'label' => __( 'Scan PDF for text', 'classifai' ), - 'input' => 'html', - 'html' => $html, - 'show_in_edit' => false, - ]; - } - - return $form_fields; } /** @@ -362,21 +277,21 @@ public static function get_read_status_ajax() { exit(); } - wp_send_json_success( self::get_read_status( $attachment_id ) ); + wp_send_json_success( self::get_read_status( [], $attachment_id ) ); } /** * Callback to get the status of the PDF read. * - * @param int $attachment_id The attachment ID. - * @return array|null Read and running status. + * @param array $status Current status. + * @param int $attachment_id The attachment ID. + * @return array Read and running status. */ - public static function get_read_status( $attachment_id = null ) { + public static function get_read_status( array $status = [], $attachment_id = null ) { if ( empty( $attachment_id ) || ! is_numeric( $attachment_id ) ) { - return; + return $status; } - // Cast to an integer $attachment_id = (int) $attachment_id; $read = ! empty( get_the_content( null, false, $attachment_id ) ); @@ -392,15 +307,17 @@ public static function get_read_status( $attachment_id = null ) { } /** - * Determine if we need to rescan the image. + * Wrapper action callback for Read cron job. * - * @param int $attachment_id Post id for the attachment + * @param string $operation_url Operation URL for checking the read status. + * @param int $attachment_id Attachment ID. */ - public function maybe_rescan_image( int $attachment_id ) { - if ( clean_input( 'rescan-pdf' ) ) { - $this->read_pdf( $attachment_id ); - return; - } + public function do_read_cron( string $operation_url, int $attachment_id ) { + error_log( 'do_read_cron' ); + $feature = new PDFTextExtraction(); + $settings = $feature->get_settings( static::ID ); + + ( new Read( $settings, intval( $attachment_id ), $feature ) )->check_read_result( $operation_url ); } /** @@ -458,19 +375,16 @@ public function smart_crop_image( array $metadata, int $attachment_id ) { * * @since 1.6.0 * - * @filter wp_generate_attachment_metadata - * - * @param array $metadata Attachment metadata. - * @param int $attachment_id Attachment ID. - * @param boolean $force Whether to force processing or not. Default false. - * @return array Filtered attachment metadata. + * @param array $metadata Attachment metadata. + * @param int $attachment_id Attachment ID. + * @return string|WP_Error */ - public function ocr_processing( array $metadata = [], int $attachment_id = 0, bool $force = false ): array { + public function ocr_processing( array $metadata = [], int $attachment_id = 0 ) { $feature = new ImageTextExtraction(); $settings = $feature->get_settings( static::ID ); if ( ! is_array( $metadata ) || ! is_array( $settings ) ) { - return $metadata; + return new WP_Error( 'invalid', esc_html__( 'Invalid data found. Please check your settings and try again.', 'classifai' ) ); } $should_ocr_scan = $feature->is_feature_enabled(); @@ -487,97 +401,19 @@ public function ocr_processing( array $metadata = [], int $attachment_id = 0, bo * * @return {bool} Whether to run OCR scanning. */ - if ( ! $force && ! apply_filters( 'classifai_should_ocr_scan_image', $should_ocr_scan, $metadata, $attachment_id ) ) { - return $metadata; + if ( ! apply_filters( 'classifai_should_ocr_scan_image', $should_ocr_scan, $metadata, $attachment_id ) ) { + return ''; } $image_url = wp_get_attachment_url( $attachment_id ); $scan = $this->scan_image( $image_url, $feature ); - $ocr = new OCR( $settings, $scan, $force ); + $ocr = new OCR( $settings, $scan ); $response = $ocr->generate_ocr_data( $metadata, $attachment_id ); set_transient( 'classifai_azure_computer_vision_image_text_extraction_latest_response', $scan, DAY_IN_SECONDS * 30 ); - if ( $force ) { - return $response; - } - - return $metadata; - } - - /** - * Scan the image and return the results. - * - * @param string $image_url Path to the uploaded image. - * @param \Classifai\Features\Feature $feature Feature instance - * @return bool|object|WP_Error - */ - protected function scan_image( string $image_url, \Classifai\Features\Feature $feature = null ) { - $settings = $feature->get_settings( static::ID ); - - // Check if valid authentication is in place. - if ( ! $feature->is_feature_enabled() ) { - return new WP_Error( 'feature_disabled', esc_html__( 'Feature not enabled.', 'classifai' ) ); - } - - $endpoint_url = $this->prep_api_url( $feature ); - - /* - * Azure AI Vision requires full image URL. So, if the file URL is relative, - * then we transform it into a full URL. - */ - if ( '/' === substr( $image_url, 0, 1 ) ) { - $image_url = get_site_url() . $image_url; - } - - $response = wp_remote_post( - $endpoint_url, - [ - 'headers' => [ - 'Ocp-Apim-Subscription-Key' => $settings['api_key'], - 'Content-Type' => 'application/json', - ], - 'body' => '{"url":"' . $image_url . '"}', - ] - ); - - if ( ! is_wp_error( $response ) ) { - $body = json_decode( wp_remote_retrieve_body( $response ) ); - - if ( 200 !== wp_remote_retrieve_response_code( $response ) && isset( $body->message ) ) { - $rtn = new WP_Error( $body->code ?? 'error', $body->message, $body ); - } else { - $rtn = $body; - } - } else { - $rtn = $response; - } - - return $rtn; - } - - /** - * Build and return the API endpoint based on settings. - * - * @param \Classifai\Features\Feature $feature Feature instance - * @return string - */ - protected function prep_api_url( \Classifai\Features\Feature $feature = null ): string { - $settings = $feature->get_settings( static::ID ); - $api_features = []; - - if ( $feature instanceof DescriptiveTextGenerator && $feature->is_feature_enabled() && ! empty( $feature->get_alt_text_settings() ) ) { - $api_features[] = 'Description'; - } - - if ( $feature instanceof ImageTagsGenerator && $feature->is_feature_enabled() ) { - $api_features[] = 'Tags'; - } - - $endpoint = add_query_arg( 'visualFeatures', implode( ',', $api_features ), trailingslashit( $settings['endpoint_url'] ) . $this->analyze_url ); - - return $endpoint; + return $response; } /** @@ -585,13 +421,18 @@ protected function prep_api_url( \Classifai\Features\Feature $feature = null ): * * @param string $image_url URL of image to process. * @param int $attachment_id Post ID for the attachment. - * @return string + * @return string|WP_Error */ - public function generate_alt_tags( string $image_url, int $attachment_id ): string { + public function generate_alt_tags( string $image_url, int $attachment_id ) { $feature = new DescriptiveTextGenerator(); $rtn = ''; - $details = $this->scan_image( $image_url, $feature ); + $details = $this->scan_image( $image_url, $feature ); + + if ( is_wp_error( $details ) ) { + return $details; + } + $captions = $details->description->captions ?? []; set_transient( 'classifai_azure_computer_vision_descriptive_text_latest_response', $details, DAY_IN_SECONDS * 30 ); @@ -630,11 +471,9 @@ public function generate_alt_tags( string $image_url, int $attachment_id ): stri } // Save all the results for later. - // TODO: do we need this? update_post_meta( $attachment_id, 'classifai_computer_vision_captions', $captions ); } - $rtn = 'This is darin'; return $rtn; } @@ -642,6 +481,7 @@ public function generate_alt_tags( string $image_url, int $attachment_id ): stri * Read PDF content and update the description of attachment. * * @param int $attachment_id Attachment ID. + * @return string|WP_Error */ public function read_pdf( int $attachment_id ) { $feature = new PDFTextExtraction(); @@ -649,7 +489,7 @@ public function read_pdf( int $attachment_id ) { $should_read_pdf = $feature->is_feature_enabled(); if ( ! $should_read_pdf ) { - return false; + return new WP_Error( 'not_enabled', esc_html__( 'PDF Text Extraction is disabled. Please check your settings.', 'classifai' ) ); } // Direct file system access is required for the current implementation of this feature. @@ -663,37 +503,30 @@ public function read_pdf( int $attachment_id ) { return new WP_Error( 'invalid_access_type', 'Invalid access type! Direct file system access is required.' ); } - $read = new Read( $settings, intval( $attachment_id ) ); + $read = new Read( $settings, intval( $attachment_id ), $feature ); return $read->read_document(); } - /** - * Wrapper action callback for Read cron job. - * - * @param string $operation_url Operation URL for checking the read status. - * @param int $attachment_id Attachment ID. - */ - public function do_read_cron( string $operation_url, int $attachment_id ) { - $settings = ( new PDFTextExtraction() )->get_settings( static::ID ); - - ( new Read( $settings, intval( $attachment_id ) ) )->check_read_result( $operation_url ); - } - /** * Generate the image tags for the passed in image. * * @param string $image_url URL of image to process. * @param int $attachment_id Post ID for the attachment. - * @return array + * @return array|WP_Error */ - public function generate_image_tags( string $image_url, int $attachment_id ): array { + public function generate_image_tags( string $image_url, int $attachment_id ) { $rtn = []; $feature = new ImageTagsGenerator(); $settings = $feature->get_settings( static::ID ); $details = $this->scan_image( $image_url, $feature ); - $tags = $details->tags ?? []; + + if ( is_wp_error( $details ) ) { + return $details; + } + + $tags = $details->tags ?? []; set_transient( 'classifai_azure_computer_vision_image_tags_latest_response', $details, DAY_IN_SECONDS * 30 ); @@ -737,13 +570,86 @@ public function generate_image_tags( string $image_url, int $attachment_id ): ar } // Save all the tags for later. - // TODO: do we need this? update_post_meta( $attachment_id, 'classifai_computer_vision_image_tags', $tags ); } return $rtn; } + /** + * Scan the image and return the results. + * + * @param string $image_url Path to the uploaded image. + * @param \Classifai\Features\Feature $feature Feature instance + * @return bool|object|WP_Error + */ + protected function scan_image( string $image_url, \Classifai\Features\Feature $feature = null ) { + $settings = $feature->get_settings( static::ID ); + + // Check if valid authentication is in place. + if ( ! $feature->is_feature_enabled() ) { + return new WP_Error( 'feature_disabled', esc_html__( 'Feature not enabled.', 'classifai' ) ); + } + + $endpoint_url = $this->prep_api_url( $feature ); + + /* + * Azure AI Vision requires full image URL. So, if the file URL is relative, + * then we transform it into a full URL. + */ + if ( '/' === substr( $image_url, 0, 1 ) ) { + $image_url = get_site_url() . $image_url; + } + + $response = wp_remote_post( + $endpoint_url, + [ + 'headers' => [ + 'Ocp-Apim-Subscription-Key' => $settings['api_key'], + 'Content-Type' => 'application/json', + ], + 'body' => '{"url":"' . $image_url . '"}', + ] + ); + + if ( ! is_wp_error( $response ) ) { + $body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) && isset( $body->message ) ) { + $rtn = new WP_Error( $body->code ?? 'error', $body->message, $body ); + } else { + $rtn = $body; + } + } else { + $rtn = $response; + } + + return $rtn; + } + + /** + * Build and return the API endpoint based on settings. + * + * @param \Classifai\Features\Feature $feature Feature instance + * @return string + */ + protected function prep_api_url( \Classifai\Features\Feature $feature = null ): string { + $settings = $feature->get_settings( static::ID ); + $api_features = []; + + if ( $feature instanceof DescriptiveTextGenerator && $feature->is_feature_enabled() && ! empty( $feature->get_alt_text_settings() ) ) { + $api_features[] = 'Description'; + } + + if ( $feature instanceof ImageTagsGenerator && $feature->is_feature_enabled() ) { + $api_features[] = 'Tags'; + } + + $endpoint = add_query_arg( 'visualFeatures', implode( ',', $api_features ), trailingslashit( $settings['endpoint_url'] ) . $this->analyze_url ); + + return $endpoint; + } + /** * Setup fields */ @@ -781,56 +687,6 @@ protected function authenticate_credentials( string $url, string $api_key ) { return $rtn; } - /** - * Register the REST API endpoints for this provider. - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'read-pdf/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'computer_vision_endpoint_callback' ], - 'args' => [ - 'id' => [ - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'Image ID to generate alt text for.', 'classifai' ), - ], - 'route' => [ 'read-pdf' ], - ], - 'permission_callback' => [ $this, 'pdf_read_permissions_check' ], - ] - ); - } - - /** - * REST request callback for Computer Vision features. - * - * @param WP_REST_Request $request Request object. - * @return WP_Error|WP_REST_Response - */ - public function computer_vision_endpoint_callback( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $custom_atts = $request->get_attributes(); - $route_to_call = empty( $custom_atts['args']['route'] ) ? false : strtolower( $custom_atts['args']['route'][0] ); - - // Check to be sure the post both exists and is an attachment. - if ( ! get_post( $attachment_id ) || 'attachment' !== get_post_type( $attachment_id ) ) { - /* translators: %1$s: the attachment ID */ - return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $attachment_id ), [ 'status' => 404 ] ); - } - - // If no args, we can't pass the call into the active provider. - if ( false === $route_to_call ) { - return new WP_Error( 'no_route', esc_html__( 'No route indicated for the provider class to use.', 'classifai' ), [ 'status' => 404 ] ); - } - - // Call the provider endpoint function - return rest_ensure_response( $this->rest_endpoint_callback( $attachment_id, $route_to_call ) ); - } - /** * Common entry point for all REST endpoints for this provider. * @@ -846,19 +702,22 @@ public function rest_endpoint_callback( $attachment_id, string $route_to_call = return new WP_Error( 'incorrect_ID', sprintf( esc_html__( '%1$d is not found or is not an attachment', 'classifai' ), $attachment_id ), [ 'status' => 404 ] ); } - $metadata = wp_get_attachment_metadata( $attachment_id ); - $image_url = get_modified_image_source_url( $attachment_id ); - - if ( 'ocr' === $route_to_call ) { - $feature = new ImageTextExtraction(); - return $feature->run( $metadata, $attachment_id, true ); + if ( 'read_pdf' === $route_to_call ) { + return $this->read_pdf( $attachment_id ); } - if ( 'read-pdf' === $route_to_call ) { - $feature = new PDFTextExtraction(); - return $feature->run( $attachment_id ); + $metadata = wp_get_attachment_metadata( $attachment_id ); + + switch ( $route_to_call ) { + case 'ocr': + return $this->ocr_processing( $metadata, $attachment_id ); + + case 'crop': + return $this->smart_crop_image( $metadata, $attachment_id ); } + $image_url = get_modified_image_source_url( $attachment_id ); + if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { $image_url = get_largest_acceptable_image_url( @@ -882,39 +741,9 @@ public function rest_endpoint_callback( $attachment_id, string $route_to_call = case 'tags': return $this->generate_image_tags( $image_url, $attachment_id ); - - case 'crop': - return $this->smart_crop_image( $metadata, $attachment_id ); } } - /** - * REST request permissions check for PDFTextExtraction feature. - * - * @param WP_REST_Request $request Request object. - * @return bool|WP_Error - */ - public function pdf_read_permissions_check( WP_REST_Request $request ) { - $attachment_id = $request->get_param( 'id' ); - $post_type = get_post_type_object( 'attachment' ); - - // Ensure attachments are allowed in REST endpoints. - if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) { - return false; - } - - // Ensure we have a logged in user that can upload and change files. - if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) || ! current_user_can( 'upload_files' ) ) { - return false; - } - - if ( ! ( new PDFTextExtraction() )->is_feature_enabled() ) { - return new WP_Error( 'not_enabled', esc_html__( 'PDF Text Extraction is disabled or Microsoft Azure authentication failed. Please check your settings.', 'classifai' ) ); - } - - return true; - } - /** * Filter the SQL clauses of an attachment query to include tags and alt text. * diff --git a/includes/Classifai/Providers/Azure/OCR.php b/includes/Classifai/Providers/Azure/OCR.php index ffd4fe78f..c665c7e09 100644 --- a/includes/Classifai/Providers/Azure/OCR.php +++ b/includes/Classifai/Providers/Azure/OCR.php @@ -1,6 +1,6 @@ settings = $settings; $this->scan = $scan; - $this->force = $force; } /** @@ -107,11 +96,6 @@ public function get_api_url(): string { * @return bool */ public function should_process( int $attachment_id ): bool { - // Bypass check if this is a force request - if ( $this->force ) { - return true; - } - $mime_type = get_post_mime_type( $attachment_id ); $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); $process = false; @@ -191,7 +175,7 @@ public function should_process( int $attachment_id ): bool { } /** - * Get and save the OCR data + * Get the OCR data * * @since 1.6.0 * @@ -251,42 +235,10 @@ public function generate_ocr_data( array $metadata, int $attachment_id ) { * * @return {string} The filtered text data. */ - $text = apply_filters( 'classifai_ocr_text', implode( ' ', $text ), $scan ); - - $content = get_the_content( null, false, $attachment_id ); - - $post_args = [ - 'ID' => $attachment_id, - 'post_content' => sanitize_text_field( $text ), - ]; - - /** - * Filter the post arguments before saving the text to post_content. - * - * This enables text to be stored in a different post or post meta field, - * or do other post data setting based on scan results. - * - * @since 1.6.0 - * @hook classifai_ocr_text_post_args - * - * @param {string} $post_args Array of post data for the attachment post update. Defaults to `ID` and `post_content`. - * @param {int} $attachment_id ID of the attachment post. - * @param {object} $scan The full scan results from the API. - * @param {string} $text The text data to be saved. - * @param {object} $scan The full scan results from the API. - * - * @return {string} The filtered text data. - */ - $post_args = apply_filters( 'classifai_ocr_text_post_args', $post_args, $attachment_id, $text, $scan ); - - if ( $content !== $text ) { - wp_update_post( $post_args ); + $rtn = apply_filters( 'classifai_ocr_text', implode( ' ', $text ), $scan ); - $rtn = $text; - - // Save all the results for later - update_post_meta( $attachment_id, 'classifai_computer_vision_ocr', $scan ); - } + // Save all the results for later + update_post_meta( $attachment_id, 'classifai_computer_vision_ocr', $scan ); } } else { $rtn = $scan; diff --git a/includes/Classifai/Providers/Azure/Read.php b/includes/Classifai/Providers/Azure/Read.php index 6b34453c8..f6e9bef5e 100644 --- a/includes/Classifai/Providers/Azure/Read.php +++ b/includes/Classifai/Providers/Azure/Read.php @@ -1,6 +1,6 @@ settings = $settings; $this->attachment_id = $attachment_id; - $this->force = $force; + $this->feature = $feature; } /** @@ -72,16 +72,11 @@ public function get_api_url( string $path = '' ): string { } /** - * Returns whether Read processing should be applied to the attachment + * Check if Read processing should be applied to the attachment. * * @return bool */ public function should_process(): bool { - // Bypass check if this is a force request - if ( $this->force ) { - return true; - } - $mime_type = get_post_mime_type( $this->attachment_id ); $matched_extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); $process = false; @@ -200,9 +195,11 @@ public function read_document() { if ( 202 === wp_remote_retrieve_response_code( $response ) ) { $operation_url = wp_remote_retrieve_header( $response, 'Operation-Location' ); + if ( ! filter_var( $operation_url, FILTER_VALIDATE_URL ) ) { return $this->log_error( new WP_Error( 'invalid_read_operation_url', esc_html__( 'Operation URL is invalid.', 'classifai' ) ) ); } + return $this->check_read_result( $operation_url ); } @@ -321,12 +318,7 @@ public function update_document_description( array $data ) { */ $lines_of_text = apply_filters( 'classifai_azure_read_text_result', $lines_of_text, $this->attachment_id, $data ); - $update = wp_update_post( - [ - 'ID' => $this->attachment_id, - 'post_content' => implode( ' ', $lines_of_text ), - ] - ); + $update = $this->feature->save( implode( ' ', $lines_of_text ), $this->attachment_id ); if ( is_wp_error( $update ) ) { return $this->log_error( $update ); diff --git a/includes/Classifai/Providers/Azure/SmartCropping.php b/includes/Classifai/Providers/Azure/SmartCropping.php index 616c89c65..c80562e3a 100644 --- a/includes/Classifai/Providers/Azure/SmartCropping.php +++ b/includes/Classifai/Providers/Azure/SmartCropping.php @@ -181,7 +181,7 @@ public function get_cropped_thumbnail( int $attachment_id, array $size_data ) { * @return {null|string} URL to be sent to Computer Vision for smart cropping. */ $url = apply_filters( 'classifai_smart_cropping_source_url', null, $attachment_id ); -$url = 'https://recipes.darinkotter.com/wp-content/uploads/sites/3/2023/11/test.png'; + if ( empty( $url ) ) { $url = get_largest_acceptable_image_url( get_attached_file( $attachment_id ), diff --git a/includes/Classifai/Services/ImageProcessing.php b/includes/Classifai/Services/ImageProcessing.php index 5345f9d15..0b4b56fa8 100644 --- a/includes/Classifai/Services/ImageProcessing.php +++ b/includes/Classifai/Services/ImageProcessing.php @@ -6,11 +6,9 @@ namespace Classifai\Services; use Classifai\Features\DescriptiveTextGenerator; -use Classifai\Providers\Azure\ComputerVision; use Classifai\Taxonomy\ImageTagTaxonomy; use function Classifai\get_asset_info; -use function Classifai\find_provider_class; class ImageProcessing extends Service { From f3c4d583ba4cf7be2c9ba6a98bcf7ae87718f7af Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 29 Jan 2024 10:46:11 -0700 Subject: [PATCH 115/127] Migrate all Text to Speech specific items from the Provider class to the Feature class --- includes/Classifai/Admin/BulkActions.php | 7 +- includes/Classifai/Admin/SavePostHandler.php | 160 ---- .../Classifai/Command/ClassifaiCommand.php | 12 +- .../Classifai/Features/RecommendedContent.php | 8 - includes/Classifai/Features/TextToSpeech.php | 703 +++++++++++++++++- includes/Classifai/Helpers.php | 20 - includes/Classifai/Providers/Azure/Speech.php | 671 +---------------- 7 files changed, 721 insertions(+), 860 deletions(-) diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php index 78b7e0454..013e98fb6 100644 --- a/includes/Classifai/Admin/BulkActions.php +++ b/includes/Classifai/Admin/BulkActions.php @@ -161,7 +161,12 @@ function ( $feature ) { break; case TextToSpeech::ID: - ( new TextToSpeech() )->run( $post_id ); + $tts = new TextToSpeech(); + $results = $tts->run( $post_id, 'synthesize' ); + + if ( $results && ! is_wp_error( $results ) ) { + $tts->save( $results, $post_id ); + } $action = $doaction; break; } diff --git a/includes/Classifai/Admin/SavePostHandler.php b/includes/Classifai/Admin/SavePostHandler.php index cdf4697e0..67b9faf84 100644 --- a/includes/Classifai/Admin/SavePostHandler.php +++ b/includes/Classifai/Admin/SavePostHandler.php @@ -2,9 +2,6 @@ namespace Classifai\Admin; -use Classifai\Features\TextToSpeech; -use Classifai\Providers\Azure\Speech; -use Classifai\Watson\Normalizer; use function Classifai\get_classification_mode; /** @@ -198,163 +195,6 @@ public function classify( int $post_id, bool $link_terms = true ): array { return $output; } - /** - * Synthesizes speech from the post title and content. - * - * @todo: This method is copied to the Azure\Speech provider. - * Once all the speech-synthesize changed are migrated, we should - * remove this method. - * - * @param int $post_id Post ID. - * @return bool|int|WP_Error - */ - public function synthesize_speech( int $post_id ) { - if ( empty( $post_id ) ) { - return new \WP_Error( - 'azure_text_to_speech_post_id_missing', - esc_html__( 'Post ID missing.', 'classifai' ) - ); - } - - // We skip the user cap check if running under WP-CLI. - if ( ! current_user_can( 'edit_post', $post_id ) && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) { - return new \WP_Error( - 'azure_text_to_speech_user_not_authorized', - esc_html__( 'Unauthorized user.', 'classifai' ) - ); - } - - $normalizer = new Normalizer(); - $feature = new TextToSpeech(); - $settings = $feature->get_settings(); - $post = get_post( $post_id ); - $post_content = $normalizer->normalize_content( $post->post_content, $post->post_title, $post_id ); - $content_hash = get_post_meta( $post_id, Speech::AUDIO_HASH_KEY, true ); - $saved_attachment_id = (int) get_post_meta( $post_id, Speech::AUDIO_ID_KEY, true ); - - // Don't regenerate the audio file it it already exists and the content hasn't changed. - if ( $saved_attachment_id ) { - - // Check if the audio file exists. - $audio_attachment_url = wp_get_attachment_url( $saved_attachment_id ); - - if ( $audio_attachment_url && ! empty( $content_hash ) && ( md5( $post_content ) === $content_hash ) ) { - return $saved_attachment_id; - } - } - - $voice = $settings['voice'] ?? ''; - $voice_data = explode( '|', $voice ); - $voice_name = ''; - $voice_gender = ''; - - // Extract the voice name and gender from the option value. - if ( 2 === count( $voice_data ) ) { - $voice_name = $voice_data[0]; - $voice_gender = $voice_data[1]; - - // Return error if voice is not set in settings. - } else { - return new \WP_Error( - 'azure_text_to_speech_voice_information_missing', - esc_html__( 'Voice data not set.', 'classifai' ) - ); - } - - // Create the request body to synthesize speech from text. - $request_body = sprintf( - "%s", - $voice_gender, - $voice_name, - $post_content - ); - - // Request parameters. - $request_params = array( - 'method' => 'POST', - 'body' => $request_body, - 'timeout' => 60, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout - 'headers' => array( - 'Ocp-Apim-Subscription-Key' => $settings['credentials']['api_key'], - 'Content-Type' => 'application/ssml+xml', - 'X-Microsoft-OutputFormat' => 'audio-16khz-128kbitrate-mono-mp3', - ), - ); - - $remote_url = sprintf( '%s%s', $settings['credentials']['url'], Speech::API_PATH ); - $response = wp_remote_post( $remote_url, $request_params ); - - if ( is_wp_error( $response ) ) { - return new \WP_Error( - 'azure_text_to_speech_http_error', - esc_html( $response->get_error_message() ) - ); - } - - $code = wp_remote_retrieve_response_code( $response ); - $response_body = wp_remote_retrieve_body( $response ); - - // return error if HTTP status code is not 200. - if ( \WP_Http::OK !== $code ) { - return new \WP_Error( - 'azure_text_to_speech_unsuccessful_request', - esc_html__( 'HTTP request unsuccessful.', 'classifai' ) - ); - } - - // If audio already exists for this post, delete it. - if ( $saved_attachment_id ) { - wp_delete_attachment( $saved_attachment_id, true ); - delete_post_meta( $post_id, Speech::AUDIO_ID_KEY ); - delete_post_meta( $post_id, Speech::AUDIO_TIMESTAMP_KEY ); - } - - // The audio file name. - $audio_file_name = sprintf( - 'post-as-audio-%1$s.mp3', - $post_id - ); - - // Upload the audio stream as an .mp3 file. - $file_data = wp_upload_bits( - $audio_file_name, - null, - $response_body - ); - - if ( isset( $file_data['error'] ) && ! empty( $file_data['error'] ) ) { - return new \WP_Error( - 'azure_text_to_speech_upload_bits_failure', - esc_html( $file_data['error'] ) - ); - } - - // Insert the audio file as attachment. - $attachment_id = wp_insert_attachment( - array( - 'guid' => $file_data['file'], - 'post_title' => $audio_file_name, - 'post_mime_type' => $file_data['type'], - ), - $file_data['file'], - $post_id - ); - - // Return error if creation of attachment fails. - if ( ! $attachment_id ) { - return new \WP_Error( - 'azure_text_to_speech_resource_creation_failure', - esc_html__( 'Audio creation failed.', 'classifai' ) - ); - } - - update_post_meta( $post_id, Speech::AUDIO_ID_KEY, absint( $attachment_id ) ); - update_post_meta( $post_id, Speech::AUDIO_TIMESTAMP_KEY, time() ); - update_post_meta( $post_id, Speech::AUDIO_HASH_KEY, md5( $post_content ) ); - - return $attachment_id; - } - /** * Lazy initializes the Post Classifier object */ diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index eb9f0fa66..41a106056 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -242,7 +242,7 @@ public function text_to_speech( $args = [], $opts = [] ) { ]; $feature_speech = new TextToSpeech(); - $allowed_post_types = $feature_speech->get_tts_supported_post_types(); + $allowed_post_types = $feature_speech->get_supported_post_types(); $opts = wp_parse_args( $opts, $defaults ); $opts['per_page'] = (int) $opts['per_page'] > 0 ? $opts['per_page'] : 100; @@ -295,7 +295,10 @@ public function text_to_speech( $args = [], $opts = [] ) { foreach ( $posts as $post_id ) { if ( ! $dry_run ) { - $result = $feature_speech->run( $post_id ); + $result = $feature_speech->run( $post_id, 'synthesize' ); + if ( $result && ! is_wp_error( $result ) ) { + $result = $feature_speech->save( $result, $post_id ); + } if ( is_wp_error( $result ) ) { \WP_CLI::log( sprintf( 'Error while processing item ID %s: %s', $post_id, $result->get_error_message() ) ); @@ -343,7 +346,10 @@ public function text_to_speech( $args = [], $opts = [] ) { } if ( ! $dry_run ) { - $result = $feature_speech->run( $post_id ); + $result = $feature_speech->run( $post_id, 'synthesize' ); + if ( $result && ! is_wp_error( $result ) ) { + $result = $feature_speech->save( $result, $post_id ); + } if ( is_wp_error( $result ) ) { \WP_CLI::log( sprintf( 'Error while processing item ID %s: %s', $post_id, $result->get_error_message() ) ); diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index 7b4f405ac..35a7e5a01 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -70,13 +70,5 @@ public function run( ...$args ) { [ ...$args ] ); } - - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); } } diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 7f0eea9ac..c8a81612b 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -4,6 +4,11 @@ use Classifai\Services\LanguageProcessing; use Classifai\Providers\Azure\Speech; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; + +use function Classifai\get_asset_info; /** * Class TextToSpeech @@ -16,6 +21,28 @@ class TextToSpeech extends Feature { */ const ID = 'feature_text_to_speech_generation'; + /** + * Meta key to get/set the ID of the speech audio file. + * + * @var string + */ + const AUDIO_ID_KEY = '_classifai_post_audio_id'; + + /** + * Meta key to get/set the timestamp indicating when the speech was generated. + * Used for cache-busting as the audio filename remains static for a given post. + * + * @var string + */ + const AUDIO_TIMESTAMP_KEY = '_classifai_post_audio_timestamp'; + + /** + * Meta key to hide/unhide already generated audio file. + * + * @var string + */ + const DISPLAY_GENERATED_AUDIO = '_classifai_display_generated_audio'; + /** * Constructor. */ @@ -31,6 +58,613 @@ public function __construct() { ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + */ + public function feature_setup() { + add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); + add_action( 'rest_api_init', [ $this, 'add_meta_to_rest_api' ] ); + + foreach ( $this->get_supported_post_types() as $post_type ) { + add_action( 'rest_insert_' . $post_type, [ $this, 'rest_handle_audio' ], 10, 2 ); + } + + add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); + add_action( 'save_post', [ $this, 'save_post_metadata' ], 5 ); + + if ( $this->is_enabled() ) { + add_filter( 'the_content', [ $this, 'render_post_audio_controls' ] ); + } + } + + /** + * Enqueue the editor scripts. + * + * @since 2.4.0 Use get_asset_info to get the asset version and dependencies. + */ + public function enqueue_editor_assets() { + if ( ! $this->is_feature_enabled() ) { + return; + } + + $post = get_post(); + + if ( empty( $post ) ) { + return; + } + + wp_enqueue_script( + 'classifai-gutenberg-plugin', + CLASSIFAI_PLUGIN_URL . 'dist/gutenberg-plugin.js', + array_merge( get_asset_info( 'gutenberg-plugin', 'dependencies' ), array( 'lodash' ) ), + get_asset_info( 'gutenberg-plugin', 'version' ), + true + ); + + wp_add_inline_script( + 'classifai-gutenberg-plugin', + sprintf( + 'var classifaiTTSEnabled = %d;', + true + ), + 'before' + ); + } + + /** + * Add audio related fields to rest API for view/edit. + */ + public function add_meta_to_rest_api() { + if ( ! $this->is_feature_enabled() ) { + return; + } + + $supported_post_types = $this->get_supported_post_types(); + + register_rest_field( + $supported_post_types, + 'classifai_synthesize_speech', + array( + 'get_callback' => function ( $data ) { + $audio_id = get_post_meta( $data['id'], self::AUDIO_ID_KEY, true ); + if ( + ( $this->get_audio_generation_initial_state( $data['id'] ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $data['id'] ) && $audio_id ) + ) { + return true; + } else { + return false; + } + }, + 'schema' => [ + 'type' => 'boolean', + 'context' => [ 'view', 'edit' ], + ], + ) + ); + + register_rest_field( + $supported_post_types, + 'classifai_display_generated_audio', + array( + 'get_callback' => function ( $data ) { + // Default to display the audio if available. + if ( metadata_exists( 'post', $data['id'], self::DISPLAY_GENERATED_AUDIO ) ) { + return (bool) get_post_meta( $data['id'], self::DISPLAY_GENERATED_AUDIO, true ); + } + return true; + }, + 'update_callback' => function ( $value, $data ) { + if ( $value ) { + delete_post_meta( $data->ID, self::DISPLAY_GENERATED_AUDIO ); + } else { + update_post_meta( $data->ID, self::DISPLAY_GENERATED_AUDIO, false ); + } + }, + 'schema' => [ + 'type' => 'boolean', + 'context' => [ 'view', 'edit' ], + ], + ) + ); + + register_rest_field( + $supported_post_types, + 'classifai_post_audio_id', + array( + 'get_callback' => function ( $data ) { + $post_audio_id = get_post_meta( $data['id'], self::AUDIO_ID_KEY, true ); + return (int) $post_audio_id; + }, + 'schema' => [ + 'type' => 'integer', + 'context' => [ 'view', 'edit' ], + ], + ) + ); + } + + /** + * Handles audio generation on REST updates / inserts. + * + * @param \WP_Post $post Inserted or updated post object. + * @param WP_REST_Request $request Request object. + */ + public function rest_handle_audio( \WP_Post $post, WP_REST_Request $request ) { + if ( ! $this->is_feature_enabled() ) { + return; + } + + $audio_id = get_post_meta( $request->get_param( 'id' ), self::AUDIO_ID_KEY, true ); + + // Since we have dynamic generation option agnostic to meta saves we need a flag to differentiate audio generation accurately + $process_content = false; + if ( + ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) + ) { + $process_content = true; + } + + // Add/update audio if it was requested. + if ( + ( $process_content && null === $request->get_param( 'classifai_synthesize_speech' ) ) || + true === $request->get_param( 'classifai_synthesize_speech' ) + ) { + $results = $this->run( $request->get_param( 'id' ), 'synthesize' ); + + if ( $results && ! is_wp_error( $results ) ) { + $this->save( $results, $request->get_param( 'id' ) ); + } + } + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'synthesize-speech/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'rest_endpoint_callback' ), + 'args' => array( + 'id' => array( + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'ID of post to run text to speech conversion on.', 'classifai' ), + ), + ), + 'permission_callback' => [ $this, 'speech_synthesis_permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to generate audio for the post. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|bool + */ + public function speech_synthesis_permissions_check( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + + // Ensure we have a logged in user that can edit the item. + if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { + return false; + } + + $post_type = get_post_type( $post_id ); + $post_type_obj = get_post_type_object( $post_type ); + + // Ensure the post type is allowed in REST endpoints. + if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) { + return false; + } + + // Ensure the post type is supported by this feature. + $supported = $this->get_supported_post_types(); + if ( ! in_array( $post_type, $supported, true ) ) { + return new WP_Error( 'not_enabled', esc_html__( 'Speech synthesis is not enabled for current item.', 'classifai' ) ); + } + + // Ensure the feature is enabled. Also runs a user check. + if ( ! $this->is_feature_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Speech synthesis is not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/synthesize-speech' ) === 0 ) { + $results = $this->run( $request->get_param( 'id' ), 'synthesize' ); + + if ( $results && ! is_wp_error( $results ) ) { + $attachment_id = $this->save( $results, $request->get_param( 'id' ) ); + + if ( ! is_wp_error( $attachment_id ) ) { + return rest_ensure_response( + array( + 'success' => true, + 'audio_id' => $attachment_id, + ) + ); + } + } + + return rest_ensure_response( + array( + 'success' => false, + 'code' => $results->get_error_code(), + 'message' => $results->get_error_message(), + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Adds a meta box for Classic content to trigger Text to Speech. + * + * @param string $post_type The post type. + */ + public function add_meta_box( string $post_type ) { + if ( + ! in_array( $post_type, $this->get_supported_post_types(), true ) || + ! $this->is_feature_enabled() + ) { + return; + } + + \add_meta_box( + 'classifai-text-to-speech-meta-box', + __( 'ClassifAI Text to Speech Processing', 'classifai' ), + [ $this, 'render_meta_box' ], + null, + 'side', + 'high', + array( '__back_compat_meta_box' => true ) + ); + } + + /** + * Render meta box content. + * + * @param \WP_Post $post WP_Post object. + */ + public function render_meta_box( \WP_Post $post ) { + wp_nonce_field( 'classifai_text_to_speech_meta_action', 'classifai_text_to_speech_meta' ); + + $source_url = false; + $audio_id = get_post_meta( $post->ID, self::AUDIO_ID_KEY, true ); + + if ( $audio_id ) { + $source_url = wp_get_attachment_url( $audio_id ); + } + + $process_content = false; + if ( + ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || + ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) + ) { + $process_content = true; + } + + $display_audio = true; + if ( metadata_exists( 'post', $post->ID, self::DISPLAY_GENERATED_AUDIO ) && + ! (bool) get_post_meta( $post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { + $display_audio = false; + } + + $post_type_label = esc_html__( 'Post', 'classifai' ); + $post_type = get_post_type_object( get_post_type( $post ) ); + if ( $post_type ) { + $post_type_label = $post_type->labels->singular_name; + } + ?> + +

    + + + + +

    + +

    > + + + + +

    + + time(), + ], + $source_url + ); + ?> + +

    + +

    + + get_supported_post_types(), true ) || + ! $this->is_feature_enabled() + ) { + return; + } + + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) { + return; + } + + if ( empty( $_POST['classifai_text_to_speech_meta'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['classifai_text_to_speech_meta'] ) ), 'classifai_text_to_speech_meta_action' ) ) { + return; + } + + if ( ! isset( $_POST['classifai_display_generated_audio'] ) ) { + update_post_meta( $post_id, self::DISPLAY_GENERATED_AUDIO, false ); + } else { + delete_post_meta( $post_id, self::DISPLAY_GENERATED_AUDIO ); + } + + if ( isset( $_POST['classifai_synthesize_speech'] ) ) { + $results = $this->run( $post_id, 'synthesize' ); + + if ( $results && ! is_wp_error( $results ) ) { + $this->save( $results, $post_id ); + } + } + } + + /** + * Save the returned result. + * + * @param string $result The results to save. + * @param int $post_id The post ID. + * @return int|WP_Error + */ + public function save( string $result, int $post_id ) { + $saved_attachment_id = (int) get_post_meta( $post_id, self::AUDIO_ID_KEY, true ); + + // The audio file name. + $audio_file_name = sprintf( + 'post-as-audio-%1$s.mp3', + $post_id + ); + + // Upload the audio stream as an .mp3 file. + $file_data = wp_upload_bits( + $audio_file_name, + null, + $result + ); + + if ( isset( $file_data['error'] ) && ! empty( $file_data['error'] ) ) { + return new WP_Error( + 'text_to_speech_upload_bits_failure', + esc_html( $file_data['error'] ) + ); + } + + // Insert the audio file as attachment. + $attachment_id = wp_insert_attachment( + array( + 'guid' => $file_data['file'], + 'post_title' => $audio_file_name, + 'post_mime_type' => $file_data['type'], + ), + $file_data['file'], + $post_id + ); + + // Return error if creation of attachment fails. + if ( ! $attachment_id ) { + return new WP_Error( + 'text_to_speech_resource_creation_failure', + esc_html__( 'Audio creation failed.', 'classifai' ) + ); + } + + // If audio already exists for this post, delete it. + if ( $saved_attachment_id ) { + wp_delete_attachment( $saved_attachment_id, true ); + delete_post_meta( $post_id, self::AUDIO_ID_KEY ); + delete_post_meta( $post_id, self::AUDIO_TIMESTAMP_KEY ); + } + + update_post_meta( $post_id, self::AUDIO_ID_KEY, absint( $attachment_id ) ); + update_post_meta( $post_id, self::AUDIO_TIMESTAMP_KEY, time() ); + + return $attachment_id; + } + + /** + * Adds audio controls to the post that has speech synthesis enabled. + * + * @param string $content Post content. + * @return string + */ + public function render_post_audio_controls( string $content ): string { + $_post = get_post(); + + if ( + ! $_post instanceof \WP_Post || + ! is_singular( $_post->post_type ) || + ! in_array( $_post->post_type, $this->get_supported_post_types(), true ) + ) { + return $content; + } + + /** + * Filter to disable the rendering of the Text to Speech block. + * + * @since 2.2.0 + * @hook classifai_disable_post_to_audio_block + * + * @param {bool} $is_disabled Whether to disable the display or not. By default - false. + * @param {WP_Post} $_post The Post object. + * + * @return {bool} Whether the audio block should be shown. + */ + if ( apply_filters( 'classifai_disable_post_to_audio_block', false, $_post ) ) { + return $content; + } + + // Respect the audio display settings of the post. + if ( metadata_exists( 'post', $_post->ID, self::DISPLAY_GENERATED_AUDIO ) && + ! (bool) get_post_meta( $_post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { + return $content; + } + + $audio_attachment_id = (int) get_post_meta( $_post->ID, self::AUDIO_ID_KEY, true ); + + if ( ! $audio_attachment_id ) { + return $content; + } + + $audio_attachment_url = wp_get_attachment_url( $audio_attachment_id ); + + if ( ! $audio_attachment_url ) { + return $content; + } + + $audio_timestamp = (int) get_post_meta( $_post->ID, self::AUDIO_TIMESTAMP_KEY, true ); + + if ( $audio_timestamp ) { + $audio_attachment_url = add_query_arg( 'ver', filter_var( $audio_timestamp, FILTER_SANITIZE_NUMBER_INT ), $audio_attachment_url ); + } + + /** + * Filters the audio player markup before display. + * + * Returning a non-false value from this filter will short-circuit building + * the block markup and instead will return your custom markup prepended to + * the post_content. + * + * Note that by using this filter, the custom CSS and JS files will no longer + * be enqueued, so you'll be responsible for either loading them yourself or + * loading custom ones. + * + * @hook classifai_pre_render_post_audio_controls + * @since 2.2.3 + * + * @param {bool|string} $markup Audio markup to use. Defaults to false. + * @param {string} $content Content of the current post. + * @param {WP_Post} $_post The Post object. + * @param {int} $audio_attachment_id The audio attachment ID. + * @param {string} $audio_attachment_url The URL to the audio attachment file. + * + * @return {bool|string} Custom audio block markup. Will be prepended to the post content. + */ + $markup = apply_filters( 'classifai_pre_render_post_audio_controls', false, $content, $_post, $audio_attachment_id, $audio_attachment_url ); + + if ( false !== $markup ) { + return (string) $markup . $content; + } + + wp_enqueue_script( + 'classifai-post-audio-player-js', + CLASSIFAI_PLUGIN_URL . 'dist/post-audio-controls.js', + get_asset_info( 'post-audio-controls', 'dependencies' ), + get_asset_info( 'post-audio-controls', 'version' ), + true + ); + + wp_enqueue_style( + 'classifai-post-audio-player-css', + CLASSIFAI_PLUGIN_URL . 'dist/post-audio-controls.css', + array(), + get_asset_info( 'post-audio-controls', 'version' ), + 'all' + ); + + ob_start(); + + ?> +
    +
    +
    + + +
    +
    + ID ), + esc_html__( 'Listen to this', 'classifai' ), + esc_html( $_post->post_type ) + ); + + echo wp_kses_post( $listen_to_post_text ); + ?> +
    +
    + +
    + get_settings( 'post_types' ); $post_types = []; @@ -135,31 +769,52 @@ public function sanitize_default_feature_settings( array $new_settings ): array } /** - * Runs the feature. + * Initial audio generation state. + * + * Fetch the initial state of audio generation prior to the audio existing for the post. * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed + * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values + * return the current global post inside the loop. A numerically valid post ID that + * points to a non-existent post returns `null`. Defaults to global $post. + * @return bool The initial state of audio generation. Default true. */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? Speech::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( Speech::ID === $provider_instance::ID ) { - /** @var Speech $provider_instance */ - return call_user_func_array( - [ $provider_instance, 'synthesize_speech' ], - [ ...$args ] - ); - } + public function get_audio_generation_initial_state( $post = null ): bool { + /** + * Initial state of the audio generation toggle when no audio already exists for the post. + * + * @since 2.3.0 + * @hook classifai_audio_generation_initial_state + * + * @param {bool} $state Initial state of audio generation toggle on a post. Default true. + * @param {WP_Post} $post The current Post object. + * + * @return {bool} Initial state the audio generation toggle should be set to when no audio exists. + */ + return apply_filters( 'classifai_audio_generation_initial_state', true, get_post( $post ) ); + } - return apply_filters( - 'classifai_' . static::ID . '_run', - $result, - $provider_instance, - $args, - $this - ); + /** + * Subsequent audio generation state. + * + * Fetch the subsequent state of audio generation once audio is generated for the post. + * + * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values + * return the current global post inside the loop. A numerically valid post ID that + * points to a non-existent post returns `null`. Defaults to global $post. + * @return bool The subsequent state of audio generation. Default false. + */ + public function get_audio_generation_subsequent_state( $post = null ): bool { + /** + * Subsequent state of the audio generation toggle when audio exists for the post. + * + * @since 2.3.0 + * @hook classifai_audio_generation_subsequent_state + * + * @param {bool} $state Subsequent state of audio generation toggle on a post. Default false. + * @param {WP_Post} $post The current Post object. + * + * @return {bool} Subsequent state the audio generation toggle should be set to when audio exists. + */ + return apply_filters( 'classifai_audio_generation_subsequent_state', false, get_post( $post ) ); } } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index e53276b5a..e616d2b19 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -3,7 +3,6 @@ namespace Classifai; use Classifai\Features\Classification; -use Classifai\Features\TextToSpeech; use Classifai\Providers\Provider; use Classifai\Admin\UserProfile; use Classifai\Providers\Watson\NLU; @@ -318,25 +317,6 @@ function get_supported_post_types(): array { return $post_types; } -/** - * The list of post types that TTS supports. - * - * @return array Supported Post Types. - */ -function get_tts_supported_post_types(): array { - $feature = new TextToSpeech( null ); - $selected = $feature->get_settings( 'post_types' ); - $post_types = []; - - foreach ( $selected as $post_type => $enabled ) { - if ( ! empty( $enabled ) ) { - $post_types[] = $post_type; - } - } - - return $post_types; -} - /** * The list of post statuses that get the ClassifAI taxonomies. * diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index d48c1c6c4..b8685958e 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -10,14 +10,8 @@ use Classifai\Features\TextToSpeech; use stdClass; use WP_Http; -use WP_REST_Server; -use WP_REST_Request; use WP_Error; -use function Classifai\get_post_types_for_language_settings; -use function Classifai\get_tts_supported_post_types; -use function Classifai\get_asset_info; - class Speech extends Provider { const ID = 'ms_azure_text_to_speech'; @@ -36,28 +30,6 @@ class Speech extends Provider { */ const API_PATH = 'cognitiveservices/v1'; - /** - * Meta key to hide/unhide already generated audio file. - * - * @var string - */ - const DISPLAY_GENERATED_AUDIO = '_classifai_display_generated_audio'; - - /** - * Meta key to get/set the ID of the speech audio file. - * - * @var string - */ - const AUDIO_ID_KEY = '_classifai_post_audio_id'; - - /** - * Meta key to get/set the timestamp indicating when the speech was generated. - * Used for cache-busting as the audio filename remains static for a given post. - * - * @var string - */ - const AUDIO_TIMESTAMP_KEY = '_classifai_post_audio_timestamp'; - /** * Meta key to get/set the audio hash that helps to indicate if there is any need * for the audio file to be regenerated or not. @@ -80,59 +52,13 @@ public function __construct( $feature_instance = null ) { $this->feature_instance = $feature_instance; - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); do_action( 'classifai_' . static::ID . '_init', $this ); } - /** - * Enqueue the editor scripts. - * - * @since 2.4.0 Use get_asset_info to get the asset version and dependencies. - */ - public function enqueue_editor_assets() { - $post = get_post(); - - if ( empty( $post ) ) { - return; - } - - wp_enqueue_script( - 'classifai-gutenberg-plugin', - CLASSIFAI_PLUGIN_URL . 'dist/gutenberg-plugin.js', - array_merge( get_asset_info( 'gutenberg-plugin', 'dependencies' ), array( 'lodash' ) ), - get_asset_info( 'gutenberg-plugin', 'version' ), - true - ); - - wp_add_inline_script( - 'classifai-gutenberg-plugin', - sprintf( - 'var classifaiTTSEnabled = %d;', - true - ), - 'before' - ); - } - /** * Register the actions needed. */ public function register() { - $tts = new TextToSpeech(); - if ( $tts->is_feature_enabled() ) { - add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); - add_action( 'rest_api_init', [ $this, 'add_synthesize_speech_meta_to_rest_api' ] ); - add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] ); - add_action( 'save_post', [ $this, 'save_post_metadata' ], 5 ); - - foreach ( $tts->get_tts_supported_post_types() as $post_type ) { - add_action( 'rest_insert_' . $post_type, [ $this, 'rest_handle_audio' ], 10, 2 ); - } - } - - if ( $tts->is_enabled() ) { - add_filter( 'the_content', [ $this, 'render_post_audio_controls' ] ); - } } /** @@ -266,17 +192,6 @@ public function sanitize_settings( array $new_settings ): array { ); } - // Sanitize the post type checkboxes - $post_types = get_post_types_for_language_settings(); - - foreach ( $post_types as $post_type ) { - if ( isset( $new_settings['post_types'][ $post_type->name ] ) ) { - $new_settings['post_types'][ $post_type->name ] = sanitize_text_field( $new_settings['post_types'][ $post_type->name ] ); - } else { - $new_settings['post_types'][ $post_type->name ] = $settings['post_types']; - } - } - $new_settings[ static::ID ]['voice'] = sanitize_text_field( $new_settings[ static::ID ]['voice'] ?? $settings[ static::ID ]['voice'] ); return $new_settings; @@ -413,133 +328,14 @@ public function get_voices_select_options(): array { } /** - * Initial audio generation state. - * - * Fetch the initial state of audio generation prior to the audio existing for the post. - * - * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values - * return the current global post inside the loop. A numerically valid post ID that - * points to a non-existent post returns `null`. Defaults to global $post. - * @return bool The initial state of audio generation. Default true. - */ - public function get_audio_generation_initial_state( $post = null ): bool { - /** - * Initial state of the audio generation toggle when no audio already exists for the post. - * - * @since 2.3.0 - * @hook classifai_audio_generation_initial_state - * - * @param {bool} $state Initial state of audio generation toggle on a post. Default true. - * @param {WP_Post} $post The current Post object. - * - * @return {bool} Initial state the audio generation toggle should be set to when no audio exists. - */ - return apply_filters( 'classifai_audio_generation_initial_state', true, get_post( $post ) ); - } - - /** - * Subsequent audio generation state. - * - * Fetch the subsequent state of audio generation once audio is generated for the post. - * - * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values - * return the current global post inside the loop. A numerically valid post ID that - * points to a non-existent post returns `null`. Defaults to global $post. - * @return bool The subsequent state of audio generation. Default false. - */ - public function get_audio_generation_subsequent_state( $post = null ): bool { - /** - * Subsequent state of the audio generation toggle when audio exists for the post. - * - * @since 2.3.0 - * @hook classifai_audio_generation_subsequent_state - * - * @param {bool} $state Subsequent state of audio generation toggle on a post. Default false. - * @param {WP_Post} $post The current Post object. - * - * @return {bool} Subsequent state the audio generation toggle should be set to when audio exists. - */ - return apply_filters( 'classifai_audio_generation_subsequent_state', false, get_post( $post ) ); - } - - /** - * Add audio related fields to rest API for view/edit. - */ - public function add_synthesize_speech_meta_to_rest_api() { - $supported_post_types = get_tts_supported_post_types(); - - register_rest_field( - $supported_post_types, - 'classifai_synthesize_speech', - array( - 'get_callback' => function ( $data ) { - $audio_id = get_post_meta( $data['id'], self::AUDIO_ID_KEY, true ); - if ( - ( $this->get_audio_generation_initial_state( $data['id'] ) && ! $audio_id ) || - ( $this->get_audio_generation_subsequent_state( $data['id'] ) && $audio_id ) - ) { - return true; - } else { - return false; - } - }, - 'schema' => [ - 'type' => 'boolean', - 'context' => [ 'view', 'edit' ], - ], - ) - ); - - register_rest_field( - $supported_post_types, - 'classifai_display_generated_audio', - array( - 'get_callback' => function ( $data ) { - // Default to display the audio if available. - if ( metadata_exists( 'post', $data['id'], self::DISPLAY_GENERATED_AUDIO ) ) { - return (bool) get_post_meta( $data['id'], self::DISPLAY_GENERATED_AUDIO, true ); - } - return true; - }, - 'update_callback' => function ( $value, $data ) { - if ( $value ) { - delete_post_meta( $data->ID, self::DISPLAY_GENERATED_AUDIO ); - } else { - update_post_meta( $data->ID, self::DISPLAY_GENERATED_AUDIO, false ); - } - }, - 'schema' => [ - 'type' => 'boolean', - 'context' => [ 'view', 'edit' ], - ], - ) - ); - - register_rest_field( - $supported_post_types, - 'classifai_post_audio_id', - array( - 'get_callback' => function ( $data ) { - $post_audio_id = get_post_meta( $data['id'], self::AUDIO_ID_KEY, true ); - return (int) $post_audio_id; - }, - 'schema' => [ - 'type' => 'integer', - 'context' => [ 'view', 'edit' ], - ], - ) - ); - } - - /** - * Synthesizes speech from the post title and content. + * Synthesizes speech from a post item. * * @param int $post_id Post ID. - * @return bool|int|WP_Error + * @return string|WP_Error */ public function synthesize_speech( int $post_id ) { if ( empty( $post_id ) ) { - return new \WP_Error( + return new WP_Error( 'azure_text_to_speech_post_id_missing', esc_html__( 'Post ID missing.', 'classifai' ) ); @@ -547,7 +343,7 @@ public function synthesize_speech( int $post_id ) { // We skip the user cap check if running under WP-CLI. if ( ! current_user_can( 'edit_post', $post_id ) && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) { - return new \WP_Error( + return new WP_Error( 'azure_text_to_speech_user_not_authorized', esc_html__( 'Unauthorized user.', 'classifai' ) ); @@ -559,7 +355,7 @@ public function synthesize_speech( int $post_id ) { $post = get_post( $post_id ); $post_content = $normalizer->normalize_content( $post->post_content, $post->post_title, $post_id ); $content_hash = get_post_meta( $post_id, self::AUDIO_HASH_KEY, true ); - $saved_attachment_id = (int) get_post_meta( $post_id, self::AUDIO_ID_KEY, true ); + $saved_attachment_id = (int) get_post_meta( $post_id, $feature::AUDIO_ID_KEY, true ); // Don't regenerate the audio file it it already exists and the content hasn't changed. if ( $saved_attachment_id ) { @@ -584,7 +380,7 @@ public function synthesize_speech( int $post_id ) { // Return error if voice is not set in settings. } else { - return new \WP_Error( + return new WP_Error( 'azure_text_to_speech_voice_information_missing', esc_html__( 'Voice data not set.', 'classifai' ) ); @@ -593,8 +389,8 @@ public function synthesize_speech( int $post_id ) { // Create the request body to synthesize speech from text. $request_body = sprintf( "%s", - $voice_gender, - $voice_name, + esc_attr( $voice_gender ), + esc_attr( $voice_name ), $post_content ); @@ -614,7 +410,7 @@ public function synthesize_speech( int $post_id ) { $response = wp_remote_post( $remote_url, $request_params ); if ( is_wp_error( $response ) ) { - return new \WP_Error( + return new WP_Error( 'azure_text_to_speech_http_error', esc_html( $response->get_error_message() ) ); @@ -625,454 +421,41 @@ public function synthesize_speech( int $post_id ) { // return error if HTTP status code is not 200. if ( \WP_Http::OK !== $code ) { - return new \WP_Error( + return new WP_Error( 'azure_text_to_speech_unsuccessful_request', esc_html__( 'HTTP request unsuccessful.', 'classifai' ) ); } - // If audio already exists for this post, delete it. - if ( $saved_attachment_id ) { - wp_delete_attachment( $saved_attachment_id, true ); - delete_post_meta( $post_id, self::AUDIO_ID_KEY ); - delete_post_meta( $post_id, self::AUDIO_TIMESTAMP_KEY ); - } - - // The audio file name. - $audio_file_name = sprintf( - 'post-as-audio-%1$s.mp3', - $post_id - ); - - // Upload the audio stream as an .mp3 file. - $file_data = wp_upload_bits( - $audio_file_name, - null, - $response_body - ); - - if ( isset( $file_data['error'] ) && ! empty( $file_data['error'] ) ) { - return new \WP_Error( - 'azure_text_to_speech_upload_bits_failure', - esc_html( $file_data['error'] ) - ); - } - - // Insert the audio file as attachment. - $attachment_id = wp_insert_attachment( - array( - 'guid' => $file_data['file'], - 'post_title' => $audio_file_name, - 'post_mime_type' => $file_data['type'], - ), - $file_data['file'], - $post_id - ); - - // Return error if creation of attachment fails. - if ( ! $attachment_id ) { - return new \WP_Error( - 'azure_text_to_speech_resource_creation_failure', - esc_html__( 'Audio creation failed.', 'classifai' ) - ); - } - - update_post_meta( $post_id, self::AUDIO_ID_KEY, absint( $attachment_id ) ); - update_post_meta( $post_id, self::AUDIO_TIMESTAMP_KEY, time() ); update_post_meta( $post_id, self::AUDIO_HASH_KEY, md5( $post_content ) ); - return $attachment_id; - } - - /** - * Handles audio generation on rest updates / inserts. - * - * @param \WP_Post $post Inserted or updated post object. - * @param \WP_REST_Request $request Request object. - */ - public function rest_handle_audio( \WP_Post $post, \WP_REST_Request $request ) { - $audio_id = get_post_meta( $request->get_param( 'id' ), self::AUDIO_ID_KEY, true ); - - // Since we have dynamic generation option agnostic to meta saves we need a flag to differentiate audio generation accurately - $process_content = false; - if ( - ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || - ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) - ) { - $process_content = true; - } - - // Add/Update audio if it was requested. - if ( - ( $process_content && null === $request->get_param( 'classifai_synthesize_speech' ) ) || - true === $request->get_param( 'classifai_synthesize_speech' ) - ) { - ( new TextToSpeech() )->run( $request->get_param( 'id' ) ); - } - } - - /** - * Add meta box to post types that support speech synthesis. - * - * @param string $post_type Post type. - */ - public function add_meta_box( string $post_type ) { - if ( ! in_array( $post_type, get_tts_supported_post_types(), true ) ) { - return; - } - - \add_meta_box( - 'classifai-text-to-speech-meta-box', - __( 'ClassifAI Text to Speech Processing', 'classifai' ), - [ $this, 'render_meta_box' ], - null, - 'side', - 'high', - array( '__back_compat_meta_box' => true ) - ); - } - - /** - * Render meta box content. - * - * @param \WP_Post $post WP_Post object. - */ - public function render_meta_box( \WP_Post $post ) { - wp_nonce_field( 'classifai_text_to_speech_meta_action', 'classifai_text_to_speech_meta' ); - - $source_url = false; - $audio_id = get_post_meta( $post->ID, self::AUDIO_ID_KEY, true ); - if ( $audio_id ) { - $source_url = wp_get_attachment_url( $audio_id ); - } - - $process_content = false; - if ( - ( $this->get_audio_generation_initial_state( $post ) && ! $audio_id ) || - ( $this->get_audio_generation_subsequent_state( $post ) && $audio_id ) - ) { - $process_content = true; - } - - $display_audio = true; - if ( metadata_exists( 'post', $post->ID, self::DISPLAY_GENERATED_AUDIO ) && - ! (bool) get_post_meta( $post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { - $display_audio = false; - } - - $post_type_label = esc_html__( 'Post', 'classifai' ); - $post_type = get_post_type_object( get_post_type( $post ) ); - if ( $post_type ) { - $post_type_label = $post_type->labels->singular_name; - } - - ?> -

    - - - - -

    - -

    > - - - - -

    - - time(), - ], - $source_url - ); - ?> - -

    - -

    - - synthesize_speech( $post_id ); - } - } - - /** - * Adds audio controls to the post that has speech synthesis enabled. - * - * @param string $content Post content. - * @return string - */ - public function render_post_audio_controls( string $content ): string { - - $_post = get_post(); - - if ( ! $_post instanceof \WP_Post ) { - return $content; - } - - if ( ! is_singular( $_post->post_type ) ) { - return $content; - } - - if ( ! in_array( $_post->post_type, get_tts_supported_post_types(), true ) ) { - return $content; - } - - /** - * Filter to disable the rendering of the Text to Speech block. - * - * @since 2.2.0 - * @hook classifai_disable_post_to_audio_block - * - * @param {bool} $is_disabled Whether to disable the display or not. By default - false. - * @param {WP_Post} $_post The Post object. - * - * @return {bool} Whether the audio block should be shown. - */ - if ( apply_filters( 'classifai_disable_post_to_audio_block', false, $_post ) ) { - return $content; - } - - // Respect the audio display settings of the post. - if ( metadata_exists( 'post', $_post->ID, self::DISPLAY_GENERATED_AUDIO ) && - ! (bool) get_post_meta( $_post->ID, self::DISPLAY_GENERATED_AUDIO, true ) ) { - return $content; - } - - $audio_attachment_id = (int) get_post_meta( $_post->ID, self::AUDIO_ID_KEY, true ); - - if ( ! $audio_attachment_id ) { - return $content; - } - - $audio_attachment_url = wp_get_attachment_url( $audio_attachment_id ); - - if ( ! $audio_attachment_url ) { - return $content; - } - - $audio_timestamp = (int) get_post_meta( $_post->ID, self::AUDIO_TIMESTAMP_KEY, true ); - - if ( $audio_timestamp ) { - $audio_attachment_url = add_query_arg( 'ver', filter_var( $audio_timestamp, FILTER_SANITIZE_NUMBER_INT ), $audio_attachment_url ); - } - - /** - * Filters the audio player markup before display. - * - * Returning a non-false value from this filter will short-circuit building - * the block markup and instead will return your custom markup prepended to - * the post_content. - * - * Note that by using this filter, the custom CSS and JS files will no longer - * be enqueued, so you'll be responsible for either loading them yourself or - * loading custom ones. - * - * @hook classifai_pre_render_post_audio_controls - * @since 2.2.3 - * - * @param {bool|string} $markup Audio markup to use. Defaults to false. - * @param {string} $content Content of the current post. - * @param {WP_Post} $_post The Post object. - * @param {int} $audio_attachment_id The audio attachment ID. - * @param {string} $audio_attachment_url The URL to the audio attachment file. - * - * @return {bool|string} Custom audio block markup. Will be prepended to the post content. - */ - $markup = apply_filters( 'classifai_pre_render_post_audio_controls', false, $content, $_post, $audio_attachment_id, $audio_attachment_url ); - - if ( false !== $markup ) { - return (string) $markup . $content; - } - - wp_enqueue_script( - 'classifai-post-audio-player-js', - CLASSIFAI_PLUGIN_URL . 'dist/post-audio-controls.js', - get_asset_info( 'post-audio-controls', 'dependencies' ), - get_asset_info( 'post-audio-controls', 'version' ), - true - ); - - wp_enqueue_style( - 'classifai-post-audio-player-css', - CLASSIFAI_PLUGIN_URL . 'dist/post-audio-controls.css', - array(), - get_asset_info( 'post-audio-controls', 'version' ), - 'all' - ); - - ob_start(); - - ?> -
    -
    -
    - - -
    -
    - ID ), - esc_html__( 'Listen to this', 'classifai' ), - esc_html( $_post->post_type ) - ); - - echo wp_kses_post( $listen_to_post_text ); - ?> -
    -
    - -
    - name ] = $post_type->label; - } - - return $options; + return $response_body; } /** - * Registers REST endpoints for this provider. - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'synthesize-speech/(?P\d+)', - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'synthesize_speech_from_text' ), - 'args' => array( - 'id' => array( - 'required' => true, - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'description' => esc_html__( 'ID of post to run text to speech conversion on.', 'classifai' ), - ), - ), - 'permission_callback' => [ $this, 'speech_synthesis_permissions_check' ], - ] - ); - } - - /** - * Generates text to speech for a post using REST. + * Common entry point for all REST endpoints for this provider. * - * @param WP_REST_Request $request Full data about the request. - * @return \WP_REST_Response|WP_Error + * @param int $post_id The post ID we're processing. + * @param string $route_to_call The name of the route we're going to be processing. + * @param array $args Optional arguments to pass to the route. + * @return array|string|WP_Error */ - public function synthesize_speech_from_text( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - $attachment_id = $this->synthesize_speech( $post_id ); - - if ( is_wp_error( $attachment_id ) ) { - return rest_ensure_response( - array( - 'success' => false, - 'code' => $attachment_id->get_error_code(), - 'message' => $attachment_id->get_error_message(), - ) - ); + public function rest_endpoint_callback( $post_id, string $route_to_call = '', array $args = [] ) { + if ( ! $post_id || ! get_post( $post_id ) ) { + return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required.', 'classifai' ) ); } - return rest_ensure_response( - array( - 'success' => true, - 'audio_id' => $attachment_id, - ) - ); - } - - /** - * Check if a given request has access to generate audio for the post. - * - * @param WP_REST_Request $request Full data about the request. - * @return WP_Error|bool - */ - public function speech_synthesis_permissions_check( WP_REST_Request $request ) { - $post_id = $request->get_param( 'id' ); - - if ( ! empty( $post_id ) && current_user_can( 'edit_post', $post_id ) ) { - $post_type = get_post_type( $post_id ); - $supported = \Classifai\get_tts_supported_post_types(); - - // Check if processing allowed. - if ( ! in_array( $post_type, $supported, true ) ) { - return new WP_Error( 'not_enabled', esc_html__( 'Azure Speech synthesis is not enabled for current post.', 'classifai' ) ); - } + $route_to_call = strtolower( $route_to_call ); + $return = ''; - return true; + // Handle all of our routes. + switch ( $route_to_call ) { + case 'synthesize': + $return = $this->synthesize_speech( $post_id, $args ); + break; } - return false; + return $return; } /** From e28c85da963515d2784bb443a484dbbaf71c338b Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 29 Jan 2024 16:29:07 -0700 Subject: [PATCH 116/127] Move most of the Watson specific code into the Watson Provider directory structure --- composer.json | 3 +- .../Classifai/Command/ClassifaiCommand.php | 19 +- .../Classifai/Features/Classification.php | 80 +---- includes/Classifai/Helpers.php | 302 ------------------ .../Classifai/{Watson => }/Normalizer.php | 5 +- includes/Classifai/Plugin.php | 14 - includes/Classifai/Providers/Azure/Speech.php | 2 +- .../Classifai/Providers/OpenAI/ChatGPT.php | 2 +- .../Classifai/Providers/OpenAI/Embeddings.php | 6 +- .../{ => Providers}/Watson/APIRequest.php | 34 +- .../{ => Providers}/Watson/Classifier.php | 31 +- .../Classifai/Providers/Watson/Helpers.php | 285 +++++++++++++++++ .../{ => Providers}/Watson/Linker.php | 39 ++- includes/Classifai/Providers/Watson/NLU.php | 142 ++++++-- .../{ => Providers/Watson}/PostClassifier.php | 58 ++-- .../Watson}/PreviewClassifierData.php | 12 +- .../Watson}/SavePostHandler.php | 46 +-- includes/Classifai/Services/Service.php | 105 ------ .../Classifai/Taxonomy/CategoryTaxonomy.php | 7 +- .../Classifai/Taxonomy/ConceptTaxonomy.php | 7 +- .../Classifai/Taxonomy/EntityTaxonomy.php | 7 +- .../Classifai/Taxonomy/KeywordTaxonomy.php | 7 +- .../Classifai/Taxonomy/TaxonomyFactory.php | 4 +- tests/Classifai/Admin/SavePostHandlerTest.php | 117 ------- tests/Classifai/HelpersTest.php | 10 +- tests/Classifai/PostClassifierTest.php | 10 +- tests/Classifai/Watson/APIRequestTest.php | 2 +- tests/Classifai/Watson/ClassifierTest.php | 4 +- tests/Classifai/Watson/LinkerTest.php | 2 +- tests/Classifai/Watson/NormalizerTest.php | 2 +- 30 files changed, 578 insertions(+), 786 deletions(-) rename includes/Classifai/{Watson => }/Normalizer.php (96%) rename includes/Classifai/{ => Providers}/Watson/APIRequest.php (81%) rename includes/Classifai/{ => Providers}/Watson/Classifier.php (82%) create mode 100644 includes/Classifai/Providers/Watson/Helpers.php rename includes/Classifai/{ => Providers}/Watson/Linker.php (92%) rename includes/Classifai/{ => Providers/Watson}/PostClassifier.php (73%) rename includes/Classifai/{Admin => Providers/Watson}/PreviewClassifierData.php (93%) rename includes/Classifai/{Admin => Providers/Watson}/SavePostHandler.php (86%) delete mode 100644 tests/Classifai/Admin/SavePostHandlerTest.php diff --git a/composer.json b/composer.json index b5a51ac7e..40a11b254 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ }, "files": [ "includes/Classifai/Helpers.php", - "includes/Classifai/Blocks.php" + "includes/Classifai/Blocks.php", + "includes/Classifai/Providers/Watson/Helpers.php" ] }, "require-dev": { diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php index 41a106056..487bbf302 100644 --- a/includes/Classifai/Command/ClassifaiCommand.php +++ b/includes/Classifai/Command/ClassifaiCommand.php @@ -7,14 +7,17 @@ use Classifai\Features\ExcerptGeneration; use Classifai\Features\ImageCropping; use Classifai\Features\TextToSpeech; -use Classifai\Watson\APIRequest; -use Classifai\Watson\Classifier; -use Classifai\Watson\Normalizer; -use Classifai\PostClassifier; +use Classifai\Providers\Watson\APIRequest; +use Classifai\Providers\Watson\Classifier; +use Classifai\Normalizer; +use Classifai\Providers\Watson\PostClassifier; use Classifai\Providers\Azure\ComputerVision; use Classifai\Providers\Azure\SmartCropping; use Classifai\Providers\OpenAI\Embeddings; +use function Classifai\Providers\Watson\get_username; +use function Classifai\Providers\Watson\get_password; + /** * ClassifaiCommand is the command line interface of the ClassifAI plugin. * It provides subcommands to test classification results and batch @@ -151,8 +154,8 @@ public function text( $args = [], $opts = [] ) { $opts = wp_parse_args( $opts, $defaults ); $classifier = new Classifier(); - $username = \Classifai\get_watson_username(); - $password = \Classifai\get_watson_password(); + $username = get_username(); + $password = get_password(); if ( empty( $username ) ) { \WP_CLI::error( 'Watson Username not found in options or constant.' ); @@ -1100,8 +1103,8 @@ public function embeddings( $args = [], $opts = [] ) { * @param array $opts Options. */ public function auth( $args = [], $opts = [] ) { - $username = \Classifai\get_watson_username(); - $password = \Classifai\get_watson_password(); + $username = get_username(); + $password = get_password(); if ( empty( $username ) ) { \WP_CLI::error( 'Watson Username not found in options or constant.' ); diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index ff7591cbe..f8d7f330b 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -5,6 +5,7 @@ use Classifai\Services\LanguageProcessing; use Classifai\Providers\Watson\NLU; use Classifai\Providers\OpenAI\Embeddings; + use function Classifai\get_post_statuses_for_language_settings; use function Classifai\get_post_types_for_language_settings; @@ -85,85 +86,6 @@ public function add_custom_settings_fields() { 'description' => __( 'Choose which post types are allowed to use this feature.', 'classifai' ), ] ); - - add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] ); - } - - /** - * Renders the previewer window for the feature. - * - * @param string $active_feature The ID of the current feature. - */ - public function render_previewer( string $active_feature = '' ) { - if ( static::ID !== $active_feature ) { - return; - } - - $settings = $this->get_settings(); - - if ( ! $settings['status'] ) { - return; - } - - ?> -
    - $supported_post_types, - 'post_status' => $supported_post_statuses, - 'posts_per_page' => 10, - ) - ); - - $features = array( - 'category' => array( - 'name' => esc_html__( 'Category', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'category' ), - 'plural' => 'categories', - ), - 'keyword' => array( - 'name' => esc_html__( 'Keyword', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'keyword' ), - 'plural' => 'keywords', - ), - 'entity' => array( - 'name' => esc_html__( 'Entity', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'entity' ), - 'plural' => 'entities', - ), - 'concept' => array( - 'name' => esc_html__( 'Concept', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'concept' ), - 'plural' => 'concepts', - ), - ); - ?> -

    -
    - - - -
    -
    - $feature ) : ?> -
    -
    -
    - -
    -
    - get_settings(); - $creds = ! empty( $settings[ NLU::ID ] ) ? $settings[ NLU::ID ] : []; - - if ( ! empty( $creds['endpoint_url'] ) ) { - return $creds['endpoint_url']; - } elseif ( defined( 'WATSON_URL' ) ) { - return WATSON_URL; - } else { - return ''; - } -} - -/** - * Returns the currently configured Watson username. Lookup order is, - * - * - Options - * - Constant - * - * @return string - */ -function get_watson_username(): string { - $settings = ( new Classification() )->get_settings( NLU::ID ); - $username = ! empty( $settings['username'] ) ? $settings['username'] : ''; - - if ( ! empty( $username ) ) { - return $username; - } elseif ( defined( 'WATSON_USERNAME' ) ) { - return WATSON_USERNAME; - } else { - return ''; - } -} - -/** - * Get Classification mode. - * - * @since 2.5.0 - * - * @return string - */ -function get_classification_mode(): string { - $feature = new Classification(); - $settings = $feature->get_settings(); - $value = $settings[ NLU::ID ]['classification_mode'] ?? ''; - - if ( $feature->is_feature_enabled() ) { - if ( empty( $value ) ) { - // existing users - // default: automatic_classification - return 'automatic_classification'; - } - } else { - // new users - // default: manual_review - return 'manual_review'; - } - - return $value; -} - -/** - * Get IBM Watson Content Classification method. - * - * @since 2.6.0 - * - * @return string - */ -function get_classification_method(): string { - $feature = new Classification(); - $settings = $feature->get_settings(); - - return $settings['classification_method'] ?? ''; -} - -/** - * Returns the currently configured Watson username. Lookup order is, - * - * - Options - * - Constant - * - * @return string - */ -function get_watson_password(): string { - $settings = ( new Classification() )->get_settings( NLU::ID ); - $password = ! empty( $settings['password'] ) ? $settings['password'] : ''; - - if ( ! empty( $password ) ) { - return $password; - } elseif ( defined( 'WATSON_PASSWORD' ) ) { - return WATSON_PASSWORD; - } else { - return ''; - } -} - /** * Get post types we want to show in the language processing settings * @@ -284,202 +178,6 @@ function get_post_statuses_for_language_settings(): array { return apply_filters( 'classifai_language_settings_post_statuses', $post_statuses ); } -/** - * The list of post types that get the ClassifAI taxonomies. - * - * Defaults to 'post'. - * - * @return array - */ -function get_supported_post_types(): array { - $feature = new Classification(); - $settings = $feature->get_settings(); - $post_types = []; - - foreach ( $settings['post_types'] as $post_type => $enabled ) { - if ( ! empty( $enabled ) ) { - $post_types[] = $post_type; - } - } - - /** - * Filter post types supported for language processing. - * - * @since 1.0.0 - * @hook classifai_post_types - * - * @param {array} $post_types Array of post types to be classified with language processing. - * - * @return {array} Array of post types. - */ - $post_types = apply_filters( 'classifai_post_types', $post_types ); - - return $post_types; -} - -/** - * The list of post statuses that get the ClassifAI taxonomies. - * - * Defaults to 'publish'. - * - * @return array - */ -function get_supported_post_statuses(): array { - $feature = new Classification(); - $settings = $feature->get_settings(); - $post_statuses = []; - - foreach ( $settings['post_statuses'] as $post_status => $enabled ) { - if ( ! empty( $enabled ) ) { - $post_statuses[] = $post_status; - } - } - - /** - * Filter post statuses supported for language processing. - * - * @since 1.7.1 - * @hook classifai_post_statuses - * - * @param {array} $post_types Array of post statuses to be classified with language processing. - * - * @return {array} Array of post statuses. - */ - $post_statuses = apply_filters( 'classifai_post_statuses', $post_statuses ); - - return $post_statuses; -} - -/** - * Returns a bool based on whether the specified feature is enabled - * - * @param string $classify_by category,keyword,entity,concept - * @return bool - */ -function get_feature_enabled( string $classify_by ): bool { - $feature = new Classification(); - $settings = $feature->get_settings( NLU::ID ); - - return filter_var( - $settings[ $classify_by ], - FILTER_VALIDATE_BOOLEAN - ); -} - -/** - * Check if any language processing features are enabled - * - * @since 1.6.0 - * - * @return bool - */ -function language_processing_features_enabled(): bool { - $features = [ - 'category', - 'concept', - 'entity', - 'keyword', - ]; - - foreach ( $features as $feature ) { - if ( get_feature_enabled( $feature ) ) { - return true; - } - } - - return false; -} - -/** - * Returns the feature threshold based on current configuration. Lookup - * order is. - * - * - Option - * - Constant - * - * Any results below the threshold will be ignored. - * - * @param string $feature The feature whose threshold to lookup - * @return float - */ -function get_feature_threshold( string $feature ): float { - $settings = get_plugin_settings( 'language_processing', 'Natural Language Understanding' ); - $threshold = 0; - - if ( ! empty( $settings ) && ! empty( $settings['features'] ) ) { - if ( ! empty( $settings['features'][ $feature . '_threshold' ] ) ) { - $threshold = filter_var( - $settings['features'][ $feature . '_threshold' ], - FILTER_VALIDATE_INT - ); - } - } - - if ( empty( $threshold ) ) { - $constant = 'WATSON_' . strtoupper( $feature ) . '_THRESHOLD'; - - if ( defined( $constant ) ) { - $threshold = intval( constant( $constant ) ); - } - } - - $threshold = empty( $threshold ) ? 0.7 : $threshold / 100; - - /** - * Filter the threshold for a specific feature. Any results below the - * threshold will be ignored. - * - * @since 1.0.0 - * @hook classifai_feature_threshold - * - * @param {float} $threshold The threshold to use, expressed as a decimal between 0 and 1 inclusive. - * @param {string} $feature The feature in question. - * - * @return {float} The filtered threshold. - */ - return apply_filters( 'classifai_feature_threshold', $threshold, $feature ); -} - -/** - * Returns the Taxonomy for the specified NLU feature. - * - * Returns defaults in config.php if options have not been configured. - * - * @param string $classify_by NLU feature name - * @return string Taxonomy mapped to the feature - */ -function get_feature_taxonomy( string $classify_by = '' ): string { - $taxonomy = 0; - - $feature = new Classification(); - $settings = $feature->get_settings( NLU::ID ); - - if ( ! empty( $settings[ $classify_by . '_taxonomy' ] ) ) { - $taxonomy = $settings[ $classify_by . '_taxonomy' ]; - } - - if ( empty( $taxonomy ) ) { - $constant = 'WATSON_' . strtoupper( $classify_by ) . '_TAXONOMY'; - - if ( defined( $constant ) ) { - $taxonomy = constant( $constant ); - } - } - - /** - * Filter the Taxonomy for the specified NLU feature. - * - * @since 1.1.0 - * @hook classifai_taxonomy_for_feature - * - * @param {string} $taxonomy The slug of the taxonomy to use. - * @param {string} $classify_by The NLU feature this taxonomy is for. - * - * @return {string} The filtered taxonomy slug. - */ - return apply_filters( 'classifai_taxonomy_for_feature', $taxonomy, $classify_by ); -} - /** * Provides the max filesize for the Computer Vision service. * diff --git a/includes/Classifai/Watson/Normalizer.php b/includes/Classifai/Normalizer.php similarity index 96% rename from includes/Classifai/Watson/Normalizer.php rename to includes/Classifai/Normalizer.php index a2c1ccaf7..eb2f33984 100644 --- a/includes/Classifai/Watson/Normalizer.php +++ b/includes/Classifai/Normalizer.php @@ -1,10 +1,10 @@ init_services(); - // TODO: is there a better place for this? - $post_types = get_supported_post_types(); - foreach ( $post_types as $post_type ) { - register_meta( - $post_type, - '_classifai_error', - [ - 'show_in_rest' => true, - 'single' => true, - 'auth_callback' => '__return_true', - ] - ); - } - // Initialize the ClassifAI Onboarding. $onboarding = new Admin\Onboarding(); $onboarding->init(); diff --git a/includes/Classifai/Providers/Azure/Speech.php b/includes/Classifai/Providers/Azure/Speech.php index b8685958e..5ddacb64e 100644 --- a/includes/Classifai/Providers/Azure/Speech.php +++ b/includes/Classifai/Providers/Azure/Speech.php @@ -6,7 +6,7 @@ namespace Classifai\Providers\Azure; use Classifai\Providers\Provider; -use Classifai\Watson\Normalizer; +use Classifai\Normalizer; use Classifai\Features\TextToSpeech; use stdClass; use WP_Http; diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 66a239b20..be7235615 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -9,7 +9,7 @@ use Classifai\Features\ExcerptGeneration; use Classifai\Features\TitleGeneration; use Classifai\Providers\Provider; -use Classifai\Watson\Normalizer; +use Classifai\Normalizer; use WP_Error; use function Classifai\get_default_prompt; diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index c0ea8d48c..7e7ea880e 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -9,7 +9,7 @@ use Classifai\Providers\OpenAI\APIRequest; use Classifai\Providers\OpenAI\Tokenizer; use Classifai\Providers\OpenAI\EmbeddingCalculations; -use Classifai\Watson\Normalizer; +use Classifai\Normalizer; use Classifai\Features\Classification; use WP_Error; @@ -94,7 +94,7 @@ public function render_provider_fields() { add_settings_field( static::ID . '_number_of_terms', - esc_html__( 'Number of titles', 'classifai' ), + esc_html__( 'Number of terms', 'classifai' ), [ $this->feature_instance, 'render_input' ], $this->feature_instance->get_option_name(), $this->feature_instance->get_option_name() . '_section', @@ -992,7 +992,7 @@ public function get_debug_information(): array { $debug_info = []; if ( $this->feature_instance instanceof Classification ) { - $debug_info[ __( 'Number of titles', 'classifai' ) ] = $provider_settings['number_of_titles'] ?? 1; + $debug_info[ __( 'Number of terms', 'classifai' ) ] = $provider_settings['number_of_terms'] ?? 1; $debug_info[ __( 'Taxonomy (category)', 'classifai' ) ] = $provider_settings['taxonomies']['category'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); $debug_info[ __( 'Taxonomy (category threshold)', 'classifai' ) ] = $provider_settings['taxonomies']['category_threshold']; $debug_info[ __( 'Taxonomy (tag)', 'classifai' ) ] = $provider_settings['taxonomies']['post_tag'] ? __( 'Enabled', 'classifai' ) : __( 'Disabled', 'classifai' ); diff --git a/includes/Classifai/Watson/APIRequest.php b/includes/Classifai/Providers/Watson/APIRequest.php similarity index 81% rename from includes/Classifai/Watson/APIRequest.php rename to includes/Classifai/Providers/Watson/APIRequest.php index c3d36de3c..382563015 100644 --- a/includes/Classifai/Watson/APIRequest.php +++ b/includes/Classifai/Providers/Watson/APIRequest.php @@ -1,6 +1,9 @@ add_headers( $options ); return $this->get_result( wp_remote_request( $url, $options ) ); } @@ -50,9 +53,9 @@ public function request( $url, $options = [] ) { * * @param string $url The Watson API url * @param array $options Additional query params - * @return array|WP_Error + * @return array|\WP_Error */ - public function get( $url, $options = [] ) { + public function get( string $url, array $options = [] ) { $this->add_headers( $options ); return $this->get_result( wp_remote_get( $url, $options ) ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get } @@ -63,9 +66,9 @@ public function get( $url, $options = [] ) { * * @param string $url The Watson API url * @param array $options Additional query params - * @return array|WP_Error + * @return array|\WP_Error */ - public function post( $url, $options = [] ) { + public function post( string $url, array $options = [] ) { $this->add_headers( $options ); return $this->get_result( wp_remote_post( $url, $options ) ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get } @@ -74,6 +77,7 @@ public function post( $url, $options = [] ) { * Get results from the response. * * @param object $response The API response. + * @return array|\WP_Error */ public function get_result( $response ) { if ( ! is_wp_error( $response ) ) { @@ -99,9 +103,9 @@ public function get_result( $response ) { * * @return string $username. */ - public function get_username() { + public function get_username(): string { if ( empty( $this->username ) ) { - $this->username = \Classifai\get_watson_username(); + $this->username = get_username(); } return $this->username; @@ -109,10 +113,12 @@ public function get_username() { /** * Get the Watson API password. + * + * @return string */ - public function get_password() { + public function get_password(): string { if ( empty( $this->password ) ) { - $this->password = \Classifai\get_watson_password(); + $this->password = get_password(); } return $this->password; @@ -123,7 +129,7 @@ public function get_password() { * * @return string The header. */ - public function get_auth_header() { + public function get_auth_header(): string { return 'Basic ' . $this->get_auth_hash(); } @@ -132,7 +138,7 @@ public function get_auth_header() { * * @return string The auth hash. */ - public function get_auth_hash() { + public function get_auth_hash(): string { $username = $this->get_username(); $password = $this->get_password(); @@ -144,7 +150,7 @@ public function get_auth_hash() { * * @param array $options The header options, passed by reference. */ - public function add_headers( &$options ) { + public function add_headers( array &$options ) { if ( empty( $options['headers'] ) ) { $options['headers'] = []; } diff --git a/includes/Classifai/Watson/Classifier.php b/includes/Classifai/Providers/Watson/Classifier.php similarity index 82% rename from includes/Classifai/Watson/Classifier.php rename to includes/Classifai/Providers/Watson/Classifier.php index 22291d608..c4f825f39 100644 --- a/includes/Classifai/Watson/Classifier.php +++ b/includes/Classifai/Providers/Watson/Classifier.php @@ -1,6 +1,6 @@ endpoint ) ) { - $base_url = trailingslashit( \Classifai\get_watson_api_url() ) . 'v1/analyze'; + $base_url = trailingslashit( get_api_url() ) . 'v1/analyze'; $this->endpoint = esc_url( add_query_arg( [ 'version' => WATSON_NLU_VERSION ], $base_url ) ); } return $this->endpoint; @@ -49,10 +49,9 @@ public function get_endpoint() { * @param string $text The plain text to classify * @param array $options NLU classification options * @param array $request_options Extra options to pass to the underlying HTTP request - * * @return array|WP_Error */ - public function classify( $text, $options = [], $request_options = [] ) { + public function classify( string $text, array $options = [], array $request_options = [] ) { $body = $this->get_body( $text, $options ); $request_options['body'] = $body; @@ -66,6 +65,7 @@ public function classify( $text, $options = [], $request_options = [] ) { $classified_data = $request->post( $this->get_endpoint(), $request_options ); set_transient( 'classifai_watson_nlu_latest_response', $classified_data, DAY_IN_SECONDS * 30 ); + /** * Filter the classified data returned from the API call. * @@ -82,12 +82,11 @@ public function classify( $text, $options = [], $request_options = [] ) { /* helpers */ /** - * Initializes or returns the api request object to use for making - * NLU api calls. + * Initializes or returns the API request object. * - * @return \Classifai\Watson\APIRequest + * @return APIRequest */ - public function get_request() { + public function get_request(): APIRequest { if ( empty( $this->request ) ) { $this->request = new APIRequest(); } @@ -100,9 +99,9 @@ public function get_request() { * * @param string $text The plain text to classify * @param array $options The NLU classification options - * @return array + * @return string|bool */ - public function get_body( $text, $options = [] ) { + public function get_body( string $text, array $options = [] ) { $options['text'] = $text; if ( empty( $options['language'] ) ) { diff --git a/includes/Classifai/Providers/Watson/Helpers.php b/includes/Classifai/Providers/Watson/Helpers.php new file mode 100644 index 000000000..a97263a7a --- /dev/null +++ b/includes/Classifai/Providers/Watson/Helpers.php @@ -0,0 +1,285 @@ +get_settings(); + $creds = ! empty( $settings[ NLU::ID ] ) ? $settings[ NLU::ID ] : []; + + if ( ! empty( $creds['endpoint_url'] ) ) { + return $creds['endpoint_url']; + } elseif ( defined( 'WATSON_URL' ) ) { + return WATSON_URL; + } else { + return ''; + } +} + +/** + * Returns the currently configured Watson username. Lookup order is, + * + * - Options + * - Constant + * + * @return string + */ +function get_username(): string { + $settings = ( new Classification() )->get_settings( NLU::ID ); + $username = ! empty( $settings['username'] ) ? $settings['username'] : ''; + + if ( ! empty( $username ) ) { + return $username; + } elseif ( defined( 'WATSON_USERNAME' ) ) { + return WATSON_USERNAME; + } else { + return ''; + } +} + +/** + * Returns the currently configured Watson username. Lookup order is, + * + * - Options + * - Constant + * + * @return string + */ +function get_password(): string { + $settings = ( new Classification() )->get_settings( NLU::ID ); + $password = ! empty( $settings['password'] ) ? $settings['password'] : ''; + + if ( ! empty( $password ) ) { + return $password; + } elseif ( defined( 'WATSON_PASSWORD' ) ) { + return WATSON_PASSWORD; + } else { + return ''; + } +} + +/** + * Get Content Classification method. + * + * @since 2.6.0 + * + * @return string + */ +function get_classification_method(): string { + $settings = ( new Classification() )->get_settings( NLU::ID ); + + return $settings['classification_method'] ?? ''; +} + +/** + * Get Classification mode. + * + * @since 2.5.0 + * + * @return string + */ +function get_classification_mode(): string { + $feature = new Classification(); + $settings = $feature->get_settings( NLU::ID ); + $value = $settings['classification_mode'] ?? ''; + + if ( $feature->is_feature_enabled() ) { + if ( empty( $value ) ) { + // existing users + // default: automatic_classification + return 'automatic_classification'; + } + } else { + // new users + // default: manual_review + return 'manual_review'; + } + + return $value; +} + +/** + * The list of post types that support classification. + * + * Defaults to 'post'. + * + * @return array + */ +function get_supported_post_types(): array { + $feature = new Classification(); + $settings = $feature->get_settings(); + $post_types = []; + + foreach ( $settings['post_types'] as $post_type => $enabled ) { + if ( ! empty( $enabled ) ) { + $post_types[] = $post_type; + } + } + + /** + * Filter post types supported for classification. + * + * @since 1.0.0 + * @hook classifai_post_types + * + * @param {array} $post_types Array of post types to be classified. + * + * @return {array} Array of post types. + */ + $post_types = apply_filters( 'classifai_post_types', $post_types ); + + return $post_types; +} + +/** + * The list of post statuses that support classification. + * + * Defaults to 'publish'. + * + * @return array + */ +function get_supported_post_statuses(): array { + $feature = new Classification(); + $settings = $feature->get_settings(); + $post_statuses = []; + + foreach ( $settings['post_statuses'] as $post_status => $enabled ) { + if ( ! empty( $enabled ) ) { + $post_statuses[] = $post_status; + } + } + + /** + * Filter post statuses supported for classification. + * + * @since 1.7.1 + * @hook classifai_post_statuses + * + * @param {array} $post_types Array of post statuses to be classified. + * + * @return {array} Array of post statuses. + */ + $post_statuses = apply_filters( 'classifai_post_statuses', $post_statuses ); + + return $post_statuses; +} + +/** + * Returns a bool based on whether the specified feature is enabled + * + * @param string $classify_by category,keyword,entity,concept + * @return bool + */ +function get_feature_enabled( string $classify_by ): bool { + $feature = new Classification(); + $settings = $feature->get_settings( NLU::ID ); + + return filter_var( + $settings[ $classify_by ], + FILTER_VALIDATE_BOOLEAN + ); +} + +/** + * Returns the feature threshold based on current configuration. Lookup + * order is. + * + * - Option + * - Constant + * + * Any results below the threshold will be ignored. + * + * @param string $feature The feature whose threshold to lookup + * @return float + */ +function get_feature_threshold( string $feature ): float { + $settings = get_plugin_settings( 'language_processing', 'Natural Language Understanding' ); + $threshold = 0; + + if ( ! empty( $settings ) && ! empty( $settings['features'] ) ) { + if ( ! empty( $settings['features'][ $feature . '_threshold' ] ) ) { + $threshold = filter_var( + $settings['features'][ $feature . '_threshold' ], + FILTER_VALIDATE_INT + ); + } + } + + if ( empty( $threshold ) ) { + $constant = 'WATSON_' . strtoupper( $feature ) . '_THRESHOLD'; + + if ( defined( $constant ) ) { + $threshold = intval( constant( $constant ) ); + } + } + + $threshold = empty( $threshold ) ? 0.7 : $threshold / 100; + + /** + * Filter the threshold for a specific feature. Any results below the + * threshold will be ignored. + * + * @since 1.0.0 + * @hook classifai_feature_threshold + * + * @param {float} $threshold The threshold to use, expressed as a decimal between 0 and 1 inclusive. + * @param {string} $feature The feature in question. + * + * @return {float} The filtered threshold. + */ + return apply_filters( 'classifai_feature_threshold', $threshold, $feature ); +} + +/** + * Returns the Taxonomy for the specified NLU feature. + * + * Returns defaults in config.php if options have not been configured. + * + * @param string $classify_by NLU feature name + * @return string Taxonomy mapped to the feature + */ +function get_feature_taxonomy( string $classify_by = '' ): string { + $taxonomy = 0; + + $feature = new Classification(); + $settings = $feature->get_settings( NLU::ID ); + + if ( ! empty( $settings[ $classify_by . '_taxonomy' ] ) ) { + $taxonomy = $settings[ $classify_by . '_taxonomy' ]; + } + + if ( empty( $taxonomy ) ) { + $constant = 'WATSON_' . strtoupper( $classify_by ) . '_TAXONOMY'; + + if ( defined( $constant ) ) { + $taxonomy = constant( $constant ); + } + } + + /** + * Filter the Taxonomy for the specified NLU feature. + * + * @since 1.1.0 + * @hook classifai_taxonomy_for_feature + * + * @param {string} $taxonomy The slug of the taxonomy to use. + * @param {string} $classify_by The NLU feature this taxonomy is for. + * + * @return {string} The filtered taxonomy slug. + */ + return apply_filters( 'classifai_taxonomy_for_feature', $taxonomy, $classify_by ); +} diff --git a/includes/Classifai/Watson/Linker.php b/includes/Classifai/Providers/Watson/Linker.php similarity index 92% rename from includes/Classifai/Watson/Linker.php rename to includes/Classifai/Providers/Watson/Linker.php index d37b1c1e5..533361eda 100644 --- a/includes/Classifai/Watson/Linker.php +++ b/includes/Classifai/Providers/Watson/Linker.php @@ -1,8 +1,6 @@ = $threshold; } else { return false; @@ -375,12 +369,13 @@ public function can_link_category( $category ) { * Checks whether an NLU keyword can be linked based on its relevance. * * @param array $keyword The keyword to check. + * @return bool */ - public function can_link_keyword( $keyword ) { + public function can_link_keyword( $keyword ): bool { if ( ! empty( $keyword['text'] ) ) { if ( ! empty( $keyword['relevance'] ) ) { $relevance = floatval( $keyword['relevance'] ); - $threshold = \Classifai\get_feature_threshold( 'keyword' ); + $threshold = get_feature_threshold( 'keyword' ); return $relevance >= $threshold; } } else { @@ -392,12 +387,13 @@ public function can_link_keyword( $keyword ) { * Checks whether an NLU concept can be linked based on its relevance. * * @param array $concept The concept to check. + * @return bool */ - public function can_link_concept( $concept ) { + public function can_link_concept( $concept ): bool { if ( ! empty( $concept['text'] ) ) { if ( ! empty( $concept['relevance'] ) ) { $relevance = floatval( $concept['relevance'] ); - $threshold = \Classifai\get_feature_threshold( 'concept' ); + $threshold = get_feature_threshold( 'concept' ); return $relevance >= $threshold; } } else { @@ -409,12 +405,13 @@ public function can_link_concept( $concept ) { * Checks whether an NLU entity can be linked based in its relevance. * * @param array $entity The entity to check. + * @return bool */ - public function can_link_entity( $entity ) { + public function can_link_entity( $entity ): bool { if ( ! empty( $entity['text'] ) ) { if ( ! empty( $entity['relevance'] ) ) { $relevance = floatval( $entity['relevance'] ); - $threshold = \Classifai\get_feature_threshold( 'entity' ); + $threshold = get_feature_threshold( 'entity' ); return $relevance >= $threshold; } } else { diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 9f2f4b5d8..1aa784611 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -5,12 +5,11 @@ namespace Classifai\Providers\Watson; -use Classifai\Admin\SavePostHandler; -use Classifai\Admin\PreviewClassifierData; use Classifai\Providers\Provider; use Classifai\Taxonomy\TaxonomyFactory; use Classifai\Features\Classification; use Classifai\Features\Feature; +use Classifai\Providers\Watson\PostClassifier; use WP_Error; use WP_REST_Request; use WP_REST_Server; @@ -174,7 +173,7 @@ function ( $args = [] ) { ); add_settings_field( - static::ID . 'classification_mode', + static::ID . '_classification_mode', esc_html__( 'Classification mode', 'classifai' ), [ $this->feature_instance, 'render_radio_group' ], $this->feature_instance->get_option_name(), @@ -192,7 +191,7 @@ function ( $args = [] ) { ); add_settings_field( - static::ID . 'classification_method', + static::ID . '_classification_method', esc_html__( 'Classification method', 'classifai' ), [ $this->feature_instance, 'render_radio_group' ], $this->feature_instance->get_option_name(), @@ -225,6 +224,82 @@ function ( $args = [] ) { ] ); } + + add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] ); + } + + /** + * Renders the previewer window for the feature. + */ + public function render_previewer() { + if ( ! ( new Classification() )->is_feature_enabled() ) { + return; + } + ?> + +
    + $supported_post_types, + 'post_status' => $supported_post_statuses, + 'posts_per_page' => 10, + ) + ); + + $features = array( + 'category' => array( + 'name' => esc_html__( 'Category', 'classifai' ), + 'enabled' => get_feature_enabled( 'category' ), + 'plural' => 'categories', + ), + 'keyword' => array( + 'name' => esc_html__( 'Keyword', 'classifai' ), + 'enabled' => get_feature_enabled( 'keyword' ), + 'plural' => 'keywords', + ), + 'entity' => array( + 'name' => esc_html__( 'Entity', 'classifai' ), + 'enabled' => get_feature_enabled( 'entity' ), + 'plural' => 'entities', + ), + 'concept' => array( + 'name' => esc_html__( 'Concept', 'classifai' ), + 'enabled' => get_feature_enabled( 'concept' ), + 'plural' => 'concepts', + ), + ); + ?> + +

    +
    + + + +
    +
    + $feature ) : + ?> +
    +
    +
    + +
    +
    + + ( new Classification() )->is_feature_enabled(), - 'supportedPostTypes' => \Classifai\get_supported_post_types(), - 'supportedPostStatues' => \Classifai\get_supported_post_statuses(), + 'supportedPostTypes' => get_supported_post_types(), + 'supportedPostStatues' => get_supported_post_statuses(), 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ), ] ); @@ -516,7 +591,7 @@ protected function nlu_authentication_check( array $settings ) { return new WP_Error( 'auth', esc_html__( 'Please enter your credentials.', 'classifai' ) ); } - $request = new \Classifai\Watson\APIRequest(); + $request = new APIRequest(); $request->username = $settings[ static::ID ]['username']; $request->password = $settings[ static::ID ]['password']; $base_url = trailingslashit( $settings[ static::ID ]['endpoint_url'] ) . 'v1/analyze'; @@ -634,8 +709,8 @@ protected function get_formatted_latest_response( $data ): string { * @param \WP_Post $post WP_Post object. */ public function add_classifai_meta_box( string $post_type, \WP_Post $post ) { - $supported_post_types = \Classifai\get_supported_post_types(); - $post_statuses = \Classifai\get_supported_post_statuses(); + $supported_post_types = get_supported_post_types(); + $post_statuses = get_supported_post_statuses(); $post_status = get_post_status( $post ); if ( in_array( $post_type, $supported_post_types, true ) && in_array( $post_status, $post_statuses, true ) ) { add_meta_box( @@ -701,7 +776,7 @@ public function classifai_save_post_metadata( int $post_id ) { return; } - $supported_post_types = \Classifai\get_supported_post_types(); + $supported_post_types = get_supported_post_types(); if ( ! in_array( get_post_type( $post_id ), $supported_post_types, true ) ) { return; } @@ -719,7 +794,7 @@ public function classifai_save_post_metadata( int $post_id ) { * Add `classifai_process_content` to rest API for view/edit. */ public function add_process_content_meta_to_rest_api() { - $supported_post_types = \Classifai\get_supported_post_types(); + $supported_post_types = get_supported_post_types(); register_rest_field( $supported_post_types, 'classifai_process_content', @@ -763,6 +838,19 @@ public function is_configured(): bool { * Register REST endpoints. */ public function register_endpoints() { + $post_types = get_supported_post_types(); + foreach ( $post_types as $post_type ) { + register_meta( + $post_type, + '_classifai_error', + [ + 'show_in_rest' => true, + 'single' => true, + 'auth_callback' => '__return_true', + ] + ); + } + register_rest_route( 'classifai/v1', 'generate-tags/(?P\d+)', @@ -828,8 +916,8 @@ public function get_all_feature_taxonomies() { // Get all feature taxonomies. $feature_taxonomies = []; foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) { - if ( \Classifai\get_feature_enabled( $feature ) ) { - $taxonomy = \Classifai\get_feature_taxonomy( $feature ); + if ( get_feature_enabled( $feature ) ) { + $taxonomy = get_feature_taxonomy( $feature ); $permission = check_term_permissions( $taxonomy ); if ( is_wp_error( $permission ) ) { @@ -900,7 +988,7 @@ public function classify_post( int $post_id ) { } foreach ( $features as $feature ) { - $taxonomy = \Classifai\get_feature_taxonomy( $feature ); + $taxonomy = get_feature_taxonomy( $feature ); $terms = wp_get_object_terms( $post_id, $taxonomy ); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { @@ -940,11 +1028,11 @@ public function generate_post_tags_permissions_check( WP_REST_Request $request ) // For all enabled features, ensure the user has proper permissions to add/edit terms. foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) { - if ( ! \Classifai\get_feature_enabled( $feature ) ) { + if ( ! get_feature_enabled( $feature ) ) { continue; } - $taxonomy = \Classifai\get_feature_taxonomy( $feature ); + $taxonomy = get_feature_taxonomy( $feature ); $permission = check_term_permissions( $taxonomy ); if ( is_wp_error( $permission ) ) { @@ -953,8 +1041,8 @@ public function generate_post_tags_permissions_check( WP_REST_Request $request ) } $post_status = get_post_status( $post_id ); - $supported = \Classifai\get_supported_post_types(); - $post_statuses = \Classifai\get_supported_post_statuses(); + $supported = get_supported_post_types(); + $post_statuses = get_supported_post_statuses(); // Check if processing allowed. if ( ! in_array( $post_status, $post_statuses, true ) || ! in_array( $post_type, $supported, true ) || ! ( new Classification() )->is_feature_enabled() ) { @@ -992,23 +1080,23 @@ public function classify( int $post_id, bool $link_terms = true ) { return false; } - $classifier = new \Classifai\PostClassifier(); + $classifier = new PostClassifier(); if ( $link_terms ) { - if ( \Classifai\get_feature_enabled( 'category' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'category' ) ); + if ( get_feature_enabled( 'category' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'category' ) ); } - if ( \Classifai\get_feature_enabled( 'keyword' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'keyword' ) ); + if ( get_feature_enabled( 'keyword' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'keyword' ) ); } - if ( \Classifai\get_feature_enabled( 'concept' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'concept' ) ); + if ( get_feature_enabled( 'concept' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'concept' ) ); } - if ( \Classifai\get_feature_enabled( 'entity' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'entity' ) ); + if ( get_feature_enabled( 'entity' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'entity' ) ); } } diff --git a/includes/Classifai/PostClassifier.php b/includes/Classifai/Providers/Watson/PostClassifier.php similarity index 73% rename from includes/Classifai/PostClassifier.php rename to includes/Classifai/Providers/Watson/PostClassifier.php index f5c14b3e1..eb001e3d2 100644 --- a/includes/Classifai/PostClassifier.php +++ b/includes/Classifai/Providers/Watson/PostClassifier.php @@ -1,43 +1,42 @@ get_classifier(); $normalizer = $this->get_normalizer(); @@ -74,9 +73,9 @@ public function classify( $post_id, $opts = [] ) { * @param int $post_id The post to classify * @param array $opts The classification options * @param bool $link_terms Whether to link the terms or not. - * @return object|boolean + * @return object|bool */ - public function classify_and_link( $post_id, $opts = [], $link_terms = true ) { + public function classify_and_link( int $post_id, array $opts = [], bool $link_terms = true ) { $output = $this->classify( $post_id, $opts ); if ( is_wp_error( $output ) ) { @@ -97,10 +96,9 @@ public function classify_and_link( $post_id, $opts = [], $link_terms = true ) { * @param array $output The classification results from Watson NLU. * @param array $opts Link options. * @param bool $link_terms Whether to link the terms or not. - * * @return array The linked output. */ - public function link( $post_id, $output, $opts = [], $link_terms = true ) { + public function link( int $post_id, array $output, array $opts = [], bool $link_terms = true ): array { $linker = $this->get_linker(); return $linker->link( $post_id, $output, $opts, $link_terms ); @@ -109,44 +107,52 @@ public function link( $post_id, $output, $opts = [], $link_terms = true ) { /* helpers */ /** - * Lazy init api request object + * Lazy init api request object. + * + * @return APIRequest */ - public function get_api_request() { + public function get_api_request(): APIRequest { if ( is_null( $this->api_request ) ) { - $this->api_request = new Watson\APIRequest(); + $this->api_request = new APIRequest(); } return $this->api_request; } /** - * Lazy init normalizer object + * Lazy init normalizer object. + * + * @return Normalizer */ - public function get_normalizer() { + public function get_normalizer(): Normalizer { if ( is_null( $this->normalizer ) ) { - $this->normalizer = new Watson\Normalizer(); + $this->normalizer = new Normalizer(); } return $this->normalizer; } /** - * Lazy init linker object + * Lazy init linker object. + * + * @return Linker */ - public function get_linker() { + public function get_linker(): Linker { if ( is_null( $this->linker ) ) { - $this->linker = new Watson\Linker(); + $this->linker = new Linker(); } return $this->linker; } /** - * Lazy init classifier object + * Lazy init classifier object. + * + * @return Classifier */ - public function get_classifier() { + public function get_classifier(): Classifier { if ( is_null( $this->classifier ) ) { - $this->classifier = new Watson\Classifier(); + $this->classifier = new Classifier(); $this->classifier->request = $this->get_api_request(); } @@ -159,7 +165,7 @@ public function get_classifier() { * * @return array */ - public function get_features() { + public function get_features(): array { $classification = new Classification(); $settings = $classification->get_settings( NLU::ID ); $features = []; diff --git a/includes/Classifai/Admin/PreviewClassifierData.php b/includes/Classifai/Providers/Watson/PreviewClassifierData.php similarity index 93% rename from includes/Classifai/Admin/PreviewClassifierData.php rename to includes/Classifai/Providers/Watson/PreviewClassifierData.php index ad2689200..fbbf7e4c0 100644 --- a/includes/Classifai/Admin/PreviewClassifierData.php +++ b/includes/Classifai/Providers/Watson/PreviewClassifierData.php @@ -1,9 +1,9 @@ normalize( $post_id ); $body = $classifier->get_body( $text_to_classify ); @@ -75,7 +75,7 @@ public function filter_classify_preview_data( array $classified_data ): array { return $classified_data; } - $classify_existing_terms = 'existing_terms' === \Classifai\get_classification_method(); + $classify_existing_terms = 'existing_terms' === get_classification_method(); if ( ! $classify_existing_terms ) { return $classified_data; } @@ -87,7 +87,7 @@ public function filter_classify_preview_data( array $classified_data ): array { 'keyword' => 'keywords', ]; foreach ( $features as $key => $feature ) { - $taxonomy = \Classifai\get_feature_taxonomy( $key ); + $taxonomy = get_feature_taxonomy( $key ); if ( ! $taxonomy ) { continue; } diff --git a/includes/Classifai/Admin/SavePostHandler.php b/includes/Classifai/Providers/Watson/SavePostHandler.php similarity index 86% rename from includes/Classifai/Admin/SavePostHandler.php rename to includes/Classifai/Providers/Watson/SavePostHandler.php index 67b9faf84..23c00a254 100644 --- a/includes/Classifai/Admin/SavePostHandler.php +++ b/includes/Classifai/Providers/Watson/SavePostHandler.php @@ -1,16 +1,16 @@ get_classifier(); if ( $link_terms ) { - if ( \Classifai\get_feature_enabled( 'category' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'category' ) ); + if ( get_feature_enabled( 'category' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'category' ) ); } - if ( \Classifai\get_feature_enabled( 'keyword' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'keyword' ) ); + if ( get_feature_enabled( 'keyword' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'keyword' ) ); } - if ( \Classifai\get_feature_enabled( 'concept' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'concept' ) ); + if ( get_feature_enabled( 'concept' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'concept' ) ); } - if ( \Classifai\get_feature_enabled( 'entity' ) ) { - wp_delete_object_term_relationships( $post_id, \Classifai\get_feature_taxonomy( 'entity' ) ); + if ( get_feature_enabled( 'entity' ) ) { + wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'entity' ) ); } } @@ -196,11 +196,13 @@ public function classify( int $post_id, bool $link_terms = true ): array { } /** - * Lazy initializes the Post Classifier object + * Lazy initializes the Post Classifier object. + * + * @return PostClassifier */ - public function get_classifier() { + public function get_classifier(): PostClassifier { if ( is_null( $this->classifier ) ) { - $this->classifier = new \Classifai\PostClassifier(); + $this->classifier = new PostClassifier(); } return $this->classifier; @@ -234,7 +236,7 @@ public function show_error_if() { ?>

    - Error: Failed to classify content with the IBM Watson NLU API. +

    @@ -322,8 +324,8 @@ public function classifai_classify_post() { /** * Add "classifai_classify" in list of query variable names to remove. * - * @param string[] $removable_query_args An array of query variable names to remove from a URL. - * @return string[] + * @param [] $removable_query_args An array of query variable names to remove from a URL. + * @return [] */ public function classifai_removable_query_args( array $removable_query_args ): array { $removable_query_args[] = 'classifai_classify'; diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 3f2536069..654e58b38 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -191,89 +191,6 @@ public function render_settings_page() { */ do_action( 'classifai_after_feature_settings_form', $active_feature ); ?> - - provider_classes ?? [], 'Natural Language Understanding' ); - if ( 'openai_embeddings' === $active_tab ) { - $provider = find_provider_class( $this->provider_classes ?? [], 'Embeddings' ); - } - - if ( - ! is_wp_error( $provider ) - && ! empty( - $provider->can_register() - && ( $provider->is_feature_enabled( 'content_classification' ) || $provider->is_feature_enabled( 'classification' ) ) - ) - ) : - ?> -

    - $supported_post_types, - 'post_status' => $supported_post_statuses, - 'posts_per_page' => 10, - ) - ); - - $features = array( - 'category' => array( - 'name' => esc_html__( 'Category', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'category' ), - 'plural' => 'categories', - ), - 'keyword' => array( - 'name' => esc_html__( 'Keyword', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'keyword' ), - 'plural' => 'keywords', - ), - 'entity' => array( - 'name' => esc_html__( 'Entity', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'entity' ), - 'plural' => 'entities', - ), - 'concept' => array( - 'name' => esc_html__( 'Concept', 'classifai' ), - 'enabled' => \Classifai\get_feature_enabled( 'concept' ), - 'plural' => 'concepts', - ), - ); - ?> - - -

    -
    - - - -
    -
    - $feature ) : - ?> -
    -
    -
    - -
    - -
    -
    @@ -309,26 +226,4 @@ public function get_service_debug_information(): array { return array_map( $make_line, $this->feature_classes ); } - - /** - * Check if the current user has permission to create and assign terms. - * - * @param string $tax Taxonomy name. - * @return bool|WP_Error - */ - public function check_term_permissions( string $tax = '' ) { - $taxonomy = get_taxonomy( $tax ); - - if ( empty( $taxonomy ) || empty( $taxonomy->show_in_rest ) ) { - return new WP_Error( 'invalid_taxonomy', esc_html__( 'Taxonomy not found. Double check your settings.', 'classifai' ) ); - } - - $create_cap = is_taxonomy_hierarchical( $taxonomy->name ) ? $taxonomy->cap->edit_terms : $taxonomy->cap->assign_terms; - - if ( ! current_user_can( $create_cap ) || ! current_user_can( $taxonomy->cap->assign_terms ) ) { - return new WP_Error( 'rest_cannot_assign_term', esc_html__( 'Sorry, you are not alllowed to create or assign to this taxonomy.', 'classifai' ) ); - } - - return true; - } } diff --git a/includes/Classifai/Taxonomy/CategoryTaxonomy.php b/includes/Classifai/Taxonomy/CategoryTaxonomy.php index 0da0853d3..e805ad882 100644 --- a/includes/Classifai/Taxonomy/CategoryTaxonomy.php +++ b/includes/Classifai/Taxonomy/CategoryTaxonomy.php @@ -2,6 +2,9 @@ namespace Classifai\Taxonomy; +use function Classifai\Providers\Watson\get_feature_enabled; +use function Classifai\Providers\Watson\get_feature_taxonomy; + /** * The Classifai Category Taxonomy. * @@ -49,7 +52,7 @@ public function get_plural_label(): string { * @return bool */ public function get_visibility(): bool { - return \Classifai\get_feature_enabled( 'category' ) && - \Classifai\get_feature_taxonomy( 'category' ) === $this->get_name(); + return get_feature_enabled( 'category' ) && + get_feature_taxonomy( 'category' ) === $this->get_name(); } } diff --git a/includes/Classifai/Taxonomy/ConceptTaxonomy.php b/includes/Classifai/Taxonomy/ConceptTaxonomy.php index fc75a6ab2..fe6d4003a 100644 --- a/includes/Classifai/Taxonomy/ConceptTaxonomy.php +++ b/includes/Classifai/Taxonomy/ConceptTaxonomy.php @@ -2,6 +2,9 @@ namespace Classifai\Taxonomy; +use function Classifai\Providers\Watson\get_feature_enabled; +use function Classifai\Providers\Watson\get_feature_taxonomy; + /** * The Classifai Concept Taxonomy. * @@ -49,7 +52,7 @@ public function get_plural_label(): string { * @return bool */ public function get_visibility(): bool { - return \Classifai\get_feature_enabled( 'concept' ) && - \Classifai\get_feature_taxonomy( 'concept' ) === $this->get_name(); + return get_feature_enabled( 'concept' ) && + get_feature_taxonomy( 'concept' ) === $this->get_name(); } } diff --git a/includes/Classifai/Taxonomy/EntityTaxonomy.php b/includes/Classifai/Taxonomy/EntityTaxonomy.php index 75726dd5a..526924fd1 100644 --- a/includes/Classifai/Taxonomy/EntityTaxonomy.php +++ b/includes/Classifai/Taxonomy/EntityTaxonomy.php @@ -2,6 +2,9 @@ namespace Classifai\Taxonomy; +use function Classifai\Providers\Watson\get_feature_enabled; +use function Classifai\Providers\Watson\get_feature_taxonomy; + /** * The ClassifAI Entity Taxonomy. * @@ -49,7 +52,7 @@ public function get_plural_label(): string { * @return bool */ public function get_visibility(): bool { - return \Classifai\get_feature_enabled( 'entity' ) && - \Classifai\get_feature_taxonomy( 'entity' ) === $this->get_name(); + return get_feature_enabled( 'entity' ) && + get_feature_taxonomy( 'entity' ) === $this->get_name(); } } diff --git a/includes/Classifai/Taxonomy/KeywordTaxonomy.php b/includes/Classifai/Taxonomy/KeywordTaxonomy.php index 6f510769e..29cc272b6 100644 --- a/includes/Classifai/Taxonomy/KeywordTaxonomy.php +++ b/includes/Classifai/Taxonomy/KeywordTaxonomy.php @@ -2,6 +2,9 @@ namespace Classifai\Taxonomy; +use function Classifai\Providers\Watson\get_feature_enabled; +use function Classifai\Providers\Watson\get_feature_taxonomy; + /** * The ClassifAI Keyword Taxonomy. * @@ -49,7 +52,7 @@ public function get_plural_label(): string { * @return bool */ public function get_visibility(): bool { - return \Classifai\get_feature_enabled( 'keyword' ) && - \Classifai\get_feature_taxonomy( 'keyword' ) === $this->get_name(); + return get_feature_enabled( 'keyword' ) && + get_feature_taxonomy( 'keyword' ) === $this->get_name(); } } diff --git a/includes/Classifai/Taxonomy/TaxonomyFactory.php b/includes/Classifai/Taxonomy/TaxonomyFactory.php index 35cad5f9b..344ca2fda 100644 --- a/includes/Classifai/Taxonomy/TaxonomyFactory.php +++ b/includes/Classifai/Taxonomy/TaxonomyFactory.php @@ -2,6 +2,8 @@ namespace Classifai\Taxonomy; +use function Classifai\Providers\Watson\get_supported_post_types; + /** * TaxonomyFactory builds the Taxonomy taxonomy class instances. Instances * are stored locally and returned from cache on subsequent build calls. @@ -45,7 +47,7 @@ class TaxonomyFactory { * frontend and backend to get these taxonomies. */ public function build_all() { - $supported_post_types = \Classifai\get_supported_post_types(); + $supported_post_types = get_supported_post_types(); foreach ( $this->get_supported_taxonomies() as $taxonomy ) { $this->build_if( $taxonomy, $supported_post_types ); diff --git a/tests/Classifai/Admin/SavePostHandlerTest.php b/tests/Classifai/Admin/SavePostHandlerTest.php deleted file mode 100644 index 80a451aff..000000000 --- a/tests/Classifai/Admin/SavePostHandlerTest.php +++ /dev/null @@ -1,117 +0,0 @@ - [ - 'watson_url' => 'url', - 'watson_username' => 'username', - 'watson_password' => 'password', - ], - ]; - - /** - * setup method - */ - function set_up() { - parent::set_up(); - - $this->save_post_handler = new SavePostHandler(); - } - - function add_options() { - update_option( 'classifai_configured', true ); - update_option( 'classifai_watson_nlu', $this->settings ); - } - - function test_is_rest_route() { - global $wp_filter; - - $saved_filters = $wp_filter['classifai_rest_bases'] ?? null; - unset( $wp_filter['classifai_rest_bases'] ); - - $this->assertEquals( false, $this->save_post_handler->is_rest_route() ); - - $_SERVER['REQUEST_URI'] = '/wp-json/wp/v2/users/me'; - $this->assertEquals( false, $this->save_post_handler->is_rest_route() ); - - $_SERVER['REQUEST_URI'] = '/wp-json/wp/v2/posts/1'; - $this->assertEquals( true, $this->save_post_handler->is_rest_route() ); - - $_SERVER['REQUEST_URI'] = '/wp-json/wp/v2/pages/1'; - $this->assertEquals( true, $this->save_post_handler->is_rest_route() ); - - $_SERVER['REQUEST_URI'] = '/wp-json/wp/v2/custom/1'; - $this->assertEquals( false, $this->save_post_handler->is_rest_route() ); - - if ( ! is_null( $saved_filters ) ) { - $wp_filter['classifai_rest_bases'] = $saved_filters; - } - - add_filter( - 'classifai_rest_bases', - function( $bases ) { - $bases[] = 'custom'; - return $bases; - } - ); - $this->assertEquals( true, $this->save_post_handler->is_rest_route() ); - } - - function test_rest_route_register() { - - $_SERVER['REQUEST_URI'] = '/wp-json/wp/v2/posts/1'; - - $this->assertEquals( false, $this->save_post_handler->can_register() ); - - $this->add_options(); - - $this->assertEquals( true, $this->save_post_handler->can_register() ); - } - - function test_is_admin_register() { - - set_current_screen( 'edit.php' ); - - $this->assertEquals( false, $this->save_post_handler->can_register() ); - - $this->add_options(); - - $this->assertEquals( true, $this->save_post_handler->can_register() ); - } - - function test_custom_register() { - - define( 'DOING_CRON', true ); - - $this->assertEquals( false, $this->save_post_handler->can_register() ); - - add_filter( - 'classifai_should_register_save_post_handler', - function( $should_register ) { - if ( defined( 'DOING_CRON' ) && DOING_CRON ) { - return true; - } - return $should_register; - } - ); - - $this->assertEquals( true, $this->save_post_handler->can_register() ); - } -} diff --git a/tests/Classifai/HelpersTest.php b/tests/Classifai/HelpersTest.php index 67172b34d..8b063cb9a 100644 --- a/tests/Classifai/HelpersTest.php +++ b/tests/Classifai/HelpersTest.php @@ -2,6 +2,12 @@ namespace Classifai; +use function Classifai\Providers\Watson\get_username; +use function Classifai\Providers\Watson\get_password; +use function Classifai\Providers\Watson\get_supported_post_types; +use function Classifai\Providers\Watson\get_feature_threshold; +use function Classifai\Providers\Watson\get_feature_taxonomy; + /** * @group helpers */ @@ -98,7 +104,7 @@ function test_it_knows_configured_username() { ] ] ); - $actual = get_watson_username(); + $actual = get_username(); $this->assertEquals( 'foo', $actual ); } @@ -110,7 +116,7 @@ function test_it_knows_configured_password() { ] ] ); - $actual = get_watson_password(); + $actual = get_password(); $this->assertEquals( 'foo', $actual ); } diff --git a/tests/Classifai/PostClassifierTest.php b/tests/Classifai/PostClassifierTest.php index 9e1f420d6..a54205e26 100644 --- a/tests/Classifai/PostClassifierTest.php +++ b/tests/Classifai/PostClassifierTest.php @@ -1,6 +1,6 @@ classifier->get_api_request(); - $this->assertInstanceOf( 'Classifai\Watson\APIRequest', $actual ); + $this->assertInstanceOf( 'Classifai\Providers\Watson\APIRequest', $actual ); } function test_it_has_a_normalizer() { $actual = $this->classifier->get_normalizer(); - $this->assertInstanceOf( 'Classifai\Watson\Normalizer', $actual ); + $this->assertInstanceOf( 'Classifai\Normalizer', $actual ); } function test_it_has_a_linker() { $actual = $this->classifier->get_linker(); - $this->assertInstanceOf( 'Classifai\Watson\Linker', $actual ); + $this->assertInstanceOf( 'Classifai\Providers\Watson\Linker', $actual ); } function test_it_has_a_classifier() { $actual = $this->classifier->get_classifier(); - $this->assertInstanceOf( 'Classifai\Watson\Classifier', $actual ); + $this->assertInstanceOf( 'Classifai\Providers\Watson\Classifier', $actual ); } function test_it_can_link_post() { diff --git a/tests/Classifai/Watson/APIRequestTest.php b/tests/Classifai/Watson/APIRequestTest.php index 44521c2d3..b5314eeb2 100644 --- a/tests/Classifai/Watson/APIRequestTest.php +++ b/tests/Classifai/Watson/APIRequestTest.php @@ -1,6 +1,6 @@ classifier->get_request(); $this->assertInstanceOf( - '\Classifai\Watson\APIRequest', + '\Classifai\Providers\Watson\APIRequest', $actual ); } diff --git a/tests/Classifai/Watson/LinkerTest.php b/tests/Classifai/Watson/LinkerTest.php index 4c3a168eb..aa4c27357 100644 --- a/tests/Classifai/Watson/LinkerTest.php +++ b/tests/Classifai/Watson/LinkerTest.php @@ -1,6 +1,6 @@ Date: Mon, 29 Jan 2024 17:13:12 -0700 Subject: [PATCH 117/127] Fix some bugs --- includes/Classifai/Features/Classification.php | 8 ++++++-- includes/Classifai/Features/ContentResizing.php | 2 +- includes/Classifai/Features/ExcerptGeneration.php | 2 +- includes/Classifai/Features/Feature.php | 2 -- includes/Classifai/Features/TitleGeneration.php | 2 +- includes/Classifai/Helpers.php | 2 +- includes/Classifai/Providers/Azure/ComputerVision.php | 10 +++++++--- includes/Classifai/Providers/OpenAI/ChatGPT.php | 2 -- includes/Classifai/Providers/Watson/NLU.php | 9 ++++++--- tests/Classifai/Azure/ComputerVisionTest.php | 2 +- tests/Classifai/Providers/Azure/ComputerVisionTest.php | 7 +------ tests/Classifai/Providers/Azure/SmartCroppingTest.php | 6 +++--- 12 files changed, 28 insertions(+), 26 deletions(-) diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index f8d7f330b..f26d161e8 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -95,8 +95,12 @@ public function add_custom_settings_fields() { */ public function get_feature_default_settings(): array { return [ - 'post_statuses' => [], - 'post_types' => [], + 'post_statuses' => [ + 'publish' => 1, + ], + 'post_types' => [ + 'post' => 1, + ], 'provider' => NLU::ID, ]; } diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 1aea08a94..213895375 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -166,7 +166,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { public function enqueue_editor_assets() { global $post; - if ( empty( $post ) ) { + if ( empty( $post ) || ! is_admin() ) { return; } diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index badacb04d..1fdad662e 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -173,7 +173,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { public function enqueue_editor_assets() { global $post; - if ( empty( $post ) ) { + if ( empty( $post ) || ! is_admin() ) { return; } diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 334cb6320..96fe2d2d7 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -61,8 +61,6 @@ public function setup() { /** * Setup any hooks the feature needs. - * - * TODO: make this abstract once implemented in all features. */ public function feature_setup() { } diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 52e5246f8..157a86046 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -177,7 +177,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { public function enqueue_editor_assets() { global $post; - if ( empty( $post ) ) { + if ( empty( $post ) || ! is_admin() ) { return; } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index be26d1fba..174cdc4be 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -597,7 +597,7 @@ function get_default_prompt( array $prompts ): ?string { $prompt_data = array_filter( $prompts, function ( $prompt ) { - return $prompt['default'] && ! $prompt['original']; + return isset( $prompt['default'] ) && ! $prompt['original']; } ); diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 7af816a87..9c21fe7b9 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -313,7 +313,6 @@ public static function get_read_status( array $status = [], $attachment_id = nul * @param int $attachment_id Attachment ID. */ public function do_read_cron( string $operation_url, int $attachment_id ) { - error_log( 'do_read_cron' ); $feature = new PDFTextExtraction(); $settings = $feature->get_settings( static::ID ); @@ -779,10 +778,15 @@ public function filter_attachment_query_keywords( array $clauses ): array { * @return array */ public function get_debug_information(): array { - $settings = $this->feature_instance->get_settings(); - $provider_settings = $settings[ static::ID ]; + $settings = []; + $provider_settings = []; $debug_info = []; + if ( $this->feature_instance ) { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + } + if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { $descriptive_text = array_filter( $provider_settings['descriptive_text_fields'], diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index be7235615..27b84305d 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -555,8 +555,6 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use /** * Returns the debug information for the provider settings. * - * TODO: this should be in feature. Or at least any settings moved to the feature should be - * * @return array */ public function get_debug_information(): array { diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 1aa784611..7de38173e 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -79,7 +79,6 @@ public function __construct( $feature = null ) { ], ]; - // TODO: if no feature is passed in, seems like this might break $this->feature_instance = $feature; add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); @@ -230,9 +229,13 @@ function ( $args = [] ) { /** * Renders the previewer window for the feature. + * + * @param string $active_feature The active feature. */ - public function render_previewer() { - if ( ! ( new Classification() )->is_feature_enabled() ) { + public function render_previewer( string $active_feature ) { + $feature = new Classification(); + + if ( $feature::ID !== $active_feature || ! $feature->is_feature_enabled() ) { return; } ?> diff --git a/tests/Classifai/Azure/ComputerVisionTest.php b/tests/Classifai/Azure/ComputerVisionTest.php index 74e6cc354..08402f795 100644 --- a/tests/Classifai/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Azure/ComputerVisionTest.php @@ -23,7 +23,7 @@ class ComputerVisionTest extends WP_UnitTestCase { function set_up() { parent::set_up(); - $this->provider = new ComputerVision( 'service_name' ); + $this->provider = new ComputerVision( new \Classifai\Features\DescriptiveTextGenerator() ); } /** diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index 3b592f11a..c7f7394a8 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -37,11 +37,6 @@ public function get_computer_vision() : ComputerVision { * @covers ::smart_crop_image */ public function test_smart_crop_image() { - $this->assertEquals( - 'non-array-data', - $this->get_computer_vision()->smart_crop_image( 'non-array-data', 999999 ) - ); - $this->assertEquals( [ 'no-smart-cropping' => 1 ], $this->get_computer_vision()->smart_crop_image( @@ -163,7 +158,7 @@ public function test_alt_text_option_reformatting() { return $options; } ); - $image_captions_settings = $this->get_computer_vision()->get_alt_text_settings(); + $image_captions_settings = ( new \Classifai\Features\DescriptiveTextGenerator() )->get_alt_text_settings(); $this->assertSame( $image_captions_settings, array( diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index 0406dc57a..e8375e8b1 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -76,7 +76,7 @@ public function with_http_request_filter( callable $callback ) { public function test_get_wp_filesystem() { $this->assertInstanceOf( WP_Filesystem_Direct::class, - $this->get_smart_cropping()->get_wp_filesystem() + ( new \Classifai\Features\ImageCropping() )->get_wp_filesystem() ); } @@ -110,14 +110,14 @@ public function test_generate_attachment_metadata() { // Test that nothing happens when the metadata contains no sizes entry. $this->assertEquals( [ 'no-sizes' => 1 ], - $this->get_smart_cropping()->generate_attachment_metadata( + $this->get_smart_cropping()->generate_cropped_images( [ 'no-sizes' => 1 ], $attachment ) ); $with_filter_cb = function() use ( $attachment ) { - $filtered_data = $this->get_smart_cropping()->generate_attachment_metadata( + $filtered_data = $this->get_smart_cropping()->generate_cropped_images( wp_get_attachment_metadata( $attachment ), $attachment ); From bf923763c97c36ec4677a9457119434f89aeb69f Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 29 Jan 2024 21:17:52 -0700 Subject: [PATCH 118/127] Fix some more bugs --- includes/Classifai/Features/Feature.php | 2 +- .../Providers/Azure/ComputerVision.php | 10 +++- .../Classifai/Providers/OpenAI/ChatGPT.php | 14 ++--- includes/Classifai/Providers/Watson/NLU.php | 4 +- includes/Classifai/Services/Service.php | 3 - .../Providers/Azure/ComputerVisionTest.php | 59 +++++++++---------- .../Providers/Azure/SmartCroppingTest.php | 2 +- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 96fe2d2d7..b969ac2c6 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -390,7 +390,7 @@ public function add_provider_fields() { } /** - * Merges the data settings with the default settings recursively, + * Merges the data settings with the default settings recursively. * * @internal * diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index 9c21fe7b9..1006f0540 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -788,6 +788,14 @@ public function get_debug_information(): array { } if ( $this->feature_instance instanceof DescriptiveTextGenerator ) { + if ( ! isset( $provider_settings['descriptive_text_fields'] ) || ! is_array( $provider_settings['descriptive_text_fields'] ) ) { + $provider_settings['descriptive_text_fields'] = array( + 'alt' => 0, + 'caption' => 0, + 'description' => 0, + ); + } + $descriptive_text = array_filter( $provider_settings['descriptive_text_fields'], function ( $type ) { @@ -801,7 +809,7 @@ function ( $type ) { } if ( $this->feature_instance instanceof ImageTagsGenerator ) { - $debug_info[ __( 'Tag taxonomy', 'classifai' ) ] = $provider_settings['tag_taxonomy']; + $debug_info[ __( 'Tag taxonomy', 'classifai' ) ] = $provider_settings['tag_taxonomy'] ?? 'image_tags'; $debug_info[ __( 'Confidence threshold', 'classifai' ) ] = $provider_settings['tag_confidence_threshold']; $debug_info[ __( 'Latest response:', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_azure_computer_vision_image_tags_latest_response' ) ); } diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 27b84305d..c1d82bf87 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -563,17 +563,17 @@ public function get_debug_information(): array { $debug_info = []; if ( $this->feature_instance instanceof TitleGeneration ) { - $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles']; - $debug_info[ __( 'Generate title prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_title_prompt'] ); + $debug_info[ __( 'No. of titles', 'classifai' ) ] = $provider_settings['number_of_titles'] ?? 1; + $debug_info[ __( 'Generate title prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_title_prompt'] ?? [] ); $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_title_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ExcerptGeneration ) { - $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length']; - $debug_info[ __( 'Generate excerpt prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_excerpt_prompt'] ); + $debug_info[ __( 'Excerpt length', 'classifai' ) ] = $settings['length'] ?? 55; + $debug_info[ __( 'Generate excerpt prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['generate_excerpt_prompt'] ?? [] ); $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_excerpt_generation_latest_response' ) ); } elseif ( $this->feature_instance instanceof ContentResizing ) { - $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions']; - $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ); - $debug_info[ __( 'Condense text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['condense_text_prompt'] ); + $debug_info[ __( 'No. of suggestions', 'classifai' ) ] = $provider_settings['number_of_suggestions'] ?? 1; + $debug_info[ __( 'Expand text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['expand_text_prompt'] ?? [] ); + $debug_info[ __( 'Condense text prompt', 'classifai' ) ] = wp_json_encode( $provider_settings['condense_text_prompt'] ?? [] ); $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_chatgpt_content_resizing_latest_response' ) ); } diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index 7de38173e..b4cd090ca 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -316,8 +316,8 @@ public function get_default_provider_settings(): array { 'apikey' => '', 'username' => '', 'password' => '', - 'classification_mode' => 'automatic_classification', - 'classification_method' => 'existing_terms', + 'classification_mode' => 'manual_review', + 'classification_method' => 'recommended_terms', ]; switch ( $this->feature_instance::ID ) { diff --git a/includes/Classifai/Services/Service.php b/includes/Classifai/Services/Service.php index 654e58b38..5edf4d043 100644 --- a/includes/Classifai/Services/Service.php +++ b/includes/Classifai/Services/Service.php @@ -5,9 +5,6 @@ namespace Classifai\Services; -use WP_Error; -use function Classifai\find_provider_class; - abstract class Service { /** diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index c7f7394a8..7c4e22f85 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -38,7 +38,7 @@ public function get_computer_vision() : ComputerVision { */ public function test_smart_crop_image() { $this->assertEquals( - [ 'no-smart-cropping' => 1 ], + [], $this->get_computer_vision()->smart_crop_image( [ 'no-smart-cropping' => 1 ], 999999 @@ -86,24 +86,27 @@ public function test_no_computer_vision_option_set() { $expected = array_merge( $defaults, [ - 'valid' => false, - 'url' => '', - 'api_key' => '', - 'enable_image_captions' => array( - 'alt' => 0, - 'caption' => 0, + 'status' => 0, + 'role_based_access' => '1', + 'roles' => [], + 'user_based_access' => 'no', + 'users' => [], + 'user_based_opt_out' => 'no', + 'descriptive_text_fields' => [ + 'alt' => 0, + 'caption' => 0, 'description' => 0, - ), - 'enable_image_tagging' => true, - 'enable_smart_cropping' => false, - 'enable_ocr' => false, - 'enable_read_pdf' => false, - 'caption_threshold' => 75, - 'tag_threshold' => 70, - 'image_tag_taxonomy' => 'classifai-image-tags', + ], + 'provider' => 'ms_computer_vision', + 'ms_computer_vision' => [ + 'endpoint_url' => '', + 'api_key' => '', + 'descriptive_confidence_threshold' => '75', + 'authenticated' => false, + ], ] ); - $settings = $this->get_computer_vision()->get_settings(); + $settings = ( new \Classifai\Features\DescriptiveTextGenerator() )->get_settings(); $this->assertSame( $expected, $settings ); } @@ -143,14 +146,14 @@ public function test_alt_text_option_reformatting() { 'ms_computer_vision' => array( 'endpoint_url' => '', 'api_key' => '', - 'descriptive_text_fields' => array( - 'alt' => 'alt', - 'caption' => '0', - 'description' => '0', - ), 'descriptive_confidence_threshold' => '75', 'authenticated' => true, ), + 'descriptive_text_fields' => array( + 'alt' => 'alt', + 'caption' => '0', + 'description' => '0', + ), ); // Test with `descriptive_text_fields` set to `alt`. @@ -161,11 +164,7 @@ public function test_alt_text_option_reformatting() { $image_captions_settings = ( new \Classifai\Features\DescriptiveTextGenerator() )->get_alt_text_settings(); $this->assertSame( $image_captions_settings, - array( - 'alt' => 'alt', - 'caption' => 0, - 'description' => 0, - ) + array( 'alt' ) ); // Test with `enable_image_captions` set to `no`. @@ -174,14 +173,10 @@ public function test_alt_text_option_reformatting() { return $options; } ); - $image_captions_settings = $this->get_computer_vision()->get_alt_text_settings(); + $image_captions_settings = ( new \Classifai\Features\DescriptiveTextGenerator() )->get_alt_text_settings(); $this->assertSame( $image_captions_settings, - array( - 'alt' => 0, - 'caption' => 0, - 'description' => 0, - ) + array() ); } } diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index e8375e8b1..d57d45c02 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -109,7 +109,7 @@ public function test_generate_attachment_metadata() { // Test that nothing happens when the metadata contains no sizes entry. $this->assertEquals( - [ 'no-sizes' => 1 ], + [], $this->get_smart_cropping()->generate_cropped_images( [ 'no-sizes' => 1 ], $attachment From b42700fbe96c41adfcb1b0edbc10b93c1ecf54b1 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Mon, 29 Jan 2024 22:16:07 -0700 Subject: [PATCH 119/127] Fix tests --- tests/Classifai/Azure/ComputerVisionTest.php | 20 ++++------- .../Providers/Azure/ComputerVisionTest.php | 17 ++++------ .../Providers/Azure/SmartCroppingTest.php | 29 ++++++---------- tests/Classifai/Watson/NLUSettingsTest.php | 34 +++++++++++++------ 4 files changed, 49 insertions(+), 51 deletions(-) diff --git a/tests/Classifai/Azure/ComputerVisionTest.php b/tests/Classifai/Azure/ComputerVisionTest.php index 08402f795..867bf5cef 100644 --- a/tests/Classifai/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Azure/ComputerVisionTest.php @@ -32,26 +32,20 @@ function set_up() { public function test_get_debug_information() { $this->assertEquals( [ - 'Authenticated', - 'API URL', - 'Caption threshold', - 'Latest response - Image Scan', - 'Latest response - Smart Cropping', - 'Latest response - OCR', + 'Generate descriptive text', + 'Confidence threshold', + 'Latest response:', ], array_keys( $this->provider->get_debug_information() ) ); $this->assertEquals( [ - 'Authenticated' => 'yes', - 'API URL' => 'my-azure-url.com', - 'Caption threshold' => 77, - 'Latest response - Image Scan' => 'N/A', - 'Latest response - Smart Cropping' => 'N/A', - 'Latest response - OCR' => 'N/A', + 'Generate descriptive text' => '0, 0, 0', + 'Confidence threshold' => 75, + 'Latest response:' => 'N/A', ], - $this->provider->test_get_debug_information( + $this->provider->get_debug_information( [ 'url' => 'my-azure-url.com', 'caption_threshold' => 77, diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index 7c4e22f85..8becb724e 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -52,19 +52,16 @@ public function test_smart_crop_image() { }; add_filter( 'filesystem_method', $filter_file_system_method ); - $this->assertEquals( + $this->assertWPError( $this->get_computer_vision()->smart_crop_image( [ 'not-direct-file-system-method' => 1 ], - $this->get_computer_vision()->smart_crop_image( - [ 'not-direct-file-system-method' => 1 ], - 999999 - ) - ); + 999999 + ) ); remove_filter( 'filesystem_method', $filter_file_system_method ); // Test that SmartCropping is initiated and runs, as will be indicated in the coverage report, though it won't // actually do anything because the data and attachment are invalid. $this->assertEquals( - [ 'my-data' => 1 ], + [], $this->get_computer_vision()->smart_crop_image( [ 'my-data' => 1 ], 999999 @@ -86,7 +83,7 @@ public function test_no_computer_vision_option_set() { $expected = array_merge( $defaults, [ - 'status' => 0, + 'status' => '0', 'role_based_access' => '1', 'roles' => [], 'user_based_access' => 'no', @@ -101,8 +98,8 @@ public function test_no_computer_vision_option_set() { 'ms_computer_vision' => [ 'endpoint_url' => '', 'api_key' => '', - 'descriptive_confidence_threshold' => '75', 'authenticated' => false, + 'descriptive_confidence_threshold' => 75, ], ] ); @@ -168,7 +165,7 @@ public function test_alt_text_option_reformatting() { ); // Test with `enable_image_captions` set to `no`. - $options['ms_computer_vision']['descriptive_text_fields']['alt'] = '0'; + $options['descriptive_text_fields']['alt'] = '0'; add_filter( 'pre_option_classifai_feature_descriptive_text_generator', function() use( $options ) { return $options; } ); diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index d57d45c02..12e781a30 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -123,8 +123,8 @@ public function test_generate_attachment_metadata() { ); $this->assertEquals( - '33772-150x150.jpg', - $filtered_data['sizes']['thumbnail']['file'] + 150, + $filtered_data['thumbnail']['width'] ); }; @@ -158,26 +158,19 @@ public function test_get_cropped_thumbnail() { // Get the uploaded image url $cropped_thumbnail_url = $this->get_smart_cropping()->get_cropped_thumbnail( $attachment, - wp_get_attachment_metadata( $attachment )['sizes']['thumbnail'] + wp_get_attachment_metadata( $attachment )['sizes']['thumbnail'], ); + // Strip out everything before /wp-content/ because it won't match. $prepped_url = substr( $cropped_thumbnail_url, strpos( $cropped_thumbnail_url , '/wp-content/' ) ); - - $this->assertEquals( - sprintf( '%s/33772-150x150.jpg', wp_upload_dir()['path'] ), - $cropped_thumbnail_url - ); - - // Test when file operations fail. - add_filter( 'classifai_smart_crop_wp_filesystem', '__return_false' ); - $this->assertWPError( - $this->get_smart_cropping()->get_cropped_thumbnail( - $attachment, - wp_get_attachment_metadata( $attachment )['sizes']['thumbnail'] - ) - ); - remove_filter( 'classifai_smart_crop_wp_filesystem', '__return_false' ); + // TODO: fix this + if ( false ) { + $this->assertEquals( + sprintf( '%s/33772-150x150.jpg', wp_upload_dir()['path'] ), + $cropped_thumbnail_url + ); + } }; $this->with_http_request_filter( $with_filter_cb ); diff --git a/tests/Classifai/Watson/NLUSettingsTest.php b/tests/Classifai/Watson/NLUSettingsTest.php index 8439dd1d4..850e57288 100644 --- a/tests/Classifai/Watson/NLUSettingsTest.php +++ b/tests/Classifai/Watson/NLUSettingsTest.php @@ -58,11 +58,18 @@ public function test_retrieving_options() { public function test_get_debug_information() { $this->assertEquals( [ - 'Configured', - 'API URL', - 'API username', - 'Post types', - 'Features', + 'Category (status)', + 'Category (threshold)', + 'Category (taxonomy)', + 'Keyword (status)', + 'Keyword (threshold)', + 'Keyword (taxonomy)', + 'Entity (status)', + 'Entity (threshold)', + 'Entity (taxonomy)', + 'Concept (status)', + 'Concept (threshold)', + 'Concept (taxonomy)', 'Latest response', ], array_keys( $this->provider->get_debug_information() ) @@ -70,11 +77,18 @@ public function test_get_debug_information() { $this->assertEquals( [ - 'Configured' => 'yes', - 'API URL' => 'my-watson-url.com', - 'API username' => 'my-watson-username', - 'Post types' => 'post, attachment, event', - 'Features' => '{"feature":true}', + 'Category (status)' => 'Enabled', + 'Category (threshold)' => 'Enabled', + 'Category (taxonomy)' => 'Enabled', + 'Keyword (status)' => 'Enabled', + 'Keyword (threshold)' => 'Enabled', + 'Keyword (taxonomy)' => 'Enabled', + 'Entity (status)' => 'Disabled', + 'Entity (threshold)' => 'Enabled', + 'Entity (taxonomy)' => 'Enabled', + 'Concept (status)' => 'Disabled', + 'Concept (threshold)' => 'Enabled', + 'Concept (taxonomy)' => 'Enabled', 'Latest response' => 'N/A', ], $this->provider->get_debug_information( From f8a6365aec8741678f35a7cdf88820a1550df2d7 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 30 Jan 2024 08:29:02 -0700 Subject: [PATCH 120/127] Fix last failing unit test --- includes/Classifai/Features/ImageCropping.php | 4 +-- .../Providers/Azure/SmartCroppingTest.php | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index e90e0e028..777e4d0cd 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -273,10 +273,8 @@ public function attachment_data_meta_box( \WP_Post $post ) { /** * Display meta data. - * - * @param \WP_Post $post The post object. */ - public function attachment_data_meta_box_content( \WP_Post $post ) { + public function attachment_data_meta_box_content() { $smart_crop = get_transient( 'classifai_azure_computer_vision_image_cropping_latest_response' ) ? __( 'Regenerate smart thumbnail', 'classifai' ) : __( 'Create smart thumbnail', 'classifai' ); ?> diff --git a/tests/Classifai/Providers/Azure/SmartCroppingTest.php b/tests/Classifai/Providers/Azure/SmartCroppingTest.php index 12e781a30..c9ae44a9a 100644 --- a/tests/Classifai/Providers/Azure/SmartCroppingTest.php +++ b/tests/Classifai/Providers/Azure/SmartCroppingTest.php @@ -155,22 +155,29 @@ public function test_get_cropped_thumbnail() { $with_filter_cb = function() use ( $attachment ) { - // Get the uploaded image url - $cropped_thumbnail_url = $this->get_smart_cropping()->get_cropped_thumbnail( + // Get the uploaded image data. + $cropped_thumbnail_data = $this->get_smart_cropping()->get_cropped_thumbnail( $attachment, wp_get_attachment_metadata( $attachment )['sizes']['thumbnail'], ); - // Strip out everything before /wp-content/ because it won't match. - $prepped_url = substr( $cropped_thumbnail_url, strpos( $cropped_thumbnail_url , '/wp-content/' ) ); + $cropped_images['thumbnail'] = [ + 'width' => 150, + 'height' => 150, + 'data' => $cropped_thumbnail_data, + ]; - // TODO: fix this - if ( false ) { - $this->assertEquals( - sprintf( '%s/33772-150x150.jpg', wp_upload_dir()['path'] ), - $cropped_thumbnail_url - ); - } + $meta = ( new \Classifai\Features\ImageCropping() )->save( $cropped_images, $attachment ); + + $this->assertEquals( + file_get_contents( DIR_TESTDATA .'/images/33772.jpg' ), + $cropped_thumbnail_data + ); + + $this->assertEquals( + '33772-150x150.jpg', + $meta['sizes']['thumbnail']['file'] + ); }; $this->with_http_request_filter( $with_filter_cb ); From ebbbcc5369d29a336ae8565acd4dd868727873e6 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 30 Jan 2024 16:08:57 -0700 Subject: [PATCH 121/127] Fix failing E2E tests. Some failures were legit bugs that have been fixed; some were failures due to DOM changes --- includes/Classifai/Features/Feature.php | 10 +- .../Classifai/Features/TitleGeneration.php | 2 +- includes/Classifai/Helpers.php | 2 +- includes/Classifai/Plugin.php | 12 - .../Providers/Azure/ComputerVision.php | 4 + .../Classifai/Providers/Watson/Helpers.php | 21 +- includes/Classifai/Providers/Watson/NLU.php | 2 +- package-lock.json | 441 +++++++++--------- package.json | 13 +- src/js/admin.js | 4 +- src/scss/admin.scss | 4 + .../admin/common-feature-fields.test.js | 81 +++- .../image-generation-openai-dalle.test.js | 46 +- .../image-processing-microsoft-azure.test.js | 202 ++++---- .../image-processing/pdf-read.test.js | 36 +- .../classify-content-ibm-watson.test.js | 161 +++---- ...lassify-content-openapi-embeddings.test.js | 56 ++- ...excerpt-generation-openapi-chatgpt.test.js | 58 ++- .../resize_content-openapi-chatgpt.test.js | 74 ++- .../speech-to-text-openapi-whisper.test.js | 32 +- .../text-to-speech-microsoft-azure.test.js | 44 +- .../title-generation-openapi-chatgpt.test.js | 48 +- tests/cypress/support/commands.js | 72 ++- 23 files changed, 731 insertions(+), 694 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index b969ac2c6..6678adc6a 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -852,7 +852,8 @@ public function render_auto_caption_fields( array $args ) { * @param array $args The args passed to add_settings_field */ public function render_radio_group( array $args = array() ) { - $setting_index = $this->get_settings(); + $option_index = isset( $args['option_index'] ) ? $args['option_index'] : false; + $setting_index = $this->get_settings( $option_index ); $value = $setting_index[ $args['label_for'] ] ?? ''; $options = $args['options'] ?? []; @@ -865,12 +866,13 @@ public function render_radio_group( array $args = array() ) { // Render radio button. printf( '

    -

    ', esc_attr( $this->get_option_name() ), + $option_index ? '[' . esc_attr( $option_index ) . ']' : '', esc_attr( $args['label_for'] ), esc_attr( $option_value ), checked( $value, $option_value, false ), diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index 157a86046..cbe0a7a51 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -384,7 +384,7 @@ public function sanitize_default_feature_settings( array $new_settings ): array $settings = $this->get_settings(); $new_settings['number_of_titles'] = sanitize_number_of_responses_field( 'number_of_titles', $new_settings, $settings ); - $new_settings['generate_title_prompt'] = sanitize_prompts( 'generate_excerpt_prompt', $new_settings ); + $new_settings['generate_title_prompt'] = sanitize_prompts( 'generate_title_prompt', $new_settings ); return $new_settings; } diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php index 174cdc4be..e91ae6bb1 100644 --- a/includes/Classifai/Helpers.php +++ b/includes/Classifai/Helpers.php @@ -597,7 +597,7 @@ function get_default_prompt( array $prompts ): ?string { $prompt_data = array_filter( $prompts, function ( $prompt ) { - return isset( $prompt['default'] ) && ! $prompt['original']; + return isset( $prompt['default'] ) && $prompt['default'] && ! $prompt['original']; } ); diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index df0200fee..faed7295a 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -39,15 +39,6 @@ public function enable() { add_action( 'admin_init', [ $this, 'add_privacy_policy_content' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_filter( 'plugin_action_links_' . CLASSIFAI_PLUGIN_BASENAME, array( $this, 'filter_plugin_action_links' ) ); - add_action( - 'admin_footer', - static function () { - printf( - '', - esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), - ); - } - ); } /** @@ -173,9 +164,6 @@ public function enqueue_admin_assets() { 'all' ); - wp_enqueue_script( 'jquery-ui-dialog' ); - wp_enqueue_style( 'wp-jquery-ui-dialog' ); - wp_enqueue_script( 'classifai-admin-script', CLASSIFAI_PLUGIN_URL . 'dist/admin.js', diff --git a/includes/Classifai/Providers/Azure/ComputerVision.php b/includes/Classifai/Providers/Azure/ComputerVision.php index e23776d47..8ae0c33ef 100644 --- a/includes/Classifai/Providers/Azure/ComputerVision.php +++ b/includes/Classifai/Providers/Azure/ComputerVision.php @@ -723,6 +723,10 @@ public function rest_endpoint_callback( $attachment_id, string $route_to_call = $metadata = wp_get_attachment_metadata( $attachment_id ); + if ( ! $metadata ) { + return new WP_Error( 'invalid', esc_html__( 'No valid metadata found.', 'classifai' ) ); + } + switch ( $route_to_call ) { case 'ocr': return $this->ocr_processing( $metadata, $attachment_id ); diff --git a/includes/Classifai/Providers/Watson/Helpers.php b/includes/Classifai/Providers/Watson/Helpers.php index a97263a7a..86942787a 100644 --- a/includes/Classifai/Providers/Watson/Helpers.php +++ b/includes/Classifai/Providers/Watson/Helpers.php @@ -207,16 +207,15 @@ function get_feature_enabled( string $classify_by ): bool { * @return float */ function get_feature_threshold( string $feature ): float { - $settings = get_plugin_settings( 'language_processing', 'Natural Language Understanding' ); - $threshold = 0; - - if ( ! empty( $settings ) && ! empty( $settings['features'] ) ) { - if ( ! empty( $settings['features'][ $feature . '_threshold' ] ) ) { - $threshold = filter_var( - $settings['features'][ $feature . '_threshold' ], - FILTER_VALIDATE_INT - ); - } + $classification_feature = new Classification(); + $settings = $classification_feature->get_settings( NLU::ID ); + $threshold = 0; + + if ( ! empty( $settings ) && ! empty( $settings[ $feature . '_threshold' ] ) ) { + $threshold = filter_var( + $settings[ $feature . '_threshold' ], + FILTER_VALIDATE_INT + ); } if ( empty( $threshold ) ) { @@ -237,7 +236,7 @@ function get_feature_threshold( string $feature ): float { * @hook classifai_feature_threshold * * @param {float} $threshold The threshold to use, expressed as a decimal between 0 and 1 inclusive. - * @param {string} $feature The feature in question. + * @param {string} $feature The feature in question. * * @return {float} The filtered threshold. */ diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php index b4cd090ca..8a87ad5a4 100644 --- a/includes/Classifai/Providers/Watson/NLU.php +++ b/includes/Classifai/Providers/Watson/NLU.php @@ -118,7 +118,7 @@ public function render_provider_fields() { 'default_value' => $settings['username'], 'input_type' => 'text', 'large' => true, - 'class' => 'classifai-provider-field ' . ( $this->use_username_password() ? 'hidden' : '' ) . ' provider-scope-' . static::ID, // Important to add this. + 'class' => 'classifai-provider-field ' . ( $this->use_username_password() ? 'hide-username' : '' ) . ' provider-scope-' . static::ID, // Important to add this. ] ); diff --git a/package-lock.json b/package-lock.json index 0f5a32279..7a14297f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,24 +9,25 @@ "version": "2.5.1", "license": "GPL-2.0-or-later", "dependencies": { - "@wordpress/icons": "^9.40.0", + "@wordpress/icons": "^9.41.0", "choices.js": "^10.2.0", "tippy.js": "^6.3.7" }, "devDependencies": { "@10up/cypress-wp-utils": "^0.2.0", - "@wordpress/env": "^9.1.0", - "@wordpress/scripts": "^27.0.0", - "cypress": "^13.6.3", + "@wordpress/env": "^9.2.0", + "@wordpress/scripts": "^27.1.0", + "cypress": "^13.6.4", "cypress-file-upload": "^5.0.8", - "cypress-mochawesome-reporter": "^3.8.0", + "cypress-mochawesome-reporter": "^3.8.1", "cypress-plugin-tab": "^1.0.5", "husky": "^8.0.3", "jsdoc": "^3.6.11", "lint-staged": "^15.2.0", + "mochawesome-json-to-md": "^0.7.2", "node-wp-i18n": "^1.2.7", "svg-react-loader": "^0.4.6", - "webpack": "^5.86.0", + "webpack": "^5.90.0", "webpack-cli": "^5.1.4", "wp-hookdoc": "^0.2.0" } @@ -186,9 +187,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.23.3.tgz", - "integrity": "sha512-9bTuNlyx7oSstodm1cR1bECj4fkiknsDa1YniISkJemMY3DGhJNYBECbe6QD/q54mp2J8VO66jW3/7uP//iFCw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.23.9.tgz", + "integrity": "sha512-xPndlO7qxiJbn0ATvfXQBjCS7qApc9xmKHArgI/FTEFxXas5dnjC/VqM37lfZun9dclRYcn+YQAr6uDFy0bB2g==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -1721,16 +1722,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", - "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", + "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.7", - "babel-plugin-polyfill-corejs3": "^0.8.7", - "babel-plugin-polyfill-regenerator": "^0.5.4", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", "semver": "^6.3.1" }, "engines": { @@ -1740,6 +1741,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2844,9 +2858,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -2860,21 +2874,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -3827,9 +3835,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { @@ -4242,16 +4250,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", - "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", + "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/type-utils": "6.19.1", - "@typescript-eslint/utils": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/type-utils": "6.20.0", + "@typescript-eslint/utils": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4310,15 +4318,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", - "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", + "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4" }, "engines": { @@ -4338,13 +4346,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", - "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", + "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1" + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4355,13 +4363,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", - "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", + "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.19.1", - "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/utils": "6.20.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -4382,9 +4390,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", - "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", + "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4395,13 +4403,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", - "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", + "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/visitor-keys": "6.20.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4480,17 +4488,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", - "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", + "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.19.1", - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/scope-manager": "6.20.0", + "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/typescript-estree": "6.20.0", "semver": "^7.5.4" }, "engines": { @@ -4538,12 +4546,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", - "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", + "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/types": "6.20.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4763,23 +4771,23 @@ } }, "node_modules/@wordpress/api-fetch": { - "version": "6.46.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.46.0.tgz", - "integrity": "sha512-SimHPw57N8LyZpQB6dK5xq1Kn1WtqP/K27GjGwvxvkb+8xbVv0TI67AF9adsN4sZbOHIZJQwqvCTSGKhNttAvQ==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.47.0.tgz", + "integrity": "sha512-NA/jWDXoVtJmiVBYhlxts2UrgKJpJM+zTGzLCfRQCZUzpJYm3LonB8x+uCQ78nEyxCY397Esod3jnbquYjOr0Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.49.0", - "@wordpress/url": "^3.50.0" + "@wordpress/i18n": "^4.50.0", + "@wordpress/url": "^3.51.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/babel-plugin-import-jsx-pragma": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.32.0.tgz", - "integrity": "sha512-ie6p5VpUxTNMPQrHdCYEPddTzmDeFTQjFi3qq17set9WbRAMaOZ8jqQhSxms0NJi8Xa6wZM9TR2ZABAlg+FTeA==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-plugin-import-jsx-pragma/-/babel-plugin-import-jsx-pragma-4.33.0.tgz", + "integrity": "sha512-CjzruFKWgzU/mO/nnQJ2l9UlzZQpqS60UC6l2vNdJ9oD2nKHR5Oou6kNic3QhWDVJrBf2JUiJJ0TC280bykXmA==", "dev": true, "engines": { "node": ">=14" @@ -4789,9 +4797,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.33.0.tgz", - "integrity": "sha512-/OonEa67xJdIn0ADWEd7AJtLhIGlYALKyc17RxTmI2Ojs0zLIQNbgAv1D/cuVguo0UKK9zsMZ9MBkhSKLF9A9Q==", + "version": "7.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-7.34.0.tgz", + "integrity": "sha512-yjFOllyTktFHtcIEgU3ghXBn8lItzr5mPLf0xdSpe0cHceFYL1hT1oprhgRL+olZweaO96Yfm0qUCCKQfJBWsA==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -4800,9 +4808,9 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/babel-plugin-import-jsx-pragma": "^4.32.0", - "@wordpress/browserslist-config": "^5.32.0", - "@wordpress/warning": "^2.49.0", + "@wordpress/babel-plugin-import-jsx-pragma": "^4.33.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/warning": "^2.50.0", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.2.0" @@ -4812,24 +4820,24 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.40.0.tgz", - "integrity": "sha512-A+HiyES4YjfbFhJAGrhCLB3QWomgWZR9wkgG7K9l6DD70/9Vd7t+go7jI1HJ1c9qGfBV0rmdQf/qNn89Aai1cg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-4.41.0.tgz", + "integrity": "sha512-MjPAZeAqvyskDXDp2wGZ0DjtYOQLOydI1WqVIZS4wnIdhsQWQD//VMeXgLrcmCzNyQg+iKTx3o+BzmXVTOD0+w==", "dev": true }, "node_modules/@wordpress/browserslist-config": { - "version": "5.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.32.0.tgz", - "integrity": "sha512-LrL4Zg/abXYfVwwbx1caugz4J1GUL+6WNqVF1MZQVDm6CHdlpTEQOvvr/KEi9mN1UY2YoTlxZtUxzvNRTo2Fsg==", + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-5.33.0.tgz", + "integrity": "sha512-dv1ZlpqGk8gaSBJPP/Z/1uOuxjtP0EBsHVKInLRu6FWLTJkK8rnCeC3xJT3/2TtJ0rasLC79RoytfhXTOODVwg==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.0.0.tgz", - "integrity": "sha512-b3j4yCB5dR04rIbZ73iHN5hMXL4kMUUoApY36Zs8AAREHpgCDTPp5vNqc67zg2bcnpDEhMUZ28DISwrY4z7weg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-5.1.0.tgz", + "integrity": "sha512-W2W+9JNAaGirAtGDSf83pjEKb63DLhgpJGgvMOpEPoRPtucgO6CCm3uMoNkJTpKoxJQ2tSZEymAhF/YdLm+ScQ==", "dev": true, "dependencies": { "json2php": "^0.0.7" @@ -4842,14 +4850,14 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.17.0.tgz", - "integrity": "sha512-WuyorK1PL4r0LtxdhwF8u31s/O7+reuU906dnM3pu6SKSPsyfhXi8O1hgQO4/VASooHygUbsn7PW0GaDdCamOA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.18.0.tgz", + "integrity": "sha512-Z8uH1dUzy/STQjOU6eb9nquVK4RC1rUx0gXY/GN1IVNDJvGN/yJxT/gNKmfiL7KpmHvNp2Q5M4bnUT9uiNcM+Q==", "dev": true, "dependencies": { - "@wordpress/api-fetch": "^6.46.0", - "@wordpress/keycodes": "^3.49.0", - "@wordpress/url": "^3.50.0", + "@wordpress/api-fetch": "^6.47.0", + "@wordpress/keycodes": "^3.50.0", + "@wordpress/url": "^3.51.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "get-port": "^5.1.1", @@ -4879,14 +4887,14 @@ } }, "node_modules/@wordpress/element": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.26.0.tgz", - "integrity": "sha512-pYZ2OsFgDN00amTxPoC7BtlkVtVBeLS/Y1+P1Mlu0CX+gHDP0Il9SUaLVEIAewLnZMN+O3ph3H5nfR0yKkSnAA==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.27.0.tgz", + "integrity": "sha512-IA5LTAfx5bDNXULPmctcNb/04i4JcnIReG0RAuPgrZ8lbMZWUxGFymh10PEQjs7ZJ++qGsI6E+6JISpjkRaDQQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@wordpress/escape-html": "^2.49.0", + "@wordpress/escape-html": "^2.50.0", "change-case": "^4.1.2", "is-plain-object": "^5.0.0", "react": "^18.2.0", @@ -4897,9 +4905,9 @@ } }, "node_modules/@wordpress/env": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.1.0.tgz", - "integrity": "sha512-IkPeYPczWmosqyulVHiu/fRQg5Q0PenCimbLieksif7ETFH8hUSwvsiWfvC/Sx//MzIB3/yGaVVodEzZnyJGgA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.2.0.tgz", + "integrity": "sha512-2gl65WYbkuTjnW2SHKjeqdpLTgnPc/xVvFiwG+2p/RJwDHSuw1xXSdFqFUh3+wC/4cuXy9b2ZBm/SYsBoc8DDw==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -4920,9 +4928,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.49.0.tgz", - "integrity": "sha512-JmVm6IWr5EhXU5m7LCwMOiSv90qJU1l8Q2xlBCQ+0bIPcWRjsHX9pFKDOJvQ6D55W/CTGO1GQk50uolktTeTtw==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.50.0.tgz", + "integrity": "sha512-hBvoMCEZocziZDGCmBanSO+uupnd054mxd7FQ6toQ4UnsZ4JwXSmEC72W2Ed+cRGB1DeJDD0dY9iC0b4xkumsQ==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -4931,16 +4939,16 @@ } }, "node_modules/@wordpress/eslint-plugin": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.6.0.tgz", - "integrity": "sha512-piANQS5eaSPmpzPXdNZdXbKcHjAyXbuHeUd9ctVA+6sOMVay70+ICQj7Isu4o61Wv43KtxugQoa2PSBqVtrRKA==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-17.7.0.tgz", + "integrity": "sha512-JSFaCogE0WlZpl0SV4q8DK8G6jwDjEzXRzOsgesWilea4OuVp1KxCamkddTorRNM3QAbjrGuPJ4NYaGrNG9QsA==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^7.33.0", - "@wordpress/prettier-config": "^3.6.0", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/prettier-config": "^3.7.0", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -5001,9 +5009,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.49.0.tgz", - "integrity": "sha512-GH546Jg8u/rw9I3fsvAhidwt8rUFNmkdXGByIPGsN3R6y+QwWMXPzsnoYdFmFOmDK9gOGCRDe5bXHikoWnaiKA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.50.0.tgz", + "integrity": "sha512-YIhwT1y0ss7Byfz46NBx08EUmXzWMu+g5DCY7FMuDNhwxSEoZMB8edKMiwNmFk4mFKBCnXM1d5FeONUPIUkJwg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0" @@ -5013,13 +5021,13 @@ } }, "node_modules/@wordpress/i18n": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.49.0.tgz", - "integrity": "sha512-8aZmmRfOHzS/3pMWg+4f6QlPci0wK5V+PDllAwtwFFrXgc0pmk8VXu7Quajh1tiVoIQDCZpK6h1sqa+qrCLpZg==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.50.0.tgz", + "integrity": "sha512-FkA2se6HMQm4eFC+/kTWvWQqs51VxpZuvY2MlWUp/L1r1d/dMBHXu049x86+/+6yk3ZNqiK5h6j6Z76dvPHZ4w==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.49.0", + "@wordpress/hooks": "^3.50.0", "gettext-parser": "^1.3.1", "memize": "^2.1.0", "sprintf-js": "^1.1.1", @@ -5049,22 +5057,22 @@ "dev": true }, "node_modules/@wordpress/icons": { - "version": "9.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.40.0.tgz", - "integrity": "sha512-NSbhur14Ypr+hbgp848430cmk2AHZ7E2e9zvj8917ZjhrVCD7zYT590hOspswJZEaFxJdY3QSnegGiBSI/MacQ==", + "version": "9.41.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.41.0.tgz", + "integrity": "sha512-L4fp9ZdxGBpMk3o2YqABgiPHNoHyu9Enid7JNkCdWP8iUgk7dEiDvo/XoiWPTAeNbF6W8Nqu54635mq01es0NQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.26.0", - "@wordpress/primitives": "^3.47.0" + "@wordpress/element": "^5.27.0", + "@wordpress/primitives": "^3.48.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/jest-console": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.20.0.tgz", - "integrity": "sha512-EXexYwBLaJSpSCUwpQeSqjJ9G7KDkzH+oCfiZp4ZYuemmCaJFOn8/HOLwfLU0o7i0bfYFAjt8lSVCr5HiYY0AA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-7.21.0.tgz", + "integrity": "sha512-o2vZRlwwJ6WoxRwnFFT5iZzfdc2d9MZvrtwB093RWPNcyK5qVtApji4VN/ieHijB4bjEHGalm0UKfKpt0EDlUQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -5078,12 +5086,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "11.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.20.0.tgz", - "integrity": "sha512-3x2ua/rc0540zfLOrHbfdrEOwS5xWPbX5/f2LUyM2T6zzmhXrnqG2WFdhftFFLAUhC8cbxuy1WNnrzgjUxGeDQ==", + "version": "11.21.0", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-11.21.0.tgz", + "integrity": "sha512-XAztKOROu02iBsz+Qosv/RYuPWB1XwwlU+FiA5Y68tRztrqFy4b/il+DFg4Jue/zXF7UECWUvosd5ow/GmKa6Q==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^7.20.0", + "@wordpress/jest-console": "^7.21.0", "babel-jest": "^29.6.2" }, "engines": { @@ -5095,22 +5103,22 @@ } }, "node_modules/@wordpress/keycodes": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.49.0.tgz", - "integrity": "sha512-Hg+kUTV/ti+CyG4+D3dmRFMmrE45E2QEv7ZKaeIf+t1wlafekLSDwIpdF7e68HxEMmZSzHmLm7bHqQTNjxAoKQ==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.50.0.tgz", + "integrity": "sha512-ykWpyCbgwcaT8i5kSfotYtd2oOHyMDpWEYR73InYrzEhl7pnS3wD7hi/KfeKLvMfYhbysUXlCVr6q/oH+qK/DQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.49.0" + "@wordpress/i18n": "^4.50.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "4.34.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.34.0.tgz", - "integrity": "sha512-mknDw+d5HIfx/1DyrhkbLJNu8XsmUEjc1SsYSgF2XCP20/khpO7YOi0LWn9uQ2QXWZrlhMc7JKSSOcTs0aLphQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-4.35.0.tgz", + "integrity": "sha512-QmkhYM4/s+2r3RuolVRRmoUa5o3lFgcHA6I3A9akaSVGZr//4p2p+iXOGmNub9njgGlj7j8SAPN8GUsCO/VqZQ==", "dev": true, "engines": { "node": ">=14" @@ -5120,12 +5128,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.33.0.tgz", - "integrity": "sha512-RqKNf8XQTdae0cXO11l6mBw+A3IOEO9dd4sD70g15e4IltrbwuxqwOT5k9muNteUszTCOQKgWgD8gp1KM2/lvQ==", + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-4.34.0.tgz", + "integrity": "sha512-OLQBSLE2q11Ik+WdcO2QfGr/O4X/zJYOGXNsychx/EaMamLzJInFcRL6kGbPX41zPINhadq5x2vFIZI2EC+Uyg==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^4.40.0", + "@wordpress/base-styles": "^4.41.0", "autoprefixer": "^10.2.5" }, "engines": { @@ -5136,9 +5144,9 @@ } }, "node_modules/@wordpress/prettier-config": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.6.0.tgz", - "integrity": "sha512-51GuCeeEGOi4qsMpzGFBmKbqEUKLqWj3eZDIwATymUaHsJPx9oT93dlIP97MqKIaWjxlhxCMt5RjxcCNT7Pckw==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-3.7.0.tgz", + "integrity": "sha512-JRTc5p7UxtcPkqdSDXSFJoJnVuS510uiRVz8anXEl5nuOx5p+SJAzi9QPrxTgOE8bN3wRABH4eIhfOcta4CFdg==", "dev": true, "engines": { "node": ">=14" @@ -5148,12 +5156,12 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.47.0.tgz", - "integrity": "sha512-ho4XrOI9PTGmQhgEYHuRBfgnPzPuq2zXJpQa2GCrbhm4fojLmZ7oWVBzrL2cGtFGD6dJhY3dbY+l+rNs97A2TA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.48.0.tgz", + "integrity": "sha512-uBoMxpl+FiZF6aRXH/+Hwol4EAL6QqlNSaGF1IzEwklFzdRF1m5wTM4vh21w8Bq7lgxiuAqyueY7X5u32v+zPw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^5.26.0", + "@wordpress/element": "^5.27.0", "classnames": "^2.3.1" }, "engines": { @@ -5161,24 +5169,24 @@ } }, "node_modules/@wordpress/scripts": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.0.0.tgz", - "integrity": "sha512-WXZPvgOaFCK1ZBov99lOOWE5Nl/eDMGTnx0sTsE1FcgAOVgKwaKvDCsRWYqYmf1O3aAhud0+YPIJyewbIHOQdQ==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-27.1.0.tgz", + "integrity": "sha512-jewyOxqaNrsct5R1NXv2lT8CA70vzrvpdZHYERCcH9LzKuvrcc32Telm9Jqso6ay1ZgHeIbjHSCd2+r2sBG7hw==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^7.33.0", - "@wordpress/browserslist-config": "^5.32.0", - "@wordpress/dependency-extraction-webpack-plugin": "^5.0.0", - "@wordpress/e2e-test-utils-playwright": "^0.17.0", - "@wordpress/eslint-plugin": "^17.6.0", - "@wordpress/jest-preset-default": "^11.20.0", - "@wordpress/npm-package-json-lint-config": "^4.34.0", - "@wordpress/postcss-plugins-preset": "^4.33.0", - "@wordpress/prettier-config": "^3.6.0", - "@wordpress/stylelint-config": "^21.32.0", + "@wordpress/babel-preset-default": "^7.34.0", + "@wordpress/browserslist-config": "^5.33.0", + "@wordpress/dependency-extraction-webpack-plugin": "^5.1.0", + "@wordpress/e2e-test-utils-playwright": "^0.18.0", + "@wordpress/eslint-plugin": "^17.7.0", + "@wordpress/jest-preset-default": "^11.21.0", + "@wordpress/npm-package-json-lint-config": "^4.35.0", + "@wordpress/postcss-plugins-preset": "^4.34.0", + "@wordpress/prettier-config": "^3.7.0", + "@wordpress/stylelint-config": "^21.33.0", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", @@ -5239,9 +5247,9 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "21.32.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.32.0.tgz", - "integrity": "sha512-cmrzU55alv+OZu1fXBC2eZGgJIUwyD47TSDDP7l0o9yF6D/w0am7FxC9ungk/S2uK1oatN05nIPsFSTkuHQSzg==", + "version": "21.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-21.33.0.tgz", + "integrity": "sha512-DwjXrjRBva0tkYILvDV7rjl3VaKXxvchlxnFfFs6l2DWL/Qo31CJ+f2rVw4XSWuuWxY1EsyIn9tOBS9URloWTQ==", "dev": true, "dependencies": { "stylelint-config-recommended": "^6.0.0", @@ -5255,9 +5263,9 @@ } }, "node_modules/@wordpress/url": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.50.0.tgz", - "integrity": "sha512-+YQzsPim5Zx55o/y9urtd0CKANUgwqZSdUNjDWYZ/1CWxtLLzPgQJOabtl79hG2yjrKvjDe9PrDPff18bCmG5A==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.51.0.tgz", + "integrity": "sha512-OjucjlP1763gfKbe8lv/k3RCisyX8AfNBrhASk7JqxAj6rFhb1ZZO7YmAgB2m+WoGB5v7fkOli0FZyDqISdYyg==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -5268,9 +5276,9 @@ } }, "node_modules/@wordpress/warning": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.49.0.tgz", - "integrity": "sha512-W2Nj9Nj0o2udPLf8jfGijRff3lzQgPOiLZcN4LFUPT6yyb9MxvNIg7ZVTBJL2TB78+KQKGrIH4ERjV5WyDRoEQ==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-2.50.0.tgz", + "integrity": "sha512-y7Zf48roDfiPgbRAWGXDwN3C8sfbEdneGq+HvXCW6rIeGYnDLdEkpX9i7RfultkFFPVeSP3FpMKVMkto2nbqzA==", "dev": true, "engines": { "node": ">=12" @@ -7539,12 +7547,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz", - "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", + "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", "dev": true, "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", @@ -7967,9 +7975,9 @@ } }, "node_modules/cypress": { - "version": "13.6.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.3.tgz", - "integrity": "sha512-d/pZvgwjAyZsoyJ3FOsJT5lDsqnxQ/clMqnNc++rkHjbkkiF2h9s0JsZSyyH4QXhVFW3zPFg82jD25roFLOdZA==", + "version": "13.6.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.4.tgz", + "integrity": "sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -8036,9 +8044,9 @@ } }, "node_modules/cypress-mochawesome-reporter": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.0.tgz", - "integrity": "sha512-awdOlkWmB0xGBsO8iitOAJi30uSTtxvM+S6bq03Xw3DP5nMUdYlbvmLbetG0bkMPFkv1wn5ogQuw58Jv603n2A==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.1.tgz", + "integrity": "sha512-oqtyDE4OOd5D7uas4+ljIb3vkO4gHWErhWKV7TbNF20YweiHWmzuOmS6L0MGk3J6IF6VbfO4h86kSa0sNsaKUg==", "dev": true, "dependencies": { "commander": "^10.0.1", @@ -15562,6 +15570,19 @@ "mocha": ">=7" } }, + "node_modules/mochawesome-json-to-md": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/mochawesome-json-to-md/-/mochawesome-json-to-md-0.7.2.tgz", + "integrity": "sha512-dxh+o73bhC6nEph6fNky9wy35R+2oK3ueXwAlJ/COAanlFgu8GuvGzQ00VNO4PPYhYGDsO4vbt4QTcMA3lv25g==", + "deprecated": "šŸ™Œ Thanks for using it. We recommend upgrading to the newer version, 1.x.x. Check out https://www.npmjs.com/package/mochawesome-json-to-md for details.", + "dev": true, + "dependencies": { + "yargs": "^17.0.1" + }, + "bin": { + "mochawesome-json-to-md": "index.js" + } + }, "node_modules/mochawesome-merge": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mochawesome-merge/-/mochawesome-merge-4.3.0.tgz", @@ -16107,9 +16128,9 @@ } }, "node_modules/npm-package-json-lint/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/npm-package-json-lint/node_modules/lru-cache": { @@ -19553,9 +19574,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", + "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -20365,9 +20386,9 @@ } }, "node_modules/terser": { - "version": "5.17.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", - "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -20383,16 +20404,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -21305,9 +21326,9 @@ } }, "node_modules/web-vitals": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.1.tgz", - "integrity": "sha512-xQ9lvIpfLxUj0eSmT79ZjRoU5wIRfIr7pNukL7ZE4EcWZSmfZQqOlhuAGfkVa3EFmzPHZhWhXfm2i5ys+THVPg==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz", + "integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==", "dev": true }, "node_modules/webidl-conversions": { @@ -21320,19 +21341,19 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.90.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.0.tgz", + "integrity": "sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", + "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.11.5", "@webassemblyjs/wasm-edit": "^1.11.5", "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", @@ -21346,7 +21367,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", + "terser-webpack-plugin": "^5.3.10", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, diff --git a/package.json b/package.json index 3447e38d7..93136f795 100644 --- a/package.json +++ b/package.json @@ -37,23 +37,24 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.2.0", - "@wordpress/env": "^9.1.0", - "@wordpress/scripts": "^27.0.0", - "cypress": "^13.6.3", + "@wordpress/env": "^9.2.0", + "@wordpress/scripts": "^27.1.0", + "cypress": "^13.6.4", "cypress-file-upload": "^5.0.8", - "cypress-mochawesome-reporter": "^3.8.0", + "cypress-mochawesome-reporter": "^3.8.1", "cypress-plugin-tab": "^1.0.5", "husky": "^8.0.3", "jsdoc": "^3.6.11", "lint-staged": "^15.2.0", + "mochawesome-json-to-md": "^0.7.2", "node-wp-i18n": "^1.2.7", "svg-react-loader": "^0.4.6", - "webpack": "^5.86.0", + "webpack": "^5.90.0", "webpack-cli": "^5.1.4", "wp-hookdoc": "^0.2.0" }, "dependencies": { - "@wordpress/icons": "^9.40.0", + "@wordpress/icons": "^9.41.0", "choices.js": "^10.2.0", "tippy.js": "^6.3.7" } diff --git a/src/js/admin.js b/src/js/admin.js index 8e8a27f6c..a2d76c721 100644 --- a/src/js/admin.js +++ b/src/js/admin.js @@ -64,9 +64,9 @@ document.addEventListener( 'DOMContentLoaded', function () { $toggler.addEventListener( 'click', ( e ) => { e.preventDefault(); - $userFieldWrapper.classList.toggle( 'hidden' ); + $userFieldWrapper.classList.toggle( 'hide-username' ); - if ( $userFieldWrapper.classList.contains( 'hidden' ) ) { + if ( $userFieldWrapper.classList.contains( 'hide-username' ) ) { $toggler.innerText = ClassifAI.use_password; $passwordFieldTitle.innerText = ClassifAI.api_key; $userField.value = 'apikey'; diff --git a/src/scss/admin.scss b/src/scss/admin.scss index af39b2ac2..62ef171e2 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -367,6 +367,10 @@ input.classifai-button { margin-bottom: 4px; } + .hide-username { + display: none; + } + .components-form-token-field__input-container input[type=text].components-form-token-field__input { height: auto; font-size: 14px; diff --git a/tests/cypress/integration/admin/common-feature-fields.test.js b/tests/cypress/integration/admin/common-feature-fields.test.js index 954702d25..fec550d4a 100644 --- a/tests/cypress/integration/admin/common-feature-fields.test.js +++ b/tests/cypress/integration/admin/common-feature-fields.test.js @@ -4,18 +4,18 @@ describe('Common Feature Fields', () => { } ); const features = { - 'feature_classification': 'Classification', - 'feature_title_generation': 'Title Generation', - 'feature_excerpt_generation': 'Excerpt Generation', - 'feature_content_resizing': 'Content Resizing', - 'feature_text_to_speech_generation': 'Text to Speech', - 'feature_audio_transcripts_generation': 'Audio Transcripts Generation', - 'feature_image_generation': 'Image Generation', - 'feature_descriptive_text_generator': 'Descriptive Text Generator', - 'feature_image_tags_generator': 'Image Tags Generator', - 'feature_image_cropping': 'Image Cropping', - 'feature_image_to_text_generator': 'Image Text Extraction', - 'feature_pdf_to_text_generation': 'PDF Text Extraction', + feature_classification: 'Classification', + feature_title_generation: 'Title Generation', + feature_excerpt_generation: 'Excerpt Generation', + feature_content_resizing: 'Content Resizing', + feature_text_to_speech_generation: 'Text to Speech', + feature_audio_transcripts_generation: 'Audio Transcripts Generation', + feature_image_generation: 'Image Generation', + feature_descriptive_text_generator: 'Descriptive Text Generator', + feature_image_tags_generator: 'Image Tags Generator', + feature_image_cropping: 'Image Cropping', + feature_image_to_text_generator: 'Image Text Extraction', + feature_pdf_to_text_generation: 'PDF Text Extraction', }; const allowedRoles = [ @@ -27,25 +27,56 @@ describe('Common Feature Fields', () => { ]; Object.keys( features ).forEach( ( feature ) => { - it( `"${ features[feature] }" feature common fields`, () => { - cy.visit( `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${feature}` ); - - cy.get( '#status' ).should( 'have.attr', 'name', `classifai_${ feature }[status]` ); - cy.get( '#role_based_access' ).should( 'have.attr', 'name', `classifai_${ feature }[role_based_access]` ); - cy.get( '#user_based_access' ).should( 'have.attr', 'name', `classifai_${ feature }[user_based_access]` ); - cy.get( '#user_based_opt_out' ).should( 'have.attr', 'name', `classifai_${ feature }[user_based_opt_out]` ); - cy.get( '#provider' ).should( 'have.attr', 'name', `classifai_${ feature }[provider]` ); + it( `"${ features[ feature ] }" feature common fields`, () => { + cy.visit( + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` + ); + + cy.get( '#status' ).should( + 'have.attr', + 'name', + `classifai_${ feature }[status]` + ); + cy.get( '#role_based_access' ).should( + 'have.attr', + 'name', + `classifai_${ feature }[role_based_access]` + ); + cy.get( '#user_based_access' ).should( + 'have.attr', + 'name', + `classifai_${ feature }[user_based_access]` + ); + cy.get( '#user_based_opt_out' ).should( + 'have.attr', + 'name', + `classifai_${ feature }[user_based_opt_out]` + ); + cy.get( '#provider' ).should( + 'have.attr', + 'name', + `classifai_${ feature }[provider]` + ); cy.get( '#role_based_access' ).check(); - for ( let role of allowedRoles ) { - if ( 'feature_image_generation' === feature && ( 'contributor' === role || 'subscriber' === role ) ) { + for ( const role of allowedRoles ) { + if ( + 'feature_image_generation' === feature && + ( 'contributor' === role || 'subscriber' === role ) + ) { continue; } - const roleField = cy.get( `#classifai_${ feature }_roles_${ role }` ); + const roleField = cy.get( + `#classifai_${ feature }_roles_${ role }` + ); roleField.should( 'be.visible' ); roleField.should( 'have.value', role ); - roleField.should( 'have.attr', 'name', `classifai_${ feature }[roles][${ role }]` ); + roleField.should( + 'have.attr', + 'name', + `classifai_${ feature }[roles][${ role }]` + ); } cy.get( '#role_based_access' ).uncheck(); @@ -58,4 +89,4 @@ describe('Common Feature Fields', () => { cy.get( '.allowed_users_row' ).should( 'not.be.visible' ); } ); } ); -}); +} ); diff --git a/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js b/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js index 70a61ca79..0e0f8eed9 100644 --- a/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js +++ b/tests/cypress/integration/image-processing/image-generation-openai-dalle.test.js @@ -2,7 +2,7 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { before( () => { cy.login(); cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); @@ -15,13 +15,15 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can save OpenAI "Image Processing" settings', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#api_key' ).clear().type( 'password' ); cy.get( '#status' ).check(); - cy.get( '#classifai_feature_image_generation_roles_administrator' ).check(); + cy.get( + '#classifai_feature_image_generation_roles_administrator' + ).check(); cy.get( '#number_of_images' ).select( '2' ); cy.get( '#image_size' ).select( '512x512' ); cy.get( '#submit' ).click(); @@ -80,7 +82,7 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature', () => { // Disable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); @@ -90,7 +92,7 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); @@ -101,11 +103,13 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can generate image directly in media library', () => { cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#status' ).check(); - cy.get( '#classifai_feature_image_generation_roles_administrator' ).check(); + cy.get( + '#classifai_feature_image_generation_roles_administrator' + ).check(); cy.get( '#submit' ).click(); cy.visit( '/wp-admin/upload.php' ); @@ -128,25 +132,23 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature by role', () => { // Enable feature. cy.visit( - '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_image_generation' + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_generation' ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_image_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_image_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyImageGenerationEnabled( false ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_image_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_image_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyImageGenerationEnabled( true ); @@ -154,19 +156,15 @@ describe( 'Image Generation (OpenAI DALLĀ·E) Tests', () => { it( 'Can enable/disable image generation feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_image_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_image_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyImageGenerationEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_image_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_image_generation', [ 'admin' ] ); // Verify that the feature is available. cy.verifyImageGenerationEnabled( true ); diff --git a/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js b/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js index 1cd1981e1..1750703ac 100644 --- a/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js +++ b/tests/cypress/integration/image-processing/image-processing-microsoft-azure.test.js @@ -16,7 +16,9 @@ describe( 'Image processing Tests', () => { ]; imageProcessingFeatures.forEach( ( feature ) => { - cy.visit( `/wp-admin/tools.php?page=classifai&tab=image_processing&feature=${ feature }` ); + cy.visit( + `/wp-admin/tools.php?page=classifai&tab=image_processing&feature=${ feature }` + ); cy.get( '#status' ).check(); cy.get( '#endpoint_url' ) .clear() @@ -33,8 +35,12 @@ describe( 'Image processing Tests', () => { } ); it( 'Can see Azure AI Vision Image processing actions on edit media page and verify Generated data.', () => { - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' + ); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' + ).check(); cy.get( '#submit' ).click(); cy.visit( '/wp-admin/upload.php?mode=grid' ); // Ensure grid mode is enabled. cy.visit( '/wp-admin/media-new.php' ); @@ -54,20 +60,20 @@ describe( 'Image processing Tests', () => { } ); // Verify Metabox with Image processing actions. - cy.get( '.postbox-header h2, #attachment_meta_box h2' ) + cy.get( '.postbox-header h2, #classifai_image_processing h2' ) .first() .contains( 'ClassifAI Image Processing' ); cy.get( - '.misc-publishing-actions label[for=rescan-captions]' + '#classifai_image_processing label[for=rescan-captions]' ).contains( 'No descriptive text? Rescan image' ); - cy.get( '.misc-publishing-actions label[for=rescan-tags]' ).contains( + cy.get( '#classifai_image_processing label[for=rescan-tags]' ).contains( 'Rescan image for new tags' ); - cy.get( '.misc-publishing-actions label[for=rescan-ocr]' ).contains( + cy.get( '#classifai_image_processing label[for=rescan-ocr]' ).contains( 'Rescan for text' ); cy.get( - '.misc-publishing-actions label[for=rescan-smart-crop]' + '#classifai_image_processing label[for=rescan-smart-crop]' ).should( 'exist' ); // Verify generated Data. @@ -87,7 +93,7 @@ describe( 'Image processing Tests', () => { } ); } ); - it( 'Can see Azure AI Vision Image processing actions on media model', () => { + it( 'Can see Azure AI Vision Image processing actions on media modal', () => { const imageId = imageEditLink.split( 'post=' )[ 1 ]?.split( '&' )[ 0 ]; mediaModelLink = `wp-admin/upload.php?item=${ imageId }`; cy.visit( mediaModelLink ); @@ -107,22 +113,36 @@ describe( 'Image processing Tests', () => { }; // Disable features - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); - cy.wait(1000); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).uncheck(); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' ).uncheck(); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' ).uncheck(); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' + ); + cy.wait( 1000 ); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' + ).uncheck(); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' + ).uncheck(); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' + ).uncheck(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' + ); cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' + ); cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' + ); cy.get( '#status' ).uncheck(); cy.get( '#submit' ).click(); @@ -130,23 +150,37 @@ describe( 'Image processing Tests', () => { cy.verifyAIVisionEnabled( false, options ); // Enable features. - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); - cy.wait(1000); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' ).check(); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' ).check(); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' + ); + cy.wait( 1000 ); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' + ).check(); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_caption' + ).check(); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_description' + ).check(); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_tags_generator' + ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_cropping' + ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_image_to_text_generator' + ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); @@ -161,50 +195,46 @@ describe( 'Image processing Tests', () => { }; // Enable features. - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' ); - cy.wait(1000); - cy.get( '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' ).check(); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_descriptive_text_generator' + ); + cy.wait( 1000 ); + cy.get( + '#classifai_feature_descriptive_text_generator_descriptive_text_fields_alt' + ).check(); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); // Disable access to admin role. - cy.disableFeatureForRoles( - 'feature_descriptive_text_generator', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_tags_generator', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_cropping', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_to_text_generator', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_descriptive_text_generator', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_tags_generator', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_cropping', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_to_text_generator', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyAIVisionEnabled( false, options ); // Enable access to admin role. - cy.enableFeatureForRoles( - 'feature_descriptive_text_generator', - [ 'administrator' ] - ); - cy.enableFeatureForRoles( - 'feature_image_tags_generator', - [ 'administrator' ] - ); - cy.enableFeatureForRoles( - 'feature_image_cropping', - [ 'administrator' ] - ); - cy.enableFeatureForRoles( - 'feature_image_to_text_generator', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_descriptive_text_generator', [ + 'administrator', + ] ); + cy.enableFeatureForRoles( 'feature_image_tags_generator', [ + 'administrator', + ] ); + cy.enableFeatureForRoles( 'feature_image_cropping', [ + 'administrator', + ] ); + cy.enableFeatureForRoles( 'feature_image_to_text_generator', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyAIVisionEnabled( true, options ); @@ -217,42 +247,30 @@ describe( 'Image processing Tests', () => { }; // Disable access to admin role. - cy.disableFeatureForRoles( - 'feature_descriptive_text_generator', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_tags_generator', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_cropping', - [ 'administrator' ] - ); - cy.disableFeatureForRoles( - 'feature_image_to_text_generator', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_descriptive_text_generator', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_tags_generator', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_cropping', [ + 'administrator', + ] ); + cy.disableFeatureForRoles( 'feature_image_to_text_generator', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyAIVisionEnabled( false, options ); - cy.enableFeatureForUsers( - 'feature_descriptive_text_generator', - [ 'admin' ] - ); - cy.enableFeatureForUsers( - 'feature_image_tags_generator', - [ 'admin' ] - ); - cy.enableFeatureForUsers( - 'feature_image_cropping', - [ 'admin' ] - ); - cy.enableFeatureForUsers( - 'feature_image_to_text_generator', - [ 'admin' ], - ); + cy.enableFeatureForUsers( 'feature_descriptive_text_generator', [ + 'admin', + ] ); + cy.enableFeatureForUsers( 'feature_image_tags_generator', [ 'admin' ] ); + cy.enableFeatureForUsers( 'feature_image_cropping', [ 'admin' ] ); + cy.enableFeatureForUsers( 'feature_image_to_text_generator', [ + 'admin', + ] ); // Verify that the feature is available. cy.verifyAIVisionEnabled( true, options ); diff --git a/tests/cypress/integration/image-processing/pdf-read.test.js b/tests/cypress/integration/image-processing/pdf-read.test.js index bff567c6d..59c79437a 100644 --- a/tests/cypress/integration/image-processing/pdf-read.test.js +++ b/tests/cypress/integration/image-processing/pdf-read.test.js @@ -3,7 +3,9 @@ import { getPDFData } from '../../plugins/functions'; describe( 'PDF read Tests', () => { before( () => { cy.login(); - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' + ); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); @@ -15,7 +17,9 @@ describe( 'PDF read Tests', () => { let pdfEditLink = ''; it( 'Can save "PDF scanning" settings', () => { - cy.visit( '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' ); + cy.visit( + '/wp-admin/tools.php?page=classifai&tab=image_processing&feature=feature_pdf_to_text_generation' + ); cy.get( '#endpoint_url' ) .clear() @@ -93,10 +97,9 @@ describe( 'PDF read Tests', () => { cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_pdf_to_text_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_pdf_to_text_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.visit( pdfEditLink ); @@ -105,10 +108,9 @@ describe( 'PDF read Tests', () => { ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_pdf_to_text_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_pdf_to_text_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.visit( pdfEditLink ); @@ -119,10 +121,9 @@ describe( 'PDF read Tests', () => { it( 'Can enable/disable PDF scanning feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_pdf_to_text_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_pdf_to_text_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.visit( pdfEditLink ); @@ -131,10 +132,9 @@ describe( 'PDF read Tests', () => { ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_pdf_to_text_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_pdf_to_text_generation', [ + 'admin', + ] ); // Verify that the feature is available. cy.visit( pdfEditLink ); diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index 9cd9b0d40..8094e05bd 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -4,19 +4,23 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#provider' ).select( 'ibm_watson_nlu' ) + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); cy.get( '#endpoint_url' ) .clear() .type( 'http://e2e-test-nlu-server.test/' ); - cy.get( '#password' ) - .clear() - .type( 'password' ); + cy.get( '#password' ).clear().type( 'password' ); + cy.get( '#classifai-waston-cred-toggle' ).click(); cy.get( '#classifai_feature_classification_post_types_post' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_publish' + ).check(); cy.get( '#status' ).check(); cy.get( '#submit' ).click(); - cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); - cy.wait( 1000 ) + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_method_recommended_terms' + ).check(); + cy.wait( 1000 ); cy.get( '#category' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); @@ -38,10 +42,18 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.get( '#status' ).check(); cy.get( '#classifai_feature_classification_post_types_post' ).check(); cy.get( '#classifai_feature_classification_post_types_page' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_draft' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_pending' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_private' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_draft' + ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_pending' + ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_private' + ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_publish' + ).check(); cy.get( '#category' ).check(); cy.get( '#keyword' ).check(); @@ -92,7 +104,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing' ); - cy.get( '#classifai_feature_classification_classification_mode_manual_review' ).check(); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_mode_manual_review' + ).check(); cy.get( '#submit' ).click(); // Create Test Post @@ -178,7 +193,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai_feature_classification_classification_mode_automatic_classification' ).check(); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_mode_automatic_classification' + ).check(); cy.get( '#submit' ).click(); // Create Test Post @@ -252,18 +270,10 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#category_threshold' ) - .clear() - .type( threshold ); - cy.get( '#keyword_threshold' ) - .clear() - .type( threshold ); - cy.get( '#entity_threshold' ) - .clear() - .type( threshold ); - cy.get( '#concept_threshold' ) - .clear() - .type( threshold ); + cy.get( '#category_threshold' ).clear().type( threshold ); + cy.get( '#keyword_threshold' ).clear().type( threshold ); + cy.get( '#entity_threshold' ).clear().type( threshold ); + cy.get( '#concept_threshold' ).clear().type( threshold ); cy.get( '#submit' ).click(); // Create Test Post @@ -286,7 +296,7 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () // Verify Each Created taxonomies. [ 'categories', 'keywords', 'concepts', 'entities' ].forEach( ( taxonomy ) => { - // cy.verifyPostTaxonomyTerms( taxonomy, threshold / 100 ); + cy.verifyPostTaxonomyTerms( taxonomy, threshold / 100 ); } ); } ); @@ -304,19 +314,14 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); - cy.get( '#category_threshold' ) - .clear() - .type( threshold1 ); - cy.get( '#keyword_threshold' ) - .clear() - .type( threshold1 ); - cy.get( '#entity_threshold' ) - .clear() - .type( threshold1 ); - cy.get( '#concept_threshold' ) - .clear() - .type( threshold1 ); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_method_recommended_terms' + ).check(); + cy.get( '#category_threshold' ).clear().type( threshold1 ); + cy.get( '#keyword_threshold' ).clear().type( threshold1 ); + cy.get( '#entity_threshold' ).clear().type( threshold1 ); + cy.get( '#concept_threshold' ).clear().type( threshold1 ); cy.get( '#submit' ).click(); // Create Test Post @@ -350,19 +355,14 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#watson_nlu_classification_method_existing_terms' ).check(); - cy.get( '#category_threshold' ) - .clear() - .type( threshold2 ); - cy.get( '#keyword_threshold' ) - .clear() - .type( threshold2 ); - cy.get( '#entity_threshold' ) - .clear() - .type( threshold2 ); - cy.get( '#concept_threshold' ) - .clear() - .type( threshold2 ); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_method_existing_terms' + ).check(); + cy.get( '#category_threshold' ).clear().type( threshold2 ); + cy.get( '#keyword_threshold' ).clear().type( threshold2 ); + cy.get( '#entity_threshold' ).clear().type( threshold2 ); + cy.get( '#concept_threshold' ).clear().type( threshold2 ); cy.get( '#submit' ).click(); // Create Test Post @@ -394,7 +394,9 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); + cy.get( + '#classifai_feature_classification_classification_method_recommended_terms' + ).check(); cy.get( '#submit' ).click(); } ); @@ -403,11 +405,18 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( '#classifai_feature_classification_classification_method_recommended_terms' ).check(); + cy.get( '#provider' ).select( 'ibm_watson_nlu' ); + cy.get( + '#classifai_feature_classification_classification_method_recommended_terms' + ).check(); cy.get( '#classifai-settings-category_taxonomy' ).select( 'post_tag' ); cy.get( '#classifai-settings-keyword_taxonomy' ).select( 'post_tag' ); cy.get( '#classifai-settings-entity_taxonomy' ).select( 'post_tag' ); cy.get( '#classifai-settings-concept_taxonomy' ).select( 'post_tag' ); + cy.get( '#category_threshold' ).clear().type( threshold ); + cy.get( '#keyword_threshold' ).clear().type( threshold ); + cy.get( '#entity_threshold' ).clear().type( threshold ); + cy.get( '#concept_threshold' ).clear().type( threshold ); cy.get( '#submit' ).click(); // Create Test Post @@ -458,9 +467,7 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' ); - cy.get( - '#role_based_access' - ).check(); + cy.get( '#role_based_access' ).check(); cy.get( '#classifai_feature_classification_roles_administrator' ).uncheck(); @@ -475,9 +482,7 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( - '#role_based_access' - ).check(); + cy.get( '#role_based_access' ).check(); cy.get( '#classifai_feature_classification_roles_administrator' ).check(); @@ -494,12 +499,8 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( - '#role_based_access' - ).uncheck(); - cy.get( - '#user_based_access' - ).uncheck(); + cy.get( '#role_based_access' ).uncheck(); + cy.get( '#user_based_access' ).uncheck(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -510,12 +511,8 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( - '#role_based_access' - ).uncheck(); - cy.get( - '#user_based_access' - ).check(); + cy.get( '#role_based_access' ).uncheck(); + cy.get( '#user_based_access' ).check(); cy.get( 'body' ).then( ( $body ) => { if ( $body.find( @@ -546,12 +543,8 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( - '#role_based_access' - ).check(); - cy.get( - '#user_based_access' - ).uncheck(); + cy.get( '#role_based_access' ).check(); + cy.get( '#user_based_access' ).uncheck(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -562,15 +555,9 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&provider=watson_nlu' ); - cy.get( - '#role_based_access' - ).check(); - cy.get( - '#user_based_access' - ).check(); - cy.get( - '#user_based_opt_out' - ).check(); + cy.get( '#role_based_access' ).check(); + cy.get( '#user_based_access' ).check(); + cy.get( '#user_based_opt_out' ).check(); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); diff --git a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js index cb1c41efb..c0ef814e7 100644 --- a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js +++ b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js @@ -23,9 +23,17 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.get( '#api_key' ).clear().type( 'password' ); cy.get( '#status' ).check(); cy.get( '#classifai_feature_classification_post_types_post' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); - cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).check(); - cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category_threshold' ).clear().type( 100 ); // "Test" requires 80% confidence. At 81%, it does not apply. + cy.get( + '#classifai_feature_classification_post_statuses_publish' + ).check(); + cy.get( + '#classifai_feature_classification_openai_embeddings_taxonomies_category' + ).check(); + cy.get( + '#classifai_feature_classification_openai_embeddings_taxonomies_category_threshold' + ) + .clear() + .type( 100 ); // "Test" requires 80% confidence. At 81%, it does not apply. cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); } ); @@ -50,7 +58,9 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing' ); - cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).uncheck(); + cy.get( + '#classifai_feature_classification_openai_embeddings_taxonomies_category' + ).uncheck(); cy.get( '#submit' ).click(); // Create test term. @@ -93,9 +103,7 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { ) .should( 'be.checked' ); cy.wrap( $panel ) - .find( - '.editor-post-taxonomies__hierarchical-terms-list' - ) + .find( '.editor-post-taxonomies__hierarchical-terms-list' ) .children() .contains( 'Test' ); } ); @@ -164,8 +172,12 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.get( '#status' ).check(); cy.get( '#classifai_feature_classification_post_types_post' ).check(); - cy.get( '#classifai_feature_classification_post_statuses_publish' ).check(); - cy.get( '#classifai_feature_classification_openai_embeddings_taxonomies_category' ).check(); + cy.get( + '#classifai_feature_classification_post_statuses_publish' + ).check(); + cy.get( + '#classifai_feature_classification_openai_embeddings_taxonomies_category' + ).check(); cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); @@ -212,19 +224,17 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_classification', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_classification', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyClassifyContentEnabled( false ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_classification', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_classification', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyClassifyContentEnabled( true ); @@ -232,19 +242,15 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { it( 'Can enable/disable content classification feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_classification', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_classification', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyClassifyContentEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_classification', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_classification', [ 'admin' ] ); // Verify that the feature is available. cy.verifyClassifyContentEnabled( true ); diff --git a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js index 9e2f31da7..e64d5a2c7 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js @@ -117,7 +117,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Add three custom prompts. cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -125,7 +125,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { .click() .click(); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' ) .parents( 'td' ) .find( '.classifai-field-type-prompt-setting' ) @@ -133,40 +133,40 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][1][title]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][1][prompt]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom excerpt prompt' ); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][title]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][prompt]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][title]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][prompt]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom excerpt prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][3][default]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -174,7 +174,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][2][default]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -183,7 +183,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_feature_excerpt_generation[openai_chatgpt][generate_excerpt_prompt][0][default]"]' + '[name="classifai_feature_excerpt_generation[generate_excerpt_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -263,19 +263,17 @@ describe( '[Language processing] Excerpt Generation Tests', () => { cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_excerpt_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_excerpt_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyExcerptGenerationEnabled( false ); // enable admin role. - cy.enableFeatureForRoles( - 'feature_excerpt_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_excerpt_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyExcerptGenerationEnabled( true ); @@ -283,24 +281,17 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can enable/disable excerpt generation feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_excerpt_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_excerpt_generation', [ + 'administrator', + ] ); - cy.enableFeatureForUsers( - 'feature_excerpt_generation', - [] - ); + cy.enableFeatureForUsers( 'feature_excerpt_generation', [] ); // Verify that the feature is not available. cy.verifyExcerptGenerationEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_excerpt_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_excerpt_generation', [ 'admin' ] ); // Verify that the feature is available. cy.verifyExcerptGenerationEnabled( true ); @@ -308,7 +299,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'User can opt-out excerpt generation feature', () => { // Enable user based opt-out. - cy.enableFeatureOptOut( 'feature_excerpt_generation', 'openai_chatgpt' ); + cy.enableFeatureOptOut( + 'feature_excerpt_generation', + 'openai_chatgpt' + ); // opt-out cy.optOutFeature( 'feature_excerpt_generation' ); diff --git a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js index ef78395dc..9d4fbb0c9 100644 --- a/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/resize_content-openapi-chatgpt.test.js @@ -21,7 +21,9 @@ describe( '[Language processing] Speech to Text Tests', () => { ); cy.get( '#status' ).check(); - cy.get( '#classifai_feature_content_resizing_roles_administrator' ).check(); + cy.get( + '#classifai_feature_content_resizing_roles_administrator' + ).check(); cy.get( '#submit' ).click(); cy.createPost( { @@ -79,7 +81,7 @@ describe( '[Language processing] Speech to Text Tests', () => { // Add three custom shrink prompts. cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -87,7 +89,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .click() .click(); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -95,7 +97,7 @@ describe( '[Language processing] Speech to Text Tests', () => { // Add three custom grow prompts. cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset:first' ) @@ -103,7 +105,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .click() .click(); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -111,77 +113,77 @@ describe( '[Language processing] Speech to Text Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][1][title]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][1][prompt]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom shrink prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][title]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][prompt]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][title]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][prompt]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom shrink prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][1][title]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][1][prompt]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom grow prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][title]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][prompt]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][title]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][prompt]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom grow prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][3][default]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) .click( { force: true } ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][3][default]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -189,7 +191,7 @@ describe( '[Language processing] Speech to Text Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][2][default]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -198,13 +200,13 @@ describe( '[Language processing] Speech to Text Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][condense_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[condense_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) .should( 'have.length', 3 ); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][2][default]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -214,7 +216,7 @@ describe( '[Language processing] Speech to Text Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_feature_content_resizing[openai_chatgpt][expand_text_prompt][0][default]"]' + '[name="classifai_feature_content_resizing[expand_text_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -294,19 +296,17 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable resize content feature by role', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_content_resizing', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_content_resizing', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyResizeContentEnabled( false ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_content_resizing', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_content_resizing', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyResizeContentEnabled( true ); @@ -314,19 +314,15 @@ describe( '[Language processing] Speech to Text Tests', () => { it( 'Can enable/disable resize content feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_content_resizing', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_content_resizing', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyResizeContentEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_content_resizing', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_content_resizing', [ 'admin' ] ); // Verify that the feature is available. cy.verifyResizeContentEnabled( true ); diff --git a/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js b/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js index c36b14825..9e04582d5 100644 --- a/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js +++ b/tests/cypress/integration/language-processing/speech-to-text-openapi-whisper.test.js @@ -24,7 +24,9 @@ describe( '[Language processing] Speech to Text Tests', () => { cy.get( '#api_key' ).clear().type( 'password' ); cy.get( '#status' ).check(); - cy.get( '#classifai_feature_audio_transcripts_generation_roles_administrator' ).check(); + cy.get( + '#classifai_feature_audio_transcripts_generation_roles_administrator' + ).check(); cy.get( '#submit' ).click(); } ); @@ -114,19 +116,17 @@ describe( '[Language processing] Speech to Text Tests', () => { }; // Disable admin role. - cy.disableFeatureForRoles( - 'feature_audio_transcripts_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_audio_transcripts_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifySpeechToTextEnabled( false, options ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_audio_transcripts_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_audio_transcripts_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifySpeechToTextEnabled( true, options ); @@ -139,19 +139,17 @@ describe( '[Language processing] Speech to Text Tests', () => { }; // Disable admin role. - cy.disableFeatureForRoles( - 'feature_audio_transcripts_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_audio_transcripts_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifySpeechToTextEnabled( false, options ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_audio_transcripts_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_audio_transcripts_generation', [ + 'admin', + ] ); // Verify that the feature is available. cy.verifySpeechToTextEnabled( true, options ); diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index 10c9480c0..e6863a18f 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -4,7 +4,9 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); + cy.get( + '#classifai_feature_text_to_speech_generation_post_types_post' + ).check( 'post' ); cy.get( '#endpoint_url' ).clear(); cy.get( '#endpoint_url' ).type( 'https://service.com' ); cy.get( '#api_key' ).type( 'password' ); @@ -112,7 +114,9 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).uncheck( 'post' ); + cy.get( + '#classifai_feature_text_to_speech_generation_post_types_post' + ).uncheck( 'post' ); cy.get( '#submit' ).click(); cy.visit( '/text-to-speech-test/' ); @@ -135,7 +139,9 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); cy.get( '#status' ).check(); - cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); + cy.get( + '#classifai_feature_text_to_speech_generation_post_types_post' + ).check( 'post' ); cy.get( '#submit' ).click(); // Verify that the feature is available. @@ -147,23 +153,23 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); - cy.get( '#classifai_feature_text_to_speech_generation_post_types_post' ).check( 'post' ); + cy.get( + '#classifai_feature_text_to_speech_generation_post_types_post' + ).check( 'post' ); cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_text_to_speech_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_text_to_speech_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyTextToSpeechEnabled( false ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_text_to_speech_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_text_to_speech_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyTextToSpeechEnabled( true ); @@ -171,19 +177,17 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can enable/disable text to speech feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_text_to_speech_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_text_to_speech_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyTextToSpeechEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_text_to_speech_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_text_to_speech_generation', [ + 'admin', + ] ); // Verify that the feature is available. cy.verifyTextToSpeechEnabled( true ); diff --git a/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js index 0113bff41..05ae7e787 100644 --- a/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/title-generation-openapi-chatgpt.test.js @@ -123,7 +123,7 @@ describe( '[Language processing] Title Generation Tests', () => { // Add three custom prompts. cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( 'button.js-classifai-add-prompt-fieldset' ) @@ -131,7 +131,7 @@ describe( '[Language processing] Title Generation Tests', () => { .click() .click(); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -139,40 +139,40 @@ describe( '[Language processing] Title Generation Tests', () => { // Set the data for each prompt. cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][1][title]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][1][title]"]' ) .clear() .type( 'First custom prompt' ); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][1][prompt]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][1][prompt]"]' ) .clear() .type( 'This is our first custom title prompt' ); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][title]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][2][title]"]' ) .clear() .type( 'Second custom prompt' ); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][prompt]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][2][prompt]"]' ) .clear() .type( 'This prompt should be deleted' ); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][title]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][3][title]"]' ) .clear() .type( 'Third custom prompt' ); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][prompt]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][3][prompt]"]' ) .clear() .type( 'This is a custom title prompt' ); // Set the third prompt as our default. cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][3][default]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][3][default]"]' ) .parent() .find( 'a.action__set_default' ) @@ -180,7 +180,7 @@ describe( '[Language processing] Title Generation Tests', () => { // Delete the second prompt. cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][2][default]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][2][default]"]' ) .parent() .find( 'a.action__remove_prompt' ) @@ -189,7 +189,7 @@ describe( '[Language processing] Title Generation Tests', () => { .find( '.button-primary' ) .click(); cy.get( - '[name="classifai_feature_title_generation[openai_chatgpt][generate_title_prompt][0][default]"]' + '[name="classifai_feature_title_generation[generate_title_prompt][0][default]"]' ) .parents( 'td:first' ) .find( '.classifai-field-type-prompt-setting' ) @@ -290,19 +290,17 @@ describe( '[Language processing] Title Generation Tests', () => { cy.get( '#submit' ).click(); // Disable admin role. - cy.disableFeatureForRoles( - 'feature_title_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_title_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyTitleGenerationEnabled( false ); // Enable admin role. - cy.enableFeatureForRoles( - 'feature_title_generation', - [ 'administrator' ] - ); + cy.enableFeatureForRoles( 'feature_title_generation', [ + 'administrator', + ] ); // Verify that the feature is available. cy.verifyTitleGenerationEnabled( true ); @@ -310,19 +308,15 @@ describe( '[Language processing] Title Generation Tests', () => { it( 'Can enable/disable title generation feature by user', () => { // Disable admin role. - cy.disableFeatureForRoles( - 'feature_title_generation', - [ 'administrator' ] - ); + cy.disableFeatureForRoles( 'feature_title_generation', [ + 'administrator', + ] ); // Verify that the feature is not available. cy.verifyTitleGenerationEnabled( false ); // Enable feature for admin user. - cy.enableFeatureForUsers( - 'feature_title_generation', - [ 'admin' ] - ); + cy.enableFeatureForUsers( 'feature_title_generation', [ 'admin' ] ); // Verify that the feature is available. cy.verifyTitleGenerationEnabled( true ); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 10ffad17a..115eeb089 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -126,8 +126,8 @@ Cypress.Commands.add( 'optInAllFeatures', () => { /** * Enable role based access for a feature. * - * @param {string} feature The feature to enable. - * @param {string} roles The roles to enable. + * @param {string} feature The feature to enable. + * @param {string} roles The roles to enable. */ Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles ) => { cy.visit( @@ -144,46 +144,40 @@ Cypress.Commands.add( 'enableFeatureForRoles', ( feature, roles ) => { /** * Disable role based access for a feature. * - * @param {string} feature The feature to disable. - * @param {string} roles The roles to disable. + * @param {string} feature The feature to disable. + * @param {string} roles The roles to disable. */ -Cypress.Commands.add( - 'disableFeatureForRoles', - ( feature, roles ) => { - cy.visit( - `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` - ); - cy.get( '#status' ).check(); - cy.get( `#role_based_access` ).check(); - roles.forEach( ( role ) => { - cy.get( `#classifai_${ feature }_roles_${ role }` ).uncheck(); - } ); - cy.get( '#submit' ).click(); - cy.get( '.notice' ).contains( 'Settings saved.' ); - } -); +Cypress.Commands.add( 'disableFeatureForRoles', ( feature, roles ) => { + cy.visit( + `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` + ); + cy.get( '#status' ).check(); + cy.get( `#role_based_access` ).check(); + roles.forEach( ( role ) => { + cy.get( `#classifai_${ feature }_roles_${ role }` ).uncheck(); + } ); + cy.get( '#submit' ).click(); + cy.get( '.notice' ).contains( 'Settings saved.' ); +} ); /** * Enable user based access for a feature. * - * @param {string} feature The feature to enable. - * @param {string} users The users to enable. + * @param {string} feature The feature to enable. + * @param {string} users The users to enable. */ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { cy.visit( `/wp-admin/tools.php?page=classifai&tab=language_processing&feature=${ feature }` ); cy.get( `#user_based_access` ).check(); - cy.wait(1000) + cy.wait( 1000 ); cy.get( '.allowed_users_row' ).then( ( $body ) => { if ( - $body.find( - `.components-form-token-field__remove-token` - ).length > 0 + $body.find( `.components-form-token-field__remove-token` ).length > + 0 ) { - cy.get( - `.components-form-token-field__remove-token` - ).click( { + cy.get( `.components-form-token-field__remove-token` ).click( { multiple: true, } ); } @@ -194,9 +188,7 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { `.allowed_users_row input.components-form-token-field__input` ).type( user ); - cy.get( - '[aria-label="admin (admin)"]' - ).click(); + cy.get( '[aria-label="admin (admin)"]' ).click(); } ); cy.get( '#submit' ).click(); cy.get( '.notice' ).contains( 'Settings saved.' ); @@ -205,7 +197,7 @@ Cypress.Commands.add( 'enableFeatureForUsers', ( feature, users ) => { /** * Enable user based opt-out for a feature. * - * @param {string} feature The feature to enable. + * @param {string} feature The feature to enable. */ Cypress.Commands.add( 'enableFeatureOptOut', ( feature ) => { cy.visit( @@ -322,11 +314,11 @@ Cypress.Commands.add( */ Cypress.Commands.add( 'verifyTextToSpeechEnabled', ( enabled = true ) => { const shouldExist = enabled ? 'exist' : 'not.exist'; - console.log( shouldExist ) + cy.visit( '/wp-admin/edit.php' ); cy.get( '#the-list tr:nth-child(1) td.title a.row-title' ).click(); cy.closeWelcomeGuide(); - cy.get( 'body' ).then( $body => { + cy.get( 'body' ).then( ( $body ) => { if ( $body.find( '.classifai-panel' ).length ) { $body.find( '.classifai-panel' ).click(); } @@ -420,17 +412,17 @@ Cypress.Commands.add( const shouldExist = enabled ? 'exist' : 'not.exist'; // Verify with Image processing features in attachment metabox. cy.visit( options.imageEditLink ); - cy.get( '.misc-publishing-actions label[for=rescan-captions]' ).should( - shouldExist - ); - cy.get( '.misc-publishing-actions label[for=rescan-tags]' ).should( + cy.get( + '#classifai_image_processing label[for=rescan-captions]' + ).should( shouldExist ); + cy.get( '#classifai_image_processing label[for=rescan-tags]' ).should( shouldExist ); - cy.get( '.misc-publishing-actions label[for=rescan-ocr]' ).should( + cy.get( '#classifai_image_processing label[for=rescan-ocr]' ).should( shouldExist ); cy.get( - '.misc-publishing-actions label[for=rescan-smart-crop]' + '#classifai_image_processing label[for=rescan-smart-crop]' ).should( shouldExist ); // Verify with Image processing features in media model. From 523b4e3b29d6536923bc063b2d9776193e3ab890 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Tue, 30 Jan 2024 17:04:00 -0700 Subject: [PATCH 122/127] Fix unit test --- .../Providers/Azure/ComputerVisionTest.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/Classifai/Providers/Azure/ComputerVisionTest.php b/tests/Classifai/Providers/Azure/ComputerVisionTest.php index 8becb724e..4f0f61890 100644 --- a/tests/Classifai/Providers/Azure/ComputerVisionTest.php +++ b/tests/Classifai/Providers/Azure/ComputerVisionTest.php @@ -37,8 +37,7 @@ public function get_computer_vision() : ComputerVision { * @covers ::smart_crop_image */ public function test_smart_crop_image() { - $this->assertEquals( - [], + $this->assertWPError( $this->get_computer_vision()->smart_crop_image( [ 'no-smart-cropping' => 1 ], 999999 @@ -58,20 +57,9 @@ public function test_smart_crop_image() { ) ); remove_filter( 'filesystem_method', $filter_file_system_method ); - // Test that SmartCropping is initiated and runs, as will be indicated in the coverage report, though it won't - // actually do anything because the data and attachment are invalid. - $this->assertEquals( - [], - $this->get_computer_vision()->smart_crop_image( - [ 'my-data' => 1 ], - 999999 - ) - ); - remove_filter( 'classifai_should_smart_crop_image', '__return_true' ); } - /** * Ensure that settings returns default settings array if the `classifai_computer_vision` is not set. */ From f1b82fc858e2bd4625031afe03c46ea0b589434b Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 31 Jan 2024 20:37:29 +0530 Subject: [PATCH 123/127] fix excerpt generation test --- .../Classifai/Features/ContentResizing.php | 39 +++++++------------ .../Classifai/Features/ExcerptGeneration.php | 39 +++++++------------ .../Classifai/Features/TitleGeneration.php | 39 +++++++------------ includes/Classifai/Plugin.php | 14 ++++++- package.json | 2 +- ...excerpt-generation-openapi-chatgpt.test.js | 10 ++--- 6 files changed, 63 insertions(+), 80 deletions(-) diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index 213895375..ed0e5303a 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -60,6 +60,21 @@ public function __construct() { public function setup() { parent::setup(); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( + 'admin_footer', + static function () { + if ( + ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_content_resizing' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + } + ); } /** @@ -193,30 +208,6 @@ public function enqueue_editor_assets() { * @param string $hook_suffix The current admin page. */ public function enqueue_admin_assets( string $hook_suffix ) { - // Load jQuery UI Dialog for prompt deletion. - if ( - ( - 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'feature_content_resizing' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ) || - 'admin_page_classifai_setup' === $hook_suffix - ) { - wp_enqueue_script( 'jquery-ui-dialog' ); - wp_enqueue_style( 'wp-jquery-ui-dialog' ); - - add_action( - 'admin_footer', - static function () { - printf( - '', - esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), - ); - } - ); - } - // Load asset in new post and edit post screens. if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { wp_enqueue_style( diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 1fdad662e..2add6f960 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -52,6 +52,21 @@ public function __construct() { public function setup() { parent::setup(); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( + 'admin_footer', + static function () { + if ( + ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_excerpt_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + } + ); } /** @@ -193,30 +208,6 @@ public function enqueue_editor_assets() { * @param string $hook_suffix The current admin page. */ public function enqueue_admin_assets( string $hook_suffix ) { - // Load jQuery UI Dialog for prompt deletion. - if ( - ( - 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'feature_excerpt_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ) || - 'admin_page_classifai_setup' === $hook_suffix - ) { - wp_enqueue_script( 'jquery-ui-dialog' ); - wp_enqueue_style( 'wp-jquery-ui-dialog' ); - - add_action( - 'admin_footer', - static function () { - printf( - '', - esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), - ); - } - ); - } - // Load asset in new post and edit post screens. if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { $screen = get_current_screen(); diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index cbe0a7a51..b43a3ccf8 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -53,6 +53,21 @@ public function __construct() { public function setup() { parent::setup(); add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + add_action( + 'admin_footer', + static function () { + if ( + ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && 'feature_title_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) { + printf( + '', + esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), + ); + } + } + ); } /** @@ -205,30 +220,6 @@ public function enqueue_editor_assets() { * @param string $hook_suffix The current admin page. */ public function enqueue_admin_assets( string $hook_suffix ) { - // Load jQuery UI Dialog for prompt deletion. - if ( - ( - 'tools_page_classifai' === $hook_suffix - && ( isset( $_GET['tab'], $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'language_processing' === sanitize_text_field( wp_unslash( $_GET['tab'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && 'feature_title_generation' === sanitize_text_field( wp_unslash( $_GET['feature'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - ) || - 'admin_page_classifai_setup' === $hook_suffix - ) { - wp_enqueue_script( 'jquery-ui-dialog' ); - wp_enqueue_style( 'wp-jquery-ui-dialog' ); - - add_action( - 'admin_footer', - static function () { - printf( - '', - esc_html__( 'Are you sure you want to delete the prompt?', 'classifai' ), - ); - } - ); - } - // Load asset in new post and edit post screens. if ( 'post.php' === $hook_suffix || 'post-new.php' === $hook_suffix ) { $screen = get_current_screen(); diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index faed7295a..b4b2af578 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -160,14 +160,24 @@ public function enqueue_admin_assets() { 'classifai-admin-style', CLASSIFAI_PLUGIN_URL . 'dist/admin.css', array( 'wp-components' ), - get_asset_info( 'admin', 'version' ), + array( + get_asset_info( 'admin', 'version' ), + array( + 'wp-jquery-ui-dialog' + ) + ), 'all' ); wp_enqueue_script( 'classifai-admin-script', CLASSIFAI_PLUGIN_URL . 'dist/admin.js', - get_asset_info( 'admin', 'dependencies' ), + array_merge( + get_asset_info( 'admin', 'dependencies' ), + array( + 'jquery-ui-dialog', + ) + ), get_asset_info( 'admin', 'version' ), true ); diff --git a/package.json b/package.json index 93136f795..c472c8b76 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "makepot": "wpi18n makepot && echo '.pot file updated'", "build:docs": "rm -rf docs && jsdoc -c hookdoc-conf.json classifai.php includes", "cypress:open": "cypress open --config-file tests/cypress/config.config.js", - "cypress:run": "cypress run --config-file tests/cypress/config.config.js", + "cypress:run": "cypress run --browser chrome --config-file tests/cypress/config.config.js", "env": "wp-env", "env:start": "wp-env start", "env:stop": "wp-env stop", diff --git a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js index e64d5a2c7..99d110318 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js @@ -7,6 +7,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); cy.get( '#status' ).check(); + cy.get( '#classifai_feature_excerpt_generation_post_types_post' ).check(); cy.get( '#submit' ).click(); cy.optInAllFeatures(); cy.disableClassicEditor(); @@ -89,11 +90,10 @@ describe( '[Language processing] Excerpt Generation Tests', () => { const data = getChatGPTData(); - cy.classicCreatePost( { - title: 'Excerpt test classic', - content: 'Test GPT content.', - postType: 'post', - } ); + cy.visit( '/wp-admin/post-new.php' ); + cy.get( '#title' ).type( 'Test ChatGPT post' ); + cy.get( '#publish' ).click(); + cy.get( '#message' ).should( 'contain.text', 'Post published' ); // Ensure excerpt metabox is shown. cy.get( '#show-settings-link' ).click(); From 6ab37ad59dde8967dae43049accf2299cad191aa Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 31 Jan 2024 09:58:38 -0700 Subject: [PATCH 124/127] Fix tests that were failing due to the Classic Editor --- includes/Classifai/Features/Feature.php | 2 + .../Classifai/Providers/OpenAI/Embeddings.php | 5 +-- ...lassify-content-openapi-embeddings.test.js | 7 +++- ...excerpt-generation-openapi-chatgpt.test.js | 10 +++-- .../text-to-speech-microsoft-azure.test.js | 4 +- tests/cypress/support/commands.js | 42 +++++++++++++++++++ 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 6678adc6a..66ac277aa 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -61,6 +61,8 @@ public function setup() { /** * Setup any hooks the feature needs. + * + * Only fires if the feature is enabled. */ public function feature_setup() { } diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php index 7e7ea880e..13ebdbea6 100644 --- a/includes/Classifai/Providers/OpenAI/Embeddings.php +++ b/includes/Classifai/Providers/OpenAI/Embeddings.php @@ -651,9 +651,8 @@ public function generate_embeddings( int $id = 0, $type = 'post' ) { $feature = new Classification(); $settings = $feature->get_settings(); - // This check should have already run but if someone were to call - // this method directly, we run it again. - if ( ! $feature->is_enabled() ) { + // Ensure the feature is enabled. + if ( ! $feature->is_feature_enabled() ) { return new WP_Error( 'not_enabled', esc_html__( 'Classification is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); } diff --git a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js index c0ef814e7..7d267e71f 100644 --- a/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js +++ b/tests/cypress/integration/language-processing/classify-content-openapi-embeddings.test.js @@ -181,7 +181,7 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { cy.get( '#number_of_terms' ).clear().type( 1 ); cy.get( '#submit' ).click(); - cy.classicCreatePost( { + cy.createClassicPost( { title: 'Embeddings test classic', content: "This feature uses OpenAI's Embeddings capabilities.", postType: 'post', @@ -194,6 +194,8 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { } ); it( 'Can enable/disable content classification feature ', () => { + cy.disableClassicEditor(); + // Disable feature. cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_classification' @@ -221,6 +223,9 @@ describe( '[Language processing] Classify Content (OpenAI) Tests', () => { '/wp-admin/tools.php?page=classifai&tab=language_processing' ); + // Disable user-based access. + cy.get( '#user_based_access' ).uncheck(); + cy.get( '#submit' ).click(); // Disable admin role. diff --git a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js index 99d110318..0e02fb5c0 100644 --- a/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js +++ b/tests/cypress/integration/language-processing/excerpt-generation-openapi-chatgpt.test.js @@ -90,10 +90,11 @@ describe( '[Language processing] Excerpt Generation Tests', () => { const data = getChatGPTData(); - cy.visit( '/wp-admin/post-new.php' ); - cy.get( '#title' ).type( 'Test ChatGPT post' ); - cy.get( '#publish' ).click(); - cy.get( '#message' ).should( 'contain.text', 'Post published' ); + cy.createClassicPost( { + title: 'Excerpt test classic', + content: 'Test GPT content.', + postType: 'post', + } ); // Ensure excerpt metabox is shown. cy.get( '#show-settings-link' ).click(); @@ -111,6 +112,7 @@ describe( '[Language processing] Excerpt Generation Tests', () => { it( 'Can set multiple custom excerpt generation prompts, select one as the default and delete one.', () => { cy.disableClassicEditor(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_excerpt_generation' ); diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index e6863a18f..17302c216 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -93,7 +93,7 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can see the enable button in a post (Classic Editor)', () => { cy.enableClassicEditor(); - cy.classicCreatePost( { + cy.createClassicPost( { title: 'Text to Speech test classic', content: "This feature uses Microsoft's Text to Speech capabilities.", @@ -111,6 +111,8 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => } ); it( 'Disable support for post type Post', () => { + cy.disableClassicEditor(); + cy.visit( '/wp-admin/tools.php?page=classifai&tab=language_processing&feature=feature_text_to_speech_generation' ); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 115eeb089..9c1513100 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -458,3 +458,45 @@ Cypress.Commands.add( 'enableClassicEditor', () => { } } ); } ); + +Cypress.Commands.add( + 'createClassicPost', + ( { + postType = 'post', + title = 'Test Post', + content = 'Test content', + status = 'publish', + beforeSave, + } ) => { + cy.visit( `/wp-admin/post-new.php?post_type=${ postType }` ); + + cy.get( '#title' ).click().clear().type( title ); + + cy.get( '#content_ifr' ).then( ( $iframe ) => { + const doc = $iframe.contents().find( 'body#tinymce' ); + cy.wrap( doc ).find( 'p:last-child' ).type( content ); + } ); + + if ( 'undefined' !== typeof beforeSave ) { + beforeSave(); + } + + cy.intercept( 'POST', '/wp-admin/post.php', ( req ) => { + req.alias = 'savePost'; + } ); + + if ( 'draft' === status ) { + cy.get( '#save-post' ) + .should( 'not.have.class', 'disabled' ) + .click(); + } else { + cy.get( '#publish' ).should( 'not.have.class', 'disabled' ).click(); + } + + cy.wait( '@savePost' ).then( ( response ) => { + const body = new URLSearchParams( response.request?.body ); + const id = body.get( 'post_ID' ); + cy.wrap( id ); + } ); + } +); From 192211864350e3e24a5a85fb2af8402c2329ad17 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 31 Jan 2024 10:00:40 -0700 Subject: [PATCH 125/127] Fix remaining PHPCS issues --- includes/Classifai/Features/Feature.php | 2 +- includes/Classifai/Plugin.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 66ac277aa..7f11af94e 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -1220,7 +1220,7 @@ public function register_endpoints() {} * @param WP_REST_Request $request The full request object. * @return \WP_REST_Response|WP_Error */ - public function rest_endpoint_callback( WP_REST_Request $request ) { + public function rest_endpoint_callback( WP_REST_Request $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found return rest_ensure_response( new WP_Error( 'invalid_route', esc_html__( 'Invalid route.', 'classifai' ) ) ); } diff --git a/includes/Classifai/Plugin.php b/includes/Classifai/Plugin.php index b4b2af578..0de37a195 100644 --- a/includes/Classifai/Plugin.php +++ b/includes/Classifai/Plugin.php @@ -163,8 +163,8 @@ public function enqueue_admin_assets() { array( get_asset_info( 'admin', 'version' ), array( - 'wp-jquery-ui-dialog' - ) + 'wp-jquery-ui-dialog', + ), ), 'all' ); From c47bcf3c4917bc9590be24e3b6be2d378172c7b2 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Wed, 31 Jan 2024 22:51:14 +0530 Subject: [PATCH 126/127] fix text to speech test --- .../text-to-speech-microsoft-azure.test.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index e6863a18f..2954e1cb2 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -93,12 +93,15 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can see the enable button in a post (Classic Editor)', () => { cy.enableClassicEditor(); - cy.classicCreatePost( { - title: 'Text to Speech test classic', - content: - "This feature uses Microsoft's Text to Speech capabilities.", - postType: 'post', + cy.visit( '/wp-admin/post-new.php' ); + cy.get( '#title' ).type( 'Text to Speech test classic' ); + cy.get('#content_ifr').then( $iframe => { + const doc = $iframe.contents().find( 'body#tinymce' ); + cy.wrap( doc ).find( 'p:last-child' ).type( "This feature uses Microsoft's Text to Speech capabilities." ); } ); + cy.wait( 2000 ); + cy.get( '#publish' ).click(); + cy.get( '#message' ).should( 'contain.text', 'Post published' ); cy.get( '#classifai-text-to-speech-meta-box' ).should( 'exist' ); cy.get( '#classifai_synthesize_speech' ).check(); From bef80818c9552f2c189af1639d24b49475ee38ff Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Wed, 31 Jan 2024 11:01:16 -0700 Subject: [PATCH 127/127] Don't run E2E tests on trunk for now as there's some known failures that need fixed upstream. Update all GitHub Action dependencies --- .github/workflows/build-release-zip.yml | 6 +++--- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/cypress.yml | 7 ++++--- .github/workflows/dependency-review.yml | 4 ++-- .github/workflows/lint.yml | 14 +++++++------- .github/workflows/release.yml | 4 ++-- .github/workflows/stable.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- .../text-to-speech-microsoft-azure.test.js | 13 +++++-------- 9 files changed, 30 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build-release-zip.yml b/.github/workflows/build-release-zip.yml index e0738668c..c8f139de0 100644 --- a/.github/workflows/build-release-zip.yml +++ b/.github/workflows/build-release-zip.yml @@ -9,9 +9,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set Node.js 16.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: .nvmrc - name: npm install and build @@ -22,7 +22,7 @@ jobs: composer install --no-dev npm run archive - name: Upload the ZIP file as an artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ github.event.repository.name }} path: release diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9f6fec6ce..a75908f2c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index efc16a857..5a6223b20 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -8,6 +8,7 @@ on: pull_request: branches: - develop + jobs: cypress: name: ${{ matrix.core.name }} @@ -17,10 +18,10 @@ jobs: core: - {name: 'WP latest', version: 'latest'} - {name: 'WP minimum', version: 'WordPress/WordPress#6.1'} - - {name: 'WP trunk', version: 'WordPress/WordPress#master'} + # - {name: 'WP trunk', version: 'WordPress/WordPress#master'} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: npm install @@ -51,7 +52,7 @@ jobs: cat ./tests/cypress/reports/mochawesome.md >> $GITHUB_STEP_SUMMARY - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: cypress-artifact-classifai diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 070280083..bbb2ff8e8 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Dependency Review - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 with: license-check: true vulnerability-check: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2c18859a8..21eabce03 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup node v16 and npm cache - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm @@ -32,7 +32,7 @@ jobs: - name: Get updated JS files id: changed-files - uses: tj-actions/changed-files@v41 + uses: tj-actions/changed-files@v42 with: files: | **/*.js @@ -52,14 +52,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set standard 10up cache directories run: | composer config -g cache-dir "${{ env.COMPOSER_CACHE }}" - name: Prepare composer cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE }} key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} @@ -78,7 +78,7 @@ jobs: - name: Get updated PHP files id: changed-files - uses: tj-actions/changed-files@v41 + uses: tj-actions/changed-files@v42 with: files: | **/*.php @@ -97,7 +97,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: VIPCS check uses: 10up/wpcs-action@stable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cfeb4000..03e5b4d17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set Node.js 16.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index cb43f76b2..c71c849fc 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -9,11 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set Node.js 16.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 16.x + node-version-file: .nvmrc - name: npm install and build run: | npm install diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b73ddbcb..7e1066fd6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,14 +27,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v4 - name: Set standard 10up cache directories run: | composer config -g cache-dir "${{ env.COMPOSER_CACHE }}" - name: Prepare composer cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE }} key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} @@ -44,7 +44,7 @@ jobs: - uses: getong/mariadb-action@v1.1 - name: Set PHP version - uses: shivammathur/setup-php@2.17.0 + uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: none diff --git a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js index bc64ed996..17302c216 100644 --- a/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js +++ b/tests/cypress/integration/language-processing/text-to-speech-microsoft-azure.test.js @@ -93,15 +93,12 @@ describe( '[Language Processing] Text to Speech (Microsoft Azure) Tests', () => it( 'Can see the enable button in a post (Classic Editor)', () => { cy.enableClassicEditor(); - cy.visit( '/wp-admin/post-new.php' ); - cy.get( '#title' ).type( 'Text to Speech test classic' ); - cy.get('#content_ifr').then( $iframe => { - const doc = $iframe.contents().find( 'body#tinymce' ); - cy.wrap( doc ).find( 'p:last-child' ).type( "This feature uses Microsoft's Text to Speech capabilities." ); + cy.createClassicPost( { + title: 'Text to Speech test classic', + content: + "This feature uses Microsoft's Text to Speech capabilities.", + postType: 'post', } ); - cy.wait( 2000 ); - cy.get( '#publish' ).click(); - cy.get( '#message' ).should( 'contain.text', 'Post published' ); cy.get( '#classifai-text-to-speech-meta-box' ).should( 'exist' ); cy.get( '#classifai_synthesize_speech' ).check();