From 58a9b37ccf3ee96a6a15bacef4c5a6351b44167b Mon Sep 17 00:00:00 2001 From: Roberto Guagliardo Date: Fri, 23 Jan 2026 18:55:52 +0000 Subject: [PATCH] Add bulk term generation functionality and enhance logging - Introduced a new JavaScript file for handling bulk term generation in the admin interface. - Implemented AJAX requests for generating terms and handling responses with appropriate logging. - Enhanced the Groq_AI_Ajax_Controller to support new options for term generation, including origin and force parameters. - Improved error handling and logging for term generation events. - Updated the user interface to reflect the status of term generation and provide feedback to the user. --- assets/css/settings.css | 17 + assets/js/category-bulk.js | 212 --- assets/js/term-bulk.js | 288 ++++ groq-ai-product-text.php | 2 +- .../Admin/class-groq-ai-settings-page.php | 1518 +++-------------- .../Core/class-groq-ai-ajax-controller.php | 76 + 6 files changed, 596 insertions(+), 1517 deletions(-) delete mode 100644 assets/js/category-bulk.js create mode 100644 assets/js/term-bulk.js 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; ?> - + - + - + @@ -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 ); ?> +

      - name ); ?> + slug ); ?> - - - -
      @@ -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; ?> - + - + - + + @@ -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(); - ?> -
      -

      -

      -
      - - search_box( __( 'Zoek logs', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'groq-ai-logs' ); ?> - display(); ?> - -
      - - - - 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'] : '',
      - name ); ?> + slug ); ?> + +