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

@@ -60,10 +60,14 @@ class Groq_AI_Logs_Table extends WP_List_Table {
$current_page = $this->get_pagenum();
$offset = ( $current_page - 1 ) * $per_page;
$orderby = isset( $_REQUEST['orderby'] ) ? sanitize_sql_orderby( wp_unslash( $_REQUEST['orderby'] ) ) : 'created_at';
if ( ! $orderby ) {
$orderby = 'created_at';
}
$allowed_orderby = [
'created_at' => 'created_at',
'provider' => 'provider',
'model' => 'model',
'status' => 'status',
];
$orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : 'created_at';
$orderby = isset( $allowed_orderby[ $orderby ] ) ? $allowed_orderby[ $orderby ] : 'created_at';
$order = isset( $_REQUEST['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) : 'DESC';
$order = in_array( $order, [ 'ASC', 'DESC' ], true ) ? $order : 'DESC';

View File

@@ -73,6 +73,18 @@ class Groq_AI_Product_Text_Product_UI {
'postId' => $post_id,
'contextDefaults' => isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields(),
'attributeIncludesDefaults' => $attribute_defaults,
'strings' => [
'loading' => __( 'AI is bezig met schrijven...', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'retry' => __( 'Probeer het opnieuw of pas je prompt/context aan.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorDefault' => __( 'Er ging iets mis bij het genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorUnknown' => __( 'Onbekende fout.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'success' => __( 'Structuur gegenereerd. Kopieer of vul velden in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'fieldApplied' => __( '%s ingevuld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'fieldApplyError' => __( 'Kon het veld niet automatisch invullen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'fieldCopied' => __( '%s gekopieerd naar het klembord.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'jsonCopied' => __( 'JSON gekopieerd naar het klembord.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'copyFailed' => __( 'Kopiëren mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
]
);
}

View File

@@ -125,17 +125,7 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
$brands_url = $this->get_page_url( 'groq-ai-product-text-brands' );
$prompt_preview = $this->plugin->build_prompt_template_preview( $settings );
$google_notice = isset( $_GET['groq_ai_google_notice'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice'] ) ) : '';
$google_status = isset( $_GET['groq_ai_google_notice_status'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice_status'] ) ) : '';
$google_message = '';
if ( isset( $_GET['groq_ai_google_notice_message'] ) ) {
$google_message = sanitize_text_field( rawurldecode( wp_unslash( $_GET['groq_ai_google_notice_message'] ) ) );
}
$google_connected = ! empty( $settings['google_oauth_refresh_token'] );
$google_connected_email = isset( $settings['google_oauth_connected_email'] ) ? (string) $settings['google_oauth_connected_email'] : '';
$google_connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0;
$oauth_redirect = add_query_arg( 'action', 'groq_ai_google_oauth_callback', admin_url( 'admin-post.php' ) );
$google_safety_settings = $this->plugin->get_google_safety_settings( $settings );
$google_safety_categories = $this->plugin->get_google_safety_categories();
$google_safety_thresholds = $this->plugin->get_google_safety_thresholds();
@@ -145,7 +135,7 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
<div class="wrap">
<h1><?php esc_html_e( 'Siti AI instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description">
<?php esc_html_e( 'Kies je AI-aanbieder, beheer API-sleutels en koppel optioneel Google Search Console/Analytics voor extra context.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
<?php esc_html_e( 'Kies je AI-aanbieder en beheer API-sleutels voor de contentgeneratie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
</p>
<p style="margin:16px 0; display:flex; flex-wrap:wrap; gap:8px;">
<a class="button" href="<?php echo esc_url( $prompt_url ); ?>"><?php esc_html_e( 'Prompt instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
@@ -155,12 +145,7 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
<a class="button" href="<?php echo esc_url( $brands_url ); ?>"><?php esc_html_e( 'Merk teksten', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
</p>
<?php settings_errors( $option_key ); ?>
<?php if ( $google_notice ) :
$class = ( 'error' === $google_status ) ? 'notice-error' : 'notice-success';
$google_message = '' !== $google_message ? $google_message : ( 'connected' === $google_notice ? __( 'Google OAuth is verbonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : ( 'disconnected' === $google_notice ? __( 'Google OAuth is ontkoppeld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Google test afgerond.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ) );
?>
<div class="notice <?php echo esc_attr( $class ); ?>"><p><?php echo esc_html( $google_message ); ?></p></div>
<?php endif; ?>
<div style="margin:16px 0; padding:16px; background:#fff; border:1px solid #dcdcde;">
<strong><?php esc_html_e( 'Huidige promptcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></strong>
<pre style="background:#f6f7f7; padding:12px; overflow:auto; margin-top:8px; white-space:pre-wrap;"><?php echo esc_html( $prompt_preview ); ?></pre>
@@ -243,6 +228,18 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
'description' => __( 'Limitering van het aantal tokens per output voor compatibiliteit met verschillende modellen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
]
);
$renderer->field(
[
'label' => __( 'Logboek retentie (dagen)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'key' => 'logs_retention_days',
'type' => 'number',
'attributes' => [
'min' => 0,
'max' => 3650,
],
'description' => __( 'Hoe lang logboekregels bewaard blijven. Zet op 0 om logs onbeperkt te bewaren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
]
);
$renderer->field(
[
'label' => __( 'Term meta key (onderste tekst)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
@@ -262,8 +259,80 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
$renderer->close_table();
?>
<p class="submit"><?php submit_button( __( 'Instellingen opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'primary', 'submit', false ); ?></p>
</form>
</div>
<?php
}
/**
* Register plugin settings with WordPress.
*/
public function register_settings() {
register_setting(
$this->plugin->get_option_key(),
$this->plugin->get_option_key(),
[ $this->plugin, 'sanitize_settings' ]
);
}
public function hide_menu_links() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<style>
#adminmenu a[href="options-general.php?page=groq-ai-product-text-modules"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-logs"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-prompts"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-term"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-log"] {
display: none !important;
}
</style>
<?php
}
public function render_modules_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$option_key = $this->plugin->get_option_key();
$settings = $this->plugin->get_settings();
$current_page = $this->get_page_url( 'groq-ai-product-text-modules' );
$oauth_redirect = add_query_arg( 'action', 'groq_ai_google_oauth_callback', admin_url( 'admin-post.php' ) );
$google_notice = isset( $_GET['groq_ai_google_notice'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice'] ) ) : '';
$google_status = isset( $_GET['groq_ai_google_notice_status'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice_status'] ) ) : '';
$google_message = '';
if ( isset( $_GET['groq_ai_google_notice_message'] ) ) {
$google_message = sanitize_text_field( rawurldecode( wp_unslash( $_GET['groq_ai_google_notice_message'] ) ) );
}
$google_connected = ! empty( $settings['google_oauth_refresh_token'] );
$google_connected_email = isset( $settings['google_oauth_connected_email'] ) ? (string) $settings['google_oauth_connected_email'] : '';
$google_connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0;
?>
<div class="wrap">
<h1><?php esc_html_e( 'Siti AI modules', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description"><?php esc_html_e( 'Schakel aanvullende integraties in en bepaal grenzen voor gegenereerde content.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<?php settings_errors( $option_key ); ?>
<?php if ( $google_notice ) :
$class = ( 'error' === $google_status ) ? 'notice-error' : 'notice-success';
$google_message = '' !== $google_message ? $google_message : ( 'connected' === $google_notice ? __( 'Google OAuth is verbonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : ( 'disconnected' === $google_notice ? __( 'Google OAuth is ontkoppeld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Google test afgerond.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ) );
?>
<div class="notice <?php echo esc_attr( $class ); ?>"><p><?php echo esc_html( $google_message ); ?></p></div>
<?php endif; ?>
<form method="post" action="options.php">
<?php settings_fields( $option_key ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Rank Math integratie', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php $this->render_rankmath_module_field(); ?></td>
</tr>
</table>
<h2><?php esc_html_e( 'Google Search Console & Analytics', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<?php
$renderer = $this->plugin->create_settings_renderer( $settings );
$renderer->open_table();
$renderer->field(
[
@@ -320,7 +389,7 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
);
$renderer->close_table();
?>
<p class="submit"><?php submit_button( __( 'Instellingen opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'primary', 'submit', false ); ?></p>
<?php submit_button( __( 'Modules opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
</form>
<div style="margin-top:24px; padding:16px; border:1px solid #dcdcde; background:#fff;">
<h2><?php esc_html_e( 'Google verbinding', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
@@ -364,59 +433,6 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
</div>
<?php
}
/**
* Register plugin settings with WordPress.
*/
public function register_settings() {
register_setting(
$this->plugin->get_option_key(),
$this->plugin->get_option_key(),
[ $this->plugin, 'sanitize_settings' ]
);
}
public function hide_menu_links() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<style>
#adminmenu a[href="options-general.php?page=groq-ai-product-text-modules"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-logs"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-prompts"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-term"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-log"] {
display: none !important;
}
</style>
<?php
}
public function render_modules_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$option_key = $this->plugin->get_option_key();
?>
<div class="wrap">
<h1><?php esc_html_e( 'Siti AI modules', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description"><?php esc_html_e( 'Schakel aanvullende integraties in en bepaal grenzen voor gegenereerde content.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<?php settings_errors( $option_key ); ?>
<form method="post" action="options.php">
<?php settings_fields( $option_key ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Rank Math integratie', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php $this->render_rankmath_module_field(); ?></td>
</tr>
</table>
<?php submit_button( __( 'Modules opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
</form>
</div>
<?php
}
public function render_prompt_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -837,6 +853,14 @@ class Groq_AI_Product_Text_Settings_Page extends Groq_AI_Admin_Base {
'placeholders' => [
'selectModel' => __( 'Selecteer een model via "Live modellen ophalen"', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
'strings' => [
'providerUnsupported' => __( 'Deze aanbieder ondersteunt dit niet.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'apiKeyRequired' => __( 'Vul eerst de API-sleutel in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'loadingModels' => __( 'Modellen worden opgehaald…', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorUnknown' => __( 'Onbekende fout', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'successModels' => __( 'Modellen bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorFetch' => __( 'Ophalen mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
];
foreach ( $this->provider_manager->get_providers() as $provider ) {

View File

@@ -89,10 +89,20 @@ abstract class Groq_AI_Term_Admin_Base extends Groq_AI_Admin_Base {
'taxonomy' => $taxonomy,
'terms' => $terms,
'allowRegenerate' => false,
'strings' => [],
'strings' => [
'unknownError' => __( 'Onbekende fout', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'unknownTerm' => __( 'Onbekende term.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'confirmStopFallback' => __( 'Stoppen?', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'logErrorDefault' => __( '%1$s: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'logSuccessDefault' => __( '%1$s gevuld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'regenerateErrorDefault' => __( '%1$s mislukt: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'regenerateDoneDefault' => __( '%s is bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
];
$config = wp_parse_args( $overrides, $defaults );
$override_strings = isset( $overrides['strings'] ) && is_array( $overrides['strings'] ) ? $overrides['strings'] : [];
$config['strings'] = array_merge( $defaults['strings'], $override_strings );
wp_localize_script( 'groq-ai-term-bulk', 'GroqAITermBulk', $config );
}
@@ -207,6 +217,14 @@ abstract class Groq_AI_Term_Admin_Base extends Groq_AI_Admin_Base {
'nonce' => wp_create_nonce( 'groq_ai_generate_term' ),
'taxonomy' => $taxonomy,
'termId' => $term_id,
'strings' => [
'promptRequired' => __( 'Vul eerst een prompt in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'loading' => __( 'AI is bezig met schrijven...', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'success' => __( 'Tekst gegenereerd. Je kunt hem toepassen en opslaan.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'applySuccess' => __( 'Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorDefault' => __( 'Er ging iets mis bij het genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'errorUnknown' => __( 'Onbekende fout', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
]
);
}

View File

@@ -435,9 +435,22 @@ class Groq_AI_Ajax_Controller {
check_ajax_referer( 'groq_ai_generate', 'nonce' );
$prompt = isset( $_POST['prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['prompt'] ) ) : '';
$prompt = isset( $_POST['prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['prompt'] ) ) : '';
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( ! $post_id ) {
wp_send_json_error( [ 'message' => __( 'Post-ID ontbreekt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 );
}
$post = get_post( $post_id );
if ( ! $post || is_wp_error( $post ) || 'product' !== $post->post_type ) {
wp_send_json_error( [ 'message' => __( 'Product niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 );
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming om dit product te bewerken.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
}
$settings = $this->plugin->get_settings();
$provider_manager = $this->plugin->get_provider_manager();
$provider_key = $settings['provider'];

View File

@@ -0,0 +1,58 @@
<?php
class Groq_AI_Compatibility_Service {
/** @var bool */
private $missing_wc_notice = false;
public function maybe_deactivate_if_woocommerce_missing() {
if ( $this->is_woocommerce_active() ) {
return;
}
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
}
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' );
}
}

View File

@@ -0,0 +1,28 @@
<?php
class Groq_AI_Log_Scheduler {
/** @var Groq_AI_Settings_Manager */
private $settings_manager;
/** @var Groq_AI_Generation_Logger */
private $logger;
public function __construct( Groq_AI_Settings_Manager $settings_manager, Groq_AI_Generation_Logger $logger ) {
$this->settings_manager = $settings_manager;
$this->logger = $logger;
}
public function ensure_logs_cleanup_schedule() {
if ( wp_next_scheduled( 'groq_ai_cleanup_logs' ) ) {
return;
}
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'groq_ai_cleanup_logs' );
}
public function cleanup_logs() {
$settings = $this->settings_manager->all();
$retention_days = $this->settings_manager->get_logs_retention_days( $settings );
$this->logger->cleanup_old_logs( $retention_days );
}
}

View File

@@ -0,0 +1,78 @@
<?php
class Groq_AI_Model_Service {
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( Groq_AI_Product_Text_Plugin::MODELS_CACHE_OPTION_KEY, $cache );
return $models;
}
private function get_models_cache() {
$cache = get_option( Groq_AI_Product_Text_Plugin::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;
}
}

View File

@@ -143,6 +143,20 @@ class Groq_AI_Generation_Logger {
update_option( self::OPTION_TABLE_CREATED, 1 );
}
public function cleanup_old_logs( $retention_days ) {
$retention_days = absint( $retention_days );
if ( $retention_days <= 0 || ! $this->logs_table_exists() ) {
return;
}
$cutoff = time() - ( $retention_days * DAY_IN_SECONDS );
$cutoff = gmdate( 'Y-m-d H:i:s', $cutoff );
global $wpdb;
$table = $this->get_logs_table_name();
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE created_at < %s", $cutoff ) );
}
private function extract_usage_token_value( $usage, $keys ) {
foreach ( (array) $keys as $key ) {
if ( isset( $usage[ $key ] ) ) {

View File

@@ -33,6 +33,7 @@ class Groq_AI_Settings_Manager {
'store_context' => '',
'default_prompt' => '',
'max_output_tokens' => 2048,
'logs_retention_days' => 90,
'product_attribute_includes' => [],
'term_bottom_description_meta_key' => '',
'groq_api_key' => '',
@@ -63,6 +64,8 @@ class Groq_AI_Settings_Manager {
$settings['modules'] = $this->sanitize_modules_settings( isset( $settings['modules'] ) ? $settings['modules'] : [] );
$settings['google_safety_settings'] = $this->sanitize_google_safety_settings( isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : [] );
$settings['model'] = Groq_AI_Model_Exclusions::ensure_allowed( $settings['provider'], isset( $settings['model'] ) ? $settings['model'] : '' );
$logs_retention_days = isset( $settings['logs_retention_days'] ) ? (int) $settings['logs_retention_days'] : 90;
$settings['logs_retention_days'] = max( 0, min( 3650, $logs_retention_days ) );
$image_mode = isset( $settings['image_context_mode'] ) ? sanitize_text_field( $settings['image_context_mode'] ) : 'url';
if ( 'none' === $image_mode ) {
@@ -108,6 +111,7 @@ class Groq_AI_Settings_Manager {
'store_context' => '',
'default_prompt' => '',
'max_output_tokens' => 2048,
'logs_retention_days' => 90,
'product_attribute_includes' => [],
'term_bottom_description_meta_key' => '',
'groq_api_key' => '',
@@ -159,6 +163,10 @@ class Groq_AI_Settings_Manager {
// Keep within sane bounds across providers.
$max_output_tokens = max( 128, min( 8192, $max_output_tokens ) );
$logs_retention_days = isset( $input['logs_retention_days'] ) ? (int) $input['logs_retention_days'] : (int) $defaults['logs_retention_days'];
// 0 = keep indefinitely, otherwise cap at 10 years.
$logs_retention_days = max( 0, min( 3650, $logs_retention_days ) );
$context_fields = $this->normalize_context_fields( $context_posted ? $raw_input['context_fields'] : $defaults['context_fields'] );
if ( 'none' === $image_mode ) {
@@ -182,6 +190,7 @@ class Groq_AI_Settings_Manager {
'store_context' => sanitize_textarea_field( $input['store_context'] ),
'default_prompt' => sanitize_textarea_field( $input['default_prompt'] ),
'max_output_tokens' => $max_output_tokens,
'logs_retention_days' => $logs_retention_days,
'product_attribute_includes' => $this->sanitize_product_attribute_includes( isset( $raw_input['product_attribute_includes'] ) ? $raw_input['product_attribute_includes'] : [] ),
'term_bottom_description_meta_key' => sanitize_key( (string) $input['term_bottom_description_meta_key'] ),
'groq_api_key' => sanitize_text_field( $input['groq_api_key'] ),
@@ -442,6 +451,15 @@ class Groq_AI_Settings_Manager {
return self::get_google_safety_thresholds_list();
}
public function get_logs_retention_days( $settings = null ) {
if ( null === $settings ) {
$settings = $this->all();
}
$value = isset( $settings['logs_retention_days'] ) ? (int) $settings['logs_retention_days'] : 90;
return max( 0, min( 3650, $value ) );
}
public function get_loggable_settings_snapshot( $settings = null ) {
if ( null === $settings ) {
$settings = $this->all();
@@ -451,6 +469,7 @@ class Groq_AI_Settings_Manager {
'store_context',
'default_prompt',
'max_output_tokens',
'logs_retention_days',
'product_attribute_includes',
'context_fields',
'modules',