From 038235a642137a62a408153df7d4740c3b5aaa5b Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:26:55 +0530 Subject: [PATCH 01/31] Add REST endpoint to retrieve excerpt target field --- .../Classifai/Features/ExcerptGeneration.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index e0bea64d0..938d60bbf 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -143,6 +143,25 @@ public function register_endpoints() { ], ] ); + + // Add endpoint to get target field value. + register_rest_route( + 'classifai/v1', + 'get-target-field-value/(?P\d+)', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_target_field_value_callback' ], + 'args' => [ + 'id' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Post ID to get target field value for.', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'generate_excerpt_permissions_check' ], + ] + ); } /** From 962a072edc8624c824880d176240e95a20a50d3e Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:27:31 +0530 Subject: [PATCH 02/31] Save to target field upon successful generation --- .../Classifai/Features/ExcerptGeneration.php | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 938d60bbf..4a6d98716 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -234,17 +234,25 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { */ $author_name = apply_filters( 'classifai_excerpt_generation_author_name', $author_name, $post_id ); - return rest_ensure_response( - $this->run( - $post_id, - 'excerpt', - [ - 'content' => $request->get_param( 'content' ), - 'title' => $request->get_param( 'title' ), - 'author' => $author_name, - ] - ) + $result = $this->run( + $post_id, + 'excerpt', + [ + 'content' => $request->get_param( 'content' ), + 'title' => $request->get_param( 'title' ), + 'author' => $author_name, + ] ); + + // If generation was successful and we have a post ID, save to target field. + if ( ! is_wp_error( $result ) && $post_id ) { + $save_result = $this->save_excerpt_to_target_field( $result, $post_id ); + if ( is_wp_error( $save_result ) ) { + return rest_ensure_response( $save_result ); + } + } + + return rest_ensure_response( $result ); } return parent::rest_endpoint_callback( $request ); From 8acc4f43715b4cb32bc6e9882f0edbf317a43b80 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:28:07 +0530 Subject: [PATCH 03/31] Include target field information in the inline script --- includes/Classifai/Features/ExcerptGeneration.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 4a6d98716..cd80259b2 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -307,15 +307,19 @@ public function enqueue_admin_assets( string $hook_suffix ) { true ); + // Get target field information for JavaScript. + $target_field_info = $this->get_target_field_info(); + wp_add_inline_script( 'classifai-plugin-classic-excerpt-generation-js', sprintf( 'var classifaiGenerateExcerpt = %s;', wp_json_encode( [ - 'path' => '/classifai/v1/generate-excerpt/', - 'buttonText' => __( 'Generate excerpt', 'classifai' ), - 'regenerateText' => __( 'Re-generate excerpt', 'classifai' ), + 'path' => '/classifai/v1/generate-excerpt/', + 'buttonText' => __( 'Generate excerpt', 'classifai' ), + 'regenerateText' => __( 'Re-generate excerpt', 'classifai' ), + 'target_field_info' => $target_field_info, ] ) ), From fd187a59a4fe7ce64f989419f28be0dd67e3637c Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:29:08 +0530 Subject: [PATCH 04/31] Include configuration options for saving generated excerpts --- includes/Classifai/Features/ExcerptGeneration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index cd80259b2..2cc3de1ac 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -336,7 +336,7 @@ public function enqueue_admin_assets( string $hook_suffix ) { * @return string */ public function get_enable_description(): string { - return esc_html__( 'A button will be added to the excerpt panel that can be used to generate an excerpt.', 'classifai' ); + return esc_html__( 'A button will be added to the excerpt panel that can be used to generate an excerpt. You can configure where the generated excerpt is saved (default excerpt field, custom meta fields, or ACF fields).', 'classifai' ); } /** From 38cd5284b486c2db777ea8355df3964275cc3173 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:29:47 +0530 Subject: [PATCH 05/31] Add target field settings for excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 2cc3de1ac..2fe4f201b 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -396,6 +396,20 @@ public function add_custom_settings_fields() { 'description' => __( 'How many words should the excerpt be? Note that the final result may not exactly match this, it often tends to exceed this number by 10-15 words.', 'classifai' ), ] ); + + // Add target field settings. + add_settings_field( + 'target_field', + esc_html__( 'Target field', 'classifai' ), + [ $this, 'render_target_field_settings' ], + $this->get_option_name(), + $this->get_option_name() . '_section', + [ + 'label_for' => 'target_field', + 'default_value' => $settings['target_field'] ?? 'post_excerpt', + 'description' => __( 'Choose where to save the generated excerpt. You can target the default excerpt field, custom meta fields, or ACF fields.', 'classifai' ), + ] + ); } /** From 619b5820fa7d2233e6891c6731d2a0d568a74e4b Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:30:11 +0530 Subject: [PATCH 06/31] Add rendering functionality for target field settings in excerpt generation --- .../Classifai/Features/ExcerptGeneration.php | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 2fe4f201b..f0478b46d 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -412,6 +412,95 @@ public function add_custom_settings_fields() { ); } + /** + * Render the target field settings. + * + * @param array $args Field arguments. + */ + public function render_target_field_settings( $args ) { + $settings = $this->get_settings(); + $target_field = $settings['target_field'] ?? 'post_excerpt'; + $field_type = $settings['target_field_type'] ?? 'post_excerpt'; + $custom_field = $settings['target_custom_field'] ?? ''; + + ?> +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + + Date: Fri, 8 Aug 2025 14:30:26 +0530 Subject: [PATCH 07/31] Add target field configuration options for excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index f0478b46d..7cdbad99e 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -520,6 +520,9 @@ public function get_feature_default_settings(): array { ], 'length' => absint( apply_filters( 'excerpt_length', 55 ) ), 'provider' => ChatGPT::ID, + 'target_field_type' => 'post_excerpt', + 'target_custom_field' => '', + 'target_acf_field' => '', ]; } From c277806ffa8c3a08fed42308f51677b808d33d57 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:30:40 +0530 Subject: [PATCH 08/31] Sanitize target field settings in excerpt generation configuration --- includes/Classifai/Features/ExcerptGeneration.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 7cdbad99e..970434dbe 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -562,6 +562,15 @@ public function sanitize_default_feature_settings( array $new_settings ): array $new_settings['length'] = absint( $new_settings['length'] ?? $settings['length'] ); + // Sanitize target field settings. + $new_settings['target_field_type'] = sanitize_text_field( $new_settings['target_field_type'] ?? 'post_excerpt' ); + + if ( 'custom_meta' === $new_settings['target_field_type'] ) { + $new_settings['target_custom_field'] = sanitize_text_field( $new_settings['target_custom_field'] ?? '' ); + } elseif ( 'acf_field' === $new_settings['target_field_type'] ) { + $new_settings['target_acf_field'] = sanitize_text_field( $new_settings['target_acf_field'] ?? '' ); + } + foreach ( $post_types as $post_type ) { if ( ! post_type_supports( $post_type->name, 'excerpt' ) ) { continue; From 255ce8f9da6fc807892751c2a8b8237d884769ac Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:30:56 +0530 Subject: [PATCH 09/31] Add default target field type for existing installations in excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 970434dbe..114600c21 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -630,6 +630,9 @@ public function migrate_settings() { $new_settings['user_based_opt_out'] = $old_settings['excerpt_generation_user_based_opt_out']; } + // Set default target field type for existing installations. + $new_settings['target_field_type'] = 'post_excerpt'; + return $new_settings; } } From 26fc6c65fb602fedbd199d31dd8ea1ca72d74aa5 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:31:42 +0530 Subject: [PATCH 10/31] Add functionality to save generated excerpts to various target fields --- .../Classifai/Features/ExcerptGeneration.php | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 114600c21..780bcbb4f 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -635,4 +635,87 @@ public function migrate_settings() { return $new_settings; } + + /** + * Save the generated excerpt to the target field. + * + * @param string $excerpt The generated excerpt. + * @param int $post_id The post ID. + * @return bool|WP_Error True on success, WP_Error on failure. + */ + public function save_excerpt_to_target_field( $excerpt, $post_id ) { + $settings = $this->get_settings(); + $field_type = $settings['target_field_type'] ?? 'post_excerpt'; + + /** + * Filter the excerpt before saving to target field. + * + * @since 3.x.x + * @hook classifai_excerpt_before_save_to_target + * + * @param {string} $excerpt The generated excerpt. + * @param {int} $post_id The post ID. + * @param {string} $field_type The target field type. + * + * @return {string} The excerpt to save. + */ + $excerpt = apply_filters( 'classifai_excerpt_before_save_to_target', $excerpt, $post_id, $field_type ); + + switch ( $field_type ) { + case 'post_excerpt': + $result = wp_update_post( [ + 'ID' => $post_id, + 'post_excerpt' => wp_kses_post( $excerpt ), + ] ); + return is_wp_error( $result ) ? $result : true; + + case 'custom_meta': + $meta_key = $settings['target_custom_field'] ?? ''; + if ( empty( $meta_key ) ) { + return new WP_Error( 'no_meta_key', __( 'No custom meta key specified.', 'classifai' ) ); + } + + /** + * Filter the meta key for custom meta field saving. + * + * @since 3.x.x + * @hook classifai_excerpt_custom_meta_key + * + * @param {string} $meta_key The meta key. + * @param {int} $post_id The post ID. + * + * @return {string} The meta key to use. + */ + $meta_key = apply_filters( 'classifai_excerpt_custom_meta_key', $meta_key, $post_id ); + + update_post_meta( $post_id, $meta_key, wp_kses_post( $excerpt ) ); + return true; + + case 'acf_field': + $acf_field_key = $settings['target_acf_field'] ?? ''; + if ( empty( $acf_field_key ) ) { + return new WP_Error( 'no_acf_field', __( 'No ACF field specified.', 'classifai' ) ); + } + if ( function_exists( 'update_field' ) ) { + update_field( $acf_field_key, wp_kses_post( $excerpt ), $post_id ); + return true; + } + return new WP_Error( 'acf_not_available', __( 'ACF plugin is not available.', 'classifai' ) ); + + default: + /** + * Filter for custom target field types. + * + * @since 3.x.x + * @hook classifai_excerpt_save_to_custom_target + * + * @param {string} $excerpt The generated excerpt. + * @param {int} $post_id The post ID. + * @param {string} $field_type The target field type. + * + * @return {bool|WP_Error} True on success, WP_Error on failure. + */ + return apply_filters( 'classifai_excerpt_save_to_custom_target', new WP_Error( 'invalid_field_type', __( 'Invalid target field type.', 'classifai' ) ), $excerpt, $post_id, $field_type ); + } + } } From a88e4575b528b3fb57f7c445a2b2baba074fe256 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:32:08 +0530 Subject: [PATCH 11/31] Add method to retrieve current value from target field in excerpt generation --- .../Classifai/Features/ExcerptGeneration.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 780bcbb4f..f32f5df3a 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -718,4 +718,46 @@ public function save_excerpt_to_target_field( $excerpt, $post_id ) { return apply_filters( 'classifai_excerpt_save_to_custom_target', new WP_Error( 'invalid_field_type', __( 'Invalid target field type.', 'classifai' ) ), $excerpt, $post_id, $field_type ); } } + + /** + * Get the current value from the target field. + * + * @param int $post_id The post ID. + * @return string The current value. + */ + public function get_current_target_field_value( $post_id ) { + $settings = $this->get_settings(); + $field_type = $settings['target_field_type'] ?? 'post_excerpt'; + + switch ( $field_type ) { + case 'post_excerpt': + return get_the_excerpt( $post_id ); + + case 'custom_meta': + $meta_key = $settings['target_custom_field'] ?? ''; + return get_post_meta( $post_id, $meta_key, true ); + + case 'acf_field': + $acf_field_key = $settings['target_acf_field'] ?? ''; + if ( function_exists( 'get_field' ) ) { + return get_field( $acf_field_key, $post_id ); + } + return ''; + + default: + /** + * Filter for custom target field types when getting current value. + * + * @since 3.x.x + * @hook classifai_excerpt_get_custom_target_value + * + * @param {string} $value The current value. + * @param {int} $post_id The post ID. + * @param {string} $field_type The target field type. + * + * @return {string} The current value. + */ + return apply_filters( 'classifai_excerpt_get_custom_target_value', '', $post_id, $field_type ); + } + } } From 1907609ae3fcee635595e1ab80e4d81849f17cfc Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:33:20 +0530 Subject: [PATCH 12/31] Add method to retrieve available ACF fields for target field selector in excerpt generation --- .../Classifai/Features/ExcerptGeneration.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index f32f5df3a..94c0115bf 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -760,4 +760,36 @@ public function get_current_target_field_value( $post_id ) { return apply_filters( 'classifai_excerpt_get_custom_target_value', '', $post_id, $field_type ); } } + + /** + * Get available ACF fields for the target field selector. + * + * @return array Array of ACF fields. + */ + public function get_available_acf_fields() { + $fields = []; + + if ( ! function_exists( 'acf_get_field_groups' ) ) { + return $fields; + } + + $field_groups = acf_get_field_groups(); + foreach ( $field_groups as $field_group ) { + $acf_fields = acf_get_fields( $field_group ); + if ( $acf_fields ) { + foreach ( $acf_fields as $field ) { + // Only include text-based fields. + if ( in_array( $field['type'], [ 'text', 'textarea', 'wysiwyg' ], true ) ) { + $fields[] = [ + 'key' => $field['key'], + 'label' => $field['label'], + 'group' => $field_group['title'], + ]; + } + } + } + } + + return $fields; + } } From e71a690e097df67bb17a570c49f4008ccd16359a Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:33:47 +0530 Subject: [PATCH 13/31] Add method to retrieve target field information for JavaScript in excerpt generation --- .../Classifai/Features/ExcerptGeneration.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 94c0115bf..862b99ca9 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -792,4 +792,38 @@ public function get_available_acf_fields() { return $fields; } + + /** + * Get target field information for JavaScript. + * + * @return array Target field information. + */ + public function get_target_field_info() { + $settings = $this->get_settings(); + $field_type = $settings['target_field_type'] ?? 'post_excerpt'; + + $info = [ + 'field_type' => $field_type, + 'field_name' => __( 'Default excerpt field', 'classifai' ), + ]; + + switch ( $field_type ) { + case 'custom_meta': + $meta_key = $settings['target_custom_field'] ?? ''; + $info['field_name'] = $meta_key ? sprintf( __( 'Custom meta: %s', 'classifai' ), $meta_key ) : __( 'Custom meta field', 'classifai' ); + break; + + case 'acf_field': + $acf_field_key = $settings['target_acf_field'] ?? ''; + if ( $acf_field_key && function_exists( 'acf_get_field' ) ) { + $acf_field = acf_get_field( $acf_field_key ); + $info['field_name'] = $acf_field ? $acf_field['label'] : __( 'ACF field', 'classifai' ); + } else { + $info['field_name'] = __( 'ACF field', 'classifai' ); + } + break; + } + + return $info; + } } From c6018369967ed19da7039a427df1ed10ca8e11a2 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:34:15 +0530 Subject: [PATCH 14/31] Add method to check if ACF is available and active in excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 862b99ca9..842cf1d61 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -826,4 +826,13 @@ public function get_target_field_info() { return $info; } + + /** + * Check if ACF is available and active. + * + * @return bool True if ACF is available. + */ + public function is_acf_available() { + return function_exists( 'acf_get_field_groups' ) && function_exists( 'acf_get_fields' ); + } } From f9a089552a6ccda559f9ccdc5c7dee5d84ea12af Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:34:42 +0530 Subject: [PATCH 15/31] Add notification for target field when excerpt is generated, with auto-dismiss after 5 seconds --- .../excerpt-generation/classic/index.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/js/features/excerpt-generation/classic/index.js b/src/js/features/excerpt-generation/classic/index.js index 5e4d000f3..120317a27 100644 --- a/src/js/features/excerpt-generation/classic/index.js +++ b/src/js/features/excerpt-generation/classic/index.js @@ -140,6 +140,27 @@ const classifaiExcerptData = window.classifaiGenerateExcerpt || {}; ? __( 'Re-generate short description', 'classifai' ) : classifaiExcerptData?.regenerateText ?? '' ); + + // Show notification about target field if different from excerpt. + if ( classifaiExcerptData?.target_field_info ) { + const targetInfo = classifaiExcerptData.target_field_info; + if ( targetInfo.field_type !== 'post_excerpt' ) { + const notification = $( '
', { + class: 'notice notice-success', + style: 'margin-top: 10px;', + } ).append( + $( '

