diff --git a/assets/css/settings.css b/assets/css/settings.css
index 18e409b..788e178 100644
--- a/assets/css/settings.css
+++ b/assets/css/settings.css
@@ -41,6 +41,14 @@
margin-top: 8px;
}
+.groq-ai-bulk-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ margin-top: 8px;
+}
+
#groq-ai-bulk-status {
margin-top: 8px;
}
@@ -89,3 +97,12 @@
background-color: transparent;
}
}
+
+.groq-ai-term-actions {
+ width: 180px;
+}
+
+.groq-ai-regenerate-term.is-busy {
+ opacity: 0.6;
+ pointer-events: none;
+}
diff --git a/assets/js/category-bulk.js b/assets/js/category-bulk.js
deleted file mode 100644
index f717552..0000000
--- a/assets/js/category-bulk.js
+++ /dev/null
@@ -1,212 +0,0 @@
-(function () {
- const data = window.GroqAICategoryBulk || {};
- const startButton = document.getElementById('groq-ai-bulk-generate');
- const stopButton = document.getElementById('groq-ai-bulk-cancel');
- const statusField = document.getElementById('groq-ai-bulk-status');
- const logList = document.getElementById('groq-ai-bulk-log');
-
- if (!startButton || !data.ajaxUrl) {
- return;
- }
-
- let queue = [];
- let totalCount = 0;
- let processed = 0;
- let successes = 0;
- let isRunning = false;
- let abortRequested = false;
-
- function formatString(template, values) {
- if (!template) {
- return '';
- }
- let autoIndex = 0;
- return template.replace(/%(\d+\$)?[sd]/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 setStatus(message, type) {
- if (!statusField) {
- return;
- }
- statusField.textContent = message || '';
- statusField.dataset.status = type || '';
- }
-
- function appendLog(message, type) {
- if (!logList || !message) {
- return;
- }
- const item = document.createElement('li');
- item.textContent = message;
- item.dataset.status = type || '';
- logList.appendChild(item);
- }
-
- function resetLog() {
- if (!logList) {
- return;
- }
- logList.innerHTML = '';
- }
-
- function toggleButtons(running) {
- isRunning = running;
- startButton.disabled = running;
- if (stopButton) {
- stopButton.hidden = !running;
- }
- }
-
- function getPendingTerms() {
- if (!Array.isArray(data.terms)) {
- return [];
- }
- return data.terms.filter((term) => !term.processed);
- }
-
- function updateRow(termId, words) {
- const row = document.querySelector('[data-groq-ai-term-id="' + termId + '"]');
- if (!row) {
- return;
- }
- row.classList.remove('groq-ai-term-missing');
- row.classList.add('groq-ai-term-updated');
- const wordCell = row.querySelector('.groq-ai-word-count');
- if (wordCell) {
- wordCell.textContent = String(typeof words === 'number' ? words : wordCell.textContent);
- }
- }
-
- function finish(state) {
- const summaryTemplate =
- state === 'done'
- ? data.strings && data.strings.statusDone
- : state === 'stopped'
- ? data.strings && data.strings.statusStopped
- : '';
-
- const summary = summaryTemplate
- ? formatString(summaryTemplate, [successes])
- : '';
-
- const statusType = state === 'done' ? 'success' : state === 'stopped' ? 'info' : '';
- setStatus(summary, statusType);
- toggleButtons(false);
- queue = [];
- totalCount = 0;
- abortRequested = false;
- }
-
- function processNext() {
- if (abortRequested) {
- finish('stopped');
- return;
- }
-
- if (!queue.length) {
- finish('done');
- return;
- }
-
- const term = queue.shift();
- const position = processed + 1;
- const progressTemplate = data.strings && data.strings.statusProgress;
- if (progressTemplate) {
- setStatus(formatString(progressTemplate, [position, totalCount, term.name || '']), 'loading');
- }
-
- const payload = new URLSearchParams();
- payload.append('action', 'groq_ai_bulk_generate_terms');
- payload.append('nonce', data.nonce || '');
- payload.append('taxonomy', data.taxonomy || 'product_cat');
- payload.append('term_id', term.id);
-
- fetch(data.ajaxUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
- },
- body: payload.toString(),
- })
- .then((response) => response.json())
- .then((json) => {
- if (!json.success) {
- const errorMessage = (json.data && json.data.message) || 'Onbekende fout';
- appendLog(formatString((data.strings && data.strings.logError) || '%1$s: %2$s', [term.name || term.id, errorMessage]), 'error');
- return;
- }
-
- term.processed = true;
- successes += 1;
- const words = json.data && typeof json.data.words !== 'undefined' ? json.data.words : 0;
- updateRow(term.id, words);
- appendLog(formatString((data.strings && data.strings.logSuccess) || '%1$s gevuld.', [term.name || term.id, words]), 'success');
- })
- .catch((error) => {
- appendLog(
- formatString((data.strings && data.strings.logError) || '%1$s: %2$s', [term.name || term.id, error && error.message ? error.message : 'Onbekende fout']),
- 'error'
- );
- })
- .finally(() => {
- processed += 1;
- if (abortRequested) {
- finish('stopped');
- } else {
- processNext();
- }
- });
- }
-
- function startBulk() {
- if (isRunning) {
- return;
- }
-
- const pending = getPendingTerms();
- if (!pending.length) {
- setStatus((data.strings && data.strings.statusEmpty) || '', 'info');
- return;
- }
-
- queue = pending.slice();
- totalCount = queue.length;
- processed = 0;
- successes = 0;
- abortRequested = false;
- resetLog();
- toggleButtons(true);
- setStatus((data.strings && data.strings.statusIdle) || '', 'info');
- processNext();
- }
-
- startButton.addEventListener('click', startBulk);
-
- if (stopButton) {
- stopButton.addEventListener('click', () => {
- if (!isRunning) {
- return;
- }
- const confirmation = ! (data.strings && data.strings.confirmStop)
- ? window.confirm('Stoppen?')
- : window.confirm(data.strings.confirmStop);
- if (confirmation) {
- abortRequested = true;
- }
- });
- }
-
- if (!Array.isArray(data.terms) || !data.terms.length) {
- setStatus((data.strings && data.strings.statusEmpty) || '', 'info');
- }
-})();
diff --git a/assets/js/term-bulk.js b/assets/js/term-bulk.js
new file mode 100644
index 0000000..390d3f5
--- /dev/null
+++ b/assets/js/term-bulk.js
@@ -0,0 +1,288 @@
+(function () {
+ const data = window.GroqAITermBulk || {};
+ const startButton = document.getElementById('groq-ai-bulk-generate');
+ const stopButton = document.getElementById('groq-ai-bulk-cancel');
+ const statusField = document.getElementById('groq-ai-bulk-status');
+ const logList = document.getElementById('groq-ai-bulk-log');
+
+ if (!data.ajaxUrl || !startButton || !statusField || !logList) {
+ return;
+ }
+
+ const strings = data.strings || {};
+ const allowRegenerate = !!data.allowRegenerate;
+ const terms = (Array.isArray(data.terms) ? data.terms : [])
+ .map((term) => {
+ const id = parseInt(term.id, 10);
+ if (!Number.isFinite(id)) {
+ return null;
+ }
+ const words = typeof term.words === 'number' ? term.words : parseInt(term.words, 10) || 0;
+ const hasDescription = !!term.hasDescription;
+ return {
+ id,
+ name: term.name || '',
+ slug: term.slug || '',
+ count: typeof term.count === 'number' ? term.count : parseInt(term.count, 10) || 0,
+ words,
+ hasDescription,
+ needsGeneration: !hasDescription,
+ };
+ })
+ .filter(Boolean);
+
+ const termMap = new Map();
+ terms.forEach((term) => termMap.set(term.id, term));
+
+ let queue = [];
+ let totalCount = 0;
+ let processed = 0;
+ let successes = 0;
+ let isRunning = false;
+ let abortRequested = false;
+
+ function formatString(template, values) {
+ if (!template) {
+ return '';
+ }
+ let autoIndex = 0;
+ return template.replace(/%(\d+\$)?[sd]/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 setStatus(message, type) {
+ statusField.textContent = message || '';
+ statusField.dataset.status = type || '';
+ }
+
+ function appendLog(message, type) {
+ if (!message) {
+ return;
+ }
+ const item = document.createElement('li');
+ item.textContent = message;
+ item.dataset.status = type || '';
+ logList.appendChild(item);
+ }
+
+ function resetLog() {
+ logList.innerHTML = '';
+ }
+
+ function toggleButtons(running) {
+ isRunning = running;
+ startButton.disabled = running;
+ if (stopButton) {
+ stopButton.hidden = !running;
+ }
+ }
+
+ function getPendingTerms() {
+ return terms.filter((term) => term.needsGeneration);
+ }
+
+ function updateRow(term) {
+ const row = document.querySelector('[data-groq-ai-term-id="' + term.id + '"]');
+ if (!row) {
+ return;
+ }
+ row.classList.remove('groq-ai-term-missing');
+ row.classList.add('groq-ai-term-updated');
+ const wordCell = row.querySelector('.groq-ai-word-count');
+ if (wordCell) {
+ wordCell.textContent = String(term.words);
+ }
+ }
+
+ function markTermCompleted(term, words) {
+ term.hasDescription = true;
+ term.needsGeneration = false;
+ if (Number.isFinite(words)) {
+ term.words = words;
+ }
+ updateRow(term);
+ }
+
+ function finish(state) {
+ const summaryTemplate = state === 'done' ? strings.statusDone : state === 'stopped' ? strings.statusStopped : '';
+ const summary = summaryTemplate ? formatString(summaryTemplate, [successes]) : '';
+ const statusType = state === 'done' ? 'success' : state === 'stopped' ? 'info' : '';
+ setStatus(summary, statusType);
+ toggleButtons(false);
+ queue = [];
+ totalCount = 0;
+ processed = 0;
+ successes = 0;
+ abortRequested = false;
+ }
+
+ function sendRequest(term, options = {}) {
+ const payload = new URLSearchParams();
+ payload.append('action', 'groq_ai_bulk_generate_terms');
+ payload.append('nonce', data.nonce || '');
+ payload.append('taxonomy', data.taxonomy || '');
+ payload.append('term_id', term.id);
+ if (options.force) {
+ payload.append('force', '1');
+ }
+
+ return fetch(data.ajaxUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ },
+ body: payload.toString(),
+ }).then((response) => response.json());
+ }
+
+ 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');
+ if (context === 'single') {
+ setStatus(formatString(strings.regenerateError || '%1$s mislukt: %2$s', [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');
+ if (context === 'single') {
+ setStatus(formatString(strings.regenerateDone || '%s is bijgewerkt.', [term.name || term.id]), 'success');
+ }
+ return true;
+ }
+
+ function processNext() {
+ if (abortRequested) {
+ finish('stopped');
+ return;
+ }
+
+ if (!queue.length) {
+ finish('done');
+ return;
+ }
+
+ const term = queue.shift();
+ const progressTemplate = strings.statusProgress;
+ if (progressTemplate) {
+ setStatus(formatString(progressTemplate, [processed + 1, totalCount, term.name || '']), 'loading');
+ }
+
+ sendRequest(term)
+ .then((json) => {
+ if (handleResponse(term, json, 'bulk')) {
+ successes += 1;
+ }
+ })
+ .catch((error) => {
+ appendLog(
+ formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, error && error.message ? error.message : 'Onbekende fout']),
+ 'error'
+ );
+ })
+ .finally(() => {
+ processed += 1;
+ if (abortRequested) {
+ finish('stopped');
+ } else {
+ processNext();
+ }
+ });
+ }
+
+ function startBulk() {
+ if (isRunning) {
+ return;
+ }
+
+ const pending = getPendingTerms();
+ if (!pending.length) {
+ setStatus(strings.statusEmpty || '', 'info');
+ return;
+ }
+
+ queue = pending.slice();
+ totalCount = queue.length;
+ processed = 0;
+ successes = 0;
+ abortRequested = false;
+ resetLog();
+ toggleButtons(true);
+ if (strings.statusIdle) {
+ setStatus(strings.statusIdle, 'info');
+ }
+ processNext();
+ }
+
+ startButton.addEventListener('click', startBulk);
+
+ if (stopButton) {
+ stopButton.addEventListener('click', () => {
+ if (!isRunning) {
+ return;
+ }
+ const confirmation = strings.confirmStop ? window.confirm(strings.confirmStop) : window.confirm('Stoppen?');
+ if (confirmation) {
+ abortRequested = true;
+ }
+ });
+ }
+
+ if (allowRegenerate) {
+ const buttons = document.querySelectorAll('.groq-ai-regenerate-term');
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ if (isRunning) {
+ setStatus(strings.regenerateBlocked || '', 'error');
+ return;
+ }
+ const termId = parseInt(button.getAttribute('data-term-id'), 10);
+ const term = termMap.get(termId);
+ if (!term) {
+ setStatus('Onbekende term.', 'error');
+ return;
+ }
+ if (strings.confirmRegenerate) {
+ const confirmed = window.confirm(formatString(strings.confirmRegenerate, [term.name || term.id]));
+ if (!confirmed) {
+ return;
+ }
+ }
+ button.classList.add('is-busy');
+ button.disabled = true;
+ if (strings.regenerateProgress) {
+ setStatus(formatString(strings.regenerateProgress, [term.name || term.id]), 'loading');
+ }
+ sendRequest(term, { force: true })
+ .then((json) => {
+ 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');
+ })
+ .finally(() => {
+ button.disabled = false;
+ button.classList.remove('is-busy');
+ });
+ });
+ });
+ }
+
+ if (getPendingTerms().length === 0) {
+ setStatus(strings.statusEmpty || '', 'info');
+ }
+})();
diff --git a/groq-ai-product-text.php b/groq-ai-product-text.php
index da817d9..03828f1 100644
--- a/groq-ai-product-text.php
+++ b/groq-ai-product-text.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: SitiAI Product Teksten
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
- * Version: 1.6.0
+ * Version: 1.6.1
* Author: SitiAI
* Text Domain: siti-ai-product-content-generator
* Domain Path: /languages
diff --git a/includes/Admin/class-groq-ai-settings-page.php b/includes/Admin/class-groq-ai-settings-page.php
index fb58200..689e4cf 100644
--- a/includes/Admin/class-groq-ai-settings-page.php
+++ b/includes/Admin/class-groq-ai-settings-page.php
@@ -4,6 +4,7 @@ class Groq_AI_Product_Text_Settings_Page {
private $plugin;
private $provider_manager;
private $brand_taxonomy = null;
+ private $term_overview_cache = [];
public function __construct( $plugin, Groq_AI_Provider_Manager $provider_manager ) {
$this->plugin = $plugin;
@@ -156,61 +157,148 @@ class Groq_AI_Product_Text_Settings_Page {
);
}
+ private function get_term_overview_data( $taxonomy ) {
+ $taxonomy = sanitize_key( (string) $taxonomy );
+
+ if ( isset( $this->term_overview_cache[ $taxonomy ] ) ) {
+ return $this->term_overview_cache[ $taxonomy ];
+ }
+
+ $rows = [];
+ $empty_rows = [];
+
+ if ( '' !== $taxonomy && taxonomy_exists( $taxonomy ) ) {
+ $terms = get_terms(
+ [
+ 'taxonomy' => $taxonomy,
+ 'hide_empty' => false,
+ 'orderby' => 'name',
+ 'order' => 'ASC',
+ 'number' => 0,
+ ]
+ );
+
+ if ( is_wp_error( $terms ) ) {
+ $terms = [];
+ }
+
+ foreach ( $terms as $term ) {
+ if ( ! $term || ! is_object( $term ) || empty( $term->term_id ) ) {
+ continue;
+ }
+
+ $words = $this->count_words( isset( $term->description ) ? $term->description : '' );
+ $has_description = $words > 0;
+
+ $row = [
+ 'id' => absint( $term->term_id ),
+ 'name' => (string) $term->name,
+ 'slug' => (string) $term->slug,
+ 'count' => isset( $term->count ) ? absint( $term->count ) : 0,
+ 'words' => $words,
+ 'has_description' => $has_description,
+ 'url' => $this->get_term_page_url( $taxonomy, $term->term_id ),
+ ];
+
+ $rows[] = $row;
+ if ( ! $has_description ) {
+ $empty_rows[] = $row;
+ }
+ }
+ }
+
+ $data = [
+ 'rows' => $rows,
+ 'empty_rows' => $empty_rows,
+ 'empty_count' => count( $empty_rows ),
+ ];
+
+ $this->term_overview_cache[ $taxonomy ] = $data;
+
+ return $data;
+ }
+
+ private function render_term_bulk_panel( $label_plural, $empty_count ) {
+ $label_plural = (string) $label_plural;
+ ?>
+
+
+ 0 ) {
+ printf(
+ /* translators: 1: amount, 2: label plural (e.g. categorieën) */
+ esc_html__( 'Er zijn %1$d %2$s zonder omschrijving. Klik op de knop hieronder om automatisch teksten te genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ (int) $empty_count,
+ esc_html( $label_plural )
+ );
+ } else {
+ printf(
+ esc_html__( 'Alle %s hebben al een omschrijving.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ esc_html( $label_plural )
+ );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+ get_term_overview_data( $taxonomy );
+ $rows = isset( $overview['rows'] ) ? $overview['rows'] : [];
+
+ $terms = [];
+ foreach ( $rows as $row ) {
+ $terms[] = [
+ 'id' => isset( $row['id'] ) ? (int) $row['id'] : 0,
+ 'name' => isset( $row['name'] ) ? (string) $row['name'] : '',
+ 'slug' => isset( $row['slug'] ) ? (string) $row['slug'] : '',
+ 'count' => isset( $row['count'] ) ? (int) $row['count'] : 0,
+ 'words' => isset( $row['words'] ) ? (int) $row['words'] : 0,
+ 'hasDescription' => ! empty( $row['has_description'] ),
+ ];
+ }
+
+ $defaults = [
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'groq_ai_bulk_generate_terms' ),
+ 'taxonomy' => $taxonomy,
+ 'terms' => $terms,
+ 'allowRegenerate' => false,
+ 'strings' => [],
+ ];
+
+ $config = wp_parse_args( $overrides, $defaults );
+
+ wp_localize_script( 'groq-ai-term-bulk', 'GroqAITermBulk', $config );
+ }
+
public function render_categories_overview_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
- $terms = get_terms(
- [
- 'taxonomy' => 'product_cat',
- 'hide_empty' => false,
- 'orderby' => 'name',
- 'order' => 'ASC',
- 'number' => 0,
- ]
- );
- if ( is_wp_error( $terms ) ) {
- $terms = [];
- }
-
- $word_map = [];
- $empty_terms = [];
- foreach ( $terms as $term ) {
- if ( ! $term || ! is_object( $term ) ) {
- continue;
- }
- $word_count = $this->count_words( isset( $term->description ) ? $term->description : '' );
- $word_map[ $term->term_id ] = $word_count;
- if ( 0 === $word_count ) {
- $empty_terms[] = $term;
- }
- }
+ $taxonomy = 'product_cat';
+ $overview = $this->get_term_overview_data( $taxonomy );
+ $rows = isset( $overview['rows'] ) ? $overview['rows'] : [];
+ $empty_count = isset( $overview['empty_count'] ) ? (int) $overview['empty_count'] : 0;
?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ render_term_bulk_panel( __( 'categorieën', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $empty_count ); ?>
@@ -221,30 +309,26 @@ class Groq_AI_Product_Text_Settings_Page {
-
+
|
-
+
get_term_page_url( 'product_cat', $term->term_id );
- $words = isset( $word_map[ $term->term_id ] ) ? $word_map[ $term->term_id ] : 0;
- $count = isset( $term->count ) ? absint( $term->count ) : 0;
$row_classes = [ 'groq-ai-term-row' ];
- if ( 0 === $words ) {
+ if ( empty( $row['has_description'] ) ) {
$row_classes[] = 'groq-ai-term-missing';
}
+ $link = isset( $row['url'] ) ? $row['url'] : '';
+ $count = isset( $row['count'] ) ? (int) $row['count'] : 0;
+ $words = isset( $row['words'] ) ? (int) $row['words'] : 0;
?>
-
+
|
- name ); ?>
+
|
- slug ); ?> |
+ |
|
-
-
-
-
- |
+ |
@@ -253,7 +337,6 @@ class Groq_AI_Product_Text_Settings_Page {
$taxonomy,
- 'hide_empty' => false,
- 'orderby' => 'name',
- 'order' => 'ASC',
- 'number' => 0,
- ]
- );
- if ( is_wp_error( $terms ) ) {
- $terms = [];
- }
+ $overview = $this->get_term_overview_data( $taxonomy );
+ $rows = isset( $overview['rows'] ) ? $overview['rows'] : [];
+ $empty_count = isset( $overview['empty_count'] ) ? (int) $overview['empty_count'] : 0;
?>
@@ -294,6 +368,8 @@ class Groq_AI_Product_Text_Settings_Page {
);
?>
+ render_term_bulk_panel( __( 'merken', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $empty_count ); ?>
+
@@ -301,25 +377,35 @@ class Groq_AI_Product_Text_Settings_Page {
|
|
|
+ |
-
- |
+
+ |
-
+
get_term_page_url( $taxonomy, $term->term_id );
- $words = $this->count_words( $term->description );
- $count = isset( $term->count ) ? absint( $term->count ) : 0;
+ $row_classes = [ 'groq-ai-term-row' ];
+ if ( empty( $row['has_description'] ) ) {
+ $row_classes[] = 'groq-ai-term-missing';
+ }
+ $link = isset( $row['url'] ) ? $row['url'] : '';
+ $count = isset( $row['count'] ) ? (int) $row['count'] : 0;
+ $words = isset( $row['words'] ) ? (int) $row['words'] : 0;
?>
-
+
|
- name ); ?>
+
|
- slug ); ?> |
+ |
|
- |
+ |
+
+
+ |
@@ -328,7 +414,6 @@ class Groq_AI_Product_Text_Settings_Page {
$taxonomy,
- 'hide_empty' => false,
- 'orderby' => 'name',
- 'order' => 'ASC',
- 'number' => 0,
- ]
- );
-
- if ( is_wp_error( $terms ) ) {
- return [];
- }
-
- $payloads = [];
- foreach ( $terms as $term ) {
- $description = isset( $term->description ) ? trim( wp_strip_all_tags( (string) $term->description ) ) : '';
- if ( '' !== $description ) {
- continue;
- }
-
- $payloads[] = [
- 'id' => isset( $term->term_id ) ? absint( $term->term_id ) : 0,
- 'name' => isset( $term->name ) ? (string) $term->name : '',
- 'slug' => isset( $term->slug ) ? (string) $term->slug : '',
- 'count' => isset( $term->count ) ? absint( $term->count ) : 0,
- 'url' => esc_url( $this->get_term_page_url( $taxonomy, isset( $term->term_id ) ? $term->term_id : 0 ) ),
- ];
- }
-
- return array_values( $payloads );
- }
-
- public function handle_save_term_content() {
- if ( ! current_user_can( 'manage_options' ) ) {
- wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
- }
-
- check_admin_referer( 'groq_ai_save_term_content' );
-
- $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : '';
- $term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0;
- $description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
- $bottom_description = isset( $_POST['groq_ai_term_bottom_description'] ) ? wp_kses_post( wp_unslash( $_POST['groq_ai_term_bottom_description'] ) ) : '';
- $custom_prompt = isset( $_POST['groq_ai_term_custom_prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['groq_ai_term_custom_prompt'] ) ) : '';
- $rankmath_meta_title = isset( $_POST['groq_ai_rankmath_meta_title'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_meta_title'] ) ) : '';
- $rankmath_meta_description = isset( $_POST['groq_ai_rankmath_meta_description'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_meta_description'] ) ) : '';
- $rankmath_focus_keywords = isset( $_POST['groq_ai_rankmath_focus_keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_focus_keywords'] ) ) : '';
-
- if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
- wp_safe_redirect( $this->get_settings_page_url() );
- exit;
- }
-
- $result = wp_update_term(
- $term_id,
- $taxonomy,
- [
- 'description' => $description,
- ]
- );
-
- if ( ! is_wp_error( $result ) ) {
- update_term_meta( $term_id, 'groq_ai_term_custom_prompt', $custom_prompt );
- $settings = $this->plugin->get_settings();
- $term = get_term( $term_id, $taxonomy );
- if ( $term && ! is_wp_error( $term ) ) {
- $bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
- $effective_bottom_meta_key = '' !== $bottom_meta_key ? $bottom_meta_key : 'groq_ai_term_bottom_description';
- update_term_meta( $term_id, $effective_bottom_meta_key, $bottom_description );
-
- $rankmath_module_enabled = $this->plugin->is_module_enabled( 'rankmath', $settings );
- if ( $rankmath_module_enabled ) {
- $rankmath_keys = $this->resolve_rankmath_term_meta_keys( $term, $settings );
- update_term_meta( $term_id, $rankmath_keys['title'], $rankmath_meta_title );
- update_term_meta( $term_id, $rankmath_keys['description'], $rankmath_meta_description );
- update_term_meta( $term_id, $rankmath_keys['focus_keyword'], $rankmath_focus_keywords );
- }
- }
- }
-
- wp_safe_redirect( $this->get_term_page_url( $taxonomy, $term_id ) );
- exit;
- }
-
- public function register_settings() {
- register_setting( 'groq_ai_product_text_group', $this->plugin->get_option_key(), [ $this->plugin, 'sanitize_settings' ] );
-
- add_settings_section(
- 'groq_ai_product_text_general',
- __( 'Algemene instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- '__return_false',
- 'groq-ai-product-text'
- );
-
- add_settings_section(
- 'groq_ai_product_text_google',
- __( 'Google koppeling (OAuth)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- '__return_false',
- 'groq-ai-product-text'
- );
-
- add_settings_field(
- 'groq_ai_provider',
- __( 'AI-aanbieder', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_provider_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_general'
- );
-
- add_settings_field(
- 'groq_ai_model',
- __( 'Model', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_model_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_general'
- );
-
- add_settings_field(
- 'groq_ai_google_oauth_client_id',
- __( 'Google Client ID', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_oauth_client_id_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- add_settings_field(
- 'groq_ai_google_oauth_client_secret',
- __( 'Google Client secret', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_oauth_client_secret_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- add_settings_field(
- 'groq_ai_google_oauth_status',
- __( 'Google status', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_oauth_status_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- add_settings_field(
- 'groq_ai_google_gsc_site_url',
- __( 'Search Console site URL', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_gsc_site_url_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- add_settings_field(
- 'groq_ai_google_ga4_property_id',
- __( 'GA4 property ID', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_ga4_property_id_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- add_settings_field(
- 'groq_ai_google_context_toggles',
- __( 'Google data gebruiken', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_google_context_toggles_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_google'
- );
-
- foreach ( $this->provider_manager->get_providers() as $provider ) {
- add_settings_field(
- 'groq_ai_api_key_' . $provider->get_key(),
- sprintf( __( '%s API-sleutel', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $provider->get_label() ),
- [ $this, 'render_provider_api_key_field' ],
- 'groq-ai-product-text',
- 'groq_ai_product_text_general',
- [
- 'provider' => $provider,
- ]
- );
- }
-
- add_settings_section(
- 'groq_ai_product_text_prompts',
- __( 'Prompt instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- '__return_false',
- 'groq-ai-product-text-prompts'
- );
-
- add_settings_field(
- 'groq_ai_store_context',
- __( 'Winkelcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_store_context_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_default_prompt',
- __( 'Standaard prompt', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_default_prompt_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_max_output_tokens',
- __( 'Max output tokens', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_max_output_tokens_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_term_bottom_description_meta_key',
- __( 'Term-veld (onderaan) meta key', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_term_bottom_description_meta_key_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_context_fields',
- __( 'Standaard productcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_context_fields_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_product_attribute_includes',
- __( 'Productattributen meesturen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_product_attribute_includes_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_response_format_compat',
- __( 'Response-format compatibiliteit', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_response_format_compat_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_image_context_mode',
- __( 'Afbeeldingen toevoegen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_image_context_mode_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_field(
- 'groq_ai_image_context_limit',
- __( 'Maximaal aantal afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_image_context_limit_field' ],
- 'groq-ai-product-text-prompts',
- 'groq_ai_product_text_prompts'
- );
-
- add_settings_section(
- 'groq_ai_product_text_modules_rankmath',
- __( 'Rank Math SEO', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- '__return_false',
- 'groq-ai-product-text-modules'
- );
-
- add_settings_field(
- 'groq_ai_module_rankmath',
- __( 'Rank Math SEO', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- [ $this, 'render_rankmath_module_field' ],
- 'groq-ai-product-text-modules',
- 'groq_ai_product_text_modules_rankmath'
- );
- }
-
- public function render_image_context_mode_field() {
- $settings = $this->plugin->get_settings();
- $mode = isset( $settings['image_context_mode'] ) ? $settings['image_context_mode'] : 'url';
- $options = [
- 'none' => __( 'Nee, geen afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'url' => __( 'Ja, voeg afbeeldings-URL’s toe aan de prompt', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'base64' => __( 'Ja, verstuur afbeeldingen als Base64 (indien ondersteund)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ];
- ?>
-
-
-
-
- plugin->get_settings();
- $limit = $this->plugin->get_image_context_limit( $settings );
- ?>
-
-
-
-
- plugin->get_settings();
- ?>
-
-
- render_google_oauth_admin_notice(); ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- plugin->get_settings();
- $value = isset( $settings['google_oauth_client_id'] ) ? $settings['google_oauth_client_id'] : '';
- ?>
-
-
-
-
- plugin->get_settings();
- $value = isset( $settings['google_oauth_client_secret'] ) ? $settings['google_oauth_client_secret'] : '';
- ?>
-
-
-
-
- plugin->get_settings();
- $connected = ! empty( $settings['google_oauth_refresh_token'] );
- $email = isset( $settings['google_oauth_connected_email'] ) ? $settings['google_oauth_connected_email'] : '';
- $connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0;
- $redirect_uri = $this->get_google_oauth_redirect_uri();
-
- $start_url = wp_nonce_url(
- admin_url( 'admin-post.php?action=groq_ai_google_oauth_start' ),
- 'groq_ai_google_oauth_start',
- '_wpnonce'
- );
-
- $disconnect_url = wp_nonce_url(
- admin_url( 'admin-post.php?action=groq_ai_google_oauth_disconnect' ),
- 'groq_ai_google_oauth_disconnect',
- '_wpnonce'
- );
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
- —
-
-
- ()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- plugin->get_settings();
- $messages = [];
- $status = 'success';
-
- $oauth = new Groq_AI_Google_OAuth_Client();
- $token = $oauth->get_access_token( $settings );
- if ( is_wp_error( $token ) ) {
- $status = 'error';
- $messages[] = sprintf( __( 'OAuth: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $token->get_error_message() );
- } else {
- $messages[] = __( 'OAuth: OK (access token opgehaald).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
- $info = $oauth->get_access_token_info( $token );
- if ( is_array( $info ) ) {
- $scope = isset( $info['scope'] ) ? trim( (string) $info['scope'] ) : '';
- if ( '' !== $scope ) {
- $messages[] = sprintf( __( 'OAuth scopes: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $scope );
- if ( false === strpos( $scope, 'https://www.googleapis.com/auth/webmasters' ) ) {
- $messages[] = __( 'Tip: je access token mist Search Console scope. Klik op "Opnieuw verbinden" zodat je toestemming opnieuw wordt gevraagd.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
- }
- }
- }
- }
-
- $range_days = 7;
- $end_date = gmdate( 'Y-m-d' );
- $start_date = gmdate( 'Y-m-d', time() - ( $range_days * DAY_IN_SECONDS ) );
-
- if ( 'error' !== $status && ! empty( $settings['google_enable_gsc'] ) ) {
- $gsc = new Groq_AI_Google_Search_Console_Client( $oauth );
- $sites = $gsc->list_sites( $settings );
- if ( is_wp_error( $sites ) ) {
- $status = 'error';
- $messages[] = sprintf( __( 'Search Console: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $sites->get_error_message() );
- } else {
- $count = is_array( $sites ) ? count( $sites ) : 0;
- $messages[] = sprintf( __( 'Search Console: OK (%d properties zichtbaar).', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $count );
- $site_url = isset( $settings['google_gsc_site_url'] ) ? trim( (string) $settings['google_gsc_site_url'] ) : '';
- if ( '' !== $site_url && is_array( $sites ) && ! in_array( $site_url, $sites, true ) ) {
- $messages[] = __( 'Let op: de ingestelde site URL is niet gevonden in jouw zichtbare GSC properties.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
- }
- }
- }
-
- if ( 'error' !== $status && ! empty( $settings['google_enable_ga'] ) ) {
- $property_id = isset( $settings['google_ga4_property_id'] ) ? trim( (string) $settings['google_ga4_property_id'] ) : '';
- if ( '' !== $property_id ) {
- $ga = new Groq_AI_Google_Analytics_Data_Client( $oauth );
- $stats = $ga->get_property_sessions_summary( $settings, $property_id, $start_date, $end_date );
- if ( is_wp_error( $stats ) ) {
- $status = 'error';
- $messages[] = sprintf( __( 'Analytics: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $stats->get_error_message() );
- } else {
- $sessions = isset( $stats['sessions'] ) ? absint( $stats['sessions'] ) : 0;
- $messages[] = sprintf( __( 'Analytics: OK (sessies laatste %1$d dagen: ~%2$d).', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $range_days, $sessions );
- }
- } else {
- $messages[] = __( 'Analytics: overgeslagen (GA4 property ID niet ingevuld).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
- }
- }
-
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => $status,
- 'groq_ai_google_oauth_message' => implode( ' ', $messages ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- public function render_google_gsc_site_url_field() {
- $settings = $this->plugin->get_settings();
- $value = isset( $settings['google_gsc_site_url'] ) ? (string) $settings['google_gsc_site_url'] : '';
- ?>
-
-
-
-
- plugin->get_settings();
- $value = isset( $settings['google_ga4_property_id'] ) ? (string) $settings['google_ga4_property_id'] : '';
- ?>
-
-
-
-
- plugin->get_settings();
- $gsc = ! empty( $settings['google_enable_gsc'] );
- $ga = ! empty( $settings['google_enable_ga'] );
- ?>
-
-
- plugin->get_settings();
- $client_id = isset( $settings['google_oauth_client_id'] ) ? trim( (string) $settings['google_oauth_client_id'] ) : '';
- $client_secret = isset( $settings['google_oauth_client_secret'] ) ? trim( (string) $settings['google_oauth_client_secret'] ) : '';
-
- if ( '' === $client_id || '' === $client_secret ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'Vul eerst Google Client ID en Client secret in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- $state = wp_generate_password( 32, false, false );
- set_transient( $this->get_google_oauth_state_key(), $state, 10 * MINUTE_IN_SECONDS );
-
- $scope = implode( ' ', $this->get_google_oauth_scopes() );
- $redirect_uri = $this->get_google_oauth_redirect_uri();
-
- $auth_url = add_query_arg(
- [
- 'client_id' => $client_id,
- 'redirect_uri' => $redirect_uri,
- 'response_type' => 'code',
- 'access_type' => 'offline',
- 'prompt' => 'consent',
- 'include_granted_scopes' => 'true',
- 'scope' => $scope,
- 'state' => $state,
- ],
- 'https://accounts.google.com/o/oauth2/v2/auth'
- );
- $auth_url = esc_url_raw( $auth_url );
- $parsed = wp_parse_url( $auth_url );
- $host = isset( $parsed['host'] ) ? strtolower( (string) $parsed['host'] ) : '';
- if ( 'accounts.google.com' !== $host ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'OAuth URL ongeldig. Controleer plugin instellingen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- // Let op: wp_safe_redirect staat standaard geen externe hosts toe en valt dan terug naar /wp-admin.
- wp_redirect( $auth_url );
- exit;
- }
-
- public function handle_google_oauth_callback() {
- if ( ! current_user_can( 'manage_options' ) ) {
- wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
- }
-
- $expected_state = get_transient( $this->get_google_oauth_state_key() );
- delete_transient( $this->get_google_oauth_state_key() );
-
- $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
- $code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
- $error = isset( $_GET['error'] ) ? sanitize_text_field( wp_unslash( $_GET['error'] ) ) : '';
-
- if ( '' !== $error ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => sprintf( __( 'Google OAuth error: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $error ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- if ( empty( $expected_state ) || '' === $state || $state !== $expected_state ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'Ongeldige OAuth state. Probeer opnieuw te verbinden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- if ( '' === $code ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'Geen OAuth code ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- $settings = $this->plugin->get_settings();
- $client_id = isset( $settings['google_oauth_client_id'] ) ? trim( (string) $settings['google_oauth_client_id'] ) : '';
- $client_secret = isset( $settings['google_oauth_client_secret'] ) ? trim( (string) $settings['google_oauth_client_secret'] ) : '';
- $redirect_uri = $this->get_google_oauth_redirect_uri();
-
- if ( '' === $client_id || '' === $client_secret ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'Client ID/secret ontbreken. Sla eerst de instellingen op.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- $token_response = wp_remote_post(
- 'https://oauth2.googleapis.com/token',
- [
- 'timeout' => 20,
- 'headers' => [
- 'Content-Type' => 'application/x-www-form-urlencoded',
- ],
- 'body' => [
- 'code' => $code,
- 'client_id' => $client_id,
- 'client_secret' => $client_secret,
- 'redirect_uri' => $redirect_uri,
- 'grant_type' => 'authorization_code',
- ],
- ]
- );
-
- if ( is_wp_error( $token_response ) ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => $token_response->get_error_message(),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- $status_code = wp_remote_retrieve_response_code( $token_response );
- $body = wp_remote_retrieve_body( $token_response );
- $data = json_decode( (string) $body, true );
-
- if ( 200 !== $status_code || ! is_array( $data ) ) {
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'error',
- 'groq_ai_google_oauth_message' => __( 'Token exchange mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- $access_token = isset( $data['access_token'] ) ? sanitize_text_field( (string) $data['access_token'] ) : '';
- $refresh_token = isset( $data['refresh_token'] ) ? sanitize_text_field( (string) $data['refresh_token'] ) : '';
-
- if ( '' === $refresh_token ) {
- $refresh_token = isset( $settings['google_oauth_refresh_token'] ) ? sanitize_text_field( (string) $settings['google_oauth_refresh_token'] ) : '';
- }
-
- $connected_email = '';
- if ( '' !== $access_token ) {
- $userinfo_response = wp_remote_get(
- 'https://openidconnect.googleapis.com/v1/userinfo',
- [
- 'timeout' => 20,
- 'headers' => [
- 'Authorization' => 'Bearer ' . $access_token,
- ],
- ]
- );
- if ( ! is_wp_error( $userinfo_response ) && 200 === wp_remote_retrieve_response_code( $userinfo_response ) ) {
- $userinfo_body = wp_remote_retrieve_body( $userinfo_response );
- $userinfo_data = json_decode( (string) $userinfo_body, true );
- if ( is_array( $userinfo_data ) && ! empty( $userinfo_data['email'] ) ) {
- $connected_email = sanitize_email( (string) $userinfo_data['email'] );
- }
- }
- }
-
- $options = get_option( $this->plugin->get_option_key(), [] );
- if ( ! is_array( $options ) ) {
- $options = [];
- }
-
- $options['google_oauth_refresh_token'] = $refresh_token;
- $options['google_oauth_connected_email'] = $connected_email;
- $options['google_oauth_connected_at'] = time();
- update_option( $this->plugin->get_option_key(), $options );
-
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'success',
- 'groq_ai_google_oauth_message' => __( 'Google succesvol verbonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- public function handle_google_oauth_disconnect() {
- if ( ! current_user_can( 'manage_options' ) ) {
- wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
- }
-
- check_admin_referer( 'groq_ai_google_oauth_disconnect' );
-
- $options = get_option( $this->plugin->get_option_key(), [] );
- if ( ! is_array( $options ) ) {
- $options = [];
- }
-
- $options['google_oauth_refresh_token'] = '';
- $options['google_oauth_connected_email'] = '';
- $options['google_oauth_connected_at'] = 0;
- update_option( $this->plugin->get_option_key(), $options );
-
- $url = add_query_arg(
- [
- 'groq_ai_google_oauth' => 'success',
- 'groq_ai_google_oauth_message' => __( 'Google koppeling verwijderd.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
- $this->get_settings_page_url()
- );
- wp_safe_redirect( $url );
- exit;
- }
-
- public function render_modules_page() {
- if ( ! current_user_can( 'manage_options' ) ) {
- return;
- }
-
- ?>
-
- plugin->get_settings();
- ?>
-
- plugin );
- $logs_table->prepare_items();
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
- —
-
-
-
- —
-
-
-
- —
-
-
-
-
-
- —
-
-
-
- —
-
-
-
- —
-
-
-
-
-
-
-
-
- plugin->get_settings();
- $providers = $this->provider_manager->get_providers();
- ?>
-
-
- plugin->get_settings();
- $current_model = $settings['model'];
- $current_provider = $settings['provider'];
- ?>
-
- plugin->get_settings();
- /** @var Groq_AI_Provider_Interface $provider */
- $provider = $args['provider'];
- $field = $provider->get_option_key();
- $provider_key = $provider->get_key();
- ?>
-
-
-
- get_label() )
- );
- ?>
-
-
- plugin->get_settings();
- ?>
-
-
- plugin->get_settings();
- ?>
-
-
- plugin->get_settings();
- $value = isset( $settings['max_output_tokens'] ) ? absint( $settings['max_output_tokens'] ) : 2048;
- $value = max( 128, min( 8192, $value ) );
- ?>
-
-
-
-
- plugin->get_settings();
- $value = isset( $settings['term_bottom_description_meta_key'] ) ? (string) $settings['term_bottom_description_meta_key'] : '';
- ?>
-
-
-
-
- plugin->get_settings();
- $values = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields();
- $definitions = $this->plugin->get_context_field_definitions();
- ?>
-
- $definition ) :
- if ( 'attributes' === $key ) {
- continue;
- }
- $checked = ! empty( $values[ $key ] );
- ?>
-
-
-
-
-
-
-
-
- plugin->get_settings();
$values = isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] )
@@ -1976,33 +860,59 @@ class Groq_AI_Product_Text_Settings_Page {
);
}
+ $bulk_taxonomy = '';
+ $bulk_allow_regen = false;
+ $bulk_strings = [];
+
if ( 'settings_page_groq-ai-product-text-categories' === $hook ) {
+ $bulk_taxonomy = 'product_cat';
+ $bulk_strings = [
+ 'statusIdle' => __( 'Bulk gestart. AI werkt de geselecteerde categorieën bij…', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusProgress' => __( 'Categorie %1$s van %2$s: %3$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusDone' => __( 'Klaar! %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusStopped' => __( 'Bulk generatie gestopt. %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusEmpty' => __( 'Geen categorieën zonder omschrijving gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'logSuccess' => __( '%1$s gevuld (%2$d woorden).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'logError' => __( '%1$s mislukt: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'confirmStop' => __( 'Weet je zeker dat je wilt stoppen? De huidige categorie kan onafgemaakt blijven.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ ];
+ } elseif ( 'settings_page_groq-ai-product-text-brands' === $hook ) {
+ $detected_taxonomy = $this->detect_brand_taxonomy();
+ if ( '' !== $detected_taxonomy ) {
+ $bulk_taxonomy = $detected_taxonomy;
+ $bulk_allow_regen = true;
+ $bulk_strings = [
+ 'statusIdle' => __( 'Bulk gestart. AI werkt de geselecteerde merken bij…', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusProgress' => __( 'Merk %1$s van %2$s: %3$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusDone' => __( 'Klaar! %d merken bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusStopped' => __( 'Bulk generatie gestopt. %d merken bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'statusEmpty' => __( 'Geen merken zonder omschrijving gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'logSuccess' => __( '%1$s gevuld (%2$d woorden).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'logError' => __( '%1$s mislukt: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'confirmStop' => __( 'Weet je zeker dat je wilt stoppen? Het huidige merk kan onafgemaakt blijven.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'confirmRegenerate' => __( 'Wil je %s opnieuw laten schrijven?', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'regenerateProgress' => __( '%s wordt opnieuw geschreven…', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'regenerateDone' => __( '%s is bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'regenerateError' => __( 'Kon %1$s niet bijwerken: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'regenerateBlocked' => __( 'Wacht tot de bulk generatie klaar is voordat je een merk opnieuw genereert.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ ];
+ }
+ }
+
+ if ( '' !== $bulk_taxonomy ) {
wp_enqueue_script(
- 'groq-ai-category-bulk',
- plugins_url( 'assets/js/category-bulk.js', GROQ_AI_PRODUCT_TEXT_FILE ),
+ 'groq-ai-term-bulk',
+ plugins_url( 'assets/js/term-bulk.js', GROQ_AI_PRODUCT_TEXT_FILE ),
[],
GROQ_AI_PRODUCT_TEXT_VERSION,
true
);
- wp_localize_script(
- 'groq-ai-category-bulk',
- 'GroqAICategoryBulk',
+ $this->localize_term_bulk_script(
+ $bulk_taxonomy,
[
- 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
- 'nonce' => wp_create_nonce( 'groq_ai_bulk_generate_terms' ),
- 'taxonomy' => 'product_cat',
- 'terms' => $this->get_terms_without_description_payload( 'product_cat' ),
- 'strings' => [
- 'statusIdle' => __( 'Klik op de knop om voor alle lege categorieën automatisch teksten te genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'statusProgress' => __( 'Bezig met categorie %1$s van %2$s: %3$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'statusDone' => __( 'Klaar! %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'statusStopped' => __( 'Bulk generatie gestopt. %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'statusEmpty' => __( 'Geen categorieën zonder omschrijving gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'logSuccess' => __( '%1$s gevuld (%2$d woorden).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'logError' => __( '%1$s mislukt: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- 'confirmStop' => __( 'Weet je zeker dat je wilt stoppen? De huidige categorie kan onafgemaakt blijven.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
- ],
+ 'allowRegenerate' => $bulk_allow_regen,
+ 'strings' => $bulk_strings,
]
);
}
diff --git a/includes/Core/class-groq-ai-ajax-controller.php b/includes/Core/class-groq-ai-ajax-controller.php
index cfbf190..110c298 100644
--- a/includes/Core/class-groq-ai-ajax-controller.php
+++ b/includes/Core/class-groq-ai-ajax-controller.php
@@ -42,6 +42,7 @@ class Groq_AI_Ajax_Controller {
[
'include_top_products' => $include_top_products,
'top_products_limit' => $top_products_limit,
+ 'origin' => 'term_manual',
]
);
@@ -92,6 +93,9 @@ class Groq_AI_Ajax_Controller {
$term
);
+ $options['origin'] = $force ? 'term_force_regenerate' : 'term_bulk_auto';
+ $options['force'] = $force;
+
$result = $this->run_term_generation( $term, $this->get_term_prompt_text( $term ), $options );
if ( is_wp_error( $result ) ) {
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
@@ -119,18 +123,26 @@ class Groq_AI_Ajax_Controller {
return new WP_Error( 'groq_ai_invalid_term', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
+ $taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
+ $term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
+
$options = wp_parse_args(
$options,
[
'include_top_products' => true,
'top_products_limit' => 10,
+ 'origin' => 'term_manual',
+ 'force' => false,
]
);
+ $origin = isset( $options['origin'] ) ? sanitize_key( (string) $options['origin'] ) : 'term_manual';
+ $force_run = ! empty( $options['force'] );
$include_top_products = ! empty( $options['include_top_products'] );
$top_products_limit = isset( $options['top_products_limit'] ) ? absint( $options['top_products_limit'] ) : 10;
$top_products_limit = max( 1, min( 25, $top_products_limit ) );
+ $logger = $this->plugin->get_generation_logger();
$settings = $this->plugin->get_settings();
$provider_manager = $this->plugin->get_provider_manager();
$provider_key = $settings['provider'];
@@ -162,6 +174,19 @@ class Groq_AI_Ajax_Controller {
? $prompt_builder->prepend_term_context_to_prompt( $prompt, $context_block )
: $prompt_builder->prepend_context_to_prompt( $prompt, $context_block );
+ $usage_meta = [
+ 'term_context' => [
+ 'taxonomy' => $taxonomy,
+ 'term_id' => $term_id,
+ 'origin' => $origin,
+ ],
+ 'term_options' => [
+ 'include_top_products' => $include_top_products,
+ 'top_products_limit' => $top_products_limit,
+ 'force' => $force_run,
+ ],
+ ];
+
$response_format = null;
$use_response_format = $this->plugin->should_use_response_format( $provider, $settings );
if ( $use_response_format && method_exists( $prompt_builder, 'get_term_response_format_definition' ) ) {
@@ -187,20 +212,71 @@ class Groq_AI_Ajax_Controller {
);
if ( is_wp_error( $result ) ) {
+ if ( $logger ) {
+ $logger->log_generation_event(
+ [
+ 'provider' => $provider_key,
+ 'model' => $model,
+ 'prompt' => $final_prompt,
+ 'response' => '',
+ 'usage' => $usage_meta,
+ 'status' => 'error',
+ 'error_message' => $result->get_error_message(),
+ 'post_id' => 0,
+ ]
+ );
+ }
return $result;
}
$response_text = $this->extract_content_text( $result );
+ $response_usage = is_array( $result ) && isset( $result['usage'] ) ? $result['usage'] : [];
+ if ( ! is_array( $response_usage ) ) {
+ $response_usage = [];
+ }
+ $response_usage['term_context'] = $usage_meta['term_context'];
+ $response_usage['term_options'] = $usage_meta['term_options'];
$parsed = null;
if ( method_exists( $prompt_builder, 'parse_term_structured_response' ) ) {
$parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings );
}
+ if ( is_wp_error( $parsed ) ) {
+ if ( $logger ) {
+ $logger->log_generation_event(
+ [
+ 'provider' => $provider_key,
+ 'model' => $model,
+ 'prompt' => $final_prompt,
+ 'response' => $response_text,
+ 'usage' => $response_usage,
+ 'status' => 'error',
+ 'error_message' => $parsed->get_error_message(),
+ 'post_id' => 0,
+ ]
+ );
+ }
+ return $parsed;
+ }
if ( ! is_array( $parsed ) ) {
$parsed = [
'description' => trim( (string) $response_text ),
];
}
+ if ( $logger ) {
+ $logger->log_generation_event(
+ [
+ 'provider' => $provider_key,
+ 'model' => $model,
+ 'prompt' => $final_prompt,
+ 'response' => $response_text,
+ 'usage' => $response_usage,
+ 'status' => 'success',
+ 'post_id' => 0,
+ ]
+ );
+ }
+
return [
'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ),
'bottom_description' => isset( $parsed['bottom_description'] ) ? $parsed['bottom_description'] : '',