Add core classes and tests for Groq AI compatibility, logging, and model services

- Implement Groq_AI_Compatibility_Service to manage WooCommerce dependency and admin notices.
- Create Groq_AI_Log_Scheduler for scheduled log cleanup based on settings.
- Develop Groq_AI_Model_Service for model selection and caching.
- Add language translations in POT file for Dutch.
- Set up PHPUnit configuration and bootstrap for testing.
- Implement unit tests for model exclusions, provider request building, settings management, and term saving functionality.
This commit is contained in:
2026-01-31 17:48:46 +00:00
parent 26aabdb2d8
commit 6cff0b6f58
25 changed files with 3131 additions and 368 deletions

View File

@@ -2,8 +2,9 @@
/**
* Plugin Name: SitiAI Product Teksten
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
* Version: 1.8.0
* Author: SitiAI
* Version: 1.9.0
* Author: Roberto Guagliardo | SitiWeb
* Author URI: https://sitiweb.nl/
* Text Domain: siti-ai-product-content-generator
* Domain Path: /languages
*/
@@ -44,6 +45,9 @@ if ( ! defined( 'GROQ_AI_DEBUG_TRACE_ADDED' ) && defined( 'WP_DEBUG' ) && WP_DEB
require_once __DIR__ . '/includes/Core/class-groq-ai-service-container.php';
require_once __DIR__ . '/includes/Core/class-groq-ai-model-exclusions.php';
require_once __DIR__ . '/includes/Core/class-groq-ai-ajax-controller.php';
require_once __DIR__ . '/includes/Core/class-groq-ai-compatibility-service.php';
require_once __DIR__ . '/includes/Core/class-groq-ai-model-service.php';
require_once __DIR__ . '/includes/Core/class-groq-ai-log-scheduler.php';
require_once __DIR__ . '/includes/Contracts/interface-groq-ai-provider.php';
require_once __DIR__ . '/includes/Providers/class-groq-ai-abstract-openai-provider.php';
require_once __DIR__ . '/includes/Providers/class-groq-ai-provider-groq.php';
@@ -106,8 +110,14 @@ final class Groq_AI_Product_Text_Plugin {
/** @var Groq_AI_Product_Text_Product_UI */
private $product_ui;
/** @var bool */
private $missing_wc_notice = false;
/** @var Groq_AI_Compatibility_Service */
private $compatibility_service;
/** @var Groq_AI_Model_Service */
private $model_service;
/** @var Groq_AI_Log_Scheduler */
private $log_scheduler;
public static function instance() {
if ( null === self::$instance ) {
@@ -119,6 +129,9 @@ final class Groq_AI_Product_Text_Plugin {
private function __construct() {
$this->register_services();
$this->compatibility_service = new Groq_AI_Compatibility_Service();
$this->model_service = new Groq_AI_Model_Service();
$this->log_scheduler = new Groq_AI_Log_Scheduler( $this->get_settings_manager(), $this->get_generation_logger() );
$this->settings_page = new Groq_AI_Product_Text_Settings_Page( $this, $this->get_provider_manager() );
$this->categories_admin = new Groq_AI_Categories_Admin( $this );
@@ -127,8 +140,11 @@ final class Groq_AI_Product_Text_Plugin {
$this->product_ui = new Groq_AI_Product_Text_Product_UI( $this );
add_action( 'init', [ $this, 'load_textdomain' ] );
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
$logger = $this->container->get( 'generation_logger' );
add_action( 'plugins_loaded', [ $logger, 'maybe_create_table' ] );
add_action( 'load-plugins.php', [ $this->compatibility_service, 'maybe_deactivate_if_woocommerce_missing' ] );
add_action( 'init', [ $this->log_scheduler, 'ensure_logs_cleanup_schedule' ] );
add_action( 'groq_ai_cleanup_logs', [ $this->log_scheduler, 'cleanup_logs' ] );
add_filter( 'groq_ai_term_google_context', [ $this, 'inject_google_term_context' ], 10, 3 );
}
@@ -238,60 +254,84 @@ final class Groq_AI_Product_Text_Plugin {
return self::OPTION_KEY;
}
public function get_provider_manager() {
return $this->container->get( 'provider_manager' );
}
public function get_settings_manager() {
return $this->container->get( 'settings_manager' );
}
public function get_prompt_builder() {
return $this->container->get( 'prompt_builder' );
}
public function get_conversation_manager() {
return $this->container->get( 'conversation_manager' );
}
public function get_generation_logger() {
return $this->container->get( 'generation_logger' );
}
public function get_settings() {
return $this->get_settings_manager()->all();
}
public function sanitize_settings( $input ) {
return $this->get_settings_manager()->sanitize( $input );
}
public function maybe_deactivate_if_woocommerce_missing() {
if ( $this->is_woocommerce_active() ) {
return;
public function __call( $name, $arguments ) {
switch ( $name ) {
case 'get_provider_manager':
return $this->container->get( 'provider_manager' );
case 'get_settings_manager':
return $this->container->get( 'settings_manager' );
case 'get_prompt_builder':
return $this->container->get( 'prompt_builder' );
case 'get_conversation_manager':
return $this->container->get( 'conversation_manager' );
case 'get_generation_logger':
return $this->container->get( 'generation_logger' );
case 'get_settings':
return $this->container->get( 'settings_manager' )->all();
case 'sanitize_settings':
return $this->container->get( 'settings_manager' )->sanitize( $arguments[0] ?? [] );
case 'get_context_field_definitions':
return $this->container->get( 'settings_manager' )->get_context_field_definitions();
case 'get_default_modules_settings':
return $this->container->get( 'settings_manager' )->get_default_modules_settings();
case 'get_default_context_fields':
return $this->container->get( 'settings_manager' )->get_default_context_fields();
case 'normalize_context_fields':
return $this->container->get( 'settings_manager' )->normalize_context_fields( $arguments[0] ?? [] );
case 'get_module_config':
return $this->container->get( 'settings_manager' )->get_module_config( $arguments[0] ?? '', $arguments[1] ?? null );
case 'is_module_enabled':
return $this->container->get( 'settings_manager' )->is_module_enabled( $arguments[0] ?? '', $arguments[1] ?? null );
case 'get_rankmath_focus_keyword_limit':
return $this->container->get( 'settings_manager' )->get_rankmath_focus_keyword_limit( $arguments[0] ?? null );
case 'get_rankmath_meta_title_pixel_limit':
return $this->container->get( 'settings_manager' )->get_rankmath_meta_title_pixel_limit( $arguments[0] ?? null );
case 'get_rankmath_meta_description_pixel_limit':
return $this->container->get( 'settings_manager' )->get_rankmath_meta_description_pixel_limit( $arguments[0] ?? null );
case 'is_response_format_compat_enabled':
return $this->container->get( 'settings_manager' )->is_response_format_compat_enabled( $arguments[0] ?? null );
case 'get_image_context_mode':
return $this->container->get( 'settings_manager' )->get_image_context_mode( $arguments[0] ?? null );
case 'get_image_context_limit':
return $this->container->get( 'settings_manager' )->get_image_context_limit( $arguments[0] ?? null );
case 'get_term_top_description_char_limit':
return $this->container->get( 'settings_manager' )->get_term_top_description_char_limit( $arguments[0] ?? null );
case 'get_term_bottom_description_char_limit':
return $this->container->get( 'settings_manager' )->get_term_bottom_description_char_limit( $arguments[0] ?? null );
case 'get_google_safety_settings':
return $this->container->get( 'settings_manager' )->get_google_safety_settings( $arguments[0] ?? null );
case 'get_google_safety_categories':
return $this->container->get( 'settings_manager' )->get_google_safety_categories();
case 'get_google_safety_thresholds':
return $this->container->get( 'settings_manager' )->get_google_safety_thresholds();
case 'get_loggable_settings_snapshot':
return $this->container->get( 'settings_manager' )->get_loggable_settings_snapshot( $arguments[0] ?? null );
case 'create_settings_renderer':
$values = $arguments[0] ?? null;
if ( null === $values ) {
$values = $this->container->get( 'settings_manager' )->all();
}
return new Groq_AI_Settings_Renderer( self::OPTION_KEY, $values );
case 'should_use_response_format':
$provider = $arguments[0] ?? null;
$settings = $arguments[1] ?? null;
if ( ! $provider instanceof Groq_AI_Provider_Interface ) {
return false;
}
return ! $this->container->get( 'settings_manager' )->is_response_format_compat_enabled( $settings ) && $provider->supports_response_format();
case 'is_rankmath_active':
return $this->compatibility_service->is_rankmath_active();
case 'is_woocommerce_active':
return $this->compatibility_service->is_woocommerce_active();
case 'get_selected_model':
return $this->model_service->get_selected_model( $arguments[0], $arguments[1] ?? [] );
case 'get_cached_models_for_provider':
return $this->model_service->get_cached_models_for_provider( $arguments[0] ?? '' );
case 'update_cached_models_for_provider':
return $this->model_service->update_cached_models_for_provider( $arguments[0] ?? '', $arguments[1] ?? [] );
}
if ( ! function_exists( 'deactivate_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
deactivate_plugins( plugin_basename( GROQ_AI_PRODUCT_TEXT_FILE ) );
$this->missing_wc_notice = true;
add_action( 'admin_notices', [ $this, 'render_missing_wc_notice' ] );
}
public function render_missing_wc_notice() {
if ( ! $this->missing_wc_notice ) {
return;
}
?>
<div class="notice notice-error">
<p>
<?php esc_html_e( 'SitiAI Product Teksten vereist WooCommerce en is gedeactiveerd omdat WooCommerce niet actief is.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
</p>
</div>
<?php
throw new BadMethodCallException( sprintf( 'Method %s does not exist.', $name ) );
}
public function build_prompt_template_preview( $settings ) {
@@ -312,203 +352,6 @@ final class Groq_AI_Product_Text_Plugin {
return implode( "\n\n", $parts );
}
public function get_context_field_definitions() {
return $this->get_settings_manager()->get_context_field_definitions();
}
public function get_default_modules_settings() {
return $this->get_settings_manager()->get_default_modules_settings();
}
public function get_default_context_fields() {
return $this->get_settings_manager()->get_default_context_fields();
}
public function normalize_context_fields( $fields ) {
return $this->get_settings_manager()->normalize_context_fields( $fields );
}
public function get_module_config( $module, $settings = null ) {
return $this->get_settings_manager()->get_module_config( $module, $settings );
}
public function is_module_enabled( $module, $settings = null ) {
return $this->get_settings_manager()->is_module_enabled( $module, $settings );
}
public function get_rankmath_focus_keyword_limit( $settings = null ) {
return $this->get_settings_manager()->get_rankmath_focus_keyword_limit( $settings );
}
public function get_rankmath_meta_title_pixel_limit( $settings = null ) {
return $this->get_settings_manager()->get_rankmath_meta_title_pixel_limit( $settings );
}
public function get_rankmath_meta_description_pixel_limit( $settings = null ) {
return $this->get_settings_manager()->get_rankmath_meta_description_pixel_limit( $settings );
}
public function is_response_format_compat_enabled( $settings = null ) {
return $this->get_settings_manager()->is_response_format_compat_enabled( $settings );
}
public function get_image_context_mode( $settings = null ) {
return $this->get_settings_manager()->get_image_context_mode( $settings );
}
public function get_image_context_limit( $settings = null ) {
return $this->get_settings_manager()->get_image_context_limit( $settings );
}
public function get_term_top_description_char_limit( $settings = null ) {
return $this->get_settings_manager()->get_term_top_description_char_limit( $settings );
}
public function get_term_bottom_description_char_limit( $settings = null ) {
return $this->get_settings_manager()->get_term_bottom_description_char_limit( $settings );
}
public function get_google_safety_settings( $settings = null ) {
return $this->get_settings_manager()->get_google_safety_settings( $settings );
}
public function get_google_safety_categories() {
return $this->get_settings_manager()->get_google_safety_categories();
}
public function get_google_safety_thresholds() {
return $this->get_settings_manager()->get_google_safety_thresholds();
}
public function get_loggable_settings_snapshot( $settings = null ) {
return $this->get_settings_manager()->get_loggable_settings_snapshot( $settings );
}
public function create_settings_renderer( $values = null ) {
if ( null === $values ) {
$values = $this->get_settings();
}
return new Groq_AI_Settings_Renderer( self::OPTION_KEY, $values );
}
public function should_use_response_format( Groq_AI_Provider_Interface $provider, $settings ) {
return ! $this->is_response_format_compat_enabled( $settings ) && $provider->supports_response_format();
}
public function is_rankmath_active() {
if ( class_exists( 'RankMath' ) ) {
return true;
}
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return function_exists( 'is_plugin_active' ) && is_plugin_active( 'seo-by-rank-math/rank-math.php' );
}
public function is_woocommerce_active() {
if ( class_exists( 'WooCommerce' ) ) {
return true;
}
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return function_exists( 'is_plugin_active' ) && is_plugin_active( 'woocommerce/woocommerce.php' );
}
public function get_selected_model( Groq_AI_Provider_Interface $provider, $settings ) {
$provider_key = $provider->get_key();
$model = ! empty( $settings['model'] ) ? $settings['model'] : '';
$model = Groq_AI_Model_Exclusions::ensure_allowed( $provider_key, $model );
if ( '' === $model ) {
$default = Groq_AI_Model_Exclusions::ensure_allowed( $provider_key, $provider->get_default_model() );
if ( '' !== $default ) {
return $default;
}
$available = Groq_AI_Model_Exclusions::filter_models( $provider_key, $provider->get_available_models() );
if ( ! empty( $available ) ) {
return $available[0];
}
}
return $model;
}
public function get_cached_models_for_provider( $provider ) {
$provider = sanitize_key( (string) $provider );
$cache = $this->get_models_cache();
return isset( $cache[ $provider ] ) ? $cache[ $provider ] : [];
}
public function update_cached_models_for_provider( $provider, $models ) {
$provider = sanitize_key( (string) $provider );
$models = $this->sanitize_models_list( $models );
$cache = $this->get_models_cache();
$cache[ $provider ] = $models;
update_option( self::MODELS_CACHE_OPTION_KEY, $cache );
return $models;
}
private function get_models_cache() {
$cache = get_option( self::MODELS_CACHE_OPTION_KEY, [] );
if ( ! is_array( $cache ) ) {
$cache = [];
}
foreach ( $cache as $provider => $models ) {
$cache[ $provider ] = $this->sanitize_models_list( $models );
}
return $cache;
}
private function sanitize_models_list( $models ) {
if ( ! is_array( $models ) ) {
return [];
}
$models = array_map( 'sanitize_text_field', $models );
$models = array_filter(
$models,
function ( $model ) {
return '' !== $model;
}
);
$models = array_values( array_unique( $models ) );
if ( ! empty( $models ) ) {
sort( $models, SORT_NATURAL | SORT_FLAG_CASE );
}
return $models;
}
public function log_debug( $message, $context = [] ) {
$this->get_generation_logger()->log_debug( $message, $context );
}
private function extract_content_text( $result ) {
if ( is_array( $result ) && isset( $result['content'] ) ) {
return (string) $result['content'];
}
return (string) $result;
}
public function maybe_create_logs_table() {
$this->get_generation_logger()->maybe_create_table();
}
public static function activate() {
$logger = new Groq_AI_Generation_Logger();