diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index d33653aa7..72864fd6c 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -5,8 +5,10 @@ use Classifai\Features\Classification; use Classifai\Services\ServicesManager; +use Classifai\Taxonomy\TaxonomyFactory; use function Classifai\get_asset_info; use function Classifai\get_plugin; +use function Classifai\get_post_types_for_language_settings; use function Classifai\get_services_menu; use function Classifai\get_post_statuses_for_language_settings; use function Classifai\is_elasticpress_installed; @@ -84,14 +86,27 @@ public function admin_enqueue_scripts( $hook_suffix ) { wp_set_script_translations( 'classifai-settings', 'classifai' ); + $post_types = get_post_types_for_language_settings(); + $excerpt_post_types = array(); + $post_type_options = array(); + foreach ( $post_types as $post_type ) { + $post_type_options[ $post_type->name ] = $post_type->label; + if ( post_type_supports( $post_type->name, 'excerpt' ) ) { + $excerpt_post_types[ $post_type->name ] = $post_type->label; + } + } + $data = array( - 'features' => $this->get_features(), - 'services' => get_services_menu(), - 'settings' => $this->get_settings(), - 'dashboardUrl' => admin_url( '/' ), - 'nonce' => wp_create_nonce( 'classifai-previewer-action' ), - 'postStatuses' => get_post_statuses_for_language_settings(), - 'isEPinstalled' => is_elasticpress_installed(), + 'features' => $this->get_features(), + 'services' => get_services_menu(), + 'settings' => $this->get_settings(), + 'dashboardUrl' => admin_url( '/' ), + 'nonce' => wp_create_nonce( 'classifai-previewer-action' ), + 'postTypes' => $post_type_options, + 'excerptPostTypes' => $excerpt_post_types, + 'postStatuses' => get_post_statuses_for_language_settings(), + 'isEPinstalled' => is_elasticpress_installed(), + 'nluTaxonomies' => $this->get_nlu_taxonomies(), ); wp_add_inline_script( @@ -142,6 +157,7 @@ public function get_features( bool $with_instance = false ): array { return $services; } + $post_types = get_post_types_for_language_settings(); foreach ( $service_manager->service_classes as $service ) { $services[ $service->get_menu_slug() ] = array(); @@ -151,13 +167,55 @@ public function get_features( bool $with_instance = false ): array { 'providers' => $feature->get_providers(), 'roles' => $feature->get_roles(), 'enable_description' => $feature->get_enable_description(), + 'taxonomies' => $feature->get_taxonomies(), ); + + // Add taxonomies under post types for language processing features to allow filtering by post type. + if ( 'language_processing' === $service->get_menu_slug() && ! empty( $post_types ) ) { + $post_types_taxonomies = array(); + foreach ( $post_types as $post_type ) { + $post_types_taxonomies[ $post_type->name ] = $feature->get_taxonomies( [ $post_type->name ] ); + } + $services[ $service->get_menu_slug() ][ $feature::ID ]['taxonomiesByPostTypes'] = $post_types_taxonomies; + } } } return $services; } + /** + * Return the list of NLU taxonomies for the Classification feature settings. + * + * @return array + */ + public function get_nlu_taxonomies(): array { + $taxonomies = []; + $taxonomy_factory = new TaxonomyFactory(); + $nlu_taxonomies = $taxonomy_factory->get_supported_taxonomies(); + foreach ( $nlu_taxonomies as $taxonomy ) { + $taxonomy_instance = $taxonomy_factory->build( $taxonomy ); + if ( ! $taxonomy_instance ) { + continue; + } + + $taxonomies[ $taxonomy_instance->get_name() ] = $taxonomy_instance->get_singular_label(); + } + + /** + * Filter IBM Watson NLU taxonomies shown in settings. + * + * @since x.x.x + * @hook classifai_settings_ibm_watson_nlu_taxonomies + * + * @param {array} $taxonomies Array of IBM Watson NLU taxonomies. + * + * @return {array} Array of taxonomies. + */ + return apply_filters( 'classifai_settings_ibm_watson_nlu_taxonomies', $taxonomies ); + } + + /** * Get the settings. * diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index 5e5047dc8..8e9c9f1c5 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -1015,37 +1015,7 @@ public function get_supported_taxonomies( array $post_types = [] ): array { } } - $taxonomies = get_taxonomies( [], 'objects' ); - $taxonomies = array_filter( $taxonomies, 'is_taxonomy_viewable' ); - $supported = []; - - foreach ( $taxonomies as $taxonomy ) { - // Remove this taxonomy if it doesn't support at least one of our post types. - if ( - ( - ! empty( $supported_post_types ) && - empty( array_intersect( $supported_post_types, $taxonomy->object_type ) ) - ) || - 'post_format' === $taxonomy->name - ) { - continue; - } - - $supported[ $taxonomy->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 ); + return $this->get_taxonomies( $supported_post_types ); } /** diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index e28e369e0..e9e76360f 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -1122,6 +1122,46 @@ public function get_supported_post_statuses(): array { return $post_statuses; } + /** + * Return the list of taxonomies for the feature settings. + * + * @param array $post_types Array of post types to filter taxonomies by, leave empty to get all taxonomies. + * @return array + */ + public function get_taxonomies( array $post_types = [] ): array { + $taxonomies = get_taxonomies( [], 'objects' ); + $taxonomies = array_filter( $taxonomies, 'is_taxonomy_viewable' ); + $supported = []; + + foreach ( $taxonomies as $taxonomy ) { + // Remove this taxonomy if it doesn't support at least one of our post types. + if ( + ( + ! empty( $post_types ) && + empty( array_intersect( $post_types, $taxonomy->object_type ) ) + ) || + 'post_format' === $taxonomy->name + ) { + continue; + } + + $supported[ $taxonomy->name ] = $taxonomy->labels->singular_name; + } + + /** + * Filter taxonomies shown in settings. + * + * @since 3.0.0 + * @hook classifai_{feature}_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 ); + } + /** * Returns array of instances of provider classes registered for the service. * diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index eccca3fb1..991a3d0ed 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -355,6 +355,34 @@ public function sanitize_default_feature_settings( array $new_settings ): array return $new_settings; } + /** + * Return the list of Attachment taxonomies for the feature settings. + * + * @param array $object_types Array of object types to filter taxonomies by, not in use. + * @return array + */ + public function get_taxonomies( array $object_types = [] ): array { + $attachment_taxonomies = get_object_taxonomies( 'attachment', 'objects' ); + $taxonomies = []; + + foreach ( $attachment_taxonomies as $name => $taxonomy ) { + $taxonomies[ $name ] = $taxonomy->label; + } + + /** + * Filter taxonomies shown in settings. + * + * @since x.x.x + * @hook classifai_feature_image_tags_generator_setting_taxonomies + * + * @param {array} $supported Array of supported image taxonomies. + * @param {object} $this Current instance of the class. + * + * @return {array} Array of taxonomies. + */ + return apply_filters( 'classifai_' . static::ID . '_setting_taxonomies', $taxonomies, $this ); + } + /** * Generates feature setting data required for migration from * ClassifAI < 3.0.0 to 3.0.0 diff --git a/src/js/settings/components/feature-additional-settings/classification.js b/src/js/settings/components/feature-additional-settings/classification.js index 10ea466f0..b8b277f0f 100644 --- a/src/js/settings/components/feature-additional-settings/classification.js +++ b/src/js/settings/components/feature-additional-settings/classification.js @@ -24,7 +24,7 @@ import { store as noticesStore } from '@wordpress/notices'; */ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -import { isFeatureActive, usePostTypes } from '../../utils/utils'; +import { isFeatureActive } from '../../utils/utils'; import { NLUFeatureSettings } from './nlu-feature'; import { AzureOpenAIEmbeddingsResults, @@ -132,8 +132,7 @@ export const ClassificationSettings = () => { ); const isConfigured = isFeatureActive( featureSettings ); const { setFeatureSettings } = useDispatch( STORE_NAME ); - const { postTypesSelectOptions } = usePostTypes(); - const { postStatuses } = window.classifAISettings; + const { postTypes, postStatuses } = window.classifAISettings; const { createSuccessNotice } = useDispatch( noticesStore ); const previewerContextData = { @@ -272,8 +271,7 @@ export const ClassificationSettings = () => { ) } className="settings-allowed-post-types" > - { postTypesSelectOptions.map( ( option ) => { - const { value: key, label } = option; + { Object.keys( postTypes || {} ).map( ( key ) => { return ( { checked={ featureSettings.post_types?.[ key ] === key } - label={ label } + label={ postTypes?.[ key ] } onChange={ ( value ) => { setFeatureSettings( { post_types: { diff --git a/src/js/settings/components/feature-additional-settings/excerpt-generation.js b/src/js/settings/components/feature-additional-settings/excerpt-generation.js index df1cf535e..9d21ddf12 100644 --- a/src/js/settings/components/feature-additional-settings/excerpt-generation.js +++ b/src/js/settings/components/feature-additional-settings/excerpt-generation.js @@ -14,7 +14,6 @@ import { import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; import { PromptRepeater } from './prompt-repeater'; -import { usePostTypes } from '../../utils/utils'; /** * Component for Excerpt Generation feature settings. @@ -27,7 +26,7 @@ export const ExcerptGenerationSettings = () => { const featureSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); - const { excerptPostTypesOptions } = usePostTypes(); + const { excerptPostTypes } = window.classifAISettings; const { setFeatureSettings } = useDispatch( STORE_NAME ); const setPromts = ( prompts ) => { setFeatureSettings( { @@ -57,8 +56,7 @@ export const ExcerptGenerationSettings = () => { ) } className="settings-allowed-post-types" > - { ( excerptPostTypesOptions || [] ).map( ( option ) => { - const { value: key, label } = option; + { Object.keys( excerptPostTypes || {} ).map( ( key ) => { return ( { checked={ featureSettings.post_types?.[ key ] === key } - label={ label } + label={ excerptPostTypes?.[ key ] } onChange={ ( value ) => { setFeatureSettings( { post_types: { diff --git a/src/js/settings/components/feature-additional-settings/image-tag-generator.js b/src/js/settings/components/feature-additional-settings/image-tag-generator.js index 96472359a..64a18f8ea 100644 --- a/src/js/settings/components/feature-additional-settings/image-tag-generator.js +++ b/src/js/settings/components/feature-additional-settings/image-tag-generator.js @@ -11,6 +11,8 @@ import { __ } from '@wordpress/i18n'; */ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; +import { useFeatureContext } from '../feature-settings/context'; +import { getFeature } from '../../utils/utils'; /** * Component for the Image Tag Generator feature settings. @@ -20,20 +22,17 @@ import { STORE_NAME } from '../../data/store'; * @return {React.ReactElement} ImageTagGeneratorSettings component. */ export const ImageTagGeneratorSettings = () => { + const { featureName } = useFeatureContext(); const featureSettings = useSelect( ( select ) => select( STORE_NAME ).getFeatureSettings() ); const { setFeatureSettings } = useDispatch( STORE_NAME ); + const { taxonomies } = getFeature( featureName ); - const attachmentTaxonomies = useSelect( ( select ) => { - const { getTaxonomies } = select( 'core' ); - return getTaxonomies( { type: 'attachment' } ) || []; - }, [] ); - - const options = attachmentTaxonomies.map( ( taxonomy ) => { + const options = Object.keys( taxonomies || {} ).map( ( slug ) => { return { - value: taxonomy.slug, - label: taxonomy.labels.name, + value: slug, + label: taxonomies[ slug ], }; } ); return ( diff --git a/src/js/settings/components/feature-additional-settings/nlu-feature.js b/src/js/settings/components/feature-additional-settings/nlu-feature.js index 326c9d15f..2f88cf41b 100644 --- a/src/js/settings/components/feature-additional-settings/nlu-feature.js +++ b/src/js/settings/components/feature-additional-settings/nlu-feature.js @@ -14,7 +14,7 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -import { useTaxonomies } from '../../utils/utils'; +import { getFeature } from '../../utils/utils'; /** * Component for render settings fields when IBM Watson NLU is selected as the provider. @@ -28,7 +28,9 @@ export const NLUFeatureSettings = () => { select( STORE_NAME ).getFeatureSettings() ); const { setFeatureSettings } = useDispatch( STORE_NAME ); - const { taxonomies = [] } = useTaxonomies(); + const classificationFeature = getFeature( 'feature_classification' ); + const taxonomies = classificationFeature.taxonomiesByPostTypes || {}; + const { nluTaxonomies = {} } = window.classifAISettings; const nluFeatures = { category: { @@ -49,42 +51,37 @@ export const NLUFeatureSettings = () => { }, }; + const optionsObjects = {}; + Object.keys( taxonomies ).forEach( ( postType ) => { + if ( featureSettings.post_types?.[ postType ] === postType ) { + const postTypeTaxonomies = taxonomies[ postType ] || {}; + Object.keys( postTypeTaxonomies ).forEach( ( taxonomy ) => { + optionsObjects[ taxonomy ] = postTypeTaxonomies[ taxonomy ]; + } ); + } + } ); + + /* + * Add NLU-specific taxonomies to the list if IBM Watson NLU is selected as the provider. + * + * This ensures that the NLU taxonomies are available in the settings, + * as NLU-specific taxonomies are registered only if the Classification feature is enabled and IBM Watson NLU is selected as the provider. + */ + if ( 'ibm_watson_nlu' === featureSettings.provider ) { + Object.keys( nluTaxonomies ).forEach( ( taxonomy ) => { + optionsObjects[ taxonomy ] = nluTaxonomies[ taxonomy ]; + } ); + } + const options = - taxonomies - ?.filter( ( taxonomy ) => { - const intersection = ( taxonomy.types || [] ).filter( - ( type ) => featureSettings.post_types?.[ type ] === type - ); - return intersection.length > 0; - } ) - ?.map( ( taxonomy ) => ( { - label: taxonomy.name, - value: taxonomy.slug, - } ) ) || []; + Object.keys( optionsObjects || {} ).map( ( taxonomy ) => ( { + label: optionsObjects[ taxonomy ], + value: taxonomy, + } ) ) || []; let features = {}; if ( 'ibm_watson_nlu' === featureSettings.provider ) { features = nluFeatures; - if ( options ) { - options.push( - { - label: __( 'Watson Category', 'classifai' ), - value: 'watson-category', - }, - { - label: __( 'Watson Keyword', 'classifai' ), - value: 'watson-keyword', - }, - { - label: __( 'Watson Entity', 'classifai' ), - value: 'watson-entity', - }, - { - label: __( 'Watson Concept', 'classifai' ), - value: 'watson-concept', - } - ); - } } else { options?.forEach( ( taxonomy ) => { features[ taxonomy.value ] = { diff --git a/src/js/settings/components/feature-additional-settings/text-to-speech.js b/src/js/settings/components/feature-additional-settings/text-to-speech.js index 4f5362e87..5c4d3b1c5 100644 --- a/src/js/settings/components/feature-additional-settings/text-to-speech.js +++ b/src/js/settings/components/feature-additional-settings/text-to-speech.js @@ -11,7 +11,6 @@ import { __ } from '@wordpress/i18n'; */ import { SettingsRow } from '../settings-row'; import { STORE_NAME } from '../../data/store'; -import { usePostTypes } from '../../utils/utils'; /** * Component for Text to Speech feature settings. @@ -25,7 +24,7 @@ export const TextToSpeechSettings = () => { select( STORE_NAME ).getFeatureSettings() ); const { setFeatureSettings } = useDispatch( STORE_NAME ); - const { postTypesSelectOptions } = usePostTypes(); + const { postTypes } = window.classifAISettings; return ( { ) } className="settings-allowed-post-types" > - { postTypesSelectOptions.map( ( option ) => { - const { value: key, label } = option; + { Object.keys( postTypes || {} ).map( ( key ) => { return ( { setFeatureSettings( { post_types: { diff --git a/src/js/settings/utils/utils.js b/src/js/settings/utils/utils.js index e9736398c..5e523c012 100644 --- a/src/js/settings/utils/utils.js +++ b/src/js/settings/utils/utils.js @@ -2,8 +2,6 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; -import { store as coreStore } from '@wordpress/core-data'; // Update URL based on the current tab and feature selected export const updateUrl = ( key, value ) => { @@ -116,76 +114,6 @@ export const isProviderConfigured = ( featureSettings ) => { return featureSettings[ selectedProvider ]?.authenticated || false; }; -/** - * Returns a helper object that contains: - * An `options` object from the available post types, to be passed to a `SelectControl`. - * - * @return {Object} The helper object related to post types. - */ -export const usePostTypes = () => { - const postTypes = useSelect( ( select ) => { - const { getPostTypes } = select( coreStore ); - const excludedPostTypes = [ 'attachment' ]; - const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter( - ( { viewable, slug } ) => - viewable && ! excludedPostTypes.includes( slug ) - ); - return filteredPostTypes; - }, [] ); - - const postTypesSelectOptions = useMemo( - () => - ( postTypes || [] ).map( ( { labels, slug } ) => ( { - label: labels.singular_name, - value: slug, - } ) ), - [ postTypes ] - ); - - const excerptPostTypes = useSelect( ( select ) => { - const { getPostTypes } = select( coreStore ); - const filteredPostTypes = getPostTypes( { per_page: -1 } )?.filter( - ( { viewable, supports } ) => viewable && supports?.excerpt - ); - return filteredPostTypes; - }, [] ); - - const excerptPostTypesOptions = useMemo( () => { - return ( excerptPostTypes || [] ).map( ( { labels, slug } ) => ( { - label: labels.singular_name, - value: slug, - } ) ); - }, [ excerptPostTypes ] ); - - return { postTypesSelectOptions, postTypes, excerptPostTypesOptions }; -}; - -/** - * Post Statuses Hook. - * Returns a helper object that contains: - * An `options` object from the available post statuses. - * - * @return {Object} The helper object related to post statuses. - */ -export const useTaxonomies = () => { - const taxonomyOptions = useSelect( ( select ) => { - // Remove the NLUs taxonomies from the list of taxonomies - const excludedTaxonomies = [ - 'watson-category', - 'watson-keyword', - 'watson-concept', - 'watson-entity', - ]; - return select( 'core' ) - .getTaxonomies() - ?.filter( ( { slug } ) => ! excludedTaxonomies.includes( slug ) ); - }, [] ); - - const taxonomies = useMemo( () => taxonomyOptions, [ taxonomyOptions ] ); - - return { taxonomies }; -}; - /** * User Permissions Preferences Hook. * diff --git a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js index 892c225f0..9d55462c5 100644 --- a/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js +++ b/tests/cypress/integration/language-processing/classify-content-ibm-watson.test.js @@ -64,9 +64,7 @@ describe( '[Language processing] Classify content (IBM Watson - NLU) Tests', () } ); it( 'Can select Watson taxonomies "Language Processing" settings', () => { - cy.intercept( '/wp-json/wp/v2/taxonomies*' ).as( 'getTaxonomies' ); cy.visitFeatureSettings( 'language_processing' ); - cy.wait( '@getTaxonomies' ); cy.enableFeature(); cy.get( '#category-taxonomy' ).select( 'watson-category' );