From 3858eca4dae6b9e66c3c33e2c1ee3d8bc1238045 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Tue, 6 Feb 2024 14:55:12 -0700
Subject: [PATCH 01/22] Start moving classification feature functionality into
the feature and out of the provider. Move most of the REST stuff from NLU
into the Feature
---
.../Classifai/Features/Classification.php | 233 ++++++++++++++++++
.../Classifai/Providers/Watson/Helpers.php | 66 -----
includes/Classifai/Providers/Watson/NLU.php | 153 ------------
3 files changed, 233 insertions(+), 219 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index f26d161e8..2db42eed9 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -5,6 +5,9 @@
use Classifai\Services\LanguageProcessing;
use Classifai\Providers\Watson\NLU;
use Classifai\Providers\OpenAI\Embeddings;
+use WP_REST_Server;
+use WP_REST_Request;
+use WP_Error;
use function Classifai\get_post_statuses_for_language_settings;
use function Classifai\get_post_types_for_language_settings;
@@ -36,6 +39,144 @@ 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() {
+ }
+
+ /**
+ * Register any needed endpoints.
+ */
+ public function register_endpoints() {
+ $post_types = $this->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',
+ 'classify/(?P\d+)',
+ [
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'rest_endpoint_callback' ],
+ 'args' => array(
+ 'id' => array(
+ 'required' => true,
+ 'type' => 'integer',
+ 'sanitize_callback' => 'absint',
+ 'description' => esc_html__( 'Post ID to classify.', 'classifai' ),
+ ),
+ 'linkTerms' => array(
+ 'type' => 'boolean',
+ 'description' => esc_html__( 'Whether to link terms or not.', 'classifai' ),
+ 'default' => true,
+ ),
+ ),
+ 'permission_callback' => [ $this, 'classify_permissions_check' ],
+ ]
+ );
+ }
+
+ /**
+ * Check if a given request has access to run classification.
+ *
+ * @param WP_REST_Request $request Full data about the request.
+ * @return WP_Error|bool
+ */
+ public function classify_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;
+ }
+
+ // For all enabled features, ensure the user has proper permissions to add/edit terms.
+ // foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) {
+ // if ( ! get_feature_enabled( $feature ) ) {
+ // continue;
+ // }
+
+ // $taxonomy = get_feature_taxonomy( $feature );
+ // $permission = check_term_permissions( $taxonomy );
+
+ // if ( is_wp_error( $permission ) ) {
+ // return $permission;
+ // }
+ // }
+
+ $post_status = get_post_status( $post_id );
+ $supported = $this->get_supported_post_types();
+ $post_statuses = $this->get_supported_post_statuses();
+
+ // Check if processing allowed.
+ if (
+ ! in_array( $post_status, $post_statuses, true ) ||
+ ! in_array( $post_type, $supported, true ) ||
+ ! $this->is_feature_enabled()
+ ) {
+ return new WP_Error( 'not_enabled', esc_html__( 'Classification not enabled for current item.', '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/classify' ) === 0 ) {
+ $results = $this->run(
+ $request->get_param( 'id' ),
+ 'classify',
+ [
+ 'link_terms' => $request->get_param( 'linkTerms' ),
+ ]
+ );
+
+ return rest_ensure_response(
+ [
+ 'terms' => $results,
+ 'feature_taxonomies' => $this->get_all_feature_taxonomies(),
+ ]
+ );
+ }
+
+ return parent::rest_endpoint_callback( $request );
+ }
+
/**
* Get the description for the enable field.
*
@@ -154,4 +295,96 @@ public function run( ...$args ) {
$this
);
}
+
+ /**
+ * The list of post types that support classification.
+ *
+ * @return array
+ */
+ public function get_supported_post_types(): array {
+ $settings = $this->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 3.0.0
+ * @hook classifai_feature_classification_post_types
+ *
+ * @param {array} $post_types Array of post types to be classified.
+ *
+ * @return {array} Array of post types.
+ */
+ $post_types = apply_filters( 'classifai_' . static::ID . '_post_types', $post_types );
+
+ return $post_types;
+ }
+
+ /**
+ * The list of post statuses that support classification.
+ *
+ * @return array
+ */
+ public function get_supported_post_statuses(): array {
+ $settings = $this->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 3.0.0
+ * @hook classifai_feature_classification_post_statuses
+ *
+ * @param {array} $post_types Array of post statuses to be classified.
+ *
+ * @return {array} Array of post statuses.
+ */
+ $post_statuses = apply_filters( 'classifai_' . static::ID . '_post_statuses', $post_statuses );
+
+ return $post_statuses;
+ }
+
+ /**
+ * Get all feature taxonomies.
+ *
+ * @return array|WP_Error
+ */
+ public function get_all_feature_taxonomies() {
+ // Get all feature taxonomies.
+ $feature_taxonomies = [];
+ foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) {
+ if ( get_feature_enabled( $feature ) ) {
+ $taxonomy = get_feature_taxonomy( $feature );
+ $permission = check_term_permissions( $taxonomy );
+
+ if ( is_wp_error( $permission ) ) {
+ return $permission;
+ }
+
+ if ( 'post_tag' === $taxonomy ) {
+ $taxonomy = 'tags';
+ }
+
+ if ( 'category' === $taxonomy ) {
+ $taxonomy = 'categories';
+ }
+
+ $feature_taxonomies[] = $taxonomy;
+ }
+ }
+
+ return $feature_taxonomies;
+ }
}
diff --git a/includes/Classifai/Providers/Watson/Helpers.php b/includes/Classifai/Providers/Watson/Helpers.php
index 075f2afc6..edf8d9f89 100644
--- a/includes/Classifai/Providers/Watson/Helpers.php
+++ b/includes/Classifai/Providers/Watson/Helpers.php
@@ -110,72 +110,6 @@ function get_classification_mode(): string {
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
*
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 1a69db4eb..b72d263b3 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -74,8 +74,6 @@ public function __construct( $feature = null ) {
];
$this->feature_instance = $feature;
-
- add_action( 'rest_api_init', [ $this, 'register_endpoints' ] );
}
/**
@@ -780,109 +778,6 @@ public function is_configured(): bool {
return $is_configured;
}
- /**
- * 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+)',
- [
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => [ $this, 'generate_post_tags' ],
- 'args' => array(
- 'id' => array(
- 'required' => true,
- 'type' => 'integer',
- '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' ],
- ]
- );
- }
-
- /**
- * 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 ) {
- $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' ) );
- }
-
- $result = $this->rest_endpoint_callback(
- $post_id,
- 'classify',
- [
- 'link_terms' => $link_terms,
- ]
- );
-
- return rest_ensure_response(
- array(
- 'terms' => $result,
- 'feature_taxonomies' => $this->get_all_feature_taxonomies(),
- )
- );
- }
-
- /**
- * Get all feature taxonomies.
- *
- * @since 2.5.0
- *
- * @return array|WP_Error
- */
- public function get_all_feature_taxonomies() {
- // Get all feature taxonomies.
- $feature_taxonomies = [];
- foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) {
- if ( get_feature_enabled( $feature ) ) {
- $taxonomy = get_feature_taxonomy( $feature );
- $permission = check_term_permissions( $taxonomy );
-
- if ( is_wp_error( $permission ) ) {
- return $permission;
- }
-
- if ( 'post_tag' === $taxonomy ) {
- $taxonomy = 'tags';
- }
- if ( 'category' === $taxonomy ) {
- $taxonomy = 'categories';
- }
- $feature_taxonomies[] = $taxonomy;
- }
- }
-
- return $feature_taxonomies;
- }
-
/**
* Common entry point for all REST endpoints for this provider.
* This is called by the Service.
@@ -950,54 +845,6 @@ public function classify_post( int $post_id ) {
}
}
- /**
- * Check if a given request has access to generate tags
- *
- * @param WP_REST_Request $request Full data about the request.
- * @return WP_Error|bool
- */
- public function generate_post_tags_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;
- }
-
- // For all enabled features, ensure the user has proper permissions to add/edit terms.
- foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) {
- if ( ! get_feature_enabled( $feature ) ) {
- continue;
- }
-
- $taxonomy = get_feature_taxonomy( $feature );
- $permission = check_term_permissions( $taxonomy );
-
- if ( is_wp_error( $permission ) ) {
- return $permission;
- }
- }
-
- $post_status = get_post_status( $post_id );
- $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() ) {
- return new WP_Error( 'not_enabled', esc_html__( 'Language Processing not enabled for current post.', 'classifai' ) );
- }
-
- return true;
- }
-
/**
* Classifies the post specified with the PostClassifier object.
* Existing terms relationships are removed before classification.
From 6cc28cae2726382b418992bc7e3b9a70e496e1b9 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Tue, 6 Feb 2024 16:26:08 -0700
Subject: [PATCH 02/22] Move most of the Watson settings into the Feature class
---
.../Classifai/Features/Classification.php | 230 +++++++++++++---
includes/Classifai/Features/Feature.php | 4 +-
includes/Classifai/Helpers.php | 53 ++++
.../Classifai/Providers/OpenAI/Embeddings.php | 7 +
.../Classifai/Providers/Watson/Helpers.php | 56 ----
.../Classifai/Providers/Watson/Linker.php | 10 +-
includes/Classifai/Providers/Watson/NLU.php | 254 +++++-------------
.../Watson/PreviewClassifierData.php | 4 +-
.../Providers/Watson/SavePostHandler.php | 19 +-
.../Classifai/Taxonomy/CategoryTaxonomy.php | 8 +-
.../Classifai/Taxonomy/ConceptTaxonomy.php | 8 +-
.../Classifai/Taxonomy/EntityTaxonomy.php | 8 +-
.../Classifai/Taxonomy/KeywordTaxonomy.php | 8 +-
.../Classifai/Taxonomy/TaxonomyFactory.php | 4 +-
src/js/gutenberg-plugin.js | 4 +-
tests/Classifai/HelpersTest.php | 6 +-
16 files changed, 365 insertions(+), 318 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 2db42eed9..0d2e3e8d9 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -11,6 +11,9 @@
use function Classifai\get_post_statuses_for_language_settings;
use function Classifai\get_post_types_for_language_settings;
+use function Classifai\check_term_permissions;
+use function Classifai\get_classification_feature_enabled;
+use function Classifai\get_classification_feature_taxonomy;
/**
* Class Classification
@@ -53,6 +56,7 @@ public function setup() {
* Set up necessary hooks.
*/
public function feature_setup() {
+ // var_dump( $this->get_settings() ); die;
}
/**
@@ -120,11 +124,11 @@ public function classify_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 ( ! get_feature_enabled( $feature ) ) {
+ // if ( ! get_classification_feature_enabled( $feature ) ) {
// continue;
// }
- // $taxonomy = get_feature_taxonomy( $feature );
+ // $taxonomy = get_classification_feature_taxonomy( $feature );
// $permission = check_term_permissions( $taxonomy );
// if ( is_wp_error( $permission ) ) {
@@ -190,8 +194,67 @@ public function get_enable_description(): string {
* Add any needed custom fields.
*/
public function add_custom_settings_fields() {
- $settings = $this->get_settings();
- $post_statuses = get_post_statuses_for_language_settings();
+ $settings = $this->get_settings();
+ $provider_instance = $this->get_feature_provider_instance();
+ $nlu_features = array();
+ $post_statuses = get_post_statuses_for_language_settings();
+ $post_types = get_post_types_for_language_settings();
+ $post_type_options = array();
+
+ if ( ! empty( $provider_instance->nlu_features ) ) {
+ $nlu_features = $provider_instance->nlu_features;
+ }
+
+ foreach ( $post_types as $post_type ) {
+ $post_type_options[ $post_type->name ] = $post_type->label;
+ }
+
+ 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' ),
+ ),
+ ]
+ );
+
+ foreach ( $nlu_features as $classify_by => $labels ) {
+ add_settings_field(
+ $classify_by,
+ esc_html( $labels['feature'] ),
+ [ $this, 'render_nlu_feature_settings' ],
+ $this->get_option_name(),
+ $this->get_option_name() . '_section',
+ [
+ 'feature' => $classify_by,
+ 'labels' => $labels,
+ 'default_value' => $settings[ $classify_by ],
+ ]
+ );
+ }
add_settings_field(
'post_statuses',
@@ -207,13 +270,6 @@ public function add_custom_settings_fields() {
]
);
- $post_types = get_post_types_for_language_settings();
- $post_type_options = array();
-
- foreach ( $post_types as $post_type ) {
- $post_type_options[ $post_type->name ] = $post_type->label;
- }
-
add_settings_field(
'post_types',
esc_html__( 'Post types', 'classifai' ),
@@ -236,13 +292,15 @@ public function add_custom_settings_fields() {
*/
public function get_feature_default_settings(): array {
return [
- 'post_statuses' => [
+ 'post_statuses' => [
'publish' => 1,
],
- 'post_types' => [
+ 'post_types' => [
'post' => 1,
],
- 'provider' => NLU::ID,
+ 'classification_mode' => 'manual_review',
+ 'classification_method' => 'recommended_terms',
+ 'provider' => NLU::ID,
];
}
@@ -253,10 +311,24 @@ public function get_feature_default_settings(): array {
* @return array
*/
public function sanitize_default_feature_settings( array $new_settings ): array {
- $settings = $this->get_settings();
+ $settings = $this->get_settings();
+ $provider_instance = $this->get_feature_provider_instance();
+
+ $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['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'];
+
+ $new_settings['post_types'] = isset( $new_settings['post_types'] ) ? array_map( 'sanitize_text_field', $new_settings['post_types'] ) : $settings['post_types'];
+
+ if ( ! empty( $provider_instance->nlu_features ) ) {
+ foreach ( $provider_instance->nlu_features as $feature_name => $feature ) {
+ $new_settings[ $feature_name ] = absint( $new_settings[ $feature_name ] ?? $settings[ $feature_name ] );
+ $new_settings[ "{$feature_name}_threshold" ] = absint( $new_settings[ "{$feature_name}_threshold" ] ?? $settings[ "{$feature_name}_threshold" ] );
+ $new_settings[ "{$feature_name}_taxonomy" ] = sanitize_text_field( $new_settings[ "{$feature_name}_taxonomy" ] ?? $settings[ "{$feature_name}_taxonomy" ] );
+ }
+ }
return $new_settings;
}
@@ -359,32 +431,124 @@ public function get_supported_post_statuses(): array {
/**
* Get all feature taxonomies.
*
- * @return array|WP_Error
+ * @return array
*/
- public function get_all_feature_taxonomies() {
- // Get all feature taxonomies.
+ public function get_all_feature_taxonomies(): array {
$feature_taxonomies = [];
- foreach ( [ 'category', 'keyword', 'concept', 'entity' ] as $feature ) {
- if ( get_feature_enabled( $feature ) ) {
- $taxonomy = get_feature_taxonomy( $feature );
- $permission = check_term_permissions( $taxonomy );
+ $provider_instance = $this->get_feature_provider_instance();
- if ( is_wp_error( $permission ) ) {
- return $permission;
- }
+ if ( empty( $provider_instance->nlu_features ) ) {
+ return $feature_taxonomies;
+ }
- if ( 'post_tag' === $taxonomy ) {
- $taxonomy = 'tags';
- }
+ foreach ( $provider_instance->nlu_features as $feature_name => $feature ) {
+ if ( ! get_classification_feature_enabled( $feature_name ) ) {
+ continue;
+ }
- if ( 'category' === $taxonomy ) {
- $taxonomy = 'categories';
- }
+ $taxonomy = get_classification_feature_taxonomy( $feature_name );
+ $permission = check_term_permissions( $taxonomy );
- $feature_taxonomies[] = $taxonomy;
+ if ( is_wp_error( $permission ) ) {
+ continue;
}
+
+ if ( 'post_tag' === $taxonomy ) {
+ $taxonomy = 'tags';
+ }
+
+ if ( 'category' === $taxonomy ) {
+ $taxonomy = 'categories';
+ }
+
+ $feature_taxonomies[] = $taxonomy;
}
return $feature_taxonomies;
}
+
+ /**
+ * Render the NLU features settings.
+ *
+ * @param array $args Settings for the inputs
+ */
+ public function render_nlu_feature_settings( array $args ) {
+ $feature = $args['feature'];
+ $labels = $args['labels'];
+
+ $taxonomies = $this->get_supported_taxonomies();
+ $features = $this->get_settings();
+ $taxonomy = isset( $features[ "{$feature}_taxonomy" ] ) ? $features[ "{$feature}_taxonomy" ] : $labels['taxonomy_default'];
+
+ // Enable classification type
+ $feature_args = [
+ 'label_for' => $feature,
+ 'input_type' => 'checkbox',
+ ];
+
+ $threshold_args = [
+ 'label_for' => "{$feature}_threshold",
+ 'input_type' => 'number',
+ 'default_value' => $labels['threshold_default'],
+ ];
+ ?>
+
+
+
+
+ render_input( $feature_args ); ?>
+
+
+
+
+
+ render_input( $threshold_args ); ?>
+
+
+
+
+
+
+ name ] = $taxonomy->labels->singular_name;
+ }
+
+ /**
+ * Filter taxonomies shown in settings.
+ *
+ * @since 3.0.0
+ * @hook classifai_feature_classification_setting_taxonomies
+ *
+ * @param {array} $supported Array of supported taxonomies.
+ * @param {object} $this Current instance of the class.
+ *
+ * @return {array} Array of taxonomies.
+ */
+ return apply_filters( 'classifai_' . static::ID . '_setting_taxonomies', $supported, $this );
+ }
}
diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php
index 74b3d76da..c7d153ae5 100644
--- a/includes/Classifai/Features/Feature.php
+++ b/includes/Classifai/Features/Feature.php
@@ -192,6 +192,7 @@ protected function get_default_settings(): array {
* @hook classifai_{feature}_get_default_settings
*
* @param {array} $defaults Default feature settings.
+ * @param {object} $this Feature instance.
*
* @return {array} Filtered default feature settings.
*/
@@ -201,7 +202,8 @@ protected function get_default_settings(): array {
$shared_defaults,
$feature_settings,
$provider_settings
- )
+ ),
+ $this
);
}
diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php
index 9e16003d3..1193ee811 100644
--- a/includes/Classifai/Helpers.php
+++ b/includes/Classifai/Helpers.php
@@ -577,3 +577,56 @@ function ( $prompt ) {
function sanitize_number_of_responses_field( string $key, array $new_settings, array $settings ): int {
return absint( $new_settings[ $key ] ?? $settings[ $key ] ?? '' );
}
+
+/**
+ * Returns a bool based on whether the specified classification feature is enabled.
+ *
+ * @param string $classify_by Feature to check.
+ * @return bool
+ */
+function get_classification_feature_enabled( string $classify_by ): bool {
+ $settings = ( new Classification() )->get_settings();
+
+ return filter_var(
+ $settings[ $classify_by ],
+ FILTER_VALIDATE_BOOLEAN
+ );
+}
+
+/**
+ * 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
+ */
+function get_classification_feature_taxonomy( string $classify_by = '' ): string {
+ $taxonomy = '';
+ $settings = ( new Classification() )->get_settings();
+
+ 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 3.0.0
+ * @hook classifai_feature_classification_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_feature_classification_taxonomy_for_feature', $taxonomy, $classify_by );
+}
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index 318c15457..526740b56 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -44,6 +44,13 @@ class Embeddings extends Provider {
*/
protected $max_tokens = 8191;
+ /**
+ * NLU features that are supported by this provider.
+ *
+ * @var array
+ */
+ public $nlu_features = [];
+
/**
* OpenAI Embeddings constructor.
*
diff --git a/includes/Classifai/Providers/Watson/Helpers.php b/includes/Classifai/Providers/Watson/Helpers.php
index edf8d9f89..45ed2ff1a 100644
--- a/includes/Classifai/Providers/Watson/Helpers.php
+++ b/includes/Classifai/Providers/Watson/Helpers.php
@@ -110,22 +110,6 @@ function get_classification_mode(): string {
return $value;
}
-/**
- * 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.
@@ -174,43 +158,3 @@ function get_feature_threshold( string $feature ): float {
*/
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/Providers/Watson/Linker.php b/includes/Classifai/Providers/Watson/Linker.php
index 533361eda..30bcdbf26 100644
--- a/includes/Classifai/Providers/Watson/Linker.php
+++ b/includes/Classifai/Providers/Watson/Linker.php
@@ -2,6 +2,8 @@
namespace Classifai\Providers\Watson;
+use function Classifai\get_classification_feature_taxonomy;
+
/**
* Linker connects Watson classification results with Taxonomy Terms.
*
@@ -92,7 +94,7 @@ public function link( int $post_id, array $output, array $options = [], bool $li
*/
public function link_categories( int $post_id, array $categories, bool $link_categories = true ) {
$terms_to_link = [];
- $taxonomy = get_feature_taxonomy( 'category' );
+ $taxonomy = get_classification_feature_taxonomy( 'category' );
$classify_existing_terms = 'existing_terms' === get_classification_method();
foreach ( $categories as $category ) {
@@ -160,7 +162,7 @@ public function link_categories( int $post_id, array $categories, bool $link_cat
*/
public function link_keywords( int $post_id, array $keywords, bool $link_keywords = true ) {
$terms_to_link = [];
- $taxonomy = get_feature_taxonomy( 'keyword' );
+ $taxonomy = get_classification_feature_taxonomy( 'keyword' );
$classify_existing_terms = 'existing_terms' === get_classification_method();
foreach ( $keywords as $keyword ) {
@@ -219,7 +221,7 @@ public function link_keywords( int $post_id, array $keywords, bool $link_keyword
*/
public function link_concepts( int $post_id, array $concepts, bool $link_concepts = true ) {
$terms_to_link = [];
- $taxonomy = get_feature_taxonomy( 'concept' );
+ $taxonomy = get_classification_feature_taxonomy( 'concept' );
$classify_existing_terms = 'existing_terms' === get_classification_method();
foreach ( $concepts as $concept ) {
@@ -285,7 +287,7 @@ public function link_concepts( int $post_id, array $concepts, bool $link_concept
*/
public function link_entities( int $post_id, array $entities, bool $link_entities = true ) {
$terms_to_link = [];
- $taxonomy = get_feature_taxonomy( 'entity' );
+ $taxonomy = get_classification_feature_taxonomy( 'entity' );
$classify_existing_terms = 'existing_terms' === get_classification_method();
foreach ( $entities as $entity ) {
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index b72d263b3..5513c6215 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -11,11 +11,8 @@
use Classifai\Features\Feature;
use Classifai\Providers\Watson\PostClassifier;
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 {
@@ -32,9 +29,11 @@ class NLU extends Provider {
public $save_post_handler;
/**
- * @var $nlu_features array The list of NLU features
+ * NLU features that are supported by this provider
+ *
+ * @var array
*/
- protected $nlu_features = [];
+ public $nlu_features = [];
/**
* Watson NLU constructor.
@@ -163,61 +162,8 @@ 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,
- esc_html( $labels['feature'] ),
- [ $this, 'render_nlu_feature_settings' ],
- $this->feature_instance->get_option_name(),
- $this->feature_instance->get_option_name() . '_section',
- [
- 'option_index' => static::ID,
- 'feature' => $classify_by,
- 'labels' => $labels,
- 'default_value' => $settings[ $classify_by ],
- 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this.
- ]
- );
- }
-
do_action( 'classifai_' . static::ID . '_render_provider_fields', $this );
- add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
+ // add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
}
/**
@@ -304,43 +250,55 @@ public function render_previewer( string $active_feature ) {
}
/**
- * Returns the default settings for this provider.
+ * Modify the default settings for the classification feature.
*
+ * @param array $settings Current settings.
+ * @param Feature $feature_instance The feature instance.
* @return array
*/
- public function get_default_provider_settings(): array {
- $common_settings = [
- 'endpoint_url' => '',
- 'apikey' => '',
- 'username' => '',
- 'password' => '',
- 'classification_mode' => 'manual_review',
- 'classification_method' => 'recommended_terms',
- ];
+ public function modify_default_feature_settings( array $settings, $feature_instance ): array {
+ remove_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
- switch ( $this->feature_instance::ID ) {
- case Classification::ID:
- return array_merge(
- $common_settings,
- [
- 'category' => true,
- 'category_threshold' => WATSON_CATEGORY_THRESHOLD,
- 'category_taxonomy' => WATSON_CATEGORY_TAXONOMY,
+ if ( $feature_instance->get_settings( 'provider' ) !== static::ID ) {
+ return $settings;
+ }
+
+ add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
- 'keyword' => true,
- 'keyword_threshold' => WATSON_KEYWORD_THRESHOLD,
- 'keyword_taxonomy' => WATSON_KEYWORD_TAXONOMY,
+ return array_merge(
+ $settings,
+ [
+ 'category' => true,
+ 'category_threshold' => WATSON_CATEGORY_THRESHOLD,
+ 'category_taxonomy' => WATSON_CATEGORY_TAXONOMY,
- 'concept' => false,
- 'concept_threshold' => WATSON_CONCEPT_THRESHOLD,
- 'concept_taxonomy' => WATSON_CONCEPT_TAXONOMY,
+ 'keyword' => true,
+ 'keyword_threshold' => WATSON_KEYWORD_THRESHOLD,
+ 'keyword_taxonomy' => WATSON_KEYWORD_TAXONOMY,
- 'entity' => false,
- 'entity_threshold' => WATSON_ENTITY_THRESHOLD,
- 'entity_taxonomy' => WATSON_ENTITY_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,
+ ]
+ );
+ }
+
+ /**
+ * Returns the default settings for this provider.
+ *
+ * @return array
+ */
+ public function get_default_provider_settings(): array {
+ $common_settings = [
+ 'endpoint_url' => '',
+ 'apikey' => '',
+ 'username' => '',
+ 'password' => '',
+ ];
return $common_settings;
}
@@ -349,25 +307,30 @@ public function get_default_provider_settings(): array {
* Register what we need for the plugin.
*/
public function register() {
+ add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
+
$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' ] );
+ 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' ] );
- // Add classifai meta box to classic editor.
- add_action( 'add_meta_boxes', [ $this, 'add_classifai_meta_box' ], 10, 2 );
- add_action( 'save_post', [ $this, 'classifai_save_post_metadata' ], 5 );
+ // // Add classifai meta box to classic editor.
+ // add_action( 'add_meta_boxes', [ $this, 'add_classifai_meta_box' ], 10, 2 );
+ // add_action( 'save_post', [ $this, 'classifai_save_post_metadata' ], 5 );
- add_filter( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
+ // add_filter( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
$this->taxonomy_factory = new TaxonomyFactory();
$this->taxonomy_factory->build_all();
- $this->save_post_handler = new SavePostHandler();
- $this->save_post_handler->register();
+ // $this->save_post_handler = new SavePostHandler();
+ // $this->save_post_handler->register();
- new PreviewClassifierData();
+ // new PreviewClassifierData();
}
}
@@ -446,80 +409,6 @@ protected function use_username_password(): bool {
return 'apikey' === $settings['username'];
}
- /**
- * Render the NLU features settings.
- *
- * @param array $args Settings for the inputs
- */
- public function render_nlu_feature_settings( array $args ) {
- $feature = $args['feature'];
- $labels = $args['labels'];
- $option_index = $args['option_index'];
-
- $taxonomies = $this->get_supported_taxonomies();
- $features = $this->feature_instance->get_settings( static::ID );
- $taxonomy = isset( $features[ "{$feature}_taxonomy" ] ) ? $features[ "{$feature}_taxonomy" ] : $labels['taxonomy_default'];
-
- // Enable classification type
- $feature_args = [
- 'label_for' => $feature,
- 'option_index' => $option_index,
- 'input_type' => 'checkbox',
- ];
-
- $threshold_args = [
- 'label_for' => "{$feature}_threshold",
- 'option_index' => $option_index,
- 'input_type' => 'number',
- 'default_value' => $labels['threshold_default'],
- ];
- ?>
-
-
+
+
feature_instance = $feature_instance;
+
+ if (
+ $this->feature_instance &&
+ method_exists( $this->feature_instance, 'get_supported_taxonomies' )
+ ) {
+ foreach ( $this->feature_instance->get_supported_taxonomies() as $tax => $label ) {
+ $this->nlu_features[ $tax ] = [
+ 'feature' => $label,
+ 'threshold' => __( 'Threshold (%)', 'classifai' ),
+ 'threshold_default' => 75,
+ 'taxonomy' => __( 'Taxonomy', 'classifai' ),
+ 'taxonomy_default' => $tax,
+ ];
+ }
+ }
}
/**
@@ -112,24 +128,8 @@ public function render_provider_fields() {
]
);
- add_settings_field(
- static::ID . '_taxonomies',
- esc_html__( 'Taxonomies', 'classifai' ),
- [ $this, 'render_checkbox_group' ],
- $this->feature_instance->get_option_name(),
- $this->feature_instance->get_option_name() . '_section',
- [
- 'option_index' => static::ID,
- 'label_for' => 'taxonomies',
- 'options' => $this->get_taxonomies_for_settings(),
- 'default_values' => $settings['taxonomies'],
- 'description' => __( 'Choose which taxonomies will be used for classification.', 'classifai' ),
- 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this.
- ]
- );
-
do_action( 'classifai_' . static::ID . '_render_provider_fields', $this );
- add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
+ // add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
}
/**
@@ -195,19 +195,36 @@ public function get_default_provider_settings(): array {
'authenticated' => false,
];
- switch ( $this->feature_instance::ID ) {
- case Classification::ID:
- return array_merge(
- $common_settings,
- [
- 'taxonomies' => [
- 'category',
- ],
- ]
- );
+ return $common_settings;
+ }
+
+ /**
+ * Modify the default settings for the classification feature.
+ *
+ * @param array $settings Current settings.
+ * @param Feature $feature_instance The feature instance.
+ * @return array
+ */
+ public function modify_default_feature_settings( array $settings, $feature_instance ): array {
+ remove_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
+
+ if ( $feature_instance->get_settings( 'provider' ) !== static::ID ) {
+ return $settings;
}
- return $common_settings;
+ add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
+
+ $defaults = [];
+
+ foreach ( array_keys( $feature_instance->get_supported_taxonomies() ) as $tax ) {
+ $enabled = 'category' === $tax ? true : false;
+
+ $defaults[ $tax ] = $enabled;
+ $defaults[ $tax . '_threshold' ] = 75;
+ $defaults[ $tax . '_taxonomy' ] = $tax;
+ }
+
+ return array_merge( $settings, $defaults );
}
/**
@@ -216,22 +233,24 @@ public function get_default_provider_settings(): array {
* This only fires if can_register returns true.
*/
public function register() {
- $feature = new Classification();
+ add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
- add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
+ // $feature = new Classification();
- if ( ! $feature->is_feature_enabled() || $feature->get_feature_provider_instance()::ID !== static::ID ) {
- return;
- }
+ // add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
- 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' ) );
}
/**
@@ -303,19 +322,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 Classification ) {
- // Sanitize the taxonomy checkboxes.
- $taxonomies = $this->get_taxonomies_for_settings();
- foreach ( $taxonomies as $taxonomy_key => $taxonomy_value ) {
- 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[ static::ID ]['taxonomies'][ $taxonomy_key ] = '0';
- }
- }
- }
-
return $new_settings;
}
@@ -946,94 +952,6 @@ public function save_metabox( int $post_id ) {
}
}
- /**
- * 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->feature_instance->get_settings( static::ID );
- $options = $args['options'] ?? [];
- $option_index = $args['option_index'];
-
- if ( ! is_array( $options ) ) {
- return;
- }
-
- // Iterate through all of our options.
- foreach ( $options as $option_value => $option_label ) {
- $value = '';
- $default_key = array_search( $option_value, $args['default_values'], true );
- $option_value_theshold = $option_value . '_threshold';
-
- // Get saved value, if any.
- if ( isset( $setting_index[ $args['label_for'] ] ) ) {
- $value = $setting_index[ $args['label_for'] ][ $option_value ] ?? '';
- $threshold_value = $setting_index[ $args['label_for'] ][ $option_value_theshold ] ?? '';
- }
-
- // 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->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.
- */
- public function render_threshold_field( array $args, string $option_value, string $value ) {
- printf(
- '
-
-
-
-
',
- esc_attr( $this->feature_instance->get_option_name() ),
- esc_attr( $args['option_index'] ),
- esc_attr( $args['label_for'] ?? '' ),
- esc_attr( $option_value ),
- esc_html__( 'Threshold (%)', 'classifai' ),
- $value ? esc_attr( $value ) : 75
- );
- }
-
/**
* Returns the debug information for the provider settings.
*
@@ -1045,16 +963,13 @@ public function get_debug_information(): array {
$debug_info = [];
if ( $this->feature_instance instanceof Classification ) {
- $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' );
- $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' ) );
+ foreach ( array_keys( $this->feature_instance->get_supported_taxonomies() ) as $tax ) {
+ $debug_info[ "Taxonomy ( $tax )" ] = Feature::get_debug_value_text( $provider_settings[ $tax ], 1 );
+ $debug_info[ "Taxonomy ($tax threshold)" ] = Feature::get_debug_value_text( $provider_settings[ $tax . '_threshold' ], 1 );
+ }
+
+ $debug_info[ __( 'Number of terms', 'classifai' ) ] = $provider_settings['number_of_terms'] ?? 1;
+ $debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_embeddings_latest_response' ) );
}
return apply_filters(
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 5513c6215..6a2286d13 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -41,6 +41,8 @@ class NLU extends Provider {
* @param \Classifai\Features\Feature $feature Feature instance (Optional, only required in admin).
*/
public function __construct( $feature = null ) {
+ $this->feature_instance = $feature;
+
$this->nlu_features = [
'category' => [
'feature' => __( 'Category', 'classifai' ),
@@ -71,8 +73,6 @@ public function __construct( $feature = null ) {
'taxonomy_default' => WATSON_CONCEPT_TAXONOMY,
],
];
-
- $this->feature_instance = $feature;
}
/**
From 2c19a656e161656ea01669da927f41138ebeb56e Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 11:33:38 -0700
Subject: [PATCH 04/22] Move more functionality from both NLU and Embeddings
Providers
---
.../Classifai/Features/Classification.php | 251 +++++++++++++-
.../Classifai/Providers/OpenAI/Embeddings.php | 325 ++----------------
includes/Classifai/Providers/Watson/NLU.php | 287 ----------------
3 files changed, 275 insertions(+), 588 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 3ba025c11..d54037bfd 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -14,6 +14,7 @@
use function Classifai\check_term_permissions;
use function Classifai\get_classification_feature_enabled;
use function Classifai\get_classification_feature_taxonomy;
+use function Classifai\get_asset_info;
/**
* Class Classification
@@ -56,7 +57,12 @@ public function setup() {
* Set up necessary hooks.
*/
public function feature_setup() {
- // var_dump( $this->get_settings() ); die;
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
+ add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
+ add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
+ add_action( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
+ add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ], 10, 2 );
+ add_action( 'save_post', [ $this, 'save_metabox' ] );
}
/**
@@ -186,13 +192,254 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
return parent::rest_endpoint_callback( $request );
}
+ /**
+ * Enqueue the admin scripts.
+ */
+ public function enqueue_admin_assets() {
+ wp_enqueue_script(
+ 'classifai-language-processing-script',
+ CLASSIFAI_PLUGIN_URL . 'dist/language-processing.js',
+ get_asset_info( 'language-processing', 'dependencies' ),
+ get_asset_info( 'language-processing', 'version' ),
+ true
+ );
+
+ wp_enqueue_style(
+ 'classifai-language-processing-style',
+ CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css',
+ array(),
+ get_asset_info( 'language-processing', 'version' ),
+ 'all'
+ );
+ }
+
+ /**
+ * Enqueue editor assets.
+ */
+ public function enqueue_editor_assets() {
+ global $post;
+
+ wp_enqueue_script(
+ 'classifai-editor',
+ CLASSIFAI_PLUGIN_URL . 'dist/editor.js',
+ get_asset_info( 'editor', 'dependencies' ),
+ get_asset_info( 'editor', 'version' ),
+ true
+ );
+
+ 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
+ );
+
+ // TODO: check JS var classifaiEmbeddingData
+
+ wp_add_inline_script(
+ 'classifai-gutenberg-plugin',
+ sprintf(
+ 'var classifaiPostData = %s;',
+ wp_json_encode(
+ [
+ 'NLUEnabled' => $this->is_feature_enabled(),
+ 'supportedPostTypes' => $this->get_supported_post_types(),
+ 'supportedPostStatues' => $this->get_supported_post_statuses(),
+ 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ),
+ ]
+ )
+ ),
+ 'before'
+ );
+ }
+
+ /**
+ * Add `classifai_process_content` to the REST API for view/edit.
+ */
+ public function add_process_content_meta_to_rest_api() {
+ $supported_post_types = $this->get_supported_post_types();
+
+ register_rest_field(
+ $supported_post_types,
+ 'classifai_process_content',
+ [
+ 'get_callback' => function ( $data ) {
+ $process_content = get_post_meta( $data['id'], '_classifai_process_content', true );
+ return ( 'no' === $process_content ) ? 'no' : 'yes';
+ },
+ 'update_callback' => function ( $value, $data ) {
+ $value = ( 'no' === $value ) ? 'no' : 'yes';
+ return update_post_meta( $data->ID, '_classifai_process_content', $value );
+ },
+ 'schema' => [
+ 'type' => 'string',
+ 'context' => [ 'view', 'edit' ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Add metabox to enable/disable language processing.
+ *
+ * @param string $post_type Post type.
+ * @param \WP_Post $post WP_Post object.
+ */
+ public function add_meta_box( string $post_type, \WP_Post $post ) {
+ $supported_post_types = $this->get_supported_post_types();
+ $post_statuses = $this->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(
+ 'classifai_language_processing_metabox',
+ __( 'ClassifAI Language Processing', 'classifai' ),
+ [ $this, 'render_meta_box' ],
+ null,
+ 'side',
+ 'high',
+ array( '__back_compat_meta_box' => true )
+ );
+ }
+ }
+
+ /**
+ * Render metabox content.
+ *
+ * @param \WP_Post $post WP_Post object.
+ */
+ public function render_meta_box( \WP_Post $post ) {
+ wp_nonce_field( 'classifai_language_processing_meta_action', 'classifai_language_processing_meta' );
+ $process_content = get_post_meta( $post->ID, '_classifai_process_content', true );
+ $process_content = ( 'no' === $process_content ) ? 'no' : 'yes';
+ ?>
+
+
+
+
+
+
+ is_feature_enabled() ) {
+ return;
+ }
+
+ $provider_instance = $this->get_feature_provider_instance();
+ $nlu_features = array();
+ $supported_post_statuses = $this->get_supported_post_statuses();
+ $supported_post_types = $this->get_supported_post_types();
+
+ $posts_to_preview = get_posts(
+ array(
+ 'post_type' => $supported_post_types,
+ 'post_status' => $supported_post_statuses,
+ 'posts_per_page' => 10,
+ )
+ );
+
+ if ( ! empty( $provider_instance->nlu_features ) ) {
+ $nlu_features = $provider_instance->nlu_features;
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $feature ) :
+ if ( ! get_classification_feature_enabled( $feature_slug ) ) {
+ continue;
+ }
+ ?>
+
+
+
+
+
+
+
+ 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_terms',
- 'input_type' => 'number',
- 'min' => 1,
- 'step' => 1,
- 'default_values' => $settings['number_of_terms'],
- 'description' => esc_html__( 'Maximum number of terms that will get auto-assigned.', 'classifai' ),
- 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, // Important to add this.
- ]
- );
-
do_action( 'classifai_' . static::ID . '_render_provider_fields', $this );
- // add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
- }
-
- /**
- * Renders the previewer window for the feature.
- *
- * @param string $active_feature The active feature.
- */
- public function render_previewer( string $active_feature ) {
- $feature = new Classification();
- $provider = $feature->get_feature_provider_instance();
-
- if (
- self::ID !== $provider::ID ||
- $feature::ID !== $active_feature ||
- ! $feature->is_feature_enabled()
- ) {
- return;
- }
- ?>
-
-
- $supported_post_types,
- 'post_status' => $supported_post_statuses,
- 'posts_per_page' => 10,
- )
- );
- ?>
-
-
-
-
-
-
-
-
-
-
-
- '',
- 'number_of_terms' => 1,
- 'authenticated' => false,
+ 'api_key' => '',
+ 'authenticated' => false,
];
return $common_settings;
}
+ /**
+ * Register what we need for the plugin.
+ *
+ * This only fires if can_register returns true.
+ */
+ public function register() {
+ add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
+
+ $feature = new Classification();
+
+ 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( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) );
+ }
+
/**
* Modify the default settings for the classification feature.
*
@@ -227,88 +176,6 @@ public function modify_default_feature_settings( array $settings, $feature_insta
return array_merge( $settings, $defaults );
}
- /**
- * Register what we need for the plugin.
- *
- * This only fires if can_register returns true.
- */
- public function register() {
- add_filter( 'classifai_feature_classification_get_default_settings', [ $this, 'modify_default_feature_settings' ], 10, 2 );
-
- // $feature = new Classification();
-
- // add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
-
- // 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' ) );
- }
-
- /**
- * Enqueue the admin scripts.
- */
- public function enqueue_admin_assets() {
- wp_enqueue_script(
- 'classifai-language-processing-script',
- CLASSIFAI_PLUGIN_URL . 'dist/language-processing.js',
- get_asset_info( 'language-processing', 'dependencies' ),
- get_asset_info( 'language-processing', 'version' ),
- true
- );
-
- wp_enqueue_style(
- 'classifai-language-processing-style',
- CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css',
- array(),
- get_asset_info( 'language-processing', 'version' ),
- 'all'
- );
- }
-
- /**
- * Enqueue editor assets.
- */
- public function enqueue_editor_assets() {
- global $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 classifaiEmbeddingData = %s;',
- wp_json_encode(
- [
- 'enabled' => true,
- 'supportedPostTypes' => $this->supported_post_types(),
- 'supportedPostStatues' => $this->supported_post_statuses(),
- 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ),
- ]
- )
- ),
- 'before'
- );
- }
-
/**
* Sanitization for the options being saved.
*
@@ -500,8 +367,6 @@ private function set_terms( int $post_id = 0, array $embedding = [] ) {
return new WP_Error( 'data_required', esc_html__( 'Valid embedding data is required to set terms.', 'classifai' ) );
}
- $settings = ( new Classification() )->get_settings();
- $number_to_add = $settings['number_of_terms'] ?? 1;
$embedding_similarity = $this->get_embeddings_similarity( $embedding );
if ( empty( $embedding_similarity ) ) {
@@ -510,14 +375,6 @@ private function set_terms( int $post_id = 0, array $embedding = [] ) {
// Set terms based on similarity.
foreach ( $embedding_similarity as $tax => $terms ) {
- // Sort embeddings from lowest to highest.
- asort( $terms );
-
- // Only add the number of terms specified in settings.
- if ( count( $terms ) > $number_to_add ) {
- $terms = array_slice( $terms, 0, $number_to_add, true );
- }
-
wp_set_object_terms( $post_id, array_map( 'absint', array_keys( $terms ) ), $tax, false );
}
}
@@ -533,8 +390,6 @@ private function get_terms( array $embedding = [] ) {
return new WP_Error( 'data_required', esc_html__( 'Valid embedding data is required to get terms.', 'classifai' ) );
}
- $settings = ( new Classification() )->get_settings();
- $number_to_add = $settings[ static::ID ]['number_of_terms'] ?? 1;
$embedding_similarity = $this->get_embeddings_similarity( $embedding, false );
if ( empty( $embedding_similarity ) ) {
@@ -560,11 +415,6 @@ private function get_terms( array $embedding = [] ) {
$term_added = 0;
foreach ( $terms as $term_id => $similarity ) {
- // Stop if we have added the number of terms specified in settings.
- if ( $number_to_add <= $term_added ) {
- break;
- }
-
// Convert $similarity to percentage.
$similarity = round( ( 1 - $similarity ), 10 );
@@ -575,11 +425,6 @@ private function get_terms( array $embedding = [] ) {
++$term_added;
}
- // Only add the number of terms specified in settings.
- if ( count( $terms ) > $number_to_add ) {
- $terms = array_slice( $terms, 0, $number_to_add, true );
- }
-
++$index;
}
@@ -835,123 +680,6 @@ public function get_content( int $id = 0, string $type = 'post' ): string {
return apply_filters( 'classifai_openai_embeddings_content', $content, $id, $type );
}
- /**
- * 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( new Classification() );
-
- register_rest_field(
- $supported_post_types,
- 'classifai_process_content',
- [
- 'get_callback' => function ( $data ) {
- $process_content = get_post_meta( $data['id'], '_classifai_process_content', true );
- return ( 'no' === $process_content ) ? 'no' : 'yes';
- },
- 'update_callback' => function ( $value, $data ) {
- $value = ( 'no' === $value ) ? 'no' : 'yes';
- return update_post_meta( $data->ID, '_classifai_process_content', $value );
- },
- 'schema' => [
- 'type' => 'string',
- 'context' => [ 'view', 'edit' ],
- ],
- ]
- );
- }
-
- /**
- * Add metabox.
- *
- * @param string $post_type Post type name.
- */
- public function add_metabox( string $post_type ) {
- if ( ! in_array( $post_type, $this->get_supported_post_types( new Classification() ), true ) ) {
- return;
- }
-
- \add_meta_box(
- 'classifai_language_processing_metabox',
- __( 'ClassifAI Language Processing', 'classifai' ),
- array( $this, 'render_metabox' ),
- null,
- 'side',
- 'default',
- [
- '__back_compat_meta_box' => true,
- ]
- );
- }
-
- /**
- * Render metabox.
- *
- * @param \WP_Post $post A WordPress post instance.
- */
- 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"';
-
- // Add nonce.
- wp_nonce_field( 'classifai_language_processing_meta_action', 'classifai_language_processing_meta' );
- wp_nonce_field( 'classifai_embeddings_save_posts', '_nonce' );
- ?>
-
- get_formatted_latest_response( get_transient( 'classifai_openai_embeddings_latest_response' ) );
}
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 6a2286d13..be342d81d 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -12,8 +12,6 @@
use Classifai\Providers\Watson\PostClassifier;
use WP_Error;
-use function Classifai\get_asset_info;
-
class NLU extends Provider {
const ID = 'ibm_watson_nlu';
@@ -163,90 +161,6 @@ function ( $args = [] ) {
);
do_action( 'classifai_' . static::ID . '_render_provider_fields', $this );
- // add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
- }
-
- /**
- * Renders the previewer window for the feature.
- *
- * @param string $active_feature The active feature.
- */
- public function render_previewer( string $active_feature ) {
- $feature = new Classification();
- $provider = $feature->get_feature_provider_instance();
-
- if (
- self::ID !== $provider::ID ||
- $feature::ID !== $active_feature ||
- ! $feature->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 ) :
- ?>
-
-
-
-
-
- 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' ] );
-
- // // Add classifai meta box to classic editor.
- // add_action( 'add_meta_boxes', [ $this, 'add_classifai_meta_box' ], 10, 2 );
- // add_action( 'save_post', [ $this, 'classifai_save_post_metadata' ], 5 );
-
- // add_filter( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
$this->taxonomy_factory = new TaxonomyFactory();
$this->taxonomy_factory->build_all();
@@ -334,65 +240,6 @@ public function register() {
}
}
- /**
- * Enqueue the editor scripts.
- */
- public function enqueue_editor_assets() {
- global $post;
- wp_enqueue_script(
- 'classifai-editor',
- CLASSIFAI_PLUGIN_URL . 'dist/editor.js',
- get_asset_info( 'editor', 'dependencies' ),
- get_asset_info( 'editor', 'version' ),
- true
- );
-
- 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', 'dependencies' ),
- get_asset_info( 'gutenberg-plugin', 'version' ),
- true
- );
-
- wp_localize_script(
- 'classifai-gutenberg-plugin',
- 'classifaiPostData',
- [
- 'NLUEnabled' => ( new Classification() )->is_feature_enabled(),
- 'supportedPostTypes' => get_supported_post_types(),
- 'supportedPostStatues' => get_supported_post_statuses(),
- 'noPermissions' => ! is_user_logged_in() || ! current_user_can( 'edit_post', $post->ID ),
- ]
- );
- }
-
- /**
- * Enqueue the admin scripts.
- */
- public function enqueue_admin_assets() {
- wp_enqueue_script(
- 'classifai-language-processing-script',
- CLASSIFAI_PLUGIN_URL . 'dist/language-processing.js',
- get_asset_info( 'language-processing', 'dependencies' ),
- get_asset_info( 'language-processing', 'version' ),
- true
- );
-
- wp_enqueue_style(
- 'classifai-language-processing-style',
- CLASSIFAI_PLUGIN_URL . 'dist/language-processing.css',
- array(),
- get_asset_info( 'language-processing', 'version' ),
- 'all'
- );
- }
-
/**
* Check if a username/password is used instead of API key.
*
@@ -514,140 +361,6 @@ protected function get_formatted_latest_response( $data ): string {
return preg_replace( '/,"/', ', "', wp_json_encode( $formatted_data ) );
}
- /**
- * Add metabox to enable/disable language processing on post/post types.
- *
- * @since 1.8.0
- *
- * @param string $post_type Post Type.
- * @param \WP_Post $post WP_Post object.
- */
- public function add_classifai_meta_box( string $post_type, \WP_Post $post ) {
- $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(
- 'classifai_language_processing_metabox',
- __( 'ClassifAI Language Processing', 'classifai' ),
- [ $this, 'render_classifai_meta_box' ],
- null,
- 'side',
- 'low',
- array( '__back_compat_meta_box' => true )
- );
- }
- }
-
- /**
- * Render metabox content.
- *
- * @since 1.8.0
- *
- * @param \WP_Post $post WP_Post object.
- */
- 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';
-
- $post_type = get_post_type_object( get_post_type( $post ) );
- $post_type_label = esc_html__( 'Post', 'classifai' );
- if ( $post_type ) {
- $post_type_label = $post_type->labels->singular_name;
- }
- ?>
-
-
-
-
- function ( $data ) {
- $process_content = get_post_meta( $data['id'], '_classifai_process_content', true );
- return ( 'no' === $process_content ) ? 'no' : 'yes';
- },
- 'update_callback' => function ( $value, $data ) {
- $value = ( 'no' === $value ) ? 'no' : 'yes';
- return update_post_meta( $data->ID, '_classifai_process_content', $value );
- },
- 'schema' => [
- 'type' => 'string',
- 'context' => [ 'view', 'edit' ],
- ],
- )
- );
- }
-
- /**
- * Returns whether the provider is configured or not.
- *
- * For backwards compat, we've maintained the use of the
- * `classifai_configured` option. We default to looking for
- * the `authenticated` setting though.
- *
- * @return bool
- */
- public function is_configured(): bool {
- $is_configured = parent::is_configured();
-
- if ( ! $is_configured ) {
- $is_configured = (bool) get_option( 'classifai_configured', false );
- }
-
- return $is_configured;
- }
-
/**
* Common entry point for all REST endpoints for this provider.
* This is called by the Service.
From 505dbd07d4e0c867824c5a09b046533860e807b7 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 14:53:01 -0700
Subject: [PATCH 05/22] Get Classic Editor functionality working for Embeddings
---
.../Classifai/Features/Classification.php | 174 +++++++++++++++++-
includes/Classifai/Helpers.php | 32 ++++
.../Classifai/Providers/OpenAI/Embeddings.php | 142 ++++++--------
.../Classifai/Providers/Watson/Helpers.php | 29 +--
includes/Classifai/Providers/Watson/NLU.php | 94 +++++-----
.../Providers/Watson/SavePostHandler.php | 171 ++---------------
src/js/language-processing.js | 2 +-
7 files changed, 303 insertions(+), 341 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index d54037bfd..955d7b490 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -15,6 +15,7 @@
use function Classifai\get_classification_feature_enabled;
use function Classifai\get_classification_feature_taxonomy;
use function Classifai\get_asset_info;
+use function Classifai\get_classification_mode;
/**
* Class Classification
@@ -63,6 +64,11 @@ public function feature_setup() {
add_action( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ], 10, 2 );
add_action( 'save_post', [ $this, 'save_metabox' ] );
+ add_action( 'admin_post_classifai_classify_post', array( $this, 'classifai_classify_post' ) );
+ add_action( 'admin_notices', [ $this, 'show_error_if' ] );
+
+ add_filter( 'default_post_metadata', [ $this, 'default_post_metadata' ], 10, 3 );
+ add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] );
}
/**
@@ -364,9 +370,165 @@ public function save_metabox( int $post_id ) {
update_post_meta( $post_id, '_classifai_process_content', 'no' );
} else {
update_post_meta( $post_id, '_classifai_process_content', 'yes' );
+
+ $results = $this->run( $post_id, 'classify', [ 'link_terms' => true ] );
+
+ if ( is_wp_error( $results ) ) {
+ update_post_meta(
+ $post_id,
+ '_classifai_error',
+ wp_json_encode(
+ [
+ 'code' => $results->get_error_code(),
+ 'message' => $results->get_error_message(),
+ ]
+ )
+ );
+ } else {
+ delete_post_meta( $post_id, '_classifai_error' );
+ }
+ }
+ }
+
+ /**
+ * Classify post manually.
+ *
+ * Fires when the Classify button is clicked
+ * in the Classic Editor.
+ */
+ 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' )
+ ) {
+ wp_die( esc_html__( 'You don\'t have permission to perform this operation.', 'classifai' ) );
+ }
+
+ $post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
+
+ if ( ! $post_id ) {
+ exit();
+ }
+
+ // Check to see if processing is disabled and overwrite that.
+ // Since we are manually classifying, we want to force this.
+ $enabled = get_post_meta( $post_id, '_classifai_process_content', true );
+ if ( 'yes' !== $enabled ) {
+ update_post_meta( $post_id, '_classifai_process_content', 'yes' );
+ }
+
+ $results = $this->run( $post_id, 'classify', [ 'link_terms' => true ] );
+
+ // Ensure the processing value is changed back to what it was.
+ if ( 'yes' !== $enabled ) {
+ update_post_meta( $post_id, '_classifai_process_content', 'no' );
+ }
+
+ $classified = array();
+
+ if ( ! is_wp_error( $results ) ) {
+ $classified = array( 'classifai_classify' => 1 );
+ }
+
+ wp_safe_redirect( esc_url_raw( add_query_arg( $classified, get_edit_post_link( $post_id, 'edit' ) ) ) );
+ exit();
+ }
+
+ /**
+ * Outputs an admin notice with the error message if needed.
+ */
+ public function show_error_if() {
+ global $post;
+
+ if ( empty( $post ) ) {
+ return;
+ }
+
+ $post_id = $post->ID;
+
+ if ( empty( $post_id ) ) {
+ return;
+ }
+
+ $error = get_post_meta( $post_id, '_classifai_error', true );
+
+ if ( ! empty( $error ) ) {
+ delete_post_meta( $post_id, '_classifai_error' );
+ $error = (array) json_decode( $error );
+ $code = ! empty( $error['code'] ) ? $error['code'] : 500;
+ $message = ! empty( $error['message'] ) ? $error['message'] : 'Unknown API error';
+
+ ?>
+
+ labels->singular_name;
+ }
+ ?>
+
+
+
+ get_settings();
$provider_id = $settings['provider'] ?? NLU::ID;
$provider_instance = $this->get_feature_provider_instance( $provider_id );
@@ -693,7 +855,7 @@ public function get_all_feature_taxonomies(): array {
return $feature_taxonomies;
}
- foreach ( $provider_instance->nlu_features as $feature_name => $feature ) {
+ foreach ( array_keys( $this->get_supported_taxonomies() ) as $feature_name ) {
if ( ! get_classification_feature_enabled( $feature_name ) ) {
continue;
}
@@ -705,14 +867,6 @@ public function get_all_feature_taxonomies(): array {
continue;
}
- if ( 'post_tag' === $taxonomy ) {
- $taxonomy = 'tags';
- }
-
- if ( 'category' === $taxonomy ) {
- $taxonomy = 'categories';
- }
-
$feature_taxonomies[] = $taxonomy;
}
diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php
index 1193ee811..d8959192f 100644
--- a/includes/Classifai/Helpers.php
+++ b/includes/Classifai/Helpers.php
@@ -6,6 +6,7 @@
use Classifai\Providers\Provider;
use Classifai\Admin\UserProfile;
use Classifai\Providers\Watson\NLU;
+use Classifai\Providers\OpenAI\Embeddings;
use Classifai\Services\Service;
use Classifai\Services\ServicesManager;
use WP_Error;
@@ -609,6 +610,10 @@ function get_classification_feature_taxonomy( string $classify_by = '' ): string
$taxonomy = $settings[ $classify_by . '_taxonomy' ];
}
+ if ( $settings['provider'] === Embeddings::ID ) {
+ $taxonomy = $classify_by;
+ }
+
if ( empty( $taxonomy ) ) {
$constant = 'WATSON_' . strtoupper( $classify_by ) . '_TAXONOMY';
@@ -630,3 +635,30 @@ function get_classification_feature_taxonomy( string $classify_by = '' ): string
*/
return apply_filters( 'classifai_feature_classification_taxonomy_for_feature', $taxonomy, $classify_by );
}
+
+/**
+ * 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;
+}
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index b4a80eb09..07c3428d3 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -192,25 +192,6 @@ public function sanitize_settings( array $new_settings ): array {
return $new_settings;
}
- /**
- * The list of supported post types.
- *
- * @return array
- */
- public function supported_post_types(): array {
- /**
- * Filter post types supported for embeddings.
- *
- * @since 2.2.0
- * @hook classifai_post_types
- *
- * @param {array} $post_types Array of post types to be classified.
- *
- * @return {array} Array of post types.
- */
- return apply_filters( 'classifai_openai_embeddings_post_types', $this->get_supported_post_types( new Classification() ) );
- }
-
/**
* Get the threshold for the similarity calculation.
*
@@ -224,7 +205,7 @@ public function get_threshold( string $taxonomy = '' ): float {
$threshold = 1;
if ( ! empty( $taxonomy ) ) {
- $threshold = isset( $settings['taxonomies'][ $taxonomy . '_threshold' ] ) ? $settings['taxonomies'][ $taxonomy . '_threshold' ] : 75;
+ $threshold = isset( $settings[ $taxonomy . '_threshold' ] ) ? $settings[ $taxonomy . '_threshold' ] : 75;
}
// Convert $threshold (%) to decimal.
@@ -244,44 +225,6 @@ public function get_threshold( string $taxonomy = '' ): float {
return apply_filters( 'classifai_threshold', $threshold, $taxonomy );
}
- /**
- * The list of supported post statuses.
- *
- * @return array
- */
- public function supported_post_statuses(): array {
- /**
- * Filter post statuses supported for embeddings.
- *
- * @since 2.2.0
- * @hook classifai_openai_embeddings_post_statuses
- *
- * @param {array} $post_types Array of post statuses to be classified.
- *
- * @return {array} Array of post statuses.
- */
- return apply_filters( 'classifai_openai_embeddings_post_statuses', $this->get_supported_post_statuses( new Classification() ) );
- }
-
- /**
- * The list of supported taxonomies.
- *
- * @return array
- */
- public function supported_taxonomies(): array {
- /**
- * Filter taxonomies supported for embeddings.
- *
- * @since 2.2.0
- * @hook classifai_openai_embeddings_taxonomies
- *
- * @param {array} $taxonomies Array of taxonomies to be classified.
- *
- * @return {array} Array of taxonomies.
- */
- return apply_filters( 'classifai_openai_embeddings_taxonomies', $this->get_supported_taxonomies( new Classification() ) );
- }
-
/**
* Get the data to preview terms.
*
@@ -307,46 +250,35 @@ public function get_post_classifier_embeddings_preview_data(): array {
* Trigger embedding generation for content being saved.
*
* @param int $post_id ID of post being saved.
- * @param bool $dryrun Whether to run the process or just return the data.
+ * @param bool $link_terms Whether to link the terms to the post.
* @return array|WP_Error
*/
- public function generate_embeddings_for_post( int $post_id, bool $dryrun = false ) {
+ public function generate_embeddings_for_post( int $post_id, bool $link_terms = true ) {
// Don't run on autosaves.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
- return;
+ return new WP_Error( 'invalid', esc_html__( 'Classification will not work during an autosave.', 'classifai' ) );
}
// Ensure the user has permissions to edit.
if ( ! current_user_can( 'edit_post', $post_id ) && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) {
- return;
- }
-
- $post = get_post( $post_id );
-
- // Only run on supported post types and statuses.
- if (
- ! $dryrun
- && (
- ! in_array( $post->post_type, $this->supported_post_types(), true ) ||
- ! in_array( $post->post_status, $this->supported_post_statuses(), true )
- )
- ) {
- return;
+ return new WP_Error( 'invalid', esc_html__( 'User does not have permission to classify this item.', 'classifai' ) );
}
// Don't run if turned off for this particular post.
- if ( 'no' === get_post_meta( $post_id, '_classifai_process_content', true ) && ! $dryrun ) {
- return;
+ if ( 'no' === get_post_meta( $post_id, '_classifai_process_content', true ) ) {
+ return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
}
- $embeddings = $this->generate_embeddings( $post_id, 'post' );
+ // TODO: $embeddings = $this->generate_embeddings( $post_id, 'post' );
+ $embeddings = get_post_meta( $post_id, 'classifai_openai_embeddings', true );
// Add terms to this item based on embedding data.
if ( $embeddings && ! is_wp_error( $embeddings ) ) {
- if ( $dryrun ) {
+ update_post_meta( $post_id, 'classifai_openai_embeddings', array_map( 'sanitize_text_field', $embeddings ) );
+
+ if ( ! $link_terms ) {
return $this->get_terms( $embeddings );
} else {
- update_post_meta( $post_id, 'classifai_openai_embeddings', array_map( 'sanitize_text_field', $embeddings ) );
return $this->set_terms( $post_id, $embeddings );
}
}
@@ -357,6 +289,7 @@ public function generate_embeddings_for_post( int $post_id, bool $dryrun = false
*
* @param int $post_id ID of post to set terms on.
* @param array $embedding Embedding data.
+ * @return array|WP_Error
*/
private function set_terms( int $post_id = 0, array $embedding = [] ) {
if ( ! $post_id || ! get_post( $post_id ) ) {
@@ -370,13 +303,15 @@ private function set_terms( int $post_id = 0, array $embedding = [] ) {
$embedding_similarity = $this->get_embeddings_similarity( $embedding );
if ( empty( $embedding_similarity ) ) {
- return;
+ return new WP_Error( 'invalid', esc_html__( 'No matching terms found.', 'classifai' ) );
}
// Set terms based on similarity.
foreach ( $embedding_similarity as $tax => $terms ) {
wp_set_object_terms( $post_id, array_map( 'absint', array_keys( $terms ) ), $tax, false );
}
+
+ return $embedding_similarity;
}
/**
@@ -390,13 +325,13 @@ private function get_terms( array $embedding = [] ) {
return new WP_Error( 'data_required', esc_html__( 'Valid embedding data is required to get terms.', 'classifai' ) );
}
- $embedding_similarity = $this->get_embeddings_similarity( $embedding, false );
+ $embedding_similarity = $this->get_embeddings_similarity( $embedding );
if ( empty( $embedding_similarity ) ) {
- return;
+ return new WP_Error( 'invalid', esc_html__( 'No matching terms found.', 'classifai' ) );
}
- // Set terms based on similarity.
+ // Sort terms based on similarity.
$index = 0;
$result = [];
@@ -437,12 +372,11 @@ private function get_terms( array $embedding = [] ) {
* @since 2.5.0
*
* @param array $embedding Embedding data.
- * @param bool $consider_threshold Whether to consider the threshold setting.
* @return array
*/
- private function get_embeddings_similarity( array $embedding, bool $consider_threshold = true ): array {
+ private function get_embeddings_similarity( array $embedding ): array {
$embedding_similarity = [];
- $taxonomies = $this->supported_taxonomies();
+ $taxonomies = $this->feature_instance->get_all_feature_taxonomies();
$calculations = new EmbeddingCalculations();
foreach ( $taxonomies as $tax ) {
@@ -477,7 +411,7 @@ private function get_embeddings_similarity( array $embedding, bool $consider_thr
if ( $term_embedding ) {
$similarity = $calculations->similarity( $embedding, $term_embedding );
- if ( false !== $similarity && ( ! $consider_threshold || $similarity <= $threshold ) ) {
+ if ( false !== $similarity && $similarity <= $threshold ) {
$embedding_similarity[ $tax ][ $term_id ] = $similarity;
}
}
@@ -490,6 +424,8 @@ private function get_embeddings_similarity( array $embedding, bool $consider_thr
/**
* Generate embedding data for all terms within a taxonomy.
*
+ * TODO: this no longer seems to be called
+ *
* @param string $taxonomy Taxonomy slug.
*/
private function trigger_taxonomy_update( string $taxonomy = '' ) {
@@ -531,7 +467,7 @@ public function generate_embeddings_for_term( int $term_id ) {
return;
}
- $taxonomies = $this->supported_taxonomies();
+ $taxonomies = $this->feature_instance->get_all_feature_taxonomies();
// Ensure this term is part of a taxonomy we support.
if ( ! in_array( $term->taxonomy, $taxonomies, true ) ) {
@@ -680,6 +616,32 @@ public function get_content( int $id = 0, string $type = 'post' ): string {
return apply_filters( 'classifai_openai_embeddings_content', $content, $id, $type );
}
+ /**
+ * Common entry point for all REST endpoints for this provider.
+ *
+ * @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, 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 run classification.', 'classifai' ) );
+ }
+
+ $route_to_call = strtolower( $route_to_call );
+ $return = '';
+
+ // Handle all of our routes.
+ switch ( $route_to_call ) {
+ case 'classify':
+ $return = $this->generate_embeddings_for_post( $post_id, $args['link_terms'] ?? true );
+ break;
+ }
+
+ return $return;
+ }
+
/**
* Returns the debug information for the provider settings.
*
@@ -692,7 +654,7 @@ public function get_debug_information(): array {
if ( $this->feature_instance instanceof Classification ) {
foreach ( array_keys( $this->feature_instance->get_supported_taxonomies() ) as $tax ) {
- $debug_info[ "Taxonomy ( $tax )" ] = Feature::get_debug_value_text( $provider_settings[ $tax ], 1 );
+ $debug_info[ "Taxonomy ($tax)" ] = Feature::get_debug_value_text( $provider_settings[ $tax ], 1 );
$debug_info[ "Taxonomy ($tax threshold)" ] = Feature::get_debug_value_text( $provider_settings[ $tax . '_threshold' ], 1 );
}
diff --git a/includes/Classifai/Providers/Watson/Helpers.php b/includes/Classifai/Providers/Watson/Helpers.php
index 45ed2ff1a..d9c25ee44 100644
--- a/includes/Classifai/Providers/Watson/Helpers.php
+++ b/includes/Classifai/Providers/Watson/Helpers.php
@@ -83,33 +83,6 @@ function get_classification_method(): string {
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;
-}
-
/**
* Returns the feature threshold based on current configuration. Lookup
* order is.
@@ -124,7 +97,7 @@ function get_classification_mode(): string {
*/
function get_feature_threshold( string $feature ): float {
$classification_feature = new Classification();
- $settings = $classification_feature->get_settings( NLU::ID );
+ $settings = $classification_feature->get_settings();
$threshold = 0;
if ( ! empty( $settings ) && ! empty( $settings[ $feature . '_threshold' ] ) ) {
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index be342d81d..0941cdb23 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -233,8 +233,8 @@ public function register() {
$this->taxonomy_factory = new TaxonomyFactory();
$this->taxonomy_factory->build_all();
- // $this->save_post_handler = new SavePostHandler();
- // $this->save_post_handler->register();
+ // $this->save_post_handler = new SavePostHandler();
+ // $this->save_post_handler->register();
// new PreviewClassifierData();
}
@@ -331,39 +331,8 @@ public function sanitize_settings( array $new_settings ): array {
return $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 ): string {
- if ( ! $data ) {
- return __( 'N/A', 'classifai' );
- }
-
- if ( is_wp_error( $data ) ) {
- return $data->get_error_message();
- }
-
- $formatted_data = array_intersect_key(
- $data,
- [
- 'usage' => 1,
- 'language' => 1,
- ]
- );
-
- foreach ( array_diff_key( $data, $formatted_data ) as $key => $value ) {
- $formatted_data[ $key ] = count( $value );
- }
-
- return preg_replace( '/,"/', ', "', wp_json_encode( $formatted_data ) );
- }
-
/**
* 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 route we are processing.
@@ -371,18 +340,17 @@ protected function get_formatted_latest_response( $data ): string {
* @return string|WP_Error
*/
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 ) ) {
- return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) );
+ return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to run classification.', 'classifai' ) );
}
- $return = '';
+ $route_to_call = strtolower( $route_to_call );
+ $return = '';
// Handle all of our routes.
switch ( $route_to_call ) {
case 'classify':
- $return = ( new Classification() )->run( $post_id, $args['link_terms'] ?? true );
+ $return = $this->classify( $post_id, $args['link_terms'] ?? true );
break;
}
@@ -497,6 +465,36 @@ public function classify( int $post_id, bool $link_terms = true ) {
return $output;
}
+ /**
+ * 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 ): string {
+ if ( ! $data ) {
+ return __( 'N/A', 'classifai' );
+ }
+
+ if ( is_wp_error( $data ) ) {
+ return $data->get_error_message();
+ }
+
+ $formatted_data = array_intersect_key(
+ $data,
+ [
+ 'usage' => 1,
+ 'language' => 1,
+ ]
+ );
+
+ foreach ( array_diff_key( $data, $formatted_data ) as $key => $value ) {
+ $formatted_data[ $key ] = count( $value );
+ }
+
+ return preg_replace( '/,"/', ', "', wp_json_encode( $formatted_data ) );
+ }
+
/**
* Returns the debug information for the provider settings.
*
@@ -508,21 +506,11 @@ public function get_debug_information(): array {
$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 );
+ foreach ( $this->nlu_features as $slug => $feature ) {
+ $debug_info[ $feature['feature'] . ' (status)' ] = Feature::get_debug_value_text( $provider_settings[ $slug ], 1 );
+ $debug_info[ $feature['feature'] . ' (threshold)' ] = Feature::get_debug_value_text( $provider_settings[ $slug . '_threshold' ], 1 );
+ $debug_info[ $feature['feature'] . ' (taxonomy)' ] = Feature::get_debug_value_text( $provider_settings[ $slug . '_taxonomy' ], 1 );
+ }
$debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_watson_nlu_latest_response' ) );
}
diff --git a/includes/Classifai/Providers/Watson/SavePostHandler.php b/includes/Classifai/Providers/Watson/SavePostHandler.php
index 3483e85c3..3f7163cd3 100644
--- a/includes/Classifai/Providers/Watson/SavePostHandler.php
+++ b/includes/Classifai/Providers/Watson/SavePostHandler.php
@@ -3,6 +3,7 @@
namespace Classifai\Providers\Watson;
use Classifai\Providers\Watson\PostClassifier;
+use Classifai\Features\Classification;
use function Classifai\get_classification_feature_enabled;
use function Classifai\get_classification_feature_taxonomy;
@@ -21,32 +22,20 @@ class SavePostHandler {
* Enables the classification on save post behaviour.
*/
public function register() {
- add_filter( 'removable_query_args', [ $this, 'classifai_removable_query_args' ] );
- add_filter( 'default_post_metadata', [ $this, 'default_post_metadata' ], 10, 3 );
add_action( 'save_post', [ $this, 'did_save_post' ] );
- add_action( 'admin_notices', [ $this, 'show_error_if' ] );
- add_action( 'admin_post_classifai_classify_post', array( $this, 'classifai_classify_post' ) );
}
/**
- * Sets the default value for the _classifai_process_content meta key.
+ * Lazy initializes the Post Classifier object.
*
- * @param mixed $value The value get_metadata() should return - a single metadata value,
- * or an array of values.
- * @param int $object_id Object ID.
- * @param string $meta_key Meta key.
- * @return mixed
+ * @return PostClassifier
*/
- 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';
- } else {
- return 'no';
- }
+ public function get_classifier(): PostClassifier {
+ if ( is_null( $this->classifier ) ) {
+ $this->classifier = new PostClassifier();
}
- return $value;
+ return $this->classifier;
}
/**
@@ -64,10 +53,10 @@ public function did_save_post( int $post_id ) {
return;
}
- $supported = get_supported_post_types();
+ $supported = ( new Classification() )->get_supported_post_types();
$post_type = get_post_type( $post_id );
$post_status = get_post_status( $post_id );
- $post_statuses = get_supported_post_statuses();
+ $post_statuses = ( new Classification() )->get_supported_post_statuses();
/**
* Filter post statuses for post type or ID.
@@ -86,10 +75,10 @@ public function did_save_post( int $post_id ) {
// Process posts in allowed post statuses, supported items and only if features are enabled
if ( in_array( $post_status, $post_statuses, true ) && in_array( $post_type, $supported, true ) ) {
// Check if processing content on save is disabled.
- $classifai_process_content = get_post_meta( $post_id, '_classifai_process_content', true );
- if ( 'no' === $classifai_process_content ) {
+ if ( 'no' === get_post_meta( $post_id, '_classifai_process_content', true ) ) {
return;
}
+
$this->classify( $post_id );
}
}
@@ -118,6 +107,7 @@ public function classify( int $post_id, bool $link_terms = true ) {
* @return {bool} Whether the post should be classified.
*/
$classifai_should_classify_post = apply_filters( 'classifai_should_classify_post', true, $post_id );
+
if ( ! $classifai_should_classify_post ) {
return false;
}
@@ -162,141 +152,4 @@ public function classify( int $post_id, bool $link_terms = true ) {
return $output;
}
-
- /**
- * Lazy initializes the Post Classifier object.
- *
- * @return PostClassifier
- */
- public function get_classifier(): PostClassifier {
- if ( is_null( $this->classifier ) ) {
- $this->classifier = new PostClassifier();
- }
-
- return $this->classifier;
- }
-
- /**
- * Outputs an Admin Notice with the error message if NLU
- * classification had failed earlier.
- */
- public function show_error_if() {
- global $post;
-
- if ( empty( $post ) ) {
- return;
- }
-
- $post_id = $post->ID;
-
- if ( empty( $post_id ) ) {
- return;
- }
-
- $error = get_post_meta( $post_id, '_classifai_error', true );
-
- if ( ! empty( $error ) ) {
- delete_post_meta( $post_id, '_classifai_error' );
- $error = (array) json_decode( $error );
- $code = ! empty( $error['code'] ) ? $error['code'] : 500;
- $message = ! empty( $error['message'] ) ? $error['message'] : 'Unknown NLU API error';
-
- ?>
-
- labels->singular_name;
- }
- ?>
-
- classify( $post_id );
- $classified = array();
- if ( ! is_wp_error( $result ) ) {
- $classified = array( 'classifai_classify' => 1 );
- }
- wp_safe_redirect( esc_url_raw( add_query_arg( $classified, get_edit_post_link( $post_id, 'edit' ) ) ) );
- exit();
- }
- } else {
- wp_die( esc_html__( 'You don\'t have permission to perform this operation.', 'classifai' ) );
- }
- }
-
- /**
- * Add "classifai_classify" in list of query variable names to remove.
- *
- * @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';
- return $removable_query_args;
- }
}
diff --git a/src/js/language-processing.js b/src/js/language-processing.js
index 5be79de71..47e22bcda 100644
--- a/src/js/language-processing.js
+++ b/src/js/language-processing.js
@@ -446,7 +446,7 @@ import '../scss/language-processing.scss';
document.addEventListener( 'DOMContentLoaded', function () {
// Display "Classify Post" button only when "Process content on update" is unchecked (Classic Editor).
const classifaiNLUCheckbox = document.getElementById(
- '_classifai_process_content'
+ 'classifai-process-content'
);
if ( classifaiNLUCheckbox ) {
classifaiNLUCheckbox.addEventListener( 'change', function () {
From db6f5f0c9261ba95300622c51d3dec33b3d455a8 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 15:31:28 -0700
Subject: [PATCH 06/22] Get Classic Editor functionality working for Watson
---
.../Classifai/Features/Classification.php | 37 ++++++++++-
.../Classifai/Providers/OpenAI/Embeddings.php | 19 +++---
includes/Classifai/Providers/Watson/NLU.php | 62 +++++++------------
.../Providers/Watson/PostClassifier.php | 6 +-
4 files changed, 68 insertions(+), 56 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 955d7b490..4ba4615dd 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -198,6 +198,25 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
return parent::rest_endpoint_callback( $request );
}
+ /**
+ * Save the classification results.
+ *
+ * @param int $post_id The post ID.
+ * @param array $results Term results.
+ */
+ public function save( int $post_id, array $results ) {
+ $provider_instance = $this->get_feature_provider_instance();
+
+ switch ( $provider_instance::ID ) {
+ case NLU::ID:
+ $results = $provider_instance->link( $post_id, $results );
+ break;
+ case Embeddings::ID:
+ $results = $provider_instance->set_terms( $post_id, $results );
+ break;
+ }
+ }
+
/**
* Enqueue the admin scripts.
*/
@@ -371,7 +390,7 @@ public function save_metabox( int $post_id ) {
} else {
update_post_meta( $post_id, '_classifai_process_content', 'yes' );
- $results = $this->run( $post_id, 'classify', [ 'link_terms' => true ] );
+ $results = $this->run( $post_id, 'classify' );
if ( is_wp_error( $results ) ) {
update_post_meta(
@@ -385,6 +404,7 @@ public function save_metabox( int $post_id ) {
)
);
} else {
+ $this->save( $post_id, $results );
delete_post_meta( $post_id, '_classifai_error' );
}
}
@@ -417,7 +437,7 @@ public function classifai_classify_post() {
update_post_meta( $post_id, '_classifai_process_content', 'yes' );
}
- $results = $this->run( $post_id, 'classify', [ 'link_terms' => true ] );
+ $results = $this->run( $post_id, 'classify' );
// Ensure the processing value is changed back to what it was.
if ( 'yes' !== $enabled ) {
@@ -427,7 +447,20 @@ public function classifai_classify_post() {
$classified = array();
if ( ! is_wp_error( $results ) ) {
+ $this->save( $post_id, $results );
$classified = array( 'classifai_classify' => 1 );
+ delete_post_meta( $post_id, '_classifai_error' );
+ } else {
+ update_post_meta(
+ $post_id,
+ '_classifai_error',
+ wp_json_encode(
+ [
+ 'code' => $results->get_error_code(),
+ 'message' => $results->get_error_message(),
+ ]
+ )
+ );
}
wp_safe_redirect( esc_url_raw( add_query_arg( $classified, get_edit_post_link( $post_id, 'edit' ) ) ) );
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index 07c3428d3..bab9ead48 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -249,11 +249,10 @@ public function get_post_classifier_embeddings_preview_data(): array {
/**
* Trigger embedding generation for content being saved.
*
- * @param int $post_id ID of post being saved.
- * @param bool $link_terms Whether to link the terms to the post.
+ * @param int $post_id ID of post being saved.
* @return array|WP_Error
*/
- public function generate_embeddings_for_post( int $post_id, bool $link_terms = true ) {
+ public function generate_embeddings_for_post( int $post_id ) {
// Don't run on autosaves.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return new WP_Error( 'invalid', esc_html__( 'Classification will not work during an autosave.', 'classifai' ) );
@@ -275,13 +274,9 @@ public function generate_embeddings_for_post( int $post_id, bool $link_terms = t
// Add terms to this item based on embedding data.
if ( $embeddings && ! is_wp_error( $embeddings ) ) {
update_post_meta( $post_id, 'classifai_openai_embeddings', array_map( 'sanitize_text_field', $embeddings ) );
-
- if ( ! $link_terms ) {
- return $this->get_terms( $embeddings );
- } else {
- return $this->set_terms( $post_id, $embeddings );
- }
}
+
+ return $embeddings;
}
/**
@@ -291,7 +286,7 @@ public function generate_embeddings_for_post( int $post_id, bool $link_terms = t
* @param array $embedding Embedding data.
* @return array|WP_Error
*/
- private function set_terms( int $post_id = 0, array $embedding = [] ) {
+ public function set_terms( int $post_id = 0, array $embedding = [] ) {
if ( ! $post_id || ! get_post( $post_id ) ) {
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to set terms.', 'classifai' ) );
}
@@ -320,7 +315,7 @@ private function set_terms( int $post_id = 0, array $embedding = [] ) {
* @param array $embedding Embedding data.
* @return array|WP_Error
*/
- private function get_terms( array $embedding = [] ) {
+ public function get_terms( array $embedding = [] ) {
if ( empty( $embedding ) ) {
return new WP_Error( 'data_required', esc_html__( 'Valid embedding data is required to get terms.', 'classifai' ) );
}
@@ -635,7 +630,7 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = ''
// Handle all of our routes.
switch ( $route_to_call ) {
case 'classify':
- $return = $this->generate_embeddings_for_post( $post_id, $args['link_terms'] ?? true );
+ $return = $this->generate_embeddings_for_post( $post_id );
break;
}
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 0941cdb23..d0d63dd9b 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -350,7 +350,7 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = ''
// Handle all of our routes.
switch ( $route_to_call ) {
case 'classify':
- $return = $this->classify( $post_id, $args['link_terms'] ?? true );
+ $return = $this->classify( $post_id );
break;
}
@@ -400,11 +400,10 @@ public function classify_post( int $post_id ) {
* 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 bool $link_terms Whether to link the terms to the post.
- * @return array|bool
+ * @param int $post_id the post to classify & link
+ * @return array|WP_Error
*/
- public function classify( int $post_id, bool $link_terms = true ) {
+ public function classify( int $post_id ) {
/**
* Filter whether ClassifAI should classify a post.
*
@@ -419,48 +418,33 @@ public function classify( int $post_id, bool $link_terms = true ) {
*
* @return {bool} Whether the post should be classified.
*/
- $classifai_should_classify_post = apply_filters( 'classifai_should_classify_post', true, $post_id );
- if ( ! $classifai_should_classify_post ) {
- return false;
+ $should_classify = apply_filters( 'classifai_should_classify_post', true, $post_id );
+ if ( ! $should_classify ) {
+ return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
}
$classifier = new PostClassifier();
- if ( $link_terms ) {
- if ( get_feature_enabled( 'category' ) ) {
- wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'category' ) );
- }
-
- if ( get_feature_enabled( 'keyword' ) ) {
- wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'keyword' ) );
- }
+ $output = $classifier->classify( $post_id );
- if ( get_feature_enabled( 'concept' ) ) {
- wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'concept' ) );
- }
+ return $output;
+ }
- if ( get_feature_enabled( 'entity' ) ) {
- wp_delete_object_term_relationships( $post_id, get_feature_taxonomy( 'entity' ) );
- }
+ /**
+ * Links the Watson NLU response output to taxonomy terms.
+ *
+ * @param int $post_id The post ID.
+ * @param array $terms The classification results from Watson NLU.
+ * @return array|WP_Error
+ */
+ public function link( int $post_id, array $terms ) {
+ if ( empty( $terms ) ) {
+ return new WP_Error( 'invalid', esc_html__( 'No terms to link.', 'classifai' ) );
}
- $output = $classifier->classify_and_link( $post_id, [], $link_terms );
-
- if ( is_wp_error( $output ) ) {
- update_post_meta(
- $post_id,
- '_classifai_error',
- wp_json_encode(
- [
- 'code' => $output->get_error_code(),
- 'message' => $output->get_error_message(),
- ]
- )
- );
- } else {
- // If there is no error, clear any existing error states.
- delete_post_meta( $post_id, '_classifai_error' );
- }
+ $classifier = new PostClassifier();
+
+ $output = $classifier->link( $post_id, $terms );
return $output;
}
diff --git a/includes/Classifai/Providers/Watson/PostClassifier.php b/includes/Classifai/Providers/Watson/PostClassifier.php
index eb001e3d2..d2dfb4189 100644
--- a/includes/Classifai/Providers/Watson/PostClassifier.php
+++ b/includes/Classifai/Providers/Watson/PostClassifier.php
@@ -47,7 +47,7 @@ class PostClassifier {
*
* @param int $post_id The post to classify
* @param array $opts The classification options
- * @return array|bool|\WP_Error
+ * @return array|\WP_Error
*/
public function classify( int $post_id, array $opts = [] ) {
$classifier = $this->get_classifier();
@@ -62,7 +62,7 @@ public function classify( int $post_id, array $opts = [] ) {
if ( ! empty( $text_to_classify ) ) {
return $classifier->classify( $text_to_classify, $opts );
} else {
- return false;
+ return new \WP_Error( 'invalid', esc_html__( 'No text found.', 'classifai' ) );
}
}
@@ -167,7 +167,7 @@ public function get_classifier(): Classifier {
*/
public function get_features(): array {
$classification = new Classification();
- $settings = $classification->get_settings( NLU::ID );
+ $settings = $classification->get_settings();
$features = [];
if ( $settings['category'] ) {
From 05aea7962e744c3e90dace9b46728e75be78d73e Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 15:41:30 -0700
Subject: [PATCH 07/22] Ensure bulk actions for classification still work
---
includes/Classifai/Admin/BulkActions.php | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php
index 6c6326360..8c2928d6c 100644
--- a/includes/Classifai/Admin/BulkActions.php
+++ b/includes/Classifai/Admin/BulkActions.php
@@ -82,7 +82,7 @@ public function register_language_processing_hooks() {
continue;
}
- foreach ( $settings['post_types'] as $key => $post_type ) {
+ foreach ( $settings['post_types'] as $post_type ) {
add_filter( "bulk_actions-edit-$post_type", [ $this, 'register_language_processing_actions' ] );
add_filter( "handle_bulk_actions-edit-$post_type", [ $this, 'language_processing_actions_handler' ], 10, 3 );
@@ -153,7 +153,25 @@ function ( $feature ) {
foreach ( $post_ids as $post_id ) {
switch ( $doaction ) {
case Classification::ID:
- ( new Classification() )->run( $post_id );
+ // Check to see if processing is disabled and overwrite that.
+ // Since we are manually classifying, we want to force this.
+ $classification_enabled = get_post_meta( $post_id, '_classifai_process_content', true );
+ if ( 'yes' !== $classification_enabled ) {
+ update_post_meta( $post_id, '_classifai_process_content', 'yes' );
+ }
+
+ $classification = new Classification();
+ $classify_results = $classification->run( $post_id, 'classify' );
+
+ // Ensure the processing value is changed back to what it was.
+ if ( 'yes' !== $classification_enabled ) {
+ update_post_meta( $post_id, '_classifai_process_content', 'no' );
+ }
+
+ if ( ! empty( $classify_results ) && ! is_wp_error( $classify_results ) ) {
+ $classification->save( $post_id, $classify_results );
+ }
+
$action = $doaction;
break;
From 610e61c02423f3289307463ed7754687bc124f10 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 15:57:26 -0700
Subject: [PATCH 08/22] Fix a few bugs with bulk actions. Move some functions
up to the feature level as they can be reused
---
includes/Classifai/Admin/BulkActions.php | 12 ++++
.../Classifai/Features/Classification.php | 60 -----------------
includes/Classifai/Features/Feature.php | 64 +++++++++++++++++++
includes/Classifai/Features/TextToSpeech.php | 18 ------
.../Classifai/Providers/OpenAI/OpenAI.php | 42 ------------
5 files changed, 76 insertions(+), 120 deletions(-)
diff --git a/includes/Classifai/Admin/BulkActions.php b/includes/Classifai/Admin/BulkActions.php
index 8c2928d6c..bcc426d43 100644
--- a/includes/Classifai/Admin/BulkActions.php
+++ b/includes/Classifai/Admin/BulkActions.php
@@ -107,6 +107,10 @@ public function register_language_processing_actions( array $bulk_actions ): arr
continue;
}
+ if ( ! in_array( get_post_type(), $feature->get_supported_post_types(), true ) ) {
+ continue;
+ }
+
$bulk_actions[ $feature::ID ] = $feature->get_label();
switch ( $feature::ID ) {
@@ -301,6 +305,14 @@ public function register_language_processing_row_action( array $actions, \WP_Pos
continue;
}
+ if ( ! in_array( get_post_type(), $feature->get_supported_post_types(), true ) ) {
+ continue;
+ }
+
+ if ( ! in_array( get_post_status(), $feature->get_supported_post_statuses(), true ) ) {
+ continue;
+ }
+
switch ( $feature::ID ) {
case Classification::ID:
$actions[ Classification::ID ] = sprintf(
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 4ba4615dd..87fe5ce82 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -815,66 +815,6 @@ public function runs( ...$args ) {
);
}
- /**
- * The list of post types that support classification.
- *
- * @return array
- */
- public function get_supported_post_types(): array {
- $settings = $this->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 3.0.0
- * @hook classifai_feature_classification_post_types
- *
- * @param {array} $post_types Array of post types to be classified.
- *
- * @return {array} Array of post types.
- */
- $post_types = apply_filters( 'classifai_' . static::ID . '_post_types', $post_types );
-
- return $post_types;
- }
-
- /**
- * The list of post statuses that support classification.
- *
- * @return array
- */
- public function get_supported_post_statuses(): array {
- $settings = $this->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 3.0.0
- * @hook classifai_feature_classification_post_statuses
- *
- * @param {array} $post_types Array of post statuses to be classified.
- *
- * @return {array} Array of post statuses.
- */
- $post_statuses = apply_filters( 'classifai_' . static::ID . '_post_statuses', $post_statuses );
-
- return $post_statuses;
- }
-
/**
* Get all feature taxonomies.
*
diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php
index 6f19a7279..013390a72 100644
--- a/includes/Classifai/Features/Feature.php
+++ b/includes/Classifai/Features/Feature.php
@@ -993,6 +993,70 @@ public function is_enabled(): bool {
return apply_filters( 'classifai_' . static::ID . '_is_enabled', $is_enabled, $settings );
}
+ /**
+ * The list of post types that are supported.
+ *
+ * @return array
+ */
+ public function get_supported_post_types(): array {
+ $settings = $this->get_settings();
+ $post_types = [];
+
+ if ( isset( $settings['post_types'] ) && is_array( $settings['post_types'] ) ) {
+ foreach ( $settings['post_types'] as $post_type => $enabled ) {
+ if ( ! empty( $enabled ) ) {
+ $post_types[] = $post_type;
+ }
+ }
+ }
+
+ /**
+ * Filter post types supported for a feature.
+ *
+ * @since 3.0.0
+ * @hook classifai_{feature}_post_types
+ *
+ * @param {array} $post_types Array of post types to be classified.
+ *
+ * @return {array} Array of post types.
+ */
+ $post_types = apply_filters( 'classifai_' . static::ID . '_post_types', $post_types );
+
+ return $post_types;
+ }
+
+ /**
+ * The list of post statuses that are supported.
+ *
+ * @return array
+ */
+ public function get_supported_post_statuses(): array {
+ $settings = $this->get_settings();
+ $post_statuses = [];
+
+ if ( ! empty( $settings ) && isset( $settings['post_statuses'] ) ) {
+ foreach ( $settings['post_statuses'] as $post_status => $enabled ) {
+ if ( ! empty( $enabled ) ) {
+ $post_statuses[] = $post_status;
+ }
+ }
+ }
+
+ /**
+ * Filter post statuses supported for a feature.
+ *
+ * @since 3.0.0
+ * @hook classifai_{feature}_post_statuses
+ *
+ * @param {array} $post_types Array of post statuses to be classified.
+ *
+ * @return {array} Array of post statuses.
+ */
+ $post_statuses = apply_filters( 'classifai_' . static::ID . '_post_statuses', $post_statuses );
+
+ return $post_statuses;
+ }
+
/**
* Returns array of instances of provider classes registered for the service.
*
diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php
index d0fdcf779..8deda3892 100644
--- a/includes/Classifai/Features/TextToSpeech.php
+++ b/includes/Classifai/Features/TextToSpeech.php
@@ -717,24 +717,6 @@ protected function get_post_types_select_options(): array {
return $options;
}
- /**
- * The list of post types that TTS supports.
- *
- * @return array Supported Post Types.
- */
- public function get_supported_post_types(): array {
- $selected = $this->get_settings( 'post_types' );
- $post_types = [];
-
- foreach ( $selected as $post_type => $enabled ) {
- if ( ! empty( $enabled ) ) {
- $post_types[] = $post_type;
- }
- }
-
- return $post_types;
- }
-
/**
* Returns the default settings for the feature.
*
diff --git a/includes/Classifai/Providers/OpenAI/OpenAI.php b/includes/Classifai/Providers/OpenAI/OpenAI.php
index 26d5dd104..992e621e5 100644
--- a/includes/Classifai/Providers/OpenAI/OpenAI.php
+++ b/includes/Classifai/Providers/OpenAI/OpenAI.php
@@ -156,48 +156,6 @@ public function get_taxonomies_for_settings(): array {
return apply_filters( 'classifai_openai_settings_taxonomies', $supported, $this );
}
- /**
- * The list of supported post types.
- *
- * @param \Classifai\Features\Feature $feature Feature to check.
- * @return array
- */
- public function get_supported_post_types( \Classifai\Features\Feature $feature ): array {
- $settings = $feature->get_settings();
- $post_types = [];
-
- if ( ! empty( $settings ) && isset( $settings['post_types'] ) ) {
- foreach ( $settings['post_types'] as $post_type => $enabled ) {
- if ( ! empty( $enabled ) ) {
- $post_types[] = $post_type;
- }
- }
- }
-
- return $post_types;
- }
-
- /**
- * The list of supported post statuses.
- *
- * @param \Classifai\Features\Feature $feature Feature to check
- * @return array
- */
- public function get_supported_post_statuses( \Classifai\Features\Feature $feature ): array {
- $settings = $feature->get_settings();
- $post_statuses = [];
-
- if ( ! empty( $settings ) && isset( $settings['post_statuses'] ) ) {
- foreach ( $settings['post_statuses'] as $post_status => $enabled ) {
- if ( ! empty( $enabled ) ) {
- $post_statuses[] = $post_status;
- }
- }
- }
-
- return $post_statuses;
- }
-
/**
* The list of supported taxonomies.
*
From 6ac32ee24d5a094d444e3547ac5726b6f0da61e3 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Wed, 7 Feb 2024 16:04:57 -0700
Subject: [PATCH 09/22] Fix our WP-CLI commands
---
.../Classifai/Command/ClassifaiCommand.php | 53 ++++++++++++-------
1 file changed, 35 insertions(+), 18 deletions(-)
diff --git a/includes/Classifai/Command/ClassifaiCommand.php b/includes/Classifai/Command/ClassifaiCommand.php
index 20ec489f6..ad50655b0 100644
--- a/includes/Classifai/Command/ClassifaiCommand.php
+++ b/includes/Classifai/Command/ClassifaiCommand.php
@@ -61,11 +61,17 @@ public function post( $args = [], $opts = [] ) {
$post_ids = $this->get_posts_to_classify( $opts );
}
- $total = count( $post_ids );
- $classifier = new PostClassifier();
- $limit = $opts['limit'];
- $link = $opts['link'];
- $link = filter_var( $link, FILTER_VALIDATE_BOOLEAN );
+ $feature = new Classification();
+ $provider = $feature->get_feature_provider_instance();
+
+ if ( Embeddings::ID !== $provider::ID ) {
+ \WP_CLI::error( 'This command is only available for the IBM Watson Provider' );
+ }
+
+ $total = count( $post_ids );
+ $limit = $opts['limit'];
+ $link = $opts['link'];
+ $link = filter_var( $link, FILTER_VALIDATE_BOOLEAN );
if ( ! empty( $total ) ) {
if ( ! empty( $limit ) ) {
@@ -83,15 +89,18 @@ public function post( $args = [], $opts = [] ) {
$progress_bar->tick();
- if ( $link ) {
- $output = $classifier->classify_and_link( $post_id, $opts );
+ $results = $feature->run( $post_id, 'classify' );
+
+ if ( is_wp_error( $results ) ) {
+ $errors[ $post_id ] = $results;
+ }
- if ( is_wp_error( $output ) ) {
- $errors[ $post_id ] = $output;
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ if ( $link ) {
+ $feature->save( $post_id, $results );
+ } else {
+ $this->print( $results, $post_id );
}
- } else {
- $output = $classifier->classify( $post_id, $opts );
- $this->print( $output, $post_id );
}
}
@@ -963,8 +972,8 @@ public function embeddings( $args = [], $opts = [] ) {
$embeddings = new Embeddings( false );
$opts = wp_parse_args( $opts, $defaults );
$opts['per_page'] = (int) $opts['per_page'] > 0 ? $opts['per_page'] : 100;
- $allowed_post_types = $embeddings->supported_post_types();
- $allowed_post_status = $embeddings->supported_post_statuses();
+ $allowed_post_types = $feature->get_supported_post_types();
+ $allowed_post_status = $feature->get_supported_post_statuses();
$count = 0;
$errors = 0;
@@ -1015,9 +1024,13 @@ public function embeddings( $args = [], $opts = [] ) {
foreach ( $posts as $post_id ) {
if ( ! $dry_run ) {
- $result = $feature->run( $post_id );
+ $results = $feature->run( $post_id, 'classify' );
- if ( is_wp_error( $result ) ) {
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ $feature->save( $post_id, $results );
+ }
+
+ if ( is_wp_error( $results ) ) {
\WP_CLI::error( sprintf( 'Error while processing item ID %s', $post_id ), false );
++$errors;
}
@@ -1063,9 +1076,13 @@ public function embeddings( $args = [], $opts = [] ) {
}
if ( ! $dry_run ) {
- $result = $feature->run( $post_id );
+ $results = $feature->run( $post_id, 'classify' );
- if ( is_wp_error( $result ) ) {
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ $feature->save( $post_id, $results );
+ }
+
+ if ( is_wp_error( $results ) ) {
\WP_CLI::error( sprintf( 'Error while processing item ID %s', $post_id ), false );
++$errors;
}
From 19919fd7207cd68b765b9dfdf2e83217b7a606d0 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Thu, 8 Feb 2024 11:30:23 -0700
Subject: [PATCH 10/22] Ensure saving in the block editor triggers
classification for both providers
---
.../Classifai/Features/Classification.php | 110 +++++++------
includes/Classifai/Features/Feature.php | 1 +
.../Classifai/Providers/OpenAI/Embeddings.php | 19 +++
includes/Classifai/Providers/Watson/NLU.php | 50 +-----
.../Providers/Watson/SavePostHandler.php | 155 ------------------
src/js/gutenberg-plugin.js | 48 ++----
6 files changed, 96 insertions(+), 287 deletions(-)
delete mode 100644 includes/Classifai/Providers/Watson/SavePostHandler.php
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 87fe5ce82..5226c6b40 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -58,16 +58,24 @@ public function setup() {
* Set up necessary hooks.
*/
public function feature_setup() {
+ $post_types = $this->get_supported_post_types();
+ if ( ! empty( $post_types ) ) {
+ foreach ( $post_types as $post_type ) {
+ add_action( 'rest_after_insert_' . $post_type, [ $this, 'rest_after_insert' ] );
+ }
+ }
+
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
add_action( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
+ add_filter( 'default_post_metadata', [ $this, 'default_post_metadata' ], 10, 3 );
+
+ // Support the Classic Editor.
add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ], 10, 2 );
- add_action( 'save_post', [ $this, 'save_metabox' ] );
+ add_action( 'save_post', [ $this, 'save_meta_box' ] );
add_action( 'admin_post_classifai_classify_post', array( $this, 'classifai_classify_post' ) );
add_action( 'admin_notices', [ $this, 'show_error_if' ] );
-
- add_filter( 'default_post_metadata', [ $this, 'default_post_metadata' ], 10, 3 );
add_filter( 'removable_query_args', [ $this, 'removable_query_args' ] );
}
@@ -187,6 +195,10 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
]
);
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ $this->save( $request->get_param( 'id' ), $results );
+ }
+
return rest_ensure_response(
[
'terms' => $results,
@@ -217,6 +229,47 @@ public function save( int $post_id, array $results ) {
}
}
+ /**
+ * Run classification after an item has been inserted via REST.
+ *
+ * @param \WP_Post $post Post object.
+ */
+ public function rest_after_insert( \WP_Post $post ) {
+ $supported_post_types = $this->get_supported_post_types();
+ $post_statuses = $this->get_supported_post_statuses();
+
+ // Ensure the post type and status is allowed.
+ if (
+ ! in_array( $post->post_type, $supported_post_types, true ) ||
+ ! in_array( $post->post_status, $post_statuses, true )
+ ) {
+ return;
+ }
+
+ // Check if processing on save is disabled.
+ if ( 'no' === get_post_meta( $post->ID, '_classifai_process_content', true ) ) {
+ return;
+ }
+
+ $results = $this->run( $post->ID, 'classify' );
+
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ $this->save( $post->ID, $results );
+ delete_post_meta( $post->ID, '_classifai_error' );
+ } elseif ( is_wp_error( $results ) ) {
+ update_post_meta(
+ $post->ID,
+ '_classifai_error',
+ wp_json_encode(
+ [
+ 'code' => $results->get_error_code(),
+ 'message' => $results->get_error_message(),
+ ]
+ )
+ );
+ }
+ }
+
/**
* Enqueue the admin scripts.
*/
@@ -264,8 +317,6 @@ public function enqueue_editor_assets() {
true
);
- // TODO: check JS var classifaiEmbeddingData
-
wp_add_inline_script(
'classifai-gutenberg-plugin',
sprintf(
@@ -367,7 +418,7 @@ public function render_meta_box( \WP_Post $post ) {
*
* @param int $post_id Current post ID.
*/
- public function save_metabox( int $post_id ) {
+ public function save_meta_box( int $post_id ) {
if (
wp_is_post_autosave( $post_id ) ||
wp_is_post_revision( $post_id ) ||
@@ -392,7 +443,10 @@ public function save_metabox( int $post_id ) {
$results = $this->run( $post_id, 'classify' );
- if ( is_wp_error( $results ) ) {
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ $this->save( $post_id, $results );
+ delete_post_meta( $post_id, '_classifai_error' );
+ } elseif ( is_wp_error( $results ) ) {
update_post_meta(
$post_id,
'_classifai_error',
@@ -403,9 +457,6 @@ public function save_metabox( int $post_id ) {
]
)
);
- } else {
- $this->save( $post_id, $results );
- delete_post_meta( $post_id, '_classifai_error' );
}
}
}
@@ -446,11 +497,11 @@ public function classifai_classify_post() {
$classified = array();
- if ( ! is_wp_error( $results ) ) {
+ if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
$this->save( $post_id, $results );
$classified = array( 'classifai_classify' => 1 );
delete_post_meta( $post_id, '_classifai_error' );
- } else {
+ } elseif ( is_wp_error( $results ) ) {
update_post_meta(
$post_id,
'_classifai_error',
@@ -780,41 +831,6 @@ 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 runs( ...$args ) {
- $settings = $this->get_settings();
- $provider_id = $settings['provider'] ?? NLU::ID;
- $provider_instance = $this->get_feature_provider_instance( $provider_id );
- $result = '';
-
- if ( NLU::ID === $provider_instance::ID ) {
- /** @var NLU $provider_instance */
- $result = call_user_func_array(
- [ $provider_instance, 'classify' ],
- [ ...$args ]
- );
- } elseif ( Embeddings::ID === $provider_instance::ID ) {
- /** @var Embeddings $provider_instance */
- $result = call_user_func_array(
- [ $provider_instance, 'generate_embeddings_for_post' ],
- [ ...$args ]
- );
- }
-
- return apply_filters(
- 'classifai_' . static::ID . '_run',
- $result,
- $provider_instance,
- $args,
- $this
- );
- }
-
/**
* Get all feature taxonomies.
*
diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php
index 013390a72..1cb0d591b 100644
--- a/includes/Classifai/Features/Feature.php
+++ b/includes/Classifai/Features/Feature.php
@@ -1244,6 +1244,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { // phpcs:ig
* @return mixed
*/
public function run( ...$args ) {
+ error_log( 'running' ); // TODO: remove
$settings = $this->get_settings();
$provider_id = $settings['provider'];
$provider_instance = $this->get_feature_provider_instance( $provider_id );
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index bab9ead48..95297c219 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -268,6 +268,25 @@ public function generate_embeddings_for_post( int $post_id ) {
return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
}
+ /**
+ * Filter whether ClassifAI should classify a post.
+ *
+ * Default is true, return false to skip classifying a post.
+ *
+ * @since 1.2.0
+ * @hook classifai_should_classify_post
+ *
+ * @param {bool} $should_classify Whether the post should be classified. Default `true`, return `false` to skip
+ * classification for this post.
+ * @param {int} $post_id The ID of the post to be considered for classification.
+ *
+ * @return {bool} Whether the post should be classified.
+ */
+ $should_classify = apply_filters( 'classifai_should_classify_post', true, $post_id );
+ if ( ! $should_classify ) {
+ return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
+ }
+
// TODO: $embeddings = $this->generate_embeddings( $post_id, 'post' );
$embeddings = get_post_meta( $post_id, 'classifai_openai_embeddings', true );
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index d0d63dd9b..b7f67cdb4 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -21,11 +21,6 @@ class NLU extends Provider {
*/
public $taxonomy_factory;
- /**
- * @var $save_post_handler SavePostHandler Triggers a classification with Watson
- */
- public $save_post_handler;
-
/**
* NLU features that are supported by this provider
*
@@ -233,9 +228,6 @@ public function register() {
$this->taxonomy_factory = new TaxonomyFactory();
$this->taxonomy_factory->build_all();
- // $this->save_post_handler = new SavePostHandler();
- // $this->save_post_handler->register();
-
// new PreviewClassifierData();
}
}
@@ -357,48 +349,10 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = ''
return $return;
}
- /**
- * 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( 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' ) );
- }
-
- $taxonomy_terms = [];
- $features = [ 'category', 'keyword', 'concept', 'entity' ];
-
- // Process post content.
- $result = $this->classify( $post_id );
-
- if ( is_wp_error( $result ) ) {
- return $result;
- }
-
- foreach ( $features as $feature ) {
- $taxonomy = get_feature_taxonomy( $feature );
- $terms = wp_get_object_terms( $post_id, $taxonomy );
- if ( ! is_wp_error( $terms ) ) {
- foreach ( $terms as $term ) {
- $taxonomy_terms[ $taxonomy ][] = $term->term_id;
- }
- }
- }
-
- // Return taxonomy terms.
- return rest_ensure_response( [ 'terms' => $taxonomy_terms ] );
- } catch ( \Exception $e ) {
- return new WP_Error( 'request_failed', $e->getMessage() );
- }
- }
-
/**
* Classifies the post specified with the PostClassifier object.
- * Existing terms relationships are removed before classification.
+ *
+ * Existing terms relationships are removed during classification.
*
* @param int $post_id the post to classify & link
* @return array|WP_Error
diff --git a/includes/Classifai/Providers/Watson/SavePostHandler.php b/includes/Classifai/Providers/Watson/SavePostHandler.php
deleted file mode 100644
index 3f7163cd3..000000000
--- a/includes/Classifai/Providers/Watson/SavePostHandler.php
+++ /dev/null
@@ -1,155 +0,0 @@
-classifier ) ) {
- $this->classifier = new PostClassifier();
- }
-
- return $this->classifier;
- }
-
- /**
- * If current post type support is enabled in ClassifAI settings, it
- * is tagged using the IBM Watson classification result.
- *
- * Skips classification if running under the Gutenberg Metabox
- * compatibility request. The classification is performed during the REST
- * lifecycle when using Gutenberg.
- *
- * @param int $post_id The post that was saved
- */
- public function did_save_post( int $post_id ) {
- if ( ! empty( $_GET['classic-editor'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- return;
- }
-
- $supported = ( new Classification() )->get_supported_post_types();
- $post_type = get_post_type( $post_id );
- $post_status = get_post_status( $post_id );
- $post_statuses = ( new Classification() )->get_supported_post_statuses();
-
- /**
- * Filter post statuses for post type or ID.
- *
- * @since 1.7.1
- * @hook classifai_post_statuses_for_post_type_or_id
- *
- * @param {array} $post_statuses Array of post statuses to be classified with language processing.
- * @param {string} $post_type The post type.
- * @param {int} $post_id The post ID.
- *
- * @return {array} Array of post statuses.
- */
- $post_statuses = apply_filters( 'classifai_post_statuses_for_post_type_or_id', $post_statuses, $post_type, $post_id );
-
- // Process posts in allowed post statuses, supported items and only if features are enabled
- if ( in_array( $post_status, $post_statuses, true ) && in_array( $post_type, $supported, true ) ) {
- // Check if processing content on save is disabled.
- if ( 'no' === get_post_meta( $post_id, '_classifai_process_content', true ) ) {
- return;
- }
-
- $this->classify( $post_id );
- }
- }
-
- /**
- * 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 bool $link_terms Whether to link the terms to the post.
- * @return object|bool
- */
- public function classify( int $post_id, bool $link_terms = true ) {
- /**
- * Filter whether ClassifAI should classify a post.
- *
- * Default is true, return false to skip classifying a post.
- *
- * @since 1.2.0
- * @hook classifai_should_classify_post
- *
- * @param {bool} $should_classify Whether the post should be classified. Default `true`, return `false` to skip
- * classification for this post.
- * @param {int} $post_id The ID of the post to be considered for classification.
- *
- * @return {bool} Whether the post should be classified.
- */
- $classifai_should_classify_post = apply_filters( 'classifai_should_classify_post', true, $post_id );
-
- if ( ! $classifai_should_classify_post ) {
- return false;
- }
-
- $classifier = $this->get_classifier();
-
- if ( $link_terms ) {
- if ( get_classification_feature_enabled( 'category' ) ) {
- wp_delete_object_term_relationships( $post_id, get_classification_feature_taxonomy( 'category' ) );
- }
-
- if ( get_classification_feature_enabled( 'keyword' ) ) {
- wp_delete_object_term_relationships( $post_id, get_classification_feature_taxonomy( 'keyword' ) );
- }
-
- if ( get_classification_feature_enabled( 'concept' ) ) {
- wp_delete_object_term_relationships( $post_id, get_classification_feature_taxonomy( 'concept' ) );
- }
-
- if ( get_classification_feature_enabled( 'entity' ) ) {
- wp_delete_object_term_relationships( $post_id, get_classification_feature_taxonomy( 'entity' ) );
- }
- }
-
- $output = $classifier->classify_and_link( $post_id, [], $link_terms );
-
- if ( is_wp_error( $output ) ) {
- update_post_meta(
- $post_id,
- '_classifai_error',
- wp_json_encode(
- [
- 'code' => $output->get_error_code(),
- 'message' => $output->get_error_message(),
- ]
- )
- );
- } else {
- // If there is no error, clear any existing error states.
- delete_post_meta( $post_id, '_classifai_error' );
- }
-
- return $output;
- }
-}
diff --git a/src/js/gutenberg-plugin.js b/src/js/gutenberg-plugin.js
index 1d7dbfeac..3a7c8700f 100644
--- a/src/js/gutenberg-plugin.js
+++ b/src/js/gutenberg-plugin.js
@@ -18,8 +18,7 @@ import TaxonomyControls from './taxonomy-controls';
import PrePubClassifyPost from './gutenberg-plugins/pre-publish-classify-post';
import { DisableFeatureButton } from './components';
-const { classifaiEmbeddingData, classifaiPostData, classifaiTTSEnabled } =
- window;
+const { classifaiPostData, classifaiTTSEnabled } = window;
/**
* Create the ClassifAI icon
@@ -29,10 +28,11 @@ const ClassifAIIcon = () => (
);
/**
- * ClassifAIToggle Component.
+ * ClassificationToggle Component.
*
+ * Used to toggle the classification process on or off.
*/
-const ClassifAIToggle = () => {
+const ClassificationToggle = () => {
// Use the datastore to retrieve all the meta for this post.
const processContent = useSelect( ( select ) =>
select( 'core/editor' ).getEditedPostAttribute(
@@ -56,9 +56,11 @@ const ClassifAIToggle = () => {
};
/**
- * Classify Post Button
+ * Classify button.
+ *
+ * Used to manually classify the content.
*/
-const ClassifAIGenerateTagsButton = () => {
+const ClassificationButton = () => {
const processContent = useSelect( ( select ) =>
select( 'core/editor' ).getEditedPostAttribute(
'classifai_process_content'
@@ -585,31 +587,18 @@ const ClassifAIPlugin = () => {
const isNLULanguageProcessingEnabled =
classifaiPostData && classifaiPostData.NLUEnabled;
- const isEmbeddingProcessingEnabled =
- classifaiEmbeddingData && classifaiEmbeddingData.enabled;
-
// Ensure we are on a supported post type, checking settings from all features.
const isNLUPostTypeSupported =
classifaiPostData &&
classifaiPostData.supportedPostTypes &&
classifaiPostData.supportedPostTypes.includes( postType );
- const isEmbeddingPostTypeSupported =
- classifaiEmbeddingData &&
- classifaiEmbeddingData.supportedPostTypes &&
- classifaiEmbeddingData.supportedPostTypes.includes( postType );
-
// Ensure we are on a supported post status, checking settings from all features.
const isNLUPostStatusSupported =
classifaiPostData &&
classifaiPostData.supportedPostStatues &&
classifaiPostData.supportedPostStatues.includes( postStatus );
- const isEmbeddingPostStatusSupported =
- classifaiEmbeddingData &&
- classifaiEmbeddingData.supportedPostStatues &&
- classifaiEmbeddingData.supportedPostStatues.includes( postStatus );
-
// Ensure the user has permissions to use the feature.
const userHasNLUPermissions =
classifaiPostData &&
@@ -618,25 +607,12 @@ const ClassifAIPlugin = () => {
1 === parseInt( classifaiPostData.noPermissions )
);
- const userHasEmbeddingPermissions =
- classifaiEmbeddingData &&
- ! (
- classifaiEmbeddingData.noPermissions &&
- 1 === parseInt( classifaiEmbeddingData.noPermissions )
- );
-
const nluPermissionCheck =
userHasNLUPermissions &&
isNLULanguageProcessingEnabled &&
isNLUPostTypeSupported &&
isNLUPostStatusSupported;
- const embeddingsPermissionCheck =
- userHasEmbeddingPermissions &&
- isEmbeddingProcessingEnabled &&
- isEmbeddingPostTypeSupported &&
- isEmbeddingPostStatusSupported;
-
return (
{
className="classifai-panel"
>
<>
- { ( nluPermissionCheck || embeddingsPermissionCheck ) && (
+ { nluPermissionCheck && (
<>
-
- { nluPermissionCheck && (
-
- ) }
+
+ { nluPermissionCheck && }
>
) }
{ classifaiTTSEnabled && }
From 5425c454c71d2121df66ca985fb1e74be1172c11 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Thu, 8 Feb 2024 11:59:28 -0700
Subject: [PATCH 11/22] Ensure that manually classifying works in the Block
Editor for Watson
---
includes/Classifai/Features/Classification.php | 15 ++++++++++-----
includes/Classifai/Providers/Watson/NLU.php | 11 +++++++----
src/js/gutenberg-plugin.js | 2 +-
3 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 5226c6b40..06eb782a2 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -195,8 +195,9 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
]
);
+ // Save results or return the results that need saved.
if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
- $this->save( $request->get_param( 'id' ), $results );
+ $results = $this->save( $request->get_param( 'id' ), $results, $request->get_param( 'linkTerms' ) ?? true );
}
return rest_ensure_response(
@@ -214,19 +215,23 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
* Save the classification results.
*
* @param int $post_id The post ID.
- * @param array $results Term results.
+ * @param array $results Term results
+ * @param bool $link Whether to link the terms or not.
+ * @return array|WP_Error
*/
- public function save( int $post_id, array $results ) {
+ public function save( int $post_id, array $results, bool $link = true ) {
$provider_instance = $this->get_feature_provider_instance();
switch ( $provider_instance::ID ) {
case NLU::ID:
- $results = $provider_instance->link( $post_id, $results );
+ $results = $provider_instance->link( $post_id, $results, $link );
break;
case Embeddings::ID:
$results = $provider_instance->set_terms( $post_id, $results );
break;
}
+
+ return $results;
}
/**
@@ -844,7 +849,7 @@ public function get_all_feature_taxonomies(): array {
return $feature_taxonomies;
}
- foreach ( array_keys( $this->get_supported_taxonomies() ) as $feature_name ) {
+ foreach ( array_keys( $provider_instance->nlu_features ) as $feature_name ) {
if ( ! get_classification_feature_enabled( $feature_name ) ) {
continue;
}
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index b7f67cdb4..10285152d 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -379,9 +379,11 @@ public function classify( int $post_id ) {
$classifier = new PostClassifier();
- $output = $classifier->classify( $post_id );
+ return get_post_meta( $post_id, 'classifai_watson_nlu_results', true ); // TODO
+ // $output = $classifier->classify( $post_id );
- return $output;
+ // update_post_meta( $post_id, 'classifai_watson_nlu_results', $output );
+ // return $output;
}
/**
@@ -389,16 +391,17 @@ public function classify( int $post_id ) {
*
* @param int $post_id The post ID.
* @param array $terms The classification results from Watson NLU.
+ * @param bool $link Whether to link the terms or not.
* @return array|WP_Error
*/
- public function link( int $post_id, array $terms ) {
+ public function link( int $post_id, array $terms, bool $link = true ) {
if ( empty( $terms ) ) {
return new WP_Error( 'invalid', esc_html__( 'No terms to link.', 'classifai' ) );
}
$classifier = new PostClassifier();
- $output = $classifier->link( $post_id, $terms );
+ $output = $classifier->link( $post_id, $terms, [], $link );
return $output;
}
diff --git a/src/js/gutenberg-plugin.js b/src/js/gutenberg-plugin.js
index 3a7c8700f..593449d86 100644
--- a/src/js/gutenberg-plugin.js
+++ b/src/js/gutenberg-plugin.js
@@ -288,7 +288,7 @@ const ClassificationButton = () => {
{ isOpen && (
Date: Thu, 8 Feb 2024 13:57:13 -0700
Subject: [PATCH 12/22] Ensure that manually classifying works in the Block
Editor for Embeddings
---
.../Classifai/Features/Classification.php | 18 +++++-
includes/Classifai/Features/Feature.php | 1 -
.../Classifai/Providers/OpenAI/Embeddings.php | 59 +++++++++++++++----
includes/Classifai/Providers/Watson/NLU.php | 6 +-
4 files changed, 63 insertions(+), 21 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 06eb782a2..bba0201ac 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -196,7 +196,7 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
);
// Save results or return the results that need saved.
- if ( ! empty( $results ) && ! is_wp_error( $results ) ) {
+ if ( ! is_wp_error( $results ) ) {
$results = $this->save( $request->get_param( 'id' ), $results, $request->get_param( 'linkTerms' ) ?? true );
}
@@ -212,7 +212,11 @@ public function rest_endpoint_callback( WP_REST_Request $request ) {
}
/**
- * Save the classification results.
+ * Save or return the classification results.
+ *
+ * If $link is true, we link the terms to the item. If
+ * it is false, we just return the terms that need linked
+ * so they can show in the UI.
*
* @param int $post_id The post ID.
* @param array $results Term results
@@ -227,7 +231,7 @@ public function save( int $post_id, array $results, bool $link = true ) {
$results = $provider_instance->link( $post_id, $results, $link );
break;
case Embeddings::ID:
- $results = $provider_instance->set_terms( $post_id, $results );
+ $results = $provider_instance->set_terms( $post_id, $results, $link );
break;
}
@@ -861,6 +865,14 @@ public function get_all_feature_taxonomies(): array {
continue;
}
+ if ( 'post_tag' === $taxonomy ) {
+ $taxonomy = 'tags';
+ }
+
+ if ( 'category' === $taxonomy ) {
+ $taxonomy = 'categories';
+ }
+
$feature_taxonomies[] = $taxonomy;
}
diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php
index 1cb0d591b..013390a72 100644
--- a/includes/Classifai/Features/Feature.php
+++ b/includes/Classifai/Features/Feature.php
@@ -1244,7 +1244,6 @@ public function rest_endpoint_callback( WP_REST_Request $request ) { // phpcs:ig
* @return mixed
*/
public function run( ...$args ) {
- error_log( 'running' ); // TODO: remove
$settings = $this->get_settings();
$provider_id = $settings['provider'];
$provider_instance = $this->get_feature_provider_instance( $provider_id );
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index 95297c219..7e3b2af2d 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -140,7 +140,6 @@ public function register() {
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' ] );
@@ -263,11 +262,6 @@ public function generate_embeddings_for_post( int $post_id ) {
return new WP_Error( 'invalid', esc_html__( 'User does not have permission to classify this item.', 'classifai' ) );
}
- // Don't run if turned off for this particular post.
- if ( 'no' === get_post_meta( $post_id, '_classifai_process_content', true ) ) {
- return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
- }
-
/**
* Filter whether ClassifAI should classify a post.
*
@@ -287,8 +281,7 @@ public function generate_embeddings_for_post( int $post_id ) {
return new WP_Error( 'invalid', esc_html__( 'Classification is disabled for this item.', 'classifai' ) );
}
- // TODO: $embeddings = $this->generate_embeddings( $post_id, 'post' );
- $embeddings = get_post_meta( $post_id, 'classifai_openai_embeddings', true );
+ $embeddings = $this->generate_embeddings( $post_id, 'post' );
// Add terms to this item based on embedding data.
if ( $embeddings && ! is_wp_error( $embeddings ) ) {
@@ -303,9 +296,10 @@ public function generate_embeddings_for_post( int $post_id ) {
*
* @param int $post_id ID of post to set terms on.
* @param array $embedding Embedding data.
+ * @param bool $link Whether to link the terms or not.
* @return array|WP_Error
*/
- public function set_terms( int $post_id = 0, array $embedding = [] ) {
+ public function set_terms( int $post_id = 0, array $embedding = [], bool $link = true ) {
if ( ! $post_id || ! get_post( $post_id ) ) {
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to set terms.', 'classifai' ) );
}
@@ -320,12 +314,34 @@ public function set_terms( int $post_id = 0, array $embedding = [] ) {
return new WP_Error( 'invalid', esc_html__( 'No matching terms found.', 'classifai' ) );
}
- // Set terms based on similarity.
+ $return = [];
+
+ /**
+ * If $link is true, immediately link all the terms
+ * to the item.
+ *
+ * If it is false, build an array of term data that
+ * can be used to display the terms in the UI.
+ */
foreach ( $embedding_similarity as $tax => $terms ) {
- wp_set_object_terms( $post_id, array_map( 'absint', array_keys( $terms ) ), $tax, false );
+ if ( $link ) {
+ wp_set_object_terms( $post_id, array_map( 'absint', array_keys( $terms ) ), $tax, false );
+ } else {
+ $terms_to_link = [];
+
+ foreach ( array_keys( $terms ) as $term_id ) {
+ $term = get_term( $term_id );
+
+ if ( $term && ! is_wp_error( $term ) ) {
+ $terms_to_link[ $term->name ] = $term_id;
+ }
+ }
+
+ $return[ $tax ] = $terms_to_link;
+ }
}
- return $embedding_similarity;
+ return empty( $return ) ? $embedding_similarity : $return;
}
/**
@@ -394,6 +410,14 @@ private function get_embeddings_similarity( array $embedding ): array {
$calculations = new EmbeddingCalculations();
foreach ( $taxonomies as $tax ) {
+ if ( 'tags' === $tax ) {
+ $tax = 'post_tag';
+ }
+
+ if ( 'categories' === $tax ) {
+ $tax = 'category';
+ }
+
if ( is_numeric( $tax ) ) {
continue;
}
@@ -481,7 +505,16 @@ public function generate_embeddings_for_term( int $term_id ) {
return;
}
- $taxonomies = $this->feature_instance->get_all_feature_taxonomies();
+ $feature = new Classification();
+ $taxonomies = $feature->get_all_feature_taxonomies();
+
+ if ( in_array( 'tags', $taxonomies, true ) ) {
+ $taxonomies[] = 'post_tag';
+ }
+
+ if ( in_array( 'categories', $taxonomies, true ) ) {
+ $taxonomies[] = 'category';
+ }
// Ensure this term is part of a taxonomy we support.
if ( ! in_array( $term->taxonomy, $taxonomies, true ) ) {
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 10285152d..761aee87e 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -379,11 +379,9 @@ public function classify( int $post_id ) {
$classifier = new PostClassifier();
- return get_post_meta( $post_id, 'classifai_watson_nlu_results', true ); // TODO
- // $output = $classifier->classify( $post_id );
+ $output = $classifier->classify( $post_id );
- // update_post_meta( $post_id, 'classifai_watson_nlu_results', $output );
- // return $output;
+ return $output;
}
/**
From 9dbd2bda1a7c7b71564276f11cbb98ef13c9b1e8 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Thu, 8 Feb 2024 14:41:03 -0700
Subject: [PATCH 13/22] Ensure preview functionality is working
---
.../Classifai/Features/Classification.php | 26 ++++
.../Classifai/Providers/OpenAI/Embeddings.php | 21 ++-
includes/Classifai/Providers/Watson/NLU.php | 108 ++++++++++++-
.../Watson/PreviewClassifierData.php | 147 ------------------
4 files changed, 147 insertions(+), 155 deletions(-)
delete mode 100644 includes/Classifai/Providers/Watson/PreviewClassifierData.php
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index bba0201ac..405cc2001 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -69,6 +69,7 @@ public function feature_setup() {
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
add_action( 'classifai_after_feature_settings_form', [ $this, 'render_previewer' ] );
add_action( 'rest_api_init', [ $this, 'add_process_content_meta_to_rest_api' ] );
+ add_action( 'wp_ajax_classifai_get_post_search_results', array( $this, 'get_post_search_results' ) );
add_filter( 'default_post_metadata', [ $this, 'default_post_metadata' ], 10, 3 );
// Support the Classic Editor.
@@ -369,6 +370,31 @@ public function add_process_content_meta_to_rest_api() {
);
}
+ /**
+ * Searches and returns posts.
+ */
+ 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-action' ) ) ) {
+ wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) );
+ }
+
+ $search_term = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
+ $post_types = isset( $_POST['post_types'] ) ? explode( ',', sanitize_text_field( wp_unslash( $_POST['post_types'] ) ) ) : 'post';
+ $post_statuses = isset( $_POST['post_status'] ) ? explode( ',', sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) ) : 'publish';
+
+ $posts = get_posts(
+ array(
+ 'post_type' => $post_types,
+ 'post_status' => $post_statuses,
+ 's' => $search_term,
+ )
+ );
+
+ wp_send_json_success( $posts );
+ }
+
/**
* Add metabox to enable/disable language processing.
*
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index 7e3b2af2d..ff27f7353 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -142,8 +142,7 @@ public function register() {
add_action( 'created_term', [ $this, 'generate_embeddings_for_term' ] );
add_action( 'edited_terms', [ $this, 'generate_embeddings_for_term' ] );
-
- // add_action( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) );
+ add_action( 'wp_ajax_get_post_classifier_embeddings_preview_data', array( $this, 'get_post_classifier_embeddings_preview_data' ) );
}
/**
@@ -240,7 +239,13 @@ public function get_post_classifier_embeddings_preview_data(): array {
$post_id = filter_input( INPUT_POST, 'post_id', FILTER_SANITIZE_NUMBER_INT );
- $embeddings_terms = $this->generate_embeddings_for_post( $post_id, true );
+ $embeddings = $this->generate_embeddings( $post_id, 'post' );
+ $embeddings_terms = [];
+
+ // Add terms to this item based on embedding data.
+ if ( $embeddings && ! is_wp_error( $embeddings ) ) {
+ $embeddings_terms = $this->get_terms( $embeddings );
+ }
return wp_send_json_success( $embeddings_terms );
}
@@ -355,7 +360,7 @@ public function get_terms( array $embedding = [] ) {
return new WP_Error( 'data_required', esc_html__( 'Valid embedding data is required to get terms.', 'classifai' ) );
}
- $embedding_similarity = $this->get_embeddings_similarity( $embedding );
+ $embedding_similarity = $this->get_embeddings_similarity( $embedding, false );
if ( empty( $embedding_similarity ) ) {
return new WP_Error( 'invalid', esc_html__( 'No matching terms found.', 'classifai' ) );
@@ -402,11 +407,13 @@ public function get_terms( array $embedding = [] ) {
* @since 2.5.0
*
* @param array $embedding Embedding data.
+ * @param bool $consider_threshold Whether to consider the threshold setting.
* @return array
*/
- private function get_embeddings_similarity( array $embedding ): array {
+ private function get_embeddings_similarity( array $embedding, bool $consider_threshold = true ): array {
+ $feature = new Classification();
$embedding_similarity = [];
- $taxonomies = $this->feature_instance->get_all_feature_taxonomies();
+ $taxonomies = $feature->get_all_feature_taxonomies();
$calculations = new EmbeddingCalculations();
foreach ( $taxonomies as $tax ) {
@@ -449,7 +456,7 @@ private function get_embeddings_similarity( array $embedding ): array {
if ( $term_embedding ) {
$similarity = $calculations->similarity( $embedding, $term_embedding );
- if ( false !== $similarity && $similarity <= $threshold ) {
+ if ( false !== $similarity && ( ! $consider_threshold || $similarity <= $threshold ) ) {
$embedding_similarity[ $tax ][ $term_id ] = $similarity;
}
}
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 761aee87e..6aa5613da 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -12,6 +12,8 @@
use Classifai\Providers\Watson\PostClassifier;
use WP_Error;
+use function Classifai\get_classification_feature_taxonomy;
+
class NLU extends Provider {
const ID = 'ibm_watson_nlu';
@@ -228,8 +230,112 @@ public function register() {
$this->taxonomy_factory = new TaxonomyFactory();
$this->taxonomy_factory->build_all();
- // new PreviewClassifierData();
+ add_action( 'wp_ajax_get_post_classifier_preview_data', array( $this, 'get_post_classifier_preview_data' ) );
+ }
+ }
+
+ /**
+ * Returns classifier data for previewing.
+ */
+ 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-action' ) ) {
+ wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) );
+ }
+
+ $post_id = filter_input( INPUT_POST, 'post_id', FILTER_SANITIZE_NUMBER_INT );
+ $classifier = new Classifier();
+ $normalizer = new \Classifai\Normalizer();
+
+ $text_to_classify = $normalizer->normalize( $post_id );
+ $body = $classifier->get_body( $text_to_classify );
+ $request_options['body'] = $body;
+ $request = $classifier->get_request();
+
+ $classified_data = $request->post( $classifier->get_endpoint(), $request_options );
+ $classified_data = $this->filter_classify_preview_data( $classified_data );
+
+ wp_send_json_success( $classified_data );
+ }
+
+ /**
+ * Filter classifier preview based on the feature settings.
+ *
+ * @param array $classified_data The classified data.
+ * @return array
+ */
+ public function filter_classify_preview_data( array $classified_data ): array {
+ if ( is_wp_error( $classified_data ) ) {
+ return $classified_data;
+ }
+
+ $classify_existing_terms = 'existing_terms' === get_classification_method();
+ if ( ! $classify_existing_terms ) {
+ return $classified_data;
+ }
+
+ $features = [
+ 'category' => 'categories',
+ 'concept' => 'concepts',
+ 'entity' => 'entities',
+ 'keyword' => 'keywords',
+ ];
+ foreach ( $features as $key => $feature ) {
+ $taxonomy = get_classification_feature_taxonomy( $key );
+ if ( ! $taxonomy ) {
+ continue;
+ }
+
+ if ( ! isset( $classified_data[ $feature ] ) || empty( $classified_data[ $feature ] ) ) {
+ continue;
+ }
+
+ // Handle categories feature.
+ if ( 'categories' === $feature ) {
+ $classified_data[ $feature ] = array_filter(
+ $classified_data[ $feature ],
+ function ( $item ) use ( $taxonomy ) {
+ $keep = false;
+ $parts = explode( '/', $item['label'] );
+ $parts = array_filter( $parts );
+ if ( ! empty( $parts ) ) {
+ foreach ( $parts as $part ) {
+ $term = get_term_by( 'name', $part, $taxonomy );
+ if ( ! empty( $term ) ) {
+ $keep = true;
+ break;
+ }
+ }
+ }
+ return $keep;
+ }
+ );
+ // Reset array keys.
+ $classified_data[ $feature ] = array_values( $classified_data[ $feature ] );
+ continue;
+ }
+
+ $classified_data[ $feature ] = array_filter(
+ $classified_data[ $feature ],
+ function ( $item ) use ( $taxonomy, $key ) {
+ $name = $item['text'];
+ if ( 'keyword' === $key ) {
+ $name = preg_replace( '#^[a-z]+ ([A-Z].*)$#', '$1', $name );
+ } elseif ( 'entity' === $key ) {
+ if ( ! empty( $item['disambiguation'] ) && ! empty( $item['disambiguation']['name'] ) ) {
+ $name = $item['disambiguation']['name'];
+ }
+ }
+ $term = get_term_by( 'name', $name, $taxonomy );
+ return ! empty( $term );
+ }
+ );
+ // Reset array keys.
+ $classified_data[ $feature ] = array_values( $classified_data[ $feature ] );
}
+
+ return $classified_data;
}
/**
diff --git a/includes/Classifai/Providers/Watson/PreviewClassifierData.php b/includes/Classifai/Providers/Watson/PreviewClassifierData.php
deleted file mode 100644
index 1777175eb..000000000
--- a/includes/Classifai/Providers/Watson/PreviewClassifierData.php
+++ /dev/null
@@ -1,147 +0,0 @@
-normalize( $post_id );
- $body = $classifier->get_body( $text_to_classify );
- $request_options['body'] = $body;
- $request = $classifier->get_request();
-
- $classified_data = $request->post( $classifier->get_endpoint(), $request_options );
- $classified_data = $this->filter_classify_preview_data( $classified_data );
-
- wp_send_json_success( $classified_data );
- }
-
- /**
- * Searches and returns posts.
- */
- 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-nonce' ) ) ) {
- wp_send_json_error( esc_html__( 'Failed nonce check.', 'classifai' ) );
- }
-
- $search_term = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
- $post_types = isset( $_POST['post_types'] ) ? explode( ',', sanitize_text_field( wp_unslash( $_POST['post_types'] ) ) ) : 'post';
- $post_statuses = isset( $_POST['post_status'] ) ? explode( ',', sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) ) : 'publish';
-
- $posts = get_posts(
- array(
- 'post_type' => $post_types,
- 'post_status' => $post_statuses,
- 's' => $search_term,
- )
- );
-
- wp_send_json_success( $posts );
- }
-
- /**
- * Filter classifier preview based on the feature settings.
- *
- * @param array $classified_data The classified data.
- * @return array
- */
- public function filter_classify_preview_data( array $classified_data ): array {
- if ( is_wp_error( $classified_data ) ) {
- return $classified_data;
- }
-
- $classify_existing_terms = 'existing_terms' === get_classification_method();
- if ( ! $classify_existing_terms ) {
- return $classified_data;
- }
-
- $features = [
- 'category' => 'categories',
- 'concept' => 'concepts',
- 'entity' => 'entities',
- 'keyword' => 'keywords',
- ];
- foreach ( $features as $key => $feature ) {
- $taxonomy = get_classification_feature_taxonomy( $key );
- if ( ! $taxonomy ) {
- continue;
- }
-
- if ( ! isset( $classified_data[ $feature ] ) || empty( $classified_data[ $feature ] ) ) {
- continue;
- }
-
- // Handle categories feature.
- if ( 'categories' === $feature ) {
- $classified_data[ $feature ] = array_filter(
- $classified_data[ $feature ],
- function ( $item ) use ( $taxonomy ) {
- $keep = false;
- $parts = explode( '/', $item['label'] );
- $parts = array_filter( $parts );
- if ( ! empty( $parts ) ) {
- foreach ( $parts as $part ) {
- $term = get_term_by( 'name', $part, $taxonomy );
- if ( ! empty( $term ) ) {
- $keep = true;
- break;
- }
- }
- }
- return $keep;
- }
- );
- // Reset array keys.
- $classified_data[ $feature ] = array_values( $classified_data[ $feature ] );
- continue;
- }
-
- $classified_data[ $feature ] = array_filter(
- $classified_data[ $feature ],
- function ( $item ) use ( $taxonomy, $key ) {
- $name = $item['text'];
- if ( 'keyword' === $key ) {
- $name = preg_replace( '#^[a-z]+ ([A-Z].*)$#', '$1', $name );
- } elseif ( 'entity' === $key ) {
- if ( ! empty( $item['disambiguation'] ) && ! empty( $item['disambiguation']['name'] ) ) {
- $name = $item['disambiguation']['name'];
- }
- }
- $term = get_term_by( 'name', $name, $taxonomy );
- return ! empty( $term );
- }
- );
- // Reset array keys.
- $classified_data[ $feature ] = array_values( $classified_data[ $feature ] );
- }
-
- return $classified_data;
- }
-}
From 7edfa2275edbbf062c22bb9f8f1102b1c981f82d Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Thu, 8 Feb 2024 14:53:42 -0700
Subject: [PATCH 14/22] Fix unit tests and PHPCS issues
---
includes/Classifai/Helpers.php | 2 +-
.../Classifai/Providers/OpenAI/Embeddings.php | 9 ++++-----
includes/Classifai/Providers/Watson/NLU.php | 11 +++++-----
tests/Classifai/HelpersTest.php | 20 ++++++++++++++-----
4 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/includes/Classifai/Helpers.php b/includes/Classifai/Helpers.php
index d8959192f..e56a0d09c 100644
--- a/includes/Classifai/Helpers.php
+++ b/includes/Classifai/Helpers.php
@@ -610,7 +610,7 @@ function get_classification_feature_taxonomy( string $classify_by = '' ): string
$taxonomy = $settings[ $classify_by . '_taxonomy' ];
}
- if ( $settings['provider'] === Embeddings::ID ) {
+ if ( Embeddings::ID === $settings['provider'] ) {
$taxonomy = $classify_by;
}
diff --git a/includes/Classifai/Providers/OpenAI/Embeddings.php b/includes/Classifai/Providers/OpenAI/Embeddings.php
index ff27f7353..505d5633c 100644
--- a/includes/Classifai/Providers/OpenAI/Embeddings.php
+++ b/includes/Classifai/Providers/OpenAI/Embeddings.php
@@ -702,14 +702,13 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = ''
* @return array
*/
public function get_debug_information(): array {
- $settings = $this->feature_instance->get_settings();
- $provider_settings = $settings[ static::ID ];
- $debug_info = [];
+ $settings = $this->feature_instance->get_settings();
+ $debug_info = [];
if ( $this->feature_instance instanceof Classification ) {
foreach ( array_keys( $this->feature_instance->get_supported_taxonomies() ) as $tax ) {
- $debug_info[ "Taxonomy ($tax)" ] = Feature::get_debug_value_text( $provider_settings[ $tax ], 1 );
- $debug_info[ "Taxonomy ($tax threshold)" ] = Feature::get_debug_value_text( $provider_settings[ $tax . '_threshold' ], 1 );
+ $debug_info[ "Taxonomy ($tax)" ] = Feature::get_debug_value_text( $settings[ $tax ], 1 );
+ $debug_info[ "Taxonomy ($tax threshold)" ] = Feature::get_debug_value_text( $settings[ $tax . '_threshold' ], 1 );
}
$debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_openai_embeddings_latest_response' ) );
diff --git a/includes/Classifai/Providers/Watson/NLU.php b/includes/Classifai/Providers/Watson/NLU.php
index 6aa5613da..251c2337f 100644
--- a/includes/Classifai/Providers/Watson/NLU.php
+++ b/includes/Classifai/Providers/Watson/NLU.php
@@ -546,15 +546,14 @@ protected function get_formatted_latest_response( $data ): string {
* @return array
*/
public function get_debug_information(): array {
- $settings = $this->feature_instance->get_settings();
- $provider_settings = $settings[ static::ID ];
- $debug_info = [];
+ $settings = $this->feature_instance->get_settings();
+ $debug_info = [];
if ( $this->feature_instance instanceof Classification ) {
foreach ( $this->nlu_features as $slug => $feature ) {
- $debug_info[ $feature['feature'] . ' (status)' ] = Feature::get_debug_value_text( $provider_settings[ $slug ], 1 );
- $debug_info[ $feature['feature'] . ' (threshold)' ] = Feature::get_debug_value_text( $provider_settings[ $slug . '_threshold' ], 1 );
- $debug_info[ $feature['feature'] . ' (taxonomy)' ] = Feature::get_debug_value_text( $provider_settings[ $slug . '_taxonomy' ], 1 );
+ $debug_info[ $feature['feature'] . ' (status)' ] = Feature::get_debug_value_text( $settings[ $slug ], 1 );
+ $debug_info[ $feature['feature'] . ' (threshold)' ] = Feature::get_debug_value_text( $settings[ $slug . '_threshold' ], 1 );
+ $debug_info[ $feature['feature'] . ' (taxonomy)' ] = Feature::get_debug_value_text( $settings[ $slug . '_taxonomy' ], 1 );
}
$debug_info[ __( 'Latest response', 'classifai' ) ] = $this->get_formatted_latest_response( get_transient( 'classifai_watson_nlu_latest_response' ) );
diff --git a/tests/Classifai/HelpersTest.php b/tests/Classifai/HelpersTest.php
index ef5f65487..e8927c3be 100644
--- a/tests/Classifai/HelpersTest.php
+++ b/tests/Classifai/HelpersTest.php
@@ -2,9 +2,10 @@
namespace Classifai;
+use Classifai\Features\Classification;
+
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\get_classification_feature_taxonomy;
@@ -13,6 +14,15 @@
*/
class HelpersTest extends \WP_UnitTestCase {
+ /**
+ * Provides a Feature instance.
+ *
+ * @return Classification
+ */
+ public function get_feature_class() : Classification {
+ return new Classification();
+ }
+
/**
* Set up method.
*/
@@ -43,7 +53,7 @@ function test_it_has_a_plugin_instance() {
}
function test_it_has_default_supported_post_types() {
- $actual = get_supported_post_types();
+ $actual = $this->get_feature_class()->get_supported_post_types();
$this->assertEquals( ['post'], $actual );
}
@@ -51,16 +61,16 @@ function test_it_can_lookup_supported_post_types_from_option() {
$this->markTestSkipped();
update_option( 'classifai_settings', [ 'post_types' => [ 'post' => 1, 'page' => 1 ] ] );
- $actual = get_supported_post_types();
+ $actual = $this->get_feature_class()->get_supported_post_types();
$this->assertEquals( [ 'post', 'page' ], $actual );
}
function test_it_can_override_supported_post_types_with_filter() {
- add_filter( 'classifai_post_types', function() {
+ add_filter( 'classifai_feature_classification_post_types', function() {
return [ 'page' ];
} );
- $actual = get_supported_post_types();
+ $actual = $this->get_feature_class()->get_supported_post_types();
$this->assertEquals( [ 'page' ], $actual );
}
From 87485114ef01b7c27568680e0ecbc1711bdaa1e6 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Thu, 8 Feb 2024 15:00:36 -0700
Subject: [PATCH 15/22] Fix fatal error
---
includes/Classifai/Features/Classification.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 405cc2001..43cd34922 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -401,7 +401,7 @@ public function get_post_search_results() {
* @param string $post_type Post type.
* @param \WP_Post $post WP_Post object.
*/
- public function add_meta_box( string $post_type, \WP_Post $post ) {
+ public function add_meta_box( string $post_type, $post ) {
$supported_post_types = $this->get_supported_post_types();
$post_statuses = $this->get_supported_post_statuses();
$post_status = get_post_status( $post );
From ef918f3d3e984fa00c3a907862a54e7f5c4f8ed3 Mon Sep 17 00:00:00 2001
From: Darin Kotter
Date: Fri, 9 Feb 2024 09:02:01 -0700
Subject: [PATCH 16/22] Fix E2E tests and bugs it found
---
.../Classifai/Features/Classification.php | 29 +++++++---
includes/Classifai/Helpers.php | 2 +-
.../Classifai/Providers/OpenAI/Embeddings.php | 15 +++--
.../Classifai/Providers/Watson/Helpers.php | 2 +-
...lassify-content-openapi-embeddings.test.js | 55 ++++++-------------
5 files changed, 53 insertions(+), 50 deletions(-)
diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php
index 43cd34922..805bde32a 100644
--- a/includes/Classifai/Features/Classification.php
+++ b/includes/Classifai/Features/Classification.php
@@ -758,6 +758,17 @@ public function add_custom_settings_fields() {
]
);
+ $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' ),
+ );
+
+ // Embeddings only supports existing terms.
+ if ( isset( $settings['provider'] ) && Embeddings::ID === $settings['provider'] ) {
+ unset( $method_options['recommended_terms'] );
+ $settings['classification_method'] = 'existing_terms';
+ }
+
add_settings_field(
'classification_method',
esc_html__( 'Classification method', 'classifai' ),
@@ -767,10 +778,7 @@ public function add_custom_settings_fields() {
[
'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' ),
- ),
+ 'options' => $method_options,
]
);
@@ -851,12 +859,17 @@ public function sanitize_default_feature_settings( array $new_settings ): array
$new_settings['classification_method'] = sanitize_text_field( $new_settings['classification_method'] ?? $settings['classification_method'] );
+ // Embeddings only supports existing terms.
+ if ( isset( $new_settings['provider'] ) && Embeddings::ID === $new_settings['provider'] ) {
+ $new_settings['classification_method'] = 'existing_terms';
+ }
+
$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'];
if ( ! empty( $provider_instance->nlu_features ) ) {
- foreach ( $provider_instance->nlu_features as $feature_name => $feature ) {
+ foreach ( array_keys( $provider_instance->nlu_features ) as $feature_name ) {
$new_settings[ $feature_name ] = absint( $new_settings[ $feature_name ] ?? $settings[ $feature_name ] );
$new_settings[ "{$feature_name}_threshold" ] = absint( $new_settings[ "{$feature_name}_threshold" ] ?? $settings[ "{$feature_name}_threshold" ] );
$new_settings[ "{$feature_name}_taxonomy" ] = sanitize_text_field( $new_settings[ "{$feature_name}_taxonomy" ] ?? $settings[ "{$feature_name}_taxonomy" ] );
@@ -951,8 +964,10 @@ public function render_nlu_feature_settings( array $args ) {
-
-