', { + text: __( 'Excerpt generated and saved to target field: ', 'classifai' ) + targetInfo.field_name, + } ) + ); + $( excerptContainer ).after( notification ); + + // Remove notification after 5 seconds. + setTimeout( () => { + notification.fadeOut(); + }, 5000 ); + } + } } ) .catch( ( error ) => { generateTextEl.css( 'opacity', '1' ); From 0de02324b470aac73dabcfebe7920ad6cfecbf12 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:35:03 +0530 Subject: [PATCH 16/31] Implement target field value retrieval on component mount in excerpt generation panel --- src/js/features/excerpt-generation/panel.js | 40 +++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/js/features/excerpt-generation/panel.js b/src/js/features/excerpt-generation/panel.js index f14d1c79a..1eba20606 100644 --- a/src/js/features/excerpt-generation/panel.js +++ b/src/js/features/excerpt-generation/panel.js @@ -28,14 +28,32 @@ import { browserAITextGeneration } from '../../helpers'; function PostExcerpt( { excerpt, onUpdateExcerpt } ) { const [ isLoading, setIsLoading ] = useState( false ); const [ error, setError ] = useState( false ); + const [ targetFieldValue, setTargetFieldValue ] = useState( '' ); - const { select } = wp.data; - const postId = select( 'core/editor' ).getCurrentPostId(); - const buttonText = - '' === excerpt - ? __( 'Generate excerpt', 'classifai' ) - : __( 'Re-generate excerpt', 'classifai' ); - const isPublishPanelOpen = select( 'core/editor' ).isPublishSidebarOpened(); + const postId = useSelect( ( select ) => + select( 'core/editor' ).getCurrentPostId() + ); + const isPublishPanelOpen = useSelect( ( select ) => + select( 'core/edit-post' ).isPublishSidebarOpened() + ); + + // Get target field value on component mount. + useEffect( () => { + if ( postId ) { + apiFetch( { + path: `/classifai/v1/get-target-field-value/${ postId }`, + } ).then( ( result ) => { + setTargetFieldValue( result.value || '' ); + } ).catch( () => { + // If endpoint doesn't exist, fall back to excerpt. + setTargetFieldValue( excerpt || '' ); + } ); + } + }, [ postId, excerpt ] ); + + const buttonText = excerpt + ? __( 'Re-generate excerpt', 'classifai' ) + : __( 'Generate excerpt', 'classifai' ); const buttonClick = async ( path ) => { const postContent = @@ -87,7 +105,9 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { } } + // Update both the excerpt field and target field value. onUpdateExcerpt( res.trim() ); + setTargetFieldValue( res.trim() ); setError( false ); setIsLoading( false ); }, @@ -150,6 +170,12 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { { error } ) } + { targetFieldValue && targetFieldValue !== excerpt && ( +

