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:
1
.phpunit.result.cache
Normal file
1
.phpunit.result.cache
Normal file
@@ -0,0 +1 @@
|
||||
{"version":1,"defects":{"SettingsManagerTest::test_logs_retention_days_negative_becomes_zero":3,"ProviderRequestBuilderTest::test_openai_request_payload_respects_settings":4,"ProviderRequestBuilderTest::test_groq_request_payload_uses_default_model_when_missing":4,"ProviderRequestBuilderTest::test_google_request_payload_builds_schema_and_images":4,"TermSaveTest::test_save_term_generation_result_saves_descriptions_and_filtered_meta_key":4},"times":{"ModelExclusionsTest::test_ensure_allowed_blocks_excluded_model":0.002,"ModelExclusionsTest::test_filter_models_removes_excluded_entries":0,"SettingsManagerTest::test_logs_retention_days_sanitized_and_capped":0,"SettingsManagerTest::test_logs_retention_days_allows_zero":0,"SettingsManagerTest::test_logs_retention_days_negative_becomes_zero":0,"SettingsManagerTest::test_sanitize_accepts_all_settings_keys":0,"ProviderRequestBuilderTest::test_openai_request_payload_respects_settings":0,"ProviderRequestBuilderTest::test_groq_request_payload_uses_default_model_when_missing":0,"ProviderRequestBuilderTest::test_google_request_payload_builds_schema_and_images":0,"TermSaveTest::test_save_term_generation_result_saves_descriptions_and_filtered_meta_key":0}}
|
||||
@@ -96,8 +96,35 @@
|
||||
statusField.setAttribute('data-status', type);
|
||||
}
|
||||
|
||||
const loadingText = window.wp && wp.i18n ? wp.i18n.__('AI is bezig met schrijven...', 'siti-ai-product-content-generator') : 'AI is bezig met schrijven...';
|
||||
const retryText = window.wp && wp.i18n ? wp.i18n.__('Probeer het opnieuw of pas je prompt/context aan.', 'siti-ai-product-content-generator') : 'Probeer het opnieuw of pas je prompt/context aan.';
|
||||
const localized = (window.GroqAIGenerator && GroqAIGenerator.strings) || {};
|
||||
const loadingText = localized.loading || (window.wp && wp.i18n ? wp.i18n.__('AI is bezig met schrijven...', 'siti-ai-product-content-generator') : 'AI is bezig met schrijven...');
|
||||
const retryText = localized.retry || (window.wp && wp.i18n ? wp.i18n.__('Probeer het opnieuw of pas je prompt/context aan.', 'siti-ai-product-content-generator') : 'Probeer het opnieuw of pas je prompt/context aan.');
|
||||
const errorDefaultText = localized.errorDefault || (window.wp && wp.i18n ? wp.i18n.__('Er ging iets mis bij het genereren.', 'siti-ai-product-content-generator') : 'Er ging iets mis bij het genereren.');
|
||||
const errorUnknownText = localized.errorUnknown || (window.wp && wp.i18n ? wp.i18n.__('Onbekende fout.', 'siti-ai-product-content-generator') : 'Onbekende fout.');
|
||||
const successText = localized.success || (window.wp && wp.i18n ? wp.i18n.__('Structuur gegenereerd. Kopieer of vul velden in.', 'siti-ai-product-content-generator') : 'Structuur gegenereerd. Kopieer of vul velden in.');
|
||||
const fieldAppliedText = localized.fieldApplied || (window.wp && wp.i18n ? wp.i18n.__('%s ingevuld.', 'siti-ai-product-content-generator') : '%s ingevuld.');
|
||||
const fieldApplyErrorText = localized.fieldApplyError || (window.wp && wp.i18n ? wp.i18n.__('Kon het veld niet automatisch invullen.', 'siti-ai-product-content-generator') : 'Kon het veld niet automatisch invullen.');
|
||||
const fieldCopiedText = localized.fieldCopied || (window.wp && wp.i18n ? wp.i18n.__('%s gekopieerd naar het klembord.', 'siti-ai-product-content-generator') : '%s gekopieerd naar het klembord.');
|
||||
const jsonCopiedText = localized.jsonCopied || (window.wp && wp.i18n ? wp.i18n.__('JSON gekopieerd naar het klembord.', 'siti-ai-product-content-generator') : 'JSON gekopieerd naar het klembord.');
|
||||
const copyFailedText = localized.copyFailed || (window.wp && wp.i18n ? wp.i18n.__('Kopiëren mislukt.', 'siti-ai-product-content-generator') : 'Kopiëren mislukt.');
|
||||
|
||||
function formatString(template, values) {
|
||||
if (!template) {
|
||||
return '';
|
||||
}
|
||||
let autoIndex = 0;
|
||||
return template.replace(/%(\d+\$)?s/g, (match, position) => {
|
||||
let valueIndex;
|
||||
if (position) {
|
||||
valueIndex = parseInt(position, 10) - 1;
|
||||
} else {
|
||||
valueIndex = autoIndex;
|
||||
autoIndex += 1;
|
||||
}
|
||||
const replacement = values[valueIndex];
|
||||
return typeof replacement === 'undefined' ? '' : String(replacement);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLoading(isLoading) {
|
||||
modal.classList.toggle('is-loading', isLoading);
|
||||
@@ -137,7 +164,7 @@
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (!json.success) {
|
||||
const errorMessage = json.data && json.data.message ? json.data.message : 'Onbekende fout';
|
||||
const errorMessage = json.data && json.data.message ? json.data.message : errorUnknownText;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
@@ -154,13 +181,12 @@
|
||||
if (jsonCopyButton) {
|
||||
jsonCopyButton.disabled = false;
|
||||
}
|
||||
setStatus('Structuur gegenereerd. Kopieer of vul velden in.', 'success');
|
||||
setStatus(successText, 'success');
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = error && error.message ? error.message : 'Er ging iets mis bij het genereren.';
|
||||
setStatus(loadingText, 'error');
|
||||
const fullMessage = `${loadingText} ${message}. ${retryText}`;
|
||||
statusField.textContent = fullMessage;
|
||||
const message = error && error.message ? error.message : errorDefaultText;
|
||||
const fullMessage = `${errorDefaultText} ${message}. ${retryText}`;
|
||||
setStatus(fullMessage, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
toggleLoading(false);
|
||||
@@ -308,10 +334,10 @@
|
||||
}
|
||||
|
||||
if (applied) {
|
||||
setStatus(entry.label + ' ingevuld.', 'success');
|
||||
setStatus(formatString(fieldAppliedText, [entry.label]), 'success');
|
||||
setFieldStatus(fieldKey, 'success');
|
||||
} else {
|
||||
setStatus('Kon het veld niet automatisch invullen.', 'error');
|
||||
setStatus(fieldApplyErrorText, 'error');
|
||||
setFieldStatus(fieldKey, 'error');
|
||||
}
|
||||
}
|
||||
@@ -340,10 +366,10 @@
|
||||
}
|
||||
copyToClipboard(entry.textarea.value)
|
||||
.then(() => {
|
||||
setStatus(entry.label + ' gekopieerd naar het klembord.', 'success');
|
||||
setStatus(formatString(fieldCopiedText, [entry.label]), 'success');
|
||||
})
|
||||
.catch(() => {
|
||||
setStatus('Kopiëren mislukt.', 'error');
|
||||
setStatus(copyFailedText, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -358,10 +384,10 @@
|
||||
const text = resultField ? resultField.textContent.trim() : '';
|
||||
copyToClipboard(text)
|
||||
.then(() => {
|
||||
setStatus('JSON gekopieerd naar het klembord.', 'success');
|
||||
setStatus(jsonCopiedText, 'success');
|
||||
})
|
||||
.catch(() => {
|
||||
setStatus('Kopiëren mislukt.', 'error');
|
||||
setStatus(copyFailedText, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const strings = data.strings || {};
|
||||
const providerUnsupportedText = strings.providerUnsupported || 'Deze aanbieder ondersteunt dit niet.';
|
||||
const apiKeyRequiredText = strings.apiKeyRequired || 'Vul eerst de API-sleutel in.';
|
||||
const loadingModelsText = strings.loadingModels || 'Modellen worden opgehaald…';
|
||||
const errorUnknownText = strings.errorUnknown || 'Onbekende fout';
|
||||
const successModelsText = strings.successModels || 'Modellen bijgewerkt.';
|
||||
const errorFetchText = strings.errorFetch || 'Ophalen mislukt.';
|
||||
|
||||
const providerSelect = document.querySelector('select[name="' + optionKey + '[provider]"]');
|
||||
const modelSelect = document.getElementById('groq-ai-model-select');
|
||||
const refreshButton = document.getElementById('groq-ai-refresh-models');
|
||||
@@ -164,19 +172,19 @@
|
||||
const provider = providerSelect ? providerSelect.value : data.currentProvider;
|
||||
const providerData = data.providers && data.providers[provider] ? data.providers[provider] : null;
|
||||
if (!providerData || !providerData.supports_live) {
|
||||
setRefreshStatus('Deze aanbieder ondersteunt dit niet.', 'error');
|
||||
setRefreshStatus(providerUnsupportedText, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const keyField = document.querySelector('[data-provider-row="' + provider + '"] input');
|
||||
const apiKey = keyField ? keyField.value.trim() : '';
|
||||
if (!apiKey) {
|
||||
setRefreshStatus('Vul eerst de API-sleutel in.', 'error');
|
||||
setRefreshStatus(apiKeyRequiredText, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
refreshButton.disabled = true;
|
||||
setRefreshStatus('Modellen worden opgehaald…', 'loading');
|
||||
setRefreshStatus(loadingModelsText, 'loading');
|
||||
|
||||
const payload = new URLSearchParams();
|
||||
payload.append('action', 'groq_ai_refresh_models');
|
||||
@@ -194,14 +202,14 @@
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (!json.success || !json.data || !Array.isArray(json.data.models)) {
|
||||
throw new Error((json.data && json.data.message) || 'Onbekende fout');
|
||||
throw new Error((json.data && json.data.message) || errorUnknownText);
|
||||
}
|
||||
data.providers[provider].models = json.data.models;
|
||||
buildModelOptions();
|
||||
setRefreshStatus('Modellen bijgewerkt.', 'success');
|
||||
setRefreshStatus(successModelsText, 'success');
|
||||
})
|
||||
.catch((error) => {
|
||||
setRefreshStatus(error.message || 'Ophalen mislukt.', 'error');
|
||||
setRefreshStatus(error.message || errorFetchText, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
refreshButton.disabled = false;
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
const includeTopProducts = document.getElementById('groq-ai-term-include-top-products');
|
||||
const topProductsLimit = document.getElementById('groq-ai-term-top-products-limit');
|
||||
|
||||
const strings = (window.GroqAITermGenerator && GroqAITermGenerator.strings) || {};
|
||||
const promptRequiredText = strings.promptRequired || 'Vul eerst een prompt in.';
|
||||
const loadingText = strings.loading || 'AI is bezig met schrijven...';
|
||||
const successText = strings.success || 'Tekst gegenereerd. Je kunt hem toepassen en opslaan.';
|
||||
const applySuccessText = strings.applySuccess || 'Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.';
|
||||
const errorDefaultText = strings.errorDefault || 'Er ging iets mis bij het genereren.';
|
||||
const errorUnknownText = strings.errorUnknown || 'Onbekende fout';
|
||||
|
||||
function setStatus(message, type) {
|
||||
if (!statusField) {
|
||||
return;
|
||||
@@ -75,7 +83,7 @@
|
||||
rankmathKeywordsField.value = outputFocusKeywordsField.value || '';
|
||||
}
|
||||
|
||||
setStatus('Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.', 'success');
|
||||
setStatus(applySuccessText, 'success');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,12 +91,12 @@
|
||||
event.preventDefault();
|
||||
const prompt = promptField ? (promptField.value || '').trim() : '';
|
||||
if (!prompt) {
|
||||
setStatus('Vul eerst een prompt in.', 'error');
|
||||
setStatus(promptRequiredText, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('AI is bezig met schrijven...', 'loading');
|
||||
setStatus(loadingText, 'loading');
|
||||
if (rawField) {
|
||||
rawField.textContent = '';
|
||||
}
|
||||
@@ -109,7 +117,7 @@
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (!json.success) {
|
||||
const errorMessage = json.data && json.data.message ? json.data.message : 'Onbekende fout';
|
||||
const errorMessage = json.data && json.data.message ? json.data.message : errorUnknownText;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
@@ -137,10 +145,10 @@
|
||||
rawField.textContent = (json.data && json.data.raw ? String(json.data.raw) : '').trim();
|
||||
}
|
||||
|
||||
setStatus('Tekst gegenereerd. Je kunt hem toepassen en opslaan.', 'success');
|
||||
setStatus(successText, 'success');
|
||||
})
|
||||
.catch((error) => {
|
||||
setStatus(error && error.message ? error.message : 'Er ging iets mis bij het genereren.', 'error');
|
||||
setStatus(error && error.message ? error.message : errorDefaultText, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
|
||||
const strings = data.strings || {};
|
||||
const allowRegenerate = !!data.allowRegenerate;
|
||||
const unknownErrorText = strings.unknownError || 'Onbekende fout';
|
||||
const unknownTermText = strings.unknownTerm || 'Onbekende term.';
|
||||
const confirmStopFallbackText = strings.confirmStopFallback || 'Stoppen?';
|
||||
const logErrorDefaultText = strings.logErrorDefault || '%1$s: %2$s';
|
||||
const logSuccessDefaultText = strings.logSuccessDefault || '%1$s gevuld.';
|
||||
const regenerateErrorDefaultText = strings.regenerateErrorDefault || '%1$s mislukt: %2$s';
|
||||
const regenerateDoneDefaultText = strings.regenerateDoneDefault || '%s is bijgewerkt.';
|
||||
const terms = (Array.isArray(data.terms) ? data.terms : [])
|
||||
.map((term) => {
|
||||
const id = parseInt(term.id, 10);
|
||||
@@ -146,19 +153,19 @@
|
||||
|
||||
function handleResponse(term, json, context) {
|
||||
if (!json || !json.success) {
|
||||
const errorMessage = (json && json.data && json.data.message) || 'Onbekende fout';
|
||||
appendLog(formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, errorMessage]), 'error');
|
||||
const errorMessage = (json && json.data && json.data.message) || unknownErrorText;
|
||||
appendLog(formatString(strings.logError || logErrorDefaultText, [term.name || term.id, errorMessage]), 'error');
|
||||
if (context === 'single') {
|
||||
setStatus(formatString(strings.regenerateError || '%1$s mislukt: %2$s', [term.name || term.id, errorMessage]), 'error');
|
||||
setStatus(formatString(strings.regenerateError || regenerateErrorDefaultText, [term.name || term.id, errorMessage]), 'error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const words = json.data && typeof json.data.words !== 'undefined' ? parseInt(json.data.words, 10) : term.words;
|
||||
markTermCompleted(term, Number.isFinite(words) ? words : term.words);
|
||||
appendLog(formatString(strings.logSuccess || '%1$s gevuld.', [term.name || term.id, term.words]), 'success');
|
||||
appendLog(formatString(strings.logSuccess || logSuccessDefaultText, [term.name || term.id, term.words]), 'success');
|
||||
if (context === 'single') {
|
||||
setStatus(formatString(strings.regenerateDone || '%s is bijgewerkt.', [term.name || term.id]), 'success');
|
||||
setStatus(formatString(strings.regenerateDone || regenerateDoneDefaultText, [term.name || term.id]), 'success');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -233,7 +240,7 @@
|
||||
if (!isRunning) {
|
||||
return;
|
||||
}
|
||||
const confirmation = strings.confirmStop ? window.confirm(strings.confirmStop) : window.confirm('Stoppen?');
|
||||
const confirmation = strings.confirmStop ? window.confirm(strings.confirmStop) : window.confirm(confirmStopFallbackText);
|
||||
if (confirmation) {
|
||||
abortRequested = true;
|
||||
}
|
||||
@@ -251,7 +258,7 @@
|
||||
const termId = parseInt(button.getAttribute('data-term-id'), 10);
|
||||
const term = termMap.get(termId);
|
||||
if (!term) {
|
||||
setStatus('Onbekende term.', 'error');
|
||||
setStatus(unknownTermText, 'error');
|
||||
return;
|
||||
}
|
||||
if (strings.confirmRegenerate) {
|
||||
@@ -270,9 +277,9 @@
|
||||
handleResponse(term, json, 'single');
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = error && error.message ? error.message : 'Onbekende fout';
|
||||
appendLog(formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, message]), 'error');
|
||||
setStatus(formatString(strings.regenerateError || '%1$s mislukt: %2$s', [term.name || term.id, message]), 'error');
|
||||
const message = error && error.message ? error.message : unknownErrorText;
|
||||
appendLog(formatString(strings.logError || logErrorDefaultText, [term.name || term.id, message]), 'error');
|
||||
setStatus(formatString(strings.regenerateError || regenerateErrorDefaultText, [term.name || term.id, message]), 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
button.disabled = false;
|
||||
|
||||
16
composer.json
Normal file
16
composer.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "sitiweb/siti-ai-product-content-generator",
|
||||
"description": "SitiAI Product Teksten WordPress plugin",
|
||||
"type": "wordpress-plugin",
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"includes/"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
1815
composer.lock
generated
Normal file
1815
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 ),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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 ),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
|
||||
58
includes/Core/class-groq-ai-compatibility-service.php
Normal file
58
includes/Core/class-groq-ai-compatibility-service.php
Normal 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' );
|
||||
}
|
||||
}
|
||||
28
includes/Core/class-groq-ai-log-scheduler.php
Normal file
28
includes/Core/class-groq-ai-log-scheduler.php
Normal 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 );
|
||||
}
|
||||
}
|
||||
78
includes/Core/class-groq-ai-model-service.php
Normal file
78
includes/Core/class-groq-ai-model-service.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 ] ) ) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
202
languages/siti-ai-product-content-generator.pot
Normal file
202
languages/siti-ai-product-content-generator.pot
Normal file
@@ -0,0 +1,202 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: SitiAI Product Teksten\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-31 00:00+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Domain: siti-ai-product-content-generator\n"
|
||||
|
||||
#: assets/js/settings.js:166
|
||||
msgid "Deze aanbieder ondersteunt dit niet."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/settings.js:173
|
||||
msgid "Vul eerst de API-sleutel in."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/settings.js:177
|
||||
msgid "Modellen worden opgehaald…"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/settings.js:197
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/settings.js:202
|
||||
msgid "Modellen bijgewerkt."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/settings.js:205
|
||||
msgid "Ophalen mislukt."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:844
|
||||
msgid "Deze aanbieder ondersteunt dit niet."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:845
|
||||
msgid "Vul eerst de API-sleutel in."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:846
|
||||
msgid "Modellen worden opgehaald…"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:847
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:848
|
||||
msgid "Modellen bijgewerkt."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-settings-page.php:849
|
||||
msgid "Ophalen mislukt."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:24
|
||||
msgid "Vul eerst een prompt in."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:25
|
||||
msgid "AI is bezig met schrijven..."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:26
|
||||
msgid "Tekst gegenereerd. Je kunt hem toepassen en opslaan."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:27
|
||||
msgid "Tekst ingevuld. Vergeet niet op \"Opslaan\" te klikken."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:28
|
||||
msgid "Er ging iets mis bij het genereren."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-admin.js:119
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:212
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:100
|
||||
msgid "AI is bezig met schrijven..."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:101
|
||||
msgid "Probeer het opnieuw of pas je prompt/context aan."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:102
|
||||
msgid "Er ging iets mis bij het genereren."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:103
|
||||
msgid "Onbekende fout."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:104
|
||||
msgid "Structuur gegenereerd. Kopieer of vul velden in."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:105
|
||||
msgid "%s ingevuld."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:106
|
||||
msgid "Kon het veld niet automatisch invullen."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:107
|
||||
msgid "%s gekopieerd naar het klembord."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:108
|
||||
msgid "JSON gekopieerd naar het klembord."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/admin.js:109
|
||||
msgid "Kopiëren mislukt."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#: includes/Admin/class-groq-ai-product-ui.php:84
|
||||
msgid "%s ingevuld."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-product-ui.php:85
|
||||
msgid "Kon het veld niet automatisch invullen."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-product-ui.php:86
|
||||
msgid "%s gekopieerd naar het klembord."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-product-ui.php:87
|
||||
msgid "JSON gekopieerd naar het klembord."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-product-ui.php:88
|
||||
msgid "Kopiëren mislukt."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:149
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:150
|
||||
msgid "%1$s: %2$s"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:152
|
||||
msgid "%1$s mislukt: %2$s"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:159
|
||||
msgid "%1$s gevuld."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:161
|
||||
msgid "%s is bijgewerkt."
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:236
|
||||
msgid "Stoppen?"
|
||||
msgstr ""
|
||||
|
||||
#: assets/js/term-bulk.js:254
|
||||
msgid "Onbekende term."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:101
|
||||
msgid "Onbekende fout"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:102
|
||||
msgid "Onbekende term."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:103
|
||||
msgid "Stoppen?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:104
|
||||
msgid "%1$s: %2$s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:105
|
||||
msgid "%1$s gevuld."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:106
|
||||
msgid "%1$s mislukt: %2$s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Admin/class-groq-ai-term-admin-base.php:107
|
||||
msgid "%s is bijgewerkt."
|
||||
msgstr ""
|
||||
13
phpunit.xml
Normal file
13
phpunit.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
failOnWarning="false"
|
||||
failOnRisky="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Plugin Tests">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
17
tests/ModelExclusionsTest.php
Normal file
17
tests/ModelExclusionsTest.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ModelExclusionsTest extends TestCase {
|
||||
public function test_ensure_allowed_blocks_excluded_model() {
|
||||
$blocked = Groq_AI_Model_Exclusions::ensure_allowed( 'groq', 'whisper-large-v3' );
|
||||
$this->assertSame( '', $blocked );
|
||||
}
|
||||
|
||||
public function test_filter_models_removes_excluded_entries() {
|
||||
$models = [ 'llama3-70b-8192', 'whisper-large-v3', 'mixtral-8x7b-32768' ];
|
||||
$filtered = Groq_AI_Model_Exclusions::filter_models( 'groq', $models );
|
||||
|
||||
$this->assertSame( [ 'llama3-70b-8192', 'mixtral-8x7b-32768' ], $filtered );
|
||||
}
|
||||
}
|
||||
102
tests/ProviderRequestBuilderTest.php
Normal file
102
tests/ProviderRequestBuilderTest.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProviderRequestBuilderTest extends TestCase {
|
||||
public function test_openai_request_payload_respects_settings() {
|
||||
$provider = new Groq_AI_Provider_OpenAI();
|
||||
$result = $provider->generate_content(
|
||||
[
|
||||
'prompt' => 'Hallo',
|
||||
'system_prompt' => 'System',
|
||||
'model' => 'gpt-4o-mini',
|
||||
'settings' => [
|
||||
'openai_api_key' => 'test-key',
|
||||
'max_output_tokens' => 512,
|
||||
],
|
||||
'temperature' => 0.5,
|
||||
'response_format' => [
|
||||
'type' => 'json_object',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertIsArray( $result );
|
||||
$payload = $result['request_payload']['body'];
|
||||
$this->assertSame( 'gpt-4o-mini', $payload['model'] );
|
||||
$this->assertSame( 0.5, $payload['temperature'] );
|
||||
$this->assertSame( 512, $payload['max_tokens'] );
|
||||
$this->assertSame( 'json_object', $payload['response_format']['type'] );
|
||||
$this->assertSame( 'System', $payload['messages'][0]['content'] );
|
||||
$this->assertSame( 'Hallo', $payload['messages'][1]['content'] );
|
||||
}
|
||||
|
||||
public function test_groq_request_payload_uses_default_model_when_missing() {
|
||||
$provider = new Groq_AI_Provider_Groq();
|
||||
$result = $provider->generate_content(
|
||||
[
|
||||
'prompt' => 'Hallo',
|
||||
'system_prompt' => 'System',
|
||||
'settings' => [
|
||||
'groq_api_key' => 'test-key',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertIsArray( $result );
|
||||
$payload = $result['request_payload']['body'];
|
||||
$this->assertSame( $provider->get_default_model(), $payload['model'] );
|
||||
$this->assertSame( 'System', $payload['messages'][0]['content'] );
|
||||
$this->assertSame( 'Hallo', $payload['messages'][1]['content'] );
|
||||
}
|
||||
|
||||
public function test_google_request_payload_builds_schema_and_images() {
|
||||
$provider = new Groq_AI_Provider_Google();
|
||||
$result = $provider->generate_content(
|
||||
[
|
||||
'prompt' => 'Hallo',
|
||||
'system_prompt' => 'System',
|
||||
'model' => 'gemini-1.5-flash',
|
||||
'settings' => [
|
||||
'google_api_key' => 'test-key',
|
||||
'max_output_tokens' => 256,
|
||||
'google_safety_settings' => [
|
||||
'HARM_CATEGORY_HARASSMENT' => 'BLOCK_LOW_AND_ABOVE',
|
||||
],
|
||||
],
|
||||
'temperature' => 0.2,
|
||||
'response_format' => [
|
||||
'type' => 'json_schema',
|
||||
'json_schema' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'name' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'image_context' => [
|
||||
[
|
||||
'label' => 'Image 1',
|
||||
'mime_type' => 'image/png',
|
||||
'data' => 'BASE64DATA',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertIsArray( $result );
|
||||
$payload = $result['request_payload']['body'];
|
||||
$this->assertSame( 0.2, $payload['generationConfig']['temperature'] );
|
||||
$this->assertSame( 256, $payload['generationConfig']['maxOutputTokens'] );
|
||||
$this->assertSame( 'application/json', $payload['generationConfig']['responseMimeType'] );
|
||||
$this->assertArrayHasKey( 'responseJsonSchema', $payload['generationConfig'] );
|
||||
$this->assertSame( 'System', $payload['contents'][0]['parts'][0]['text'] );
|
||||
$this->assertSame( 'Hallo', $payload['contents'][0]['parts'][1]['text'] );
|
||||
$this->assertSame( 'image/png', $payload['contents'][0]['parts'][3]['inline_data']['mime_type'] );
|
||||
$this->assertSame( 'BASE64DATA', $payload['contents'][0]['parts'][3]['inline_data']['data'] );
|
||||
}
|
||||
}
|
||||
112
tests/SettingsManagerTest.php
Normal file
112
tests/SettingsManagerTest.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SettingsManagerTest extends TestCase {
|
||||
private function make_manager() {
|
||||
$provider_manager = new Groq_AI_Provider_Manager();
|
||||
return new Groq_AI_Settings_Manager( 'groq_ai_test_settings', $provider_manager );
|
||||
}
|
||||
|
||||
public function test_logs_retention_days_sanitized_and_capped() {
|
||||
$manager = $this->make_manager();
|
||||
|
||||
$result = $manager->sanitize( [
|
||||
'logs_retention_days' => 5000,
|
||||
] );
|
||||
|
||||
$this->assertSame( 3650, $result['logs_retention_days'] );
|
||||
}
|
||||
|
||||
public function test_logs_retention_days_allows_zero() {
|
||||
$manager = $this->make_manager();
|
||||
|
||||
$result = $manager->sanitize( [
|
||||
'logs_retention_days' => 0,
|
||||
] );
|
||||
|
||||
$this->assertSame( 0, $result['logs_retention_days'] );
|
||||
}
|
||||
|
||||
public function test_logs_retention_days_negative_becomes_zero() {
|
||||
$manager = $this->make_manager();
|
||||
|
||||
$result = $manager->sanitize( [
|
||||
'logs_retention_days' => -5,
|
||||
] );
|
||||
|
||||
$this->assertSame( 0, $result['logs_retention_days'] );
|
||||
}
|
||||
|
||||
public function test_sanitize_accepts_all_settings_keys() {
|
||||
$manager = $this->make_manager();
|
||||
$context_fields = $manager->get_default_context_fields();
|
||||
$modules = $manager->get_default_modules_settings();
|
||||
$google_categories = Groq_AI_Settings_Manager::get_google_safety_categories_list();
|
||||
$first_category = array_key_first( $google_categories );
|
||||
|
||||
$input = [
|
||||
'provider' => 'openai',
|
||||
'model' => 'gpt-4o-mini',
|
||||
'store_context' => 'Test winkelcontext',
|
||||
'default_prompt' => 'Schrijf een korte tekst',
|
||||
'max_output_tokens' => 2048,
|
||||
'logs_retention_days' => 30,
|
||||
'product_attribute_includes' => [ '__all__', '__custom__', 'pa_color', 'invalid key' ],
|
||||
'term_bottom_description_meta_key' => 'custom_bottom_key',
|
||||
'groq_api_key' => 'groq-key',
|
||||
'openai_api_key' => 'openai-key',
|
||||
'google_api_key' => 'google-key',
|
||||
'google_oauth_client_id' => 'client-id',
|
||||
'google_oauth_client_secret' => 'client-secret',
|
||||
'google_oauth_refresh_token' => 'refresh-token',
|
||||
'google_oauth_connected_email' => 'user@example.com',
|
||||
'google_oauth_connected_at' => 123456,
|
||||
'google_enable_gsc' => true,
|
||||
'google_enable_ga' => false,
|
||||
'google_gsc_site_url' => 'https://example.com/',
|
||||
'google_ga4_property_id' => '123456',
|
||||
'google_safety_settings' => $first_category ? [ $first_category => 'BLOCK_LOW_AND_ABOVE' ] : [],
|
||||
'context_fields' => $context_fields,
|
||||
'modules' => $modules,
|
||||
'image_context_mode' => 'base64',
|
||||
'image_context_limit' => 5,
|
||||
'response_format_compat' => true,
|
||||
'term_top_description_char_limit' => 700,
|
||||
'term_bottom_description_char_limit' => 1400,
|
||||
];
|
||||
|
||||
$result = $manager->sanitize( $input );
|
||||
|
||||
$this->assertSame( 'openai', $result['provider'] );
|
||||
$this->assertSame( 'gpt-4o-mini', $result['model'] );
|
||||
$this->assertSame( 'Test winkelcontext', $result['store_context'] );
|
||||
$this->assertSame( 'Schrijf een korte tekst', $result['default_prompt'] );
|
||||
$this->assertSame( 2048, $result['max_output_tokens'] );
|
||||
$this->assertSame( 30, $result['logs_retention_days'] );
|
||||
$this->assertContains( '__all__', $result['product_attribute_includes'] );
|
||||
$this->assertContains( '__custom__', $result['product_attribute_includes'] );
|
||||
$this->assertContains( 'pa_color', $result['product_attribute_includes'] );
|
||||
$this->assertSame( 'custom_bottom_key', $result['term_bottom_description_meta_key'] );
|
||||
$this->assertSame( 'groq-key', $result['groq_api_key'] );
|
||||
$this->assertSame( 'openai-key', $result['openai_api_key'] );
|
||||
$this->assertSame( 'google-key', $result['google_api_key'] );
|
||||
$this->assertSame( 'client-id', $result['google_oauth_client_id'] );
|
||||
$this->assertSame( 'client-secret', $result['google_oauth_client_secret'] );
|
||||
$this->assertSame( 'refresh-token', $result['google_oauth_refresh_token'] );
|
||||
$this->assertSame( 'user@example.com', $result['google_oauth_connected_email'] );
|
||||
$this->assertSame( 123456, $result['google_oauth_connected_at'] );
|
||||
$this->assertTrue( $result['google_enable_gsc'] );
|
||||
$this->assertFalse( $result['google_enable_ga'] );
|
||||
$this->assertSame( 'https://example.com/', $result['google_gsc_site_url'] );
|
||||
$this->assertSame( '123456', $result['google_ga4_property_id'] );
|
||||
$this->assertIsArray( $result['google_safety_settings'] );
|
||||
$this->assertIsArray( $result['context_fields'] );
|
||||
$this->assertIsArray( $result['modules'] );
|
||||
$this->assertSame( 'base64', $result['image_context_mode'] );
|
||||
$this->assertSame( 5, $result['image_context_limit'] );
|
||||
$this->assertTrue( $result['response_format_compat'] );
|
||||
$this->assertSame( 700, $result['term_top_description_char_limit'] );
|
||||
$this->assertSame( 1400, $result['term_bottom_description_char_limit'] );
|
||||
}
|
||||
}
|
||||
67
tests/TermSaveTest.php
Normal file
67
tests/TermSaveTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TermSaveTest extends TestCase {
|
||||
protected function setUp(): void {
|
||||
$GLOBALS['wp_term_updates'] = [];
|
||||
$GLOBALS['wp_term_meta_updates'] = [];
|
||||
$GLOBALS['wp_filters'] = [];
|
||||
}
|
||||
|
||||
public function test_save_term_generation_result_saves_descriptions_and_filtered_meta_key() {
|
||||
$plugin = new class {
|
||||
public function get_settings() {
|
||||
return [ 'term_bottom_description_meta_key' => '' ];
|
||||
}
|
||||
public function is_module_enabled( $module, $settings = null ) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$controller_ref = new ReflectionClass( Groq_AI_Ajax_Controller::class );
|
||||
$controller = $controller_ref->newInstanceWithoutConstructor();
|
||||
$plugin_prop = $controller_ref->getProperty( 'plugin' );
|
||||
$plugin_prop->setAccessible( true );
|
||||
$plugin_prop->setValue( $controller, $plugin );
|
||||
|
||||
add_filter(
|
||||
'groq_ai_term_bottom_description_meta_key',
|
||||
function ( $default_key ) {
|
||||
return 'Custom Key';
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
|
||||
$term = (object) [
|
||||
'term_id' => 12,
|
||||
'taxonomy' => 'product_cat',
|
||||
'name' => 'Test',
|
||||
'description' => '',
|
||||
];
|
||||
|
||||
$result = [
|
||||
'top_description' => '<p>Dit is een test.</p>',
|
||||
'bottom_description' => '<p>Onderste tekst.</p>',
|
||||
];
|
||||
|
||||
$settings = $plugin->get_settings();
|
||||
|
||||
$method = $controller_ref->getMethod( 'save_term_generation_result' );
|
||||
$method->setAccessible( true );
|
||||
$saved = $method->invoke( $controller, $term, $result, $settings );
|
||||
|
||||
$this->assertIsArray( $saved );
|
||||
$this->assertSame( 4, $saved['words'] );
|
||||
|
||||
$this->assertCount( 1, $GLOBALS['wp_term_updates'] );
|
||||
$this->assertSame( 12, $GLOBALS['wp_term_updates'][0]['term_id'] );
|
||||
$this->assertSame( 'product_cat', $GLOBALS['wp_term_updates'][0]['taxonomy'] );
|
||||
$this->assertSame( '<p>Dit is een test.</p>', $GLOBALS['wp_term_updates'][0]['args']['description'] );
|
||||
|
||||
$this->assertArrayHasKey( 12, $GLOBALS['wp_term_meta_updates'] );
|
||||
$this->assertArrayHasKey( 'customkey', $GLOBALS['wp_term_meta_updates'][12] );
|
||||
$this->assertSame( '<p>Onderste tekst.</p>', $GLOBALS['wp_term_meta_updates'][12]['customkey'] );
|
||||
}
|
||||
}
|
||||
258
tests/bootstrap.php
Normal file
258
tests/bootstrap.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'GROQ_AI_PRODUCT_TEXT_DOMAIN' ) ) {
|
||||
define( 'GROQ_AI_PRODUCT_TEXT_DOMAIN', 'siti-ai-product-content-generator' );
|
||||
}
|
||||
|
||||
if ( ! defined( 'HOUR_IN_SECONDS' ) ) {
|
||||
define( 'HOUR_IN_SECONDS', 3600 );
|
||||
}
|
||||
|
||||
if ( ! defined( 'DAY_IN_SECONDS' ) ) {
|
||||
define( 'DAY_IN_SECONDS', 86400 );
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WP_Error' ) ) {
|
||||
class WP_Error {
|
||||
private $message;
|
||||
|
||||
public function __construct( $code = '', $message = '' ) {
|
||||
$this->message = (string) $message;
|
||||
}
|
||||
|
||||
public function get_error_message() {
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'is_wp_error' ) ) {
|
||||
function is_wp_error( $thing ) {
|
||||
return $thing instanceof WP_Error;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( '__' ) ) {
|
||||
function __( $text ) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_parse_args' ) ) {
|
||||
function wp_parse_args( $args, $defaults = [] ) {
|
||||
if ( is_object( $args ) ) {
|
||||
$args = get_object_vars( $args );
|
||||
}
|
||||
if ( ! is_array( $args ) ) {
|
||||
$args = [];
|
||||
}
|
||||
return array_merge( $defaults, $args );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'sanitize_text_field' ) ) {
|
||||
function sanitize_text_field( $text ) {
|
||||
return trim( (string) $text );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'sanitize_textarea_field' ) ) {
|
||||
function sanitize_textarea_field( $text ) {
|
||||
return trim( (string) $text );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'sanitize_key' ) ) {
|
||||
function sanitize_key( $key ) {
|
||||
$key = strtolower( (string) $key );
|
||||
return preg_replace( '/[^a-z0-9_\-]/', '', $key );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'absint' ) ) {
|
||||
function absint( $number ) {
|
||||
return abs( (int) $number );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'esc_url_raw' ) ) {
|
||||
function esc_url_raw( $url ) {
|
||||
return (string) $url;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'add_filter' ) ) {
|
||||
function add_filter( $tag, $callback, $priority = 10, $accepted_args = 1 ) {
|
||||
$GLOBALS['wp_filters'][ $tag ][ $priority ][] = [
|
||||
'callback' => $callback,
|
||||
'accepted_args' => (int) $accepted_args,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'apply_filters' ) ) {
|
||||
function apply_filters( $tag, $value ) {
|
||||
$args = func_get_args();
|
||||
if ( empty( $GLOBALS['wp_filters'][ $tag ] ) ) {
|
||||
return $value;
|
||||
}
|
||||
ksort( $GLOBALS['wp_filters'][ $tag ] );
|
||||
foreach ( $GLOBALS['wp_filters'][ $tag ] as $callbacks ) {
|
||||
foreach ( $callbacks as $filter ) {
|
||||
$accepted = isset( $filter['accepted_args'] ) ? (int) $filter['accepted_args'] : 1;
|
||||
$call_args = array_slice( $args, 0, max( 1, $accepted ) );
|
||||
$call_args[0] = $value;
|
||||
$value = call_user_func_array( $filter['callback'], $call_args );
|
||||
$args[0] = $value;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_kses_post' ) ) {
|
||||
function wp_kses_post( $content ) {
|
||||
return (string) $content;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_strip_all_tags' ) ) {
|
||||
function wp_strip_all_tags( $text ) {
|
||||
return strip_tags( (string) $text );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_update_term' ) ) {
|
||||
function wp_update_term( $term_id, $taxonomy, $args = [] ) {
|
||||
$GLOBALS['wp_term_updates'][] = [
|
||||
'term_id' => (int) $term_id,
|
||||
'taxonomy' => (string) $taxonomy,
|
||||
'args' => $args,
|
||||
];
|
||||
return [ 'term_id' => (int) $term_id ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'update_term_meta' ) ) {
|
||||
function update_term_meta( $term_id, $meta_key, $meta_value ) {
|
||||
$term_id = (int) $term_id;
|
||||
if ( ! isset( $GLOBALS['wp_term_meta_updates'][ $term_id ] ) ) {
|
||||
$GLOBALS['wp_term_meta_updates'][ $term_id ] = [];
|
||||
}
|
||||
$GLOBALS['wp_term_meta_updates'][ $term_id ][ (string) $meta_key ] = $meta_value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_json_encode' ) ) {
|
||||
function wp_json_encode( $data, $options = 0, $depth = 512 ) {
|
||||
return json_encode( $data, $options, $depth );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'add_query_arg' ) ) {
|
||||
function add_query_arg( $args, $url = '' ) {
|
||||
if ( is_string( $args ) ) {
|
||||
return $url;
|
||||
}
|
||||
$query = http_build_query( (array) $args );
|
||||
$separator = strpos( $url, '?' ) === false ? '?' : '&';
|
||||
return $url . $separator . $query;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_remote_post' ) ) {
|
||||
function wp_remote_post( $url, $args = [] ) {
|
||||
$GLOBALS['wp_last_http_request'] = [
|
||||
'url' => $url,
|
||||
'args' => $args,
|
||||
];
|
||||
|
||||
$body = json_encode(
|
||||
[
|
||||
'choices' => [
|
||||
[
|
||||
'message' => [
|
||||
'content' => 'ok',
|
||||
],
|
||||
'finish_reason' => 'stop',
|
||||
],
|
||||
],
|
||||
'usage' => [
|
||||
'prompt_tokens' => 10,
|
||||
'completion_tokens' => 20,
|
||||
'total_tokens' => 30,
|
||||
],
|
||||
'candidates' => [
|
||||
[
|
||||
'content' => [
|
||||
'parts' => [
|
||||
[ 'text' => 'ok' ],
|
||||
],
|
||||
],
|
||||
'finishReason' => 'STOP',
|
||||
],
|
||||
],
|
||||
'usageMetadata' => [
|
||||
'promptTokenCount' => 10,
|
||||
'candidatesTokenCount' => 20,
|
||||
'totalTokenCount' => 30,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return [
|
||||
'body' => $body,
|
||||
'response' => [ 'code' => 200 ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_remote_get' ) ) {
|
||||
function wp_remote_get( $url, $args = [] ) {
|
||||
$GLOBALS['wp_last_http_request'] = [
|
||||
'url' => $url,
|
||||
'args' => $args,
|
||||
];
|
||||
|
||||
return [
|
||||
'body' => json_encode( [ 'data' => [], 'models' => [] ] ),
|
||||
'response' => [ 'code' => 200 ],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_remote_retrieve_body' ) ) {
|
||||
function wp_remote_retrieve_body( $response ) {
|
||||
return isset( $response['body'] ) ? $response['body'] : '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_remote_retrieve_response_code' ) ) {
|
||||
function wp_remote_retrieve_response_code( $response ) {
|
||||
return isset( $response['response']['code'] ) ? (int) $response['response']['code'] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_option' ) ) {
|
||||
function get_option( $key, $default = false ) {
|
||||
return isset( $GLOBALS['wp_options'][ $key ] ) ? $GLOBALS['wp_options'][ $key ] : $default;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'update_option' ) ) {
|
||||
function update_option( $key, $value ) {
|
||||
$GLOBALS['wp_options'][ $key ] = $value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../includes/Core/class-groq-ai-model-exclusions.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';
|
||||
require_once __DIR__ . '/../includes/Providers/class-groq-ai-provider-openai.php';
|
||||
require_once __DIR__ . '/../includes/Providers/class-groq-ai-provider-google.php';
|
||||
require_once __DIR__ . '/../includes/Providers/class-groq-ai-provider-manager.php';
|
||||
require_once __DIR__ . '/../includes/Services/Settings/class-groq-ai-settings-manager.php';
|
||||
require_once __DIR__ . '/../includes/Core/class-groq-ai-ajax-controller.php';
|
||||
Reference in New Issue
Block a user