Add Google and Groq AI providers, enhance provider manager, and implement conversation and logging services
- Introduced `Groq_AI_Provider_Google` and `Groq_AI_Provider_Groq` classes for handling AI interactions with Google and Groq respectively. - Enhanced `Groq_AI_Provider_Manager` to register and manage multiple AI providers. - Implemented `Groq_AI_Conversation_Manager` for managing conversation IDs and context hashes. - Added `Groq_AI_Generation_Logger` for logging AI generation events and managing log tables. - Developed `Groq_AI_Prompt_Builder` for constructing prompts and processing AI responses. - Established `Groq_AI_Settings_Manager` for managing plugin settings, including context fields and module configurations.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Beheert conversatie-ID's per provider + context-hash.
|
||||
*
|
||||
* Verplaatst vanuit groq-ai-product-text.php:
|
||||
* - ensure_conversation_id()
|
||||
* - get/save_conversation_states()
|
||||
* - get_context_hash()
|
||||
*/
|
||||
class Groq_AI_Conversation_Manager {
|
||||
/** @var string */
|
||||
private $option_key;
|
||||
|
||||
public function __construct( $option_key ) {
|
||||
$this->option_key = $option_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourneert of creëert een conversatie-ID.
|
||||
*
|
||||
* @param string $provider_key
|
||||
* @param string $store_context
|
||||
* @return string
|
||||
*/
|
||||
public function ensure_id( $provider_key, $store_context ) {
|
||||
$states = $this->get_states();
|
||||
$context_hash = $this->get_context_hash( $store_context );
|
||||
|
||||
if ( isset( $states[ $provider_key ]['hash'], $states[ $provider_key ]['id'] ) && $states[ $provider_key ]['hash'] === $context_hash ) {
|
||||
return $states[ $provider_key ]['id'];
|
||||
}
|
||||
|
||||
$conversation_id = wp_generate_uuid4();
|
||||
$states[ $provider_key ] = [
|
||||
'hash' => $context_hash,
|
||||
'id' => $conversation_id,
|
||||
];
|
||||
$this->save_states( $states );
|
||||
|
||||
return $conversation_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_states() {
|
||||
$states = get_option( $this->option_key, [] );
|
||||
|
||||
return is_array( $states ) ? $states : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $states
|
||||
* @return void
|
||||
*/
|
||||
private function save_states( $states ) {
|
||||
update_option( $this->option_key, $states, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $store_context
|
||||
* @return string
|
||||
*/
|
||||
private function get_context_hash( $store_context ) {
|
||||
return md5( wp_json_encode( trim( (string) $store_context ) ) );
|
||||
}
|
||||
}
|
||||
138
includes/Services/Logging/class-groq-ai-generation-logger.php
Normal file
138
includes/Services/Logging/class-groq-ai-generation-logger.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Loggingservice voor AI-generaties en DB-tabellen.
|
||||
*
|
||||
* Te importeren logica:
|
||||
* - log_generation_event()
|
||||
* - log_debug()
|
||||
* - get_logs_table_name()/create_logs_table()/maybe_create_logs_table()
|
||||
*/
|
||||
class Groq_AI_Generation_Logger {
|
||||
const OPTION_TABLE_CREATED = 'groq_ai_logs_table_created';
|
||||
|
||||
/** @var WC_Logger|null */
|
||||
private $woo_logger = null;
|
||||
|
||||
/** @var bool|null */
|
||||
private $logs_table_exists = null;
|
||||
|
||||
public function log_generation_event( array $args ) {
|
||||
if ( ! $this->logs_table_exists() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$table = $this->get_logs_table_name();
|
||||
|
||||
$usage = isset( $args['usage'] ) && is_array( $args['usage'] ) ? $args['usage'] : [];
|
||||
$prompt_tokens = isset( $usage['prompt_tokens'] ) ? absint( $usage['prompt_tokens'] ) : null;
|
||||
$completion_tokens = isset( $usage['completion_tokens'] ) ? absint( $usage['completion_tokens'] ) : null;
|
||||
$total_tokens = isset( $usage['total_tokens'] ) ? absint( $usage['total_tokens'] ) : null;
|
||||
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
[
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
'user_id' => get_current_user_id(),
|
||||
'post_id' => isset( $args['post_id'] ) ? absint( $args['post_id'] ) : 0,
|
||||
'provider' => isset( $args['provider'] ) ? sanitize_text_field( $args['provider'] ) : '',
|
||||
'model' => isset( $args['model'] ) ? sanitize_text_field( $args['model'] ) : '',
|
||||
'prompt' => isset( $args['prompt'] ) ? $args['prompt'] : '',
|
||||
'response' => isset( $args['response'] ) ? $args['response'] : '',
|
||||
'tokens_prompt' => $prompt_tokens,
|
||||
'tokens_completion' => $completion_tokens,
|
||||
'tokens_total' => $total_tokens,
|
||||
'status' => isset( $args['status'] ) ? sanitize_text_field( $args['status'] ) : 'success',
|
||||
'error_message' => isset( $args['error_message'] ) ? $args['error_message'] : '',
|
||||
'usage_json' => ! empty( $usage ) ? wp_json_encode( $usage ) : null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function log_debug( $message, $context = [] ) {
|
||||
if ( class_exists( 'WC_Logger' ) ) {
|
||||
if ( ! $this->woo_logger ) {
|
||||
$this->woo_logger = wc_get_logger();
|
||||
}
|
||||
|
||||
if ( $this->woo_logger ) {
|
||||
$context_string = ! empty( $context ) ? ' ' . wp_json_encode( $context ) : '';
|
||||
$this->woo_logger->debug( '[GroqAI] ' . $message . $context_string, [ 'source' => 'groq-ai-product-text' ] );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
$entry = '[GroqAI] ' . $message;
|
||||
|
||||
if ( ! empty( $context ) ) {
|
||||
$entry .= ' ' . wp_json_encode( $context );
|
||||
}
|
||||
|
||||
error_log( $entry );
|
||||
}
|
||||
}
|
||||
|
||||
public function maybe_create_table() {
|
||||
if ( get_option( self::OPTION_TABLE_CREATED ) ) {
|
||||
$this->logs_table_exists = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->create_table();
|
||||
}
|
||||
|
||||
public function create_table() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->get_logs_table_name();
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
$sql = "CREATE TABLE {$table} (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
created_at datetime NOT NULL,
|
||||
user_id bigint(20) unsigned DEFAULT NULL,
|
||||
post_id bigint(20) unsigned DEFAULT NULL,
|
||||
provider varchar(50) NOT NULL,
|
||||
model varchar(100) NOT NULL,
|
||||
prompt longtext NOT NULL,
|
||||
response longtext DEFAULT NULL,
|
||||
tokens_prompt int unsigned DEFAULT NULL,
|
||||
tokens_completion int unsigned DEFAULT NULL,
|
||||
tokens_total int unsigned DEFAULT NULL,
|
||||
status varchar(20) NOT NULL,
|
||||
error_message text DEFAULT NULL,
|
||||
usage_json longtext DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY provider (provider),
|
||||
KEY post_id (post_id)
|
||||
) {$charset_collate};";
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
$this->logs_table_exists = true;
|
||||
update_option( self::OPTION_TABLE_CREATED, 1 );
|
||||
}
|
||||
|
||||
private function get_logs_table_name() {
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->prefix . 'groq_ai_generation_logs';
|
||||
}
|
||||
|
||||
private function logs_table_exists() {
|
||||
if ( null !== $this->logs_table_exists ) {
|
||||
return $this->logs_table_exists;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$table = $this->get_logs_table_name();
|
||||
$result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
|
||||
$this->logs_table_exists = ( $result === $table );
|
||||
|
||||
return $this->logs_table_exists;
|
||||
}
|
||||
}
|
||||
353
includes/Services/Prompt/class-groq-ai-prompt-builder.php
Normal file
353
includes/Services/Prompt/class-groq-ai-prompt-builder.php
Normal file
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bouwt prompts, verwerkt responses en verzamelt context.
|
||||
*/
|
||||
class Groq_AI_Prompt_Builder {
|
||||
/** @var Groq_AI_Settings_Manager */
|
||||
private $settings_manager;
|
||||
|
||||
public function __construct( Groq_AI_Settings_Manager $settings_manager ) {
|
||||
$this->settings_manager = $settings_manager;
|
||||
}
|
||||
|
||||
public function build_system_prompt( $settings, $conversation_id ) {
|
||||
$context = isset( $settings['store_context'] ) ? trim( $settings['store_context'] ) : '';
|
||||
$base_instruction = __( 'Je bent een copywriter voor een WooCommerce winkel en schrijft overtuigende productbeschrijvingen.', 'groq-ai-product-text' );
|
||||
|
||||
if ( $context ) {
|
||||
$base_instruction = sprintf(
|
||||
__( 'Je bent een copywriter voor een WooCommerce winkel. Gebruik de volgende context indien beschikbaar: %s', 'groq-ai-product-text' ),
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
__( 'Conversatie-ID: %1$s. %2$s', 'groq-ai-product-text' ),
|
||||
$conversation_id,
|
||||
$base_instruction
|
||||
);
|
||||
}
|
||||
|
||||
public function append_response_instructions( $prompt, $settings ) {
|
||||
$instructions = (string) ( $this->get_structured_response_instructions( $settings ) ?? '' );
|
||||
$prompt = trim( (string) $prompt );
|
||||
|
||||
if ( '' === $instructions ) {
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
if ( false !== strpos( $prompt, $instructions ) ) {
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
return $prompt . "\n\n" . $instructions;
|
||||
}
|
||||
|
||||
public function parse_structured_response( $raw, $settings = null ) {
|
||||
if ( empty( $raw ) ) {
|
||||
return new WP_Error( 'groq_ai_empty_response', __( 'Geen data ontvangen van de AI.', 'groq-ai-product-text' ) );
|
||||
}
|
||||
|
||||
$clean = trim( $raw );
|
||||
|
||||
if ( preg_match( '/```(?:json)?\s*(.*?)```/is', $clean, $matches ) ) {
|
||||
$clean = trim( $matches[1] );
|
||||
}
|
||||
|
||||
$decoded = json_decode( $clean, true );
|
||||
|
||||
if ( ! is_array( $decoded ) ) {
|
||||
return new WP_Error( 'groq_ai_parse_error', __( 'Kon de AI-respons niet als JSON lezen. Probeer het opnieuw.', 'groq-ai-product-text' ) );
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'title' => trim( (string) ( $decoded['title'] ?? '' ) ),
|
||||
'short_description' => trim( (string) ( $decoded['short_description'] ?? '' ) ),
|
||||
'description' => trim( (string) ( $decoded['description'] ?? '' ) ),
|
||||
];
|
||||
|
||||
if ( $this->settings_manager->is_module_enabled( 'rankmath', $settings ) ) {
|
||||
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
||||
$focus_keywords = [];
|
||||
$raw_keyword_set = isset( $decoded['focus_keywords'] ) ? $decoded['focus_keywords'] : [];
|
||||
|
||||
if ( is_array( $raw_keyword_set ) ) {
|
||||
foreach ( $raw_keyword_set as $keyword ) {
|
||||
$keyword = trim( (string) $keyword );
|
||||
if ( '' !== $keyword ) {
|
||||
$focus_keywords[] = $keyword;
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $raw_keyword_set ) ) {
|
||||
$parts = preg_split( '/[,\\n]+/', $raw_keyword_set );
|
||||
if ( is_array( $parts ) ) {
|
||||
foreach ( $parts as $part ) {
|
||||
$part = trim( (string) $part );
|
||||
if ( '' !== $part ) {
|
||||
$focus_keywords[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$focus_keywords = array_slice( array_unique( $focus_keywords ), 0, $keyword_limit );
|
||||
|
||||
$fields['meta_title'] = $this->truncate_meta_field( (string) ( $decoded['meta_title'] ?? '' ), 60 );
|
||||
$fields['meta_description'] = $this->truncate_meta_field( (string) ( $decoded['meta_description'] ?? '' ), 160 );
|
||||
$fields['focus_keywords'] = implode( ', ', $focus_keywords );
|
||||
}
|
||||
|
||||
if ( implode( '', $fields ) === '' ) {
|
||||
return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen bruikbare velden.', 'groq-ai-product-text' ) );
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function parse_context_fields_from_request( $raw, $settings ) {
|
||||
if ( empty( $raw ) ) {
|
||||
return $settings['context_fields'];
|
||||
}
|
||||
|
||||
$decoded = json_decode( wp_unslash( $raw ), true );
|
||||
|
||||
if ( ! is_array( $decoded ) ) {
|
||||
return $settings['context_fields'];
|
||||
}
|
||||
|
||||
$normalized = $this->settings_manager->normalize_context_fields( $decoded );
|
||||
|
||||
if ( ! array_filter( $normalized ) ) {
|
||||
return $settings['context_fields'];
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
public function build_product_context_block( $post_id, $fields ) {
|
||||
$post_id = absint( $post_id );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
|
||||
if ( ! empty( $fields['title'] ) ) {
|
||||
$title = get_the_title( $post_id );
|
||||
if ( $title ) {
|
||||
$parts[] = sprintf( __( 'Titel: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $title ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields['short_description'] ) ) {
|
||||
$excerpt = get_post_field( 'post_excerpt', $post_id );
|
||||
if ( $excerpt ) {
|
||||
$parts[] = sprintf( __( 'Korte beschrijving: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $excerpt ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields['description'] ) ) {
|
||||
$content = get_post_field( 'post_content', $post_id );
|
||||
if ( $content ) {
|
||||
$parts[] = sprintf( __( 'Beschrijving: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $content ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields['attributes'] ) ) {
|
||||
$attributes = $this->get_product_attributes_text( $post_id );
|
||||
if ( $attributes ) {
|
||||
$parts[] = sprintf( __( 'Attributen: %s', 'groq-ai-product-text' ), $attributes );
|
||||
}
|
||||
}
|
||||
|
||||
return implode( "\n\n", array_filter( $parts ) );
|
||||
}
|
||||
|
||||
public function prepend_context_to_prompt( $prompt, $context ) {
|
||||
$context = trim( (string) $context );
|
||||
|
||||
if ( '' === $context ) {
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
$intro = __( 'Gebruik de volgende productcontext bij het schrijven:', 'groq-ai-product-text' );
|
||||
|
||||
return $intro . "\n" . $context . "\n\n" . $prompt;
|
||||
}
|
||||
|
||||
public function get_response_format_definition( $settings = null ) {
|
||||
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
||||
$title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings );
|
||||
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
||||
|
||||
$properties = [
|
||||
'title' => [
|
||||
'type' => 'string',
|
||||
'description' => __( 'Korte, overtuigende producttitel in het Nederlands.', 'groq-ai-product-text' ),
|
||||
'minLength' => 3,
|
||||
],
|
||||
'short_description' => [
|
||||
'type' => 'string',
|
||||
'description' => __( "Korte HTML-beschrijving in <p>-tags (maximaal 2 alinea's).", 'groq-ai-product-text' ),
|
||||
'minLength' => 10,
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'string',
|
||||
'description' => __( 'Uitgebreide HTML-productbeschrijving met paragrafen en eventueel lijsten.', 'groq-ai-product-text' ),
|
||||
'minLength' => 20,
|
||||
],
|
||||
];
|
||||
|
||||
if ( $rankmath_enabled ) {
|
||||
$properties['meta_title'] = [
|
||||
'type' => 'string',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: maximum character count, 2: maximum pixels */
|
||||
__( 'SEO-meta title (max. %1$d tekens en %2$d pixels).', 'groq-ai-product-text' ),
|
||||
60,
|
||||
$title_pixels
|
||||
),
|
||||
'maxLength' => 120,
|
||||
];
|
||||
$properties['meta_description'] = [
|
||||
'type' => 'string',
|
||||
'description' => sprintf(
|
||||
/* translators: 1: maximum character count, 2: maximum pixels */
|
||||
__( 'SEO-meta description (max. %1$d tekens en %2$d pixels).', 'groq-ai-product-text' ),
|
||||
160,
|
||||
$desc_pixels
|
||||
),
|
||||
'maxLength' => 320,
|
||||
];
|
||||
$properties['focus_keywords'] = [
|
||||
'type' => 'array',
|
||||
'description' => __( 'Lijst met korte zoekwoorden zonder hashtags of extra tekst.', 'groq-ai-product-text' ),
|
||||
'maxItems' => max( 1, $keyword_limit ),
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$schema = [
|
||||
'type' => 'object',
|
||||
'properties' => $properties,
|
||||
'required' => [ 'title', 'short_description', 'description' ],
|
||||
'additionalProperties' => false,
|
||||
];
|
||||
|
||||
return [
|
||||
'type' => 'json_schema',
|
||||
'json_schema' => [
|
||||
'name' => 'groq_ai_product_text',
|
||||
'schema' => $schema,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_structured_response_instructions( $settings = null ) {
|
||||
$schema_parts = [
|
||||
'"title":"..."',
|
||||
'"short_description":"..."',
|
||||
'"description":"..."',
|
||||
];
|
||||
|
||||
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||
if ( $rankmath_enabled ) {
|
||||
$schema_parts[] = '"meta_title":"..."';
|
||||
$schema_parts[] = '"meta_description":"..."';
|
||||
$schema_parts[] = '"focus_keywords":["...","..."]';
|
||||
}
|
||||
|
||||
$json_structure = '{' . implode( ',', $schema_parts ) . '}';
|
||||
|
||||
$instruction = sprintf(
|
||||
/* translators: %s: JSON structure example */
|
||||
__( 'Geef ALLEEN een geldig JSON-object terug met deze structuur: %s. Gebruik dubbele aanhalingstekens, geen Markdown of extra tekst. Gebruik \\n voor regeleinden. Zorg dat zowel short_description als description nooit leeg zijn.', 'groq-ai-product-text' ),
|
||||
$json_structure
|
||||
);
|
||||
|
||||
if ( $rankmath_enabled ) {
|
||||
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
||||
$title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings );
|
||||
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
||||
$instruction .= ' ' . sprintf(
|
||||
/* translators: 1: focus keyword limit, 2: meta title pixel limit, 3: meta description pixel limit */
|
||||
__( 'Beperk meta_title tot maximaal 60 tekens en %2$d pixels en meta_description tot maximaal 160 tekens en %3$d pixels. Lever maximaal %1$d focuskeywords in het focus_keywords-array (korte termen zonder hashtag of extra tekst).', 'groq-ai-product-text' ),
|
||||
$keyword_limit,
|
||||
$title_pixels,
|
||||
$desc_pixels
|
||||
);
|
||||
}
|
||||
|
||||
$instruction .= ' ' . __( 'Zorg dat short_description en description geldige HTML bevatten (gebruik minimaal <p>-tags en waar relevant lijstjes of benadrukking). Voeg geen extra tekst buiten het JSON-object toe.', 'groq-ai-product-text' );
|
||||
|
||||
return $instruction;
|
||||
}
|
||||
|
||||
private function truncate_meta_field( $text, $limit ) {
|
||||
$text = trim( (string) $text );
|
||||
|
||||
if ( '' === $text || $limit <= 0 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( function_exists( 'mb_strlen' ) ) {
|
||||
if ( mb_strlen( $text ) <= $limit ) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return mb_substr( $text, 0, $limit );
|
||||
}
|
||||
|
||||
if ( strlen( $text ) <= $limit ) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return substr( $text, 0, $limit );
|
||||
}
|
||||
|
||||
private function get_product_attributes_text( $post_id ) {
|
||||
if ( ! function_exists( 'wc_get_product' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attributes = $product->get_attributes();
|
||||
|
||||
if ( empty( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
|
||||
foreach ( $attributes as $attribute ) {
|
||||
if ( $attribute->is_taxonomy() ) {
|
||||
$terms = wc_get_product_terms( $post_id, $attribute->get_name(), [ 'fields' => 'names' ] );
|
||||
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $terms ) );
|
||||
$label = wc_attribute_label( $attribute->get_name() );
|
||||
} else {
|
||||
$options = $attribute->get_options();
|
||||
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $options ) );
|
||||
$label = sanitize_text_field( $attribute->get_name() );
|
||||
}
|
||||
|
||||
$value = trim( $value );
|
||||
|
||||
if ( '' !== $value ) {
|
||||
$lines[] = sprintf( '%s: %s', $label, $value );
|
||||
}
|
||||
}
|
||||
|
||||
return implode( '; ', $lines );
|
||||
}
|
||||
}
|
||||
295
includes/Services/Settings/class-groq-ai-settings-manager.php
Normal file
295
includes/Services/Settings/class-groq-ai-settings-manager.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Beheert alle plugininstellingen: ophalen, standaardiseren en sanitizen.
|
||||
*/
|
||||
class Groq_AI_Settings_Manager {
|
||||
/** @var string */
|
||||
private $option_key;
|
||||
|
||||
/** @var Groq_AI_Provider_Manager */
|
||||
private $provider_manager;
|
||||
|
||||
/** @var array|null */
|
||||
private $context_field_definitions = null;
|
||||
|
||||
/** @var array|null */
|
||||
private $default_modules = null;
|
||||
|
||||
public function __construct( $option_key, Groq_AI_Provider_Manager $provider_manager ) {
|
||||
$this->option_key = $option_key;
|
||||
$this->provider_manager = $provider_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geeft samengestelde instellingen terug (voormalige get_settings()).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all() {
|
||||
$defaults = [
|
||||
'provider' => 'groq',
|
||||
'model' => '',
|
||||
'store_context' => '',
|
||||
'default_prompt' => '',
|
||||
'groq_api_key' => '',
|
||||
'openai_api_key' => '',
|
||||
'google_api_key' => '',
|
||||
'context_fields' => $this->get_default_context_fields(),
|
||||
'modules' => $this->get_default_modules_settings(),
|
||||
'response_format_compat' => false,
|
||||
];
|
||||
|
||||
$settings = get_option( $this->option_key, [] );
|
||||
$settings = wp_parse_args( (array) $settings, $defaults );
|
||||
$settings['context_fields'] = $this->normalize_context_fields( isset( $settings['context_fields'] ) ? $settings['context_fields'] : [] );
|
||||
$settings['modules'] = $this->sanitize_modules_settings( isset( $settings['modules'] ) ? $settings['modules'] : [] );
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizelogica voor register_setting callback.
|
||||
*
|
||||
* @param array $input
|
||||
* @return array
|
||||
*/
|
||||
public function sanitize( $input ) {
|
||||
$base_defaults = [
|
||||
'provider' => 'groq',
|
||||
'model' => '',
|
||||
'store_context' => '',
|
||||
'default_prompt' => '',
|
||||
'groq_api_key' => '',
|
||||
'openai_api_key' => '',
|
||||
'google_api_key' => '',
|
||||
'context_fields' => $this->get_default_context_fields(),
|
||||
'modules' => $this->get_default_modules_settings(),
|
||||
'response_format_compat' => false,
|
||||
];
|
||||
|
||||
$current_settings = $this->all();
|
||||
$defaults = wp_parse_args( $current_settings, $base_defaults );
|
||||
$raw_input = (array) $input;
|
||||
$input = wp_parse_args( $raw_input, $defaults );
|
||||
$context_posted = array_key_exists( 'context_fields', $raw_input );
|
||||
$modules_posted = array_key_exists( 'modules', $raw_input );
|
||||
|
||||
$provider = sanitize_text_field( $input['provider'] );
|
||||
if ( ! $this->provider_manager->get_provider( $provider ) ) {
|
||||
$provider = 'groq';
|
||||
}
|
||||
|
||||
return [
|
||||
'provider' => $provider,
|
||||
'model' => sanitize_text_field( $input['model'] ),
|
||||
'store_context' => sanitize_textarea_field( $input['store_context'] ),
|
||||
'default_prompt' => sanitize_textarea_field( $input['default_prompt'] ),
|
||||
'groq_api_key' => sanitize_text_field( $input['groq_api_key'] ),
|
||||
'openai_api_key' => sanitize_text_field( $input['openai_api_key'] ),
|
||||
'google_api_key' => sanitize_text_field( $input['google_api_key'] ),
|
||||
'response_format_compat' => ! empty( $raw_input['response_format_compat'] ),
|
||||
'context_fields' => $this->normalize_context_fields( $context_posted ? $raw_input['context_fields'] : $defaults['context_fields'] ),
|
||||
'modules' => $this->sanitize_modules_settings(
|
||||
$modules_posted ? $raw_input['modules'] : [],
|
||||
$defaults['modules'],
|
||||
isset( $current_settings['modules'] ) ? (array) $current_settings['modules'] : $this->get_default_modules_settings(),
|
||||
$modules_posted
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function get_context_field_definitions() {
|
||||
if ( null === $this->context_field_definitions ) {
|
||||
$this->context_field_definitions = [
|
||||
'title' => [
|
||||
'label' => __( 'Producttitel', 'groq-ai-product-text' ),
|
||||
'description' => __( 'Voeg de huidige producttitel toe als context.', 'groq-ai-product-text' ),
|
||||
'default' => true,
|
||||
],
|
||||
'short_description' => [
|
||||
'label' => __( 'Korte beschrijving', 'groq-ai-product-text' ),
|
||||
'description' => __( 'Gebruik de bestaande korte beschrijving (indien aanwezig).', 'groq-ai-product-text' ),
|
||||
'default' => true,
|
||||
],
|
||||
'description' => [
|
||||
'label' => __( 'Volledige beschrijving', 'groq-ai-product-text' ),
|
||||
'description' => __( 'Stuurt de huidige productbeschrijving mee als bronmateriaal.', 'groq-ai-product-text' ),
|
||||
'default' => true,
|
||||
],
|
||||
'attributes' => [
|
||||
'label' => __( 'Attributen', 'groq-ai-product-text' ),
|
||||
'description' => __( 'Voeg gestructureerde productattributen toe (zoals kleur, maat, materiaal).', 'groq-ai-product-text' ),
|
||||
'default' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->context_field_definitions;
|
||||
}
|
||||
|
||||
public function get_default_context_fields() {
|
||||
$definitions = $this->get_context_field_definitions();
|
||||
$defaults = [];
|
||||
|
||||
foreach ( $definitions as $key => $data ) {
|
||||
$defaults[ $key ] = ! empty( $data['default'] );
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
public function normalize_context_fields( $fields ) {
|
||||
$definitions = $this->get_context_field_definitions();
|
||||
$normalized = [];
|
||||
|
||||
foreach ( $definitions as $key => $data ) {
|
||||
$normalized[ $key ] = false;
|
||||
}
|
||||
|
||||
if ( ! is_array( $fields ) ) {
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
foreach ( $fields as $key => $value ) {
|
||||
if ( is_int( $key ) ) {
|
||||
$key = sanitize_text_field( $value );
|
||||
$value = true;
|
||||
}
|
||||
|
||||
if ( array_key_exists( $key, $normalized ) ) {
|
||||
$normalized[ $key ] = (bool) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
public function get_default_modules_settings() {
|
||||
if ( null === $this->default_modules ) {
|
||||
$this->default_modules = [
|
||||
'rankmath' => [
|
||||
'enabled' => true,
|
||||
'focus_keyword_limit' => 3,
|
||||
'meta_title_pixel_limit' => 580,
|
||||
'meta_description_pixel_limit' => 920,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->default_modules;
|
||||
}
|
||||
|
||||
public function get_module_config( $module, $settings = null ) {
|
||||
if ( null === $settings ) {
|
||||
$settings = $this->all();
|
||||
}
|
||||
|
||||
$defaults = $this->get_default_modules_settings();
|
||||
$modules = isset( $settings['modules'] ) && is_array( $settings['modules'] ) ? $settings['modules'] : [];
|
||||
$config = isset( $modules[ $module ] ) ? (array) $modules[ $module ] : [];
|
||||
|
||||
return wp_parse_args( $config, isset( $defaults[ $module ] ) ? $defaults[ $module ] : [] );
|
||||
}
|
||||
|
||||
public function is_module_enabled( $module, $settings = null ) {
|
||||
$config = $this->get_module_config( $module, $settings );
|
||||
|
||||
return ! empty( $config['enabled'] );
|
||||
}
|
||||
|
||||
public function get_rankmath_focus_keyword_limit( $settings = null ) {
|
||||
$config = $this->get_module_config( 'rankmath', $settings );
|
||||
$limit = isset( $config['focus_keyword_limit'] ) ? absint( $config['focus_keyword_limit'] ) : 3;
|
||||
|
||||
return max( 1, min( 10, $limit ) );
|
||||
}
|
||||
|
||||
public function get_rankmath_meta_title_pixel_limit( $settings = null ) {
|
||||
$config = $this->get_module_config( 'rankmath', $settings );
|
||||
$value = isset( $config['meta_title_pixel_limit'] ) ? absint( $config['meta_title_pixel_limit'] ) : 580;
|
||||
|
||||
return max( 200, min( 1200, $value ) );
|
||||
}
|
||||
|
||||
public function get_rankmath_meta_description_pixel_limit( $settings = null ) {
|
||||
$config = $this->get_module_config( 'rankmath', $settings );
|
||||
$value = isset( $config['meta_description_pixel_limit'] ) ? absint( $config['meta_description_pixel_limit'] ) : 920;
|
||||
|
||||
return max( 200, min( 2000, $value ) );
|
||||
}
|
||||
|
||||
public function is_response_format_compat_enabled( $settings = null ) {
|
||||
if ( null === $settings ) {
|
||||
$settings = $this->all();
|
||||
}
|
||||
|
||||
return ! empty( $settings['response_format_compat'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $modules
|
||||
* @param array|null $defaults
|
||||
* @param array|null $current
|
||||
* @param bool $is_posted
|
||||
* @return array
|
||||
*/
|
||||
private function sanitize_modules_settings( $modules, $defaults = null, $current = null, $is_posted = false ) {
|
||||
$module_defaults = $this->get_default_modules_settings();
|
||||
|
||||
if ( ! is_array( $defaults ) ) {
|
||||
$defaults = $module_defaults;
|
||||
}
|
||||
|
||||
if ( ! is_array( $current ) ) {
|
||||
$current = $defaults;
|
||||
}
|
||||
|
||||
$current = wp_parse_args( $current, $defaults );
|
||||
|
||||
if ( ! is_array( $modules ) ) {
|
||||
$modules = [];
|
||||
}
|
||||
|
||||
if ( ! $is_posted ) {
|
||||
$clean = [];
|
||||
foreach ( $module_defaults as $module_key => $module_default_config ) {
|
||||
$raw = isset( $modules[ $module_key ] ) ? (array) $modules[ $module_key ] : [];
|
||||
$clean[ $module_key ] = wp_parse_args( $raw, isset( $current[ $module_key ] ) ? $current[ $module_key ] : $module_default_config );
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
$result = $current;
|
||||
|
||||
foreach ( $module_defaults as $module_key => $module_default_config ) {
|
||||
$raw = isset( $modules[ $module_key ] ) ? (array) $modules[ $module_key ] : [];
|
||||
$current_config = isset( $current[ $module_key ] ) ? (array) $current[ $module_key ] : $module_default_config;
|
||||
|
||||
$result[ $module_key ]['enabled'] = isset( $raw['enabled'] ) ? (bool) $raw['enabled'] : ( isset( $current_config['enabled'] ) ? (bool) $current_config['enabled'] : false );
|
||||
|
||||
if ( 'rankmath' === $module_key ) {
|
||||
$limit = isset( $raw['focus_keyword_limit'] ) ? absint( $raw['focus_keyword_limit'] ) : ( isset( $current_config['focus_keyword_limit'] ) ? absint( $current_config['focus_keyword_limit'] ) : $module_default_config['focus_keyword_limit'] );
|
||||
if ( $limit <= 0 ) {
|
||||
$limit = $module_default_config['focus_keyword_limit'];
|
||||
}
|
||||
$result[ $module_key ]['focus_keyword_limit'] = max( 1, min( 10, $limit ) );
|
||||
|
||||
$title_pixel_limit = isset( $raw['meta_title_pixel_limit'] ) ? absint( $raw['meta_title_pixel_limit'] ) : ( isset( $current_config['meta_title_pixel_limit'] ) ? absint( $current_config['meta_title_pixel_limit'] ) : $module_default_config['meta_title_pixel_limit'] );
|
||||
if ( $title_pixel_limit <= 0 ) {
|
||||
$title_pixel_limit = $module_default_config['meta_title_pixel_limit'];
|
||||
}
|
||||
$result[ $module_key ]['meta_title_pixel_limit'] = max( 200, min( 1200, $title_pixel_limit ) );
|
||||
|
||||
$pixel_limit = isset( $raw['meta_description_pixel_limit'] ) ? absint( $raw['meta_description_pixel_limit'] ) : ( isset( $current_config['meta_description_pixel_limit'] ) ? absint( $current_config['meta_description_pixel_limit'] ) : $module_default_config['meta_description_pixel_limit'] );
|
||||
if ( $pixel_limit <= 0 ) {
|
||||
$pixel_limit = $module_default_config['meta_description_pixel_limit'];
|
||||
}
|
||||
$result[ $module_key ]['meta_description_pixel_limit'] = max( 200, min( 2000, $pixel_limit ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user