+ { __( 'Target field value:', 'classifai' ) } +

{ targetFieldValue }

+
+ ) }
); From d07c2652d6962bc343e34d460c822140e6262ab7 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:35:19 +0530 Subject: [PATCH 17/31] Add SelectControl for target field selection in excerpt generation settings --- .../excerpt-generation.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/js/settings/components/feature-additional-settings/excerpt-generation.js b/src/js/settings/components/feature-additional-settings/excerpt-generation.js index bcadef3e4..ce1840fdd 100644 --- a/src/js/settings/components/feature-additional-settings/excerpt-generation.js +++ b/src/js/settings/components/feature-additional-settings/excerpt-generation.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; import { __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis CheckboxControl, + SelectControl, } from '@wordpress/components'; /** @@ -34,6 +35,9 @@ export const ExcerptGenerationSettings = () => { } ); }; + // Get available ACF fields if ACF is active. + const acfFields = window.classifAISettings?.acfFields || []; + return ( <> { } /> + + 0 ? [ + { + label: __( 'ACF field', 'classifai' ), + value: 'acf_field', + }, + ] : [] ), + ] } + onChange={ ( value ) => + setFeatureSettings( { target_field_type: value } ) + } + /> + { featureSettings.target_field_type === 'custom_meta' && ( + + setFeatureSettings( { target_custom_field: value } ) + } + placeholder={ __( 'e.g., editorial_subtitle, custom_excerpt', 'classifai' ) } + /> + ) } + { featureSettings.target_field_type === 'acf_field' && ( + ( { + label: field.label, + value: field.key, + } ) ), + ] } + onChange={ ( value ) => + setFeatureSettings( { target_acf_field: value } ) + } + /> + ) } + ); }; From a83c6f994650f2dca403fa9416db80ac47e25ea6 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 14:46:45 +0530 Subject: [PATCH 18/31] Add REST API callback to retrieve target field value in excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 842cf1d61..88dd84cdf 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -258,6 +258,21 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { return parent::rest_endpoint_callback( $request ); } + /** + * REST API callback to get target field value. + * + * @param WP_REST_Request $request The request object. + * @return WP_REST_Response + */ + public function get_target_field_value_callback( WP_REST_Request $request ) { + $post_id = $request->get_param( 'id' ); + $value = $this->get_current_target_field_value( $post_id ); + + return rest_ensure_response( [ + 'value' => $value, + ] ); + } + /** * Enqueue the editor scripts. */ From f5a65d2aa5eeb718d94f06025a5ca48b54159df7 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 15:03:19 +0530 Subject: [PATCH 19/31] Ensure proper handling of generated content in excerpt generation --- includes/Classifai/Features/ExcerptGeneration.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 88dd84cdf..9207d37e7 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -246,7 +246,9 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { // If generation was successful and we have a post ID, save to target field. if ( ! is_wp_error( $result ) && $post_id ) { - $save_result = $this->save_excerpt_to_target_field( $result, $post_id ); + $excerpt = is_array( $result ) ? $result['content'] : $result; + $excerpt = is_string( $excerpt ) ? $excerpt : ''; + $save_result = $this->save_excerpt_to_target_field( $excerpt, $post_id ); if ( is_wp_error( $save_result ) ) { return rest_ensure_response( $save_result ); } From 7835411c9ed48557a901ad34ff6037caff50b3a9 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 15:03:34 +0530 Subject: [PATCH 20/31] Improve state management and update UI labels --- src/js/features/excerpt-generation/panel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/features/excerpt-generation/panel.js b/src/js/features/excerpt-generation/panel.js index 1eba20606..2b9dba228 100644 --- a/src/js/features/excerpt-generation/panel.js +++ b/src/js/features/excerpt-generation/panel.js @@ -3,9 +3,9 @@ */ import { __ } from '@wordpress/i18n'; import { Button, ExternalLink, TextareaControl } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { withSelect, withDispatch, useSelect, select } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { useState } from '@wordpress/element'; +import { useState, useEffect } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; /** @@ -172,7 +172,7 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { ) } { targetFieldValue && targetFieldValue !== excerpt && (
- { __( 'Target field value:', 'classifai' ) } + { __( 'Custom excerpt:', 'classifai' ) }

{ targetFieldValue }

) } From 316ca46406850695b77da79683b2bb007af93132 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 16:48:00 +0530 Subject: [PATCH 21/31] Add REST API endpoint to retrieve target field settings and enhance permission checks in excerpt generation --- .../Classifai/Features/ExcerptGeneration.php | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index 9207d37e7..7a1c2102c 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -162,6 +162,17 @@ public function register_endpoints() { 'permission_callback' => [ $this, 'generate_excerpt_permissions_check' ], ] ); + + // Add endpoint to get target field settings. + register_rest_route( + 'classifai/v1', + 'get-target-field-settings', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_target_field_settings_callback' ], + 'permission_callback' => [ $this, 'generate_excerpt_permissions_check' ], + ] + ); } /** @@ -178,8 +189,23 @@ public function register_endpoints() { public function generate_excerpt_permissions_check( WP_REST_Request $request ) { $post_id = $request->get_param( 'id' ); + // For endpoints that don't require a post ID (like get-target-field-settings), + // just check if the feature is enabled and user has appropriate permissions. + if ( empty( $post_id ) ) { + if ( ! current_user_can( 'edit_posts' ) ) { + 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; + } + // Ensure we have a logged in user that can edit the item. - if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { + if ( ! current_user_can( 'edit_post', $post_id ) ) { return false; } @@ -246,9 +272,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { // If generation was successful and we have a post ID, save to target field. if ( ! is_wp_error( $result ) && $post_id ) { - $excerpt = is_array( $result ) ? $result['content'] : $result; - $excerpt = is_string( $excerpt ) ? $excerpt : ''; - $save_result = $this->save_excerpt_to_target_field( $excerpt, $post_id ); + $save_result = $this->save_excerpt_to_target_field( $result, $post_id ); if ( is_wp_error( $save_result ) ) { return rest_ensure_response( $save_result ); } @@ -275,6 +299,17 @@ public function get_target_field_value_callback( WP_REST_Request $request ) { ] ); } + /** + * REST API callback to get target field settings. + * + * @return WP_REST_Response + */ + public function get_target_field_settings_callback() { + $settings = $this->get_target_field_info(); + + return rest_ensure_response( $settings ); + } + /** * Enqueue the editor scripts. */ @@ -828,6 +863,7 @@ public function get_target_field_info() { case 'custom_meta': $meta_key = $settings['target_custom_field'] ?? ''; $info['field_name'] = $meta_key ? sprintf( __( 'Custom meta: %s', 'classifai' ), $meta_key ) : __( 'Custom meta field', 'classifai' ); + $info['meta_key'] = $meta_key; break; case 'acf_field': @@ -835,6 +871,7 @@ public function get_target_field_info() { if ( $acf_field_key && function_exists( 'acf_get_field' ) ) { $acf_field = acf_get_field( $acf_field_key ); $info['field_name'] = $acf_field ? $acf_field['label'] : __( 'ACF field', 'classifai' ); + $info['meta_key'] = $acf_field_key; } else { $info['field_name'] = __( 'ACF field', 'classifai' ); } From bb9e7116781d838c040c1b43dea45933def42133 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 17:10:36 +0530 Subject: [PATCH 22/31] Enhance excerpt generation panel by adding custom field display and event listeners for meta field changes --- src/js/features/excerpt-generation/panel.js | 225 +++++++++++++++++--- 1 file changed, 198 insertions(+), 27 deletions(-) diff --git a/src/js/features/excerpt-generation/panel.js b/src/js/features/excerpt-generation/panel.js index 2b9dba228..a4b16b88c 100644 --- a/src/js/features/excerpt-generation/panel.js +++ b/src/js/features/excerpt-generation/panel.js @@ -2,7 +2,7 @@ * External Dependencies. */ import { __ } from '@wordpress/i18n'; -import { Button, ExternalLink, TextareaControl } from '@wordpress/components'; +import { Button, ExternalLink, TextareaControl, Notice } from '@wordpress/components'; import { withSelect, withDispatch, useSelect, select } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { useState, useEffect } from '@wordpress/element'; @@ -29,6 +29,7 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { const [ isLoading, setIsLoading ] = useState( false ); const [ error, setError ] = useState( false ); const [ targetFieldValue, setTargetFieldValue ] = useState( '' ); + const [ targetFieldSettings, setTargetFieldSettings ] = useState( null ); const postId = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostId() @@ -37,15 +38,29 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { select( 'core/edit-post' ).isPublishSidebarOpened() ); - // Get target field value on component mount. + // Get target field settings and value on component mount. useEffect( () => { if ( postId ) { + // Get target field settings + apiFetch( { + path: '/classifai/v1/get-target-field-settings/', + } ).then( ( result ) => { + setTargetFieldSettings( result ); + } ).catch( () => { + // Fall back to default settings. + setTargetFieldSettings( { + field_type: 'post_excerpt', + field_name: __( 'Default excerpt field', 'classifai' ), + } ); + } ); + + // Get target field value apiFetch( { path: `/classifai/v1/get-target-field-value/${ postId }`, } ).then( ( result ) => { setTargetFieldValue( result.value || '' ); } ).catch( () => { - // If endpoint doesn't exist, fall back to excerpt. + // Fall back to default excerpt. setTargetFieldValue( excerpt || '' ); } ); } @@ -110,6 +125,9 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { setTargetFieldValue( res.trim() ); setError( false ); setIsLoading( false ); + + // Update visible meta field values on the page if they exist. + updateVisibleMetaFields( res.trim() ); }, ( err ) => { setError( err?.message ); @@ -118,28 +136,187 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { ); }; + // Function to update visible meta field values on the page. + const updateVisibleMetaFields = ( value ) => { + if ( ! targetFieldSettings || targetFieldSettings.field_type === 'post_excerpt' ) { + return; + } + + const metaKey = targetFieldSettings.meta_key || targetFieldSettings.field_name; + if ( ! metaKey ) { + return; + } + + // Update meta field input that is visible on the page. + // Directly target the textarea by its name attribute using the metaKey. + const metaInput = document.querySelector( `textarea[name="meta[${ metaKey }][value]"]` ); + if ( metaInput ) { + metaInput.value = value; + + // Trigger input event to notify any listeners. + const inputEvent = new Event( 'input', { bubbles: true } ); + metaInput.dispatchEvent( inputEvent ); + + // Also trigger change event. + const changeEvent = new Event( 'change', { bubbles: true } ); + metaInput.dispatchEvent( changeEvent ); + } + + // Also check for ACF fields if this is an ACF field + if ( targetFieldSettings.field_type === 'acf_field' && targetFieldSettings.meta_key ) { + const acfInputs = document.querySelectorAll( `input[name*="${targetFieldSettings.meta_key}"], textarea[name*="${targetFieldSettings.meta_key}"]` ); + acfInputs.forEach( ( input ) => { + input.value = value; + + // Trigger change events + const inputEvent = new Event( 'input', { bubbles: true } ); + input.dispatchEvent( inputEvent ); + + const changeEvent = new Event( 'change', { bubbles: true } ); + input.dispatchEvent( changeEvent ); + } ); + } + }; + + // Function to update the custom field display when meta field values change + const updateCustomFieldDisplay = ( value ) => { + if ( ! shouldShowCustomField ) { + return; + } + + // Update the target field value state + setTargetFieldValue( value ); + }; + + // Function to listen for changes in meta fields and update our custom field display + const setupMetaFieldListeners = () => { + if ( ! targetFieldSettings || targetFieldSettings.field_type === 'post_excerpt' ) { + return; + } + + const metaKey = targetFieldSettings.meta_key || targetFieldSettings.field_name; + if ( ! metaKey ) { + return; + } + + // Listen for changes in meta field inputs (including both textarea and input for consistency). + // Directly target the textarea by its name attribute using the metaKey. + const metaInput = document.querySelector( `textarea[name="meta[${ metaKey }][value]"]` ); + + if ( metaInput ) { + metaInput.addEventListener( 'input', ( event ) => { + updateCustomFieldDisplay( event.target.value ); + } ); + + metaInput.addEventListener( 'change', ( event ) => { + updateCustomFieldDisplay( event.target.value ); + } ); + } + + // Listen for ACF field changes + if ( targetFieldSettings.field_type === 'acf_field' && targetFieldSettings.meta_key ) { + const acfInputs = document.querySelectorAll( `input[name*="${targetFieldSettings.meta_key}"], textarea[name*="${targetFieldSettings.meta_key}"]` ); + acfInputs.forEach( ( input ) => { + input.addEventListener( 'input', ( event ) => { + updateCustomFieldDisplay( event.target.value ); + } ); + + input.addEventListener( 'change', ( event ) => { + updateCustomFieldDisplay( event.target.value ); + } ); + } ); + } + }; + + // Set up listeners when component mounts or target field settings change + useEffect( () => { + if ( targetFieldSettings && targetFieldSettings.field_type !== 'post_excerpt' ) { + // Small delay to ensure DOM elements are available. + const timer = setTimeout( () => { + setupMetaFieldListeners(); + }, 100 ); + + return () => { + clearTimeout( timer ); + }; + } + }, [ targetFieldSettings ] ); + + // Check if we should show the custom field instead of the default excerpt field + const shouldShowCustomField = targetFieldSettings && targetFieldSettings.field_type !== 'post_excerpt'; + const fieldLabel = shouldShowCustomField + ? targetFieldSettings?.field_name || __( 'Custom excerpt', 'classifai' ) + : __( 'Write an excerpt (optional)', 'classifai' ); + return (
- onUpdateExcerpt( value ) } - value={ excerpt } - /> - { ! isPublishPanelOpen && ( - + onUpdateExcerpt( value ) } + value={ excerpt } + /> + { ! isPublishPanelOpen && ( + + { __( 'Learn more about manual excerpts' ) } + ) } - > - { __( 'Learn more about manual excerpts' ) } - + + ) } + + { shouldShowCustomField && ( + <> + + + { __( 'This excerpt is stored in a custom field. To edit it, you can:', 'classifai' ) } +
    +
  • + { __( 'Use the button below to regenerate it', 'classifai' ) } +
  • +
  • + { __( 'Edit the custom field directly in the post editor or custom fields panel', 'classifai' ) } +
  • +
  • + { __( 'Change the target field in ', 'classifai' ) } + + { __( 'ClassifAI Settings', 'classifai' ) } + +
  • +
+
+ ) } +
); From 2fb4cb9f354ffbbe1e50f4bc3f0f8504c7d2fb72 Mon Sep 17 00:00:00 2001 From: faisal-alvi Date: Fri, 8 Aug 2025 17:44:47 +0530 Subject: [PATCH 23/31] Classic Editor: Dynamically handle custom field settings and update UI accordingly --- .../excerpt-generation/classic/index.js | 385 +++++++++++++++++- 1 file changed, 377 insertions(+), 8 deletions(-) diff --git a/src/js/features/excerpt-generation/classic/index.js b/src/js/features/excerpt-generation/classic/index.js index 120317a27..8ffee08fa 100644 --- a/src/js/features/excerpt-generation/classic/index.js +++ b/src/js/features/excerpt-generation/classic/index.js @@ -39,8 +39,13 @@ const classifaiExcerptData = window.classifaiGenerateExcerpt || {}; // Boolean indicating whether generation is in progress. let isProcessing = false; + // Get target field settings and value. + let targetFieldSettings = null; + let targetFieldValue = ''; + const postId = $( '#post_ID' ).val(); + // Creates and appends the "Generate excerpt" button. - $( '', { + const regenerateButton = $( '', { text: buttonText, class: 'classifai-excerpt-generation__excerpt-generate-btn--text', } ) @@ -52,8 +57,10 @@ const classifaiExcerptData = window.classifaiGenerateExcerpt || {}; $( '', { class: 'classifai-excerpt-generation__excerpt-generate-btn--spinner', } ) - ) - .insertAfter( excerptContainer ); + ); + + // Insert the button after the excerpt container, but we'll move it later if custom field is enabled. + regenerateButton.insertAfter( excerptContainer ); $( '

