diff --git a/assets/css/settings.css b/assets/css/settings.css index 440c472..18e409b 100644 --- a/assets/css/settings.css +++ b/assets/css/settings.css @@ -29,3 +29,63 @@ align-items: center; margin-top: 8px; } + +.groq-ai-bulk-panel { + margin: 16px 0; + padding: 16px; + background: #fff; + border: 1px solid #dcdcde; +} + +.groq-ai-bulk-panel .description { + margin-top: 8px; +} + +#groq-ai-bulk-status { + margin-top: 8px; +} + +#groq-ai-bulk-status[data-status='error'] { + color: #b32d2e; +} + +#groq-ai-bulk-status[data-status='success'] { + color: #008a20; +} + +.groq-ai-bulk-log { + margin: 12px 0 0; + padding-left: 18px; + max-height: 220px; + overflow-y: auto; + font-size: 13px; +} + +.groq-ai-bulk-log li { + margin-bottom: 4px; +} + +.groq-ai-bulk-log li[data-status='error'] { + color: #b32d2e; +} + +.groq-ai-bulk-log li[data-status='success'] { + color: #008a20; +} + +.groq-ai-term-row.groq-ai-term-missing td { + background: #fff8e5; +} + +.groq-ai-term-row.groq-ai-term-updated td { + animation: groqAiTermPulse 1.8s ease-out 1; +} + +@keyframes groqAiTermPulse { + from { + background-color: #e3f8eb; + } + to { + background-color: transparent; + } +} diff --git a/assets/js/category-bulk.js b/assets/js/category-bulk.js new file mode 100644 index 0000000..f717552 --- /dev/null +++ b/assets/js/category-bulk.js @@ -0,0 +1,212 @@ +(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/groq-ai-product-text.php b/groq-ai-product-text.php index d922ba0..da817d9 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.5.0 + * Version: 1.6.0 * 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 164e4d8..fb58200 100644 --- a/includes/Admin/class-groq-ai-settings-page.php +++ b/includes/Admin/class-groq-ai-settings-page.php @@ -173,10 +173,44 @@ class Groq_AI_Product_Text_Settings_Page { 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; + } + } ?>

+
+ +

+ +

+

+ + +

+
+
    + +

    + +
    @@ -193,16 +227,24 @@ class Groq_AI_Product_Text_Settings_Page { get_term_page_url( 'product_cat', $term->term_id ); - $words = $this->count_words( $term->description ); + $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 ) { + $row_classes[] = 'groq-ai-term-missing'; + } ?> - + - + @@ -334,10 +376,7 @@ class Groq_AI_Product_Text_Settings_Page { $rankmath_description = (string) get_term_meta( $term_id, $rankmath_keys['description'], true ); $rankmath_focus_keywords = (string) get_term_meta( $term_id, $rankmath_keys['focus_keyword'], true ); } - $default_prompt = (string) $meta_prompt; - if ( '' === trim( $default_prompt ) ) { - $default_prompt = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en

    -tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); - } + $default_prompt = $this->get_term_prompt_text( $term, $meta_prompt ); ?>

    @@ -496,6 +535,57 @@ class Groq_AI_Product_Text_Settings_Page { ]; } + private function get_term_prompt_text( $term, $custom_prompt = null ) { + $prompt = ( null !== $custom_prompt ) ? $custom_prompt : ''; + + if ( null === $custom_prompt && $term && isset( $term->term_id ) ) { + $prompt = get_term_meta( $term->term_id, 'groq_ai_term_custom_prompt', true ); + } + + $prompt = trim( (string) $prompt ); + if ( '' !== $prompt ) { + return $prompt; + } + + $default_prompt = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en

    -tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + + return apply_filters( 'groq_ai_default_term_prompt', $default_prompt, $term ); + } + + private function get_terms_without_description_payload( $taxonomy ) { + $terms = get_terms( + [ + 'taxonomy' => $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 ) ); @@ -1886,6 +1976,37 @@ class Groq_AI_Product_Text_Settings_Page { ); } + if ( 'settings_page_groq-ai-product-text-categories' === $hook ) { + wp_enqueue_script( + 'groq-ai-category-bulk', + plugins_url( 'assets/js/category-bulk.js', GROQ_AI_PRODUCT_TEXT_FILE ), + [], + GROQ_AI_PRODUCT_TEXT_VERSION, + true + ); + + wp_localize_script( + 'groq-ai-category-bulk', + 'GroqAICategoryBulk', + [ + '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 ), + ], + ] + ); + } + $current_settings = $this->plugin->get_settings(); $data = [ 'optionKey' => $this->plugin->get_option_key(), diff --git a/includes/Core/class-groq-ai-ajax-controller.php b/includes/Core/class-groq-ai-ajax-controller.php index dc442b4..cfbf190 100644 --- a/includes/Core/class-groq-ai-ajax-controller.php +++ b/includes/Core/class-groq-ai-ajax-controller.php @@ -10,6 +10,7 @@ class Groq_AI_Ajax_Controller { add_action( 'wp_ajax_groq_ai_generate_text', [ $this, 'handle_generate_text' ] ); add_action( 'wp_ajax_groq_ai_refresh_models', [ $this, 'handle_refresh_models' ] ); add_action( 'wp_ajax_groq_ai_generate_term_text', [ $this, 'handle_generate_term_text' ] ); + add_action( 'wp_ajax_groq_ai_bulk_generate_terms', [ $this, 'handle_bulk_generate_terms_request' ] ); } public function handle_generate_term_text() { @@ -35,6 +36,101 @@ class Groq_AI_Ajax_Controller { wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 ); } + $result = $this->run_term_generation( + $term, + $prompt, + [ + 'include_top_products' => $include_top_products, + 'top_products_limit' => $top_products_limit, + ] + ); + + if ( is_wp_error( $result ) ) { + wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); + } + + wp_send_json_success( $result ); + } + + public function handle_bulk_generate_terms_request() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 ); + } + + check_ajax_referer( 'groq_ai_bulk_generate_terms', 'nonce' ); + + $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : ''; + $term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0; + $force = ! empty( $_POST['force'] ); + + if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) { + wp_send_json_error( [ 'message' => __( 'Taxonomie en term_id zijn verplicht.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 ); + } + + $term = get_term( $term_id, $taxonomy ); + if ( ! $term || is_wp_error( $term ) ) { + wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 ); + } + + $current_description = isset( $term->description ) ? trim( wp_strip_all_tags( (string) $term->description ) ) : ''; + if ( '' !== $current_description && ! $force ) { + wp_send_json_error( + [ + 'message' => __( 'Categorie heeft al een omschrijving.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), + 'code' => 'groq_ai_term_has_description', + ], + 400 + ); + } + + $options = apply_filters( + 'groq_ai_bulk_term_generation_options', + [ + 'include_top_products' => true, + 'top_products_limit' => 10, + ], + $term + ); + + $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 ); + } + + $settings = $this->plugin->get_settings(); + $saved = $this->save_term_generation_result( $term, $result, $settings ); + + if ( is_wp_error( $saved ) ) { + wp_send_json_error( [ 'message' => $saved->get_error_message() ], 500 ); + } + + wp_send_json_success( + [ + 'term_id' => $term_id, + 'name' => isset( $term->name ) ? (string) $term->name : '', + 'words' => isset( $saved['words'] ) ? absint( $saved['words'] ) : 0, + 'count' => isset( $term->count ) ? absint( $term->count ) : 0, + ] + ); + } + + private function run_term_generation( $term, $prompt, $options = [] ) { + if ( ! $term || ! is_object( $term ) ) { + return new WP_Error( 'groq_ai_invalid_term', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + } + + $options = wp_parse_args( + $options, + [ + 'include_top_products' => true, + 'top_products_limit' => 10, + ] + ); + + $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 ) ); + $settings = $this->plugin->get_settings(); $provider_manager = $this->plugin->get_provider_manager(); $provider_key = $settings['provider']; @@ -51,14 +147,13 @@ class Groq_AI_Ajax_Controller { ? $prompt_builder->build_term_system_prompt( $settings, $conversation_id, $term ) : $prompt_builder->build_system_prompt( $settings, $conversation_id ); - $model = $this->plugin->get_selected_model( $provider, $settings ); $context_block = ''; if ( method_exists( $prompt_builder, 'build_term_context_block' ) ) { $context_block = $prompt_builder->build_term_context_block( $term, [ 'include_top_products' => $include_top_products, - 'top_products_limit' => $top_products_limit, + 'top_products_limit' => $top_products_limit, ], $settings ); @@ -78,6 +173,7 @@ class Groq_AI_Ajax_Controller { $final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings ); } + $model = $this->plugin->get_selected_model( $provider, $settings ); $result = $provider->generate_content( [ 'prompt' => $final_prompt, @@ -91,11 +187,11 @@ class Groq_AI_Ajax_Controller { ); if ( is_wp_error( $result ) ) { - wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); + return $result; } $response_text = $this->extract_content_text( $result ); - $parsed = null; + $parsed = null; if ( method_exists( $prompt_builder, 'parse_term_structured_response' ) ) { $parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings ); } @@ -105,17 +201,127 @@ class Groq_AI_Ajax_Controller { ]; } - wp_send_json_success( + return [ + 'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ), + 'bottom_description' => isset( $parsed['bottom_description'] ) ? $parsed['bottom_description'] : '', + 'meta_title' => isset( $parsed['meta_title'] ) ? $parsed['meta_title'] : '', + 'meta_description' => isset( $parsed['meta_description'] ) ? $parsed['meta_description'] : '', + 'focus_keywords' => isset( $parsed['focus_keywords'] ) ? $parsed['focus_keywords'] : '', + 'description' => isset( $parsed['description'] ) ? $parsed['description'] : ( isset( $parsed['top_description'] ) ? $parsed['top_description'] : '' ), + 'raw' => $response_text, + ]; + } + + private function save_term_generation_result( $term, $result, $settings ) { + $term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0; + $taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : ''; + + if ( ! $term_id || '' === $taxonomy ) { + return new WP_Error( 'groq_ai_invalid_term', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + } + + $top_description = ''; + if ( isset( $result['top_description'] ) && '' !== trim( (string) $result['top_description'] ) ) { + $top_description = (string) $result['top_description']; + } elseif ( isset( $result['description'] ) ) { + $top_description = (string) $result['description']; + } + + if ( '' === trim( wp_strip_all_tags( $top_description ) ) ) { + return new WP_Error( 'groq_ai_missing_description', __( 'De AI gaf geen omschrijving terug.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + } + + $update = wp_update_term( + $term_id, + $taxonomy, [ - 'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ), - 'bottom_description' => isset( $parsed['bottom_description'] ) ? $parsed['bottom_description'] : '', - 'meta_title' => isset( $parsed['meta_title'] ) ? $parsed['meta_title'] : '', - 'meta_description' => isset( $parsed['meta_description'] ) ? $parsed['meta_description'] : '', - 'focus_keywords' => isset( $parsed['focus_keywords'] ) ? $parsed['focus_keywords'] : '', - 'description' => isset( $parsed['description'] ) ? $parsed['description'] : ( isset( $parsed['top_description'] ) ? $parsed['top_description'] : '' ), - 'raw' => $response_text, + 'description' => wp_kses_post( $top_description ), ] ); + + if ( is_wp_error( $update ) ) { + return $update; + } + + $bottom_key = $this->get_bottom_meta_key( $term, $settings ); + if ( '' !== $bottom_key ) { + $bottom_description = isset( $result['bottom_description'] ) ? (string) $result['bottom_description'] : ''; + update_term_meta( $term_id, $bottom_key, wp_kses_post( $bottom_description ) ); + } + + if ( $this->plugin->is_module_enabled( 'rankmath', $settings ) ) { + $rankmath_keys = $this->get_rankmath_term_meta_keys( $term, $settings ); + update_term_meta( $term_id, $rankmath_keys['title'], sanitize_text_field( isset( $result['meta_title'] ) ? $result['meta_title'] : '' ) ); + update_term_meta( $term_id, $rankmath_keys['description'], sanitize_text_field( isset( $result['meta_description'] ) ? $result['meta_description'] : '' ) ); + update_term_meta( $term_id, $rankmath_keys['focus_keyword'], sanitize_text_field( isset( $result['focus_keywords'] ) ? $result['focus_keywords'] : '' ) ); + } + + return [ + 'words' => $this->count_words( $top_description ), + ]; + } + + private function get_bottom_meta_key( $term, $settings ) { + $default_key = ''; + if ( is_array( $settings ) && isset( $settings['term_bottom_description_meta_key'] ) ) { + $default_key = sanitize_key( (string) $settings['term_bottom_description_meta_key'] ); + } + + $key = apply_filters( 'groq_ai_term_bottom_description_meta_key', $default_key, $term, $settings ); + $key = sanitize_key( (string) $key ); + + return '' !== $key ? $key : 'groq_ai_term_bottom_description'; + } + + private function get_rankmath_term_meta_keys( $term, $settings ) { + $defaults = [ + 'title' => 'rank_math_title', + 'description' => 'rank_math_description', + 'focus_keyword' => 'rank_math_focus_keyword', + ]; + + $keys = apply_filters( 'groq_ai_rankmath_term_meta_keys', $defaults, $term, $settings ); + if ( ! is_array( $keys ) ) { + $keys = $defaults; + } + + return [ + 'title' => isset( $keys['title'] ) ? sanitize_key( (string) $keys['title'] ) : 'rank_math_title', + 'description' => isset( $keys['description'] ) ? sanitize_key( (string) $keys['description'] ) : 'rank_math_description', + 'focus_keyword' => isset( $keys['focus_keyword'] ) ? sanitize_key( (string) $keys['focus_keyword'] ) : 'rank_math_focus_keyword', + ]; + } + + private function get_term_prompt_text( $term ) { + $prompt = ''; + + if ( $term && isset( $term->term_id ) ) { + $prompt = (string) get_term_meta( $term->term_id, 'groq_ai_term_custom_prompt', true ); + } + + $prompt = trim( $prompt ); + if ( '' !== $prompt ) { + return $prompt; + } + + $default_prompt = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en

    -tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + + return apply_filters( 'groq_ai_default_term_prompt', $default_prompt, $term ); + } + + private function count_words( $text ) { + $text = wp_strip_all_tags( (string) $text ); + $text = trim( preg_replace( '/\s+/u', ' ', $text ) ); + + if ( '' === $text ) { + return 0; + } + + if ( preg_match_all( '/\pL[\pL\pN\']*/u', $text, $matches ) ) { + return count( $matches[0] ); + } + + return str_word_count( $text ); } public function handle_generate_text() {

    name ); ?> slug ); ?> + + + +