', { class: 'classifai-excerpt-generation__excerpt-generate-error', @@ -87,10 +94,210 @@ const classifaiExcerptData = window.classifaiGenerateExcerpt || {}; ); } - // The current post ID. - const postId = $( '#post_ID' ).val(); + // Get target field settings. + apiFetch( { + path: '/classifai/v1/get-target-field-settings/', + } ).then( ( result ) => { + targetFieldSettings = result; + + // If custom field is enabled, hide the default excerpt field and show custom field info. + if ( targetFieldSettings && targetFieldSettings.field_type !== 'post_excerpt' ) { + hideDefaultExcerptField(); + showCustomFieldInfo(); + } + } ).catch( () => { + // If endpoint doesn't exist, use default settings. + targetFieldSettings = { + field_type: 'post_excerpt', + field_name: __( 'Default excerpt field', 'classifai' ), + }; + } ); + + // Function to hide the default excerpt field. + function hideDefaultExcerptField() { + // Hide the default excerpt textarea and label, but keep the #postexcerpt container. + const excerptTextarea = $( '#excerpt' ); + const excerptLabel = $( 'label[for="excerpt"]' ); + + if ( excerptTextarea.length ) { + excerptTextarea.hide(); + } + + if ( excerptLabel.length ) { + excerptLabel.hide(); + } + + // Hide any other default excerpt content, but not the regenerate button. + const defaultExcerptContent = $( '#postexcerpt .inside' ); + if ( defaultExcerptContent.length ) { + defaultExcerptContent.hide(); + } + + // Make sure the regenerate button is visible. + const regenerateButton = $( '#classifai-excerpt-generation__excerpt-generate-btn' ); + if ( regenerateButton.length ) { + regenerateButton.show(); + } + } + + // Function to show custom field info. + function showCustomFieldInfo() { + if ( ! targetFieldSettings || targetFieldSettings.field_type === 'post_excerpt' ) { + return; + } + + // Get the current value. + apiFetch( { + path: `/classifai/v1/get-target-field-value/${ postId }`, + } ).then( ( result ) => { + targetFieldValue = result.value || ''; + + // Create custom field info container. + const customFieldContainer = $( '

', { + class: 'classifai-custom-excerpt-info', + style: 'margin: 0; padding: 15px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 4px;', + } ); + + const label = $( '