2 Commits

4 changed files with 886 additions and 38 deletions

View File

@@ -33,7 +33,7 @@ class SitiWebUpdater {
require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php';
} }
$this->plugin = get_plugin_data( $this->file ); $this->plugin = get_plugin_data( $this->file, false, false );
$this->basename = plugin_basename( $this->file ); $this->basename = plugin_basename( $this->file );
$this->active = is_plugin_active( $this->basename ); $this->active = is_plugin_active( $this->basename );
} }

View File

@@ -2,7 +2,7 @@
/** /**
* Plugin Name: SitiAI Product Teksten * Plugin Name: SitiAI Product Teksten
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce. * Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
* Version: 1.6.2 * Version: 1.6.3
* Author: SitiAI * Author: SitiAI
* Text Domain: siti-ai-product-content-generator * Text Domain: siti-ai-product-content-generator
* Domain Path: /languages * Domain Path: /languages
@@ -108,7 +108,6 @@ final class Groq_AI_Product_Text_Plugin {
$this->settings_page = new Groq_AI_Product_Text_Settings_Page( $this, $this->get_provider_manager() ); $this->settings_page = new Groq_AI_Product_Text_Settings_Page( $this, $this->get_provider_manager() );
$this->product_ui = new Groq_AI_Product_Text_Product_UI( $this ); $this->product_ui = new Groq_AI_Product_Text_Product_UI( $this );
add_action( 'plugins_loaded', [ $this, 'maybe_load_textdomain_early' ], 0 );
add_action( 'init', [ $this, 'load_textdomain' ] ); add_action( 'init', [ $this, 'load_textdomain' ] );
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] ); add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] ); add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
@@ -131,14 +130,6 @@ final class Groq_AI_Product_Text_Plugin {
$this->textdomain_loaded = true; $this->textdomain_loaded = true;
} }
public function maybe_load_textdomain_early() {
if ( did_action( 'init' ) ) {
return;
}
$this->load_textdomain();
}
private function register_services() { private function register_services() {
$this->container = new Groq_AI_Service_Container(); $this->container = new Groq_AI_Service_Container();

View File

@@ -136,28 +136,15 @@ class Groq_AI_Logs_Table extends WP_List_Table {
protected function column_created_at( $item ) { protected function column_created_at( $item ) {
$date = esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $item['created_at'] ) ); $date = esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $item['created_at'] ) );
$usage = $this->get_usage_meta( $item ); $url = add_query_arg(
$payload = [ [
'created_at' => $item['created_at'], 'page' => 'groq-ai-product-text-log',
'user' => $this->column_default( $item, 'user_id' ), 'log_id' => isset( $item['id'] ) ? (int) $item['id'] : 0,
'post_title' => $item['post_title'], ],
'provider' => $item['provider'], admin_url( 'options-general.php' )
'model' => $item['model'],
'status' => $item['status'],
'tokens_prompt' => isset( $item['tokens_prompt'] ) ? (int) $item['tokens_prompt'] : null,
'tokens_completion' => isset( $item['tokens_completion'] ) ? (int) $item['tokens_completion'] : null,
'tokens_total' => isset( $item['tokens_total'] ) ? (int) $item['tokens_total'] : null,
'prompt' => $item['prompt'],
'response' => $item['response'],
'error_message' => $item['error_message'],
'image_context' => isset( $usage['image_context'] ) ? $usage['image_context'] : null,
];
$encoded = esc_attr( wp_json_encode( $payload ) );
return sprintf(
'<a href="#" class="groq-ai-log-row" data-groq-log="%s">%s</a>',
$encoded,
$date
); );
return sprintf( '<a href="%s">%s</a>', esc_url( $url ), $date );
} }
private function get_usage_meta( $item ) { private function get_usage_meta( $item ) {

View File

@@ -84,6 +84,311 @@ class Groq_AI_Product_Text_Settings_Page {
[ $this, 'render_prompt_settings_page' ] [ $this, 'render_prompt_settings_page' ]
); );
add_submenu_page(
'options-general.php',
__( 'Siti AI Log detail', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
__( 'Siti AI Log detail', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'manage_options',
'groq-ai-product-text-log',
[ $this, 'render_log_detail_page' ]
);
}
private function get_page_url( $slug = 'groq-ai-product-text', $args = [] ) {
$slug = sanitize_key( (string) $slug );
$url = add_query_arg(
[
'page' => $slug,
],
admin_url( 'options-general.php' )
);
if ( ! empty( $args ) ) {
$url = add_query_arg( $args, $url );
}
return $url;
}
private function get_request_redirect_url( $field, $page_slug = 'groq-ai-product-text' ) {
$default = $this->get_page_url( $page_slug );
$value = isset( $_REQUEST[ $field ] ) ? wp_unslash( $_REQUEST[ $field ] ) : '';
if ( '' === $value ) {
return $default;
}
return wp_validate_redirect( $value, $default );
}
private function parse_oauth_state( $value ) {
$value = (string) $value;
if ( '' === $value ) {
return [];
}
$decoded = base64_decode( $value, true );
if ( ! is_string( $decoded ) || '' === $decoded ) {
return [];
}
$data = json_decode( $decoded, true );
return is_array( $data ) ? $data : [];
}
private function redirect_with_google_notice( $type, $message = '', $redirect = null, $status = 'success' ) {
$redirect = $redirect ? $redirect : $this->get_page_url();
$args = [
'groq_ai_google_notice' => sanitize_key( (string) $type ),
'groq_ai_google_notice_status' => sanitize_key( (string) $status ),
];
if ( '' !== $message ) {
$args['groq_ai_google_notice_message'] = rawurlencode( (string) $message );
}
wp_safe_redirect( add_query_arg( $args, $redirect ) );
exit;
}
private function redirect_with_term_notice( $taxonomy, $term_id, $type, $message = '', $status = 'success' ) {
$url = ( $taxonomy && $term_id ) ? $this->get_term_page_url( $taxonomy, $term_id ) : $this->get_page_url( 'groq-ai-product-text-categories' );
$args = [
'groq_ai_term_notice' => sanitize_key( (string) $type ),
'groq_ai_term_status' => sanitize_key( (string) $status ),
];
if ( '' !== $message ) {
$args['groq_ai_term_notice_message'] = rawurlencode( (string) $message );
}
wp_safe_redirect( add_query_arg( $args, $url ) );
exit;
}
private function get_google_redirect_uri() {
return add_query_arg(
'action',
'groq_ai_google_oauth_callback',
admin_url( 'admin-post.php' )
);
}
private function update_settings_partial( array $updates ) {
$option_key = $this->plugin->get_option_key();
$current = get_option( $option_key, [] );
if ( ! is_array( $current ) ) {
$current = [];
}
foreach ( $updates as $key => $value ) {
$current[ $key ] = $value;
}
update_option( $option_key, $current );
}
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$option_key = $this->plugin->get_option_key();
$settings = $this->plugin->get_settings();
$providers = $this->provider_manager->get_providers();
$current_page = $this->get_page_url();
$prompt_url = $this->get_page_url( 'groq-ai-product-text-prompts' );
$modules_url = $this->get_page_url( 'groq-ai-product-text-modules' );
$logs_url = $this->get_page_url( 'groq-ai-product-text-logs' );
$categories_url = $this->get_page_url( 'groq-ai-product-text-categories' );
$brands_url = $this->get_page_url( 'groq-ai-product-text-brands' );
$prompt_preview = $this->plugin->build_prompt_template_preview( $settings );
$google_notice = isset( $_GET['groq_ai_google_notice'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice'] ) ) : '';
$google_status = isset( $_GET['groq_ai_google_notice_status'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_google_notice_status'] ) ) : '';
$google_message = '';
if ( isset( $_GET['groq_ai_google_notice_message'] ) ) {
$google_message = sanitize_text_field( rawurldecode( wp_unslash( $_GET['groq_ai_google_notice_message'] ) ) );
}
$google_connected = ! empty( $settings['google_oauth_refresh_token'] );
$google_connected_email = isset( $settings['google_oauth_connected_email'] ) ? (string) $settings['google_oauth_connected_email'] : '';
$google_connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0;
?>
<div class="wrap">
<h1><?php esc_html_e( 'Siti AI instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description">
<?php esc_html_e( 'Kies je AI-aanbieder, beheer API-sleutels en koppel optioneel Google Search Console/Analytics voor extra context.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
</p>
<p style="margin:16px 0; display:flex; flex-wrap:wrap; gap:8px;">
<a class="button" href="<?php echo esc_url( $prompt_url ); ?>"><?php esc_html_e( 'Prompt instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
<a class="button" href="<?php echo esc_url( $modules_url ); ?>"><?php esc_html_e( 'Modules', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
<a class="button" href="<?php echo esc_url( $logs_url ); ?>"><?php esc_html_e( 'AI-logboek', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
<a class="button" href="<?php echo esc_url( $categories_url ); ?>"><?php esc_html_e( 'Categorie teksten', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
<a class="button" href="<?php echo esc_url( $brands_url ); ?>"><?php esc_html_e( 'Merk teksten', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
</p>
<?php settings_errors( $option_key ); ?>
<?php if ( $google_notice ) :
$class = ( 'error' === $google_status ) ? 'notice-error' : 'notice-success';
$google_message = '' !== $google_message ? $google_message : ( 'connected' === $google_notice ? __( 'Google OAuth is verbonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : ( 'disconnected' === $google_notice ? __( 'Google OAuth is ontkoppeld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Google test afgerond.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ) );
?>
<div class="notice <?php echo esc_attr( $class ); ?>"><p><?php echo esc_html( $google_message ); ?></p></div>
<?php endif; ?>
<div style="margin:16px 0; padding:16px; background:#fff; border:1px solid #dcdcde;">
<strong><?php esc_html_e( 'Huidige promptcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></strong>
<pre style="background:#f6f7f7; padding:12px; overflow:auto; margin-top:8px; white-space:pre-wrap;"><?php echo esc_html( $prompt_preview ); ?></pre>
</div>
<form method="post" action="options.php">
<?php settings_fields( $option_key ); ?>
<h2><?php esc_html_e( 'AI-aanbieder', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="groq-ai-provider"><?php esc_html_e( 'Aanbieder', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<select id="groq-ai-provider" name="<?php echo esc_attr( $option_key ); ?>[provider]">
<?php foreach ( $providers as $provider ) :
$provider_key = $provider->get_key();
?>
<option value="<?php echo esc_attr( $provider_key ); ?>" <?php selected( $settings['provider'], $provider_key ); ?>><?php echo esc_html( $provider->get_label() ); ?></option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Selecteer welke aanbieder de product- en termteksten schrijft.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="groq-ai-model-select"><?php esc_html_e( 'Model', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<div class="groq-ai-model-field">
<select id="groq-ai-model-select" name="<?php echo esc_attr( $option_key ); ?>[model]" data-current-model="<?php echo esc_attr( isset( $settings['model'] ) ? $settings['model'] : '' ); ?>">
<option value="" selected="selected"><?php esc_html_e( 'Selecteer eerst een aanbieder', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></option>
</select>
</div>
<button type="button" class="button" id="groq-ai-refresh-models"><?php esc_html_e( 'Live modellen ophalen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
<p id="groq-ai-refresh-models-status" class="description"></p>
</td>
</tr>
<?php foreach ( $providers as $provider ) :
$provider_key = $provider->get_key();
$option_field = $provider->get_option_key();
$value = isset( $settings[ $option_field ] ) ? (string) $settings[ $option_field ] : '';
?>
<tr id="groq_ai_api_key_<?php echo esc_attr( $provider_key ); ?>" data-provider-row="<?php echo esc_attr( $provider_key ); ?>">
<th scope="row"><label for="groq-ai-api-<?php echo esc_attr( $provider_key ); ?>"><?php esc_html_e( 'API-sleutel', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<input type="password" id="groq-ai-api-<?php echo esc_attr( $provider_key ); ?>" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[<?php echo esc_attr( $option_field ); ?>]" value="<?php echo esc_attr( $value ); ?>" autocomplete="off" />
<p class="description"><?php printf( esc_html__( 'Voer de API-sleutel in voor %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), esc_html( $provider->get_label() ) ); ?></p>
</td>
</tr>
<?php endforeach; ?>
</table>
<h2><?php esc_html_e( 'Algemene instellingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="groq-ai-max-output"><?php esc_html_e( 'Maximale output tokens', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<input type="number" id="groq-ai-max-output" name="<?php echo esc_attr( $option_key ); ?>[max_output_tokens]" value="<?php echo esc_attr( isset( $settings['max_output_tokens'] ) ? (int) $settings['max_output_tokens'] : 2048 ); ?>" min="128" max="8192" />
<p class="description"><?php esc_html_e( 'Limitering van het aantal tokens per output voor compatibiliteit met verschillende modellen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="groq-ai-term-bottom-meta"><?php esc_html_e( 'Term meta key (onderste tekst)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<input type="text" id="groq-ai-term-bottom-meta" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[term_bottom_description_meta_key]" value="<?php echo esc_attr( isset( $settings['term_bottom_description_meta_key'] ) ? $settings['term_bottom_description_meta_key'] : '' ); ?>" />
<p class="description"><?php esc_html_e( 'Optioneel: overschrijf in welke term meta key de onderste omschrijving moet landen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Response format fallback', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<?php $this->render_response_format_compat_field(); ?>
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Google Search Console & Analytics', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="groq-ai-google-client-id"><?php esc_html_e( 'Google OAuth client ID', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<input type="text" id="groq-ai-google-client-id" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[google_oauth_client_id]" value="<?php echo esc_attr( isset( $settings['google_oauth_client_id'] ) ? $settings['google_oauth_client_id'] : '' ); ?>" autocomplete="off" />
</td>
</tr>
<tr>
<th scope="row"><label for="groq-ai-google-client-secret"><?php esc_html_e( 'Google OAuth client secret', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<input type="password" id="groq-ai-google-client-secret" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[google_oauth_client_secret]" value="<?php echo esc_attr( isset( $settings['google_oauth_client_secret'] ) ? $settings['google_oauth_client_secret'] : '' ); ?>" autocomplete="off" />
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Search Console koppeling', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<label>
<input type="checkbox" name="<?php echo esc_attr( $option_key ); ?>[google_enable_gsc]" value="1" <?php checked( ! empty( $settings['google_enable_gsc'] ) ); ?> />
<?php esc_html_e( 'Search Console data gebruiken in term prompts', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
</label>
<p>
<input type="url" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[google_gsc_site_url]" value="<?php echo esc_attr( isset( $settings['google_gsc_site_url'] ) ? $settings['google_gsc_site_url'] : '' ); ?>" placeholder="sc-domain:voorbeeld.nl" />
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Analytics koppeling', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<label>
<input type="checkbox" name="<?php echo esc_attr( $option_key ); ?>[google_enable_ga]" value="1" <?php checked( ! empty( $settings['google_enable_ga'] ) ); ?> />
<?php esc_html_e( 'GA4 data meesturen (landing page statistieken)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
</label>
<p>
<input type="text" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[google_ga4_property_id]" value="<?php echo esc_attr( isset( $settings['google_ga4_property_id'] ) ? $settings['google_ga4_property_id'] : '' ); ?>" placeholder="properties/123456789" />
</p>
</td>
</tr>
</table>
<p class="submit"><?php submit_button( __( 'Instellingen opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'primary', 'submit', false ); ?></p>
</form>
<div style="margin-top:24px; padding:16px; border:1px solid #dcdcde; background:#fff;">
<h2><?php esc_html_e( 'Google verbinding', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<p>
<?php
if ( $google_connected ) {
$timestamp = $google_connected_at ? date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $google_connected_at ) : '';
printf(
esc_html__( 'Verbonden als %1$s%2$s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$google_connected_email ? esc_html( $google_connected_email ) : esc_html__( 'onbekende gebruiker', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$timestamp ? ' — ' . esc_html( $timestamp ) : ''
);
} else {
esc_html_e( 'Nog niet gekoppeld aan Google OAuth.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
?>
</p>
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'groq_ai_google_oauth', '_wpnonce' ); ?>
<input type="hidden" name="action" value="groq_ai_google_oauth_start" />
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $current_page ); ?>" />
<button type="submit" class="button button-primary"><?php echo $google_connected ? esc_html__( 'Opnieuw verbinden', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : esc_html__( 'Verbind met Google', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
</form>
<?php if ( $google_connected ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'groq_ai_google_disconnect', '_wpnonce' ); ?>
<input type="hidden" name="action" value="groq_ai_google_oauth_disconnect" />
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $current_page ); ?>" />
<button type="submit" class="button"><?php esc_html_e( 'Ontkoppelen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
</form>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'groq_ai_google_test_connection', '_wpnonce' ); ?>
<input type="hidden" name="action" value="groq_ai_google_test_connection" />
<input type="hidden" name="redirect_to" value="<?php echo esc_url( $current_page ); ?>" />
<button type="submit" class="button button-secondary"><?php esc_html_e( 'Verbinding testen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
</form>
<?php endif; ?>
</div>
</div>
</div>
<?php
} }
/** /**
@@ -106,7 +411,8 @@ class Groq_AI_Product_Text_Settings_Page {
#adminmenu a[href="options-general.php?page=groq-ai-product-text-modules"], #adminmenu a[href="options-general.php?page=groq-ai-product-text-modules"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-logs"], #adminmenu a[href="options-general.php?page=groq-ai-product-text-logs"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-prompts"], #adminmenu a[href="options-general.php?page=groq-ai-product-text-prompts"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-term"] { #adminmenu a[href="options-general.php?page=groq-ai-product-text-term"],
#adminmenu a[href="options-general.php?page=groq-ai-product-text-log"] {
display: none !important; display: none !important;
} }
</style> </style>
@@ -425,6 +731,239 @@ class Groq_AI_Product_Text_Settings_Page {
</div> </div>
<?php <?php
} }
public function render_modules_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$option_key = $this->plugin->get_option_key();
?>
<div class="wrap">
<h1><?php esc_html_e( 'Siti AI modules', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description"><?php esc_html_e( 'Schakel aanvullende integraties in en bepaal grenzen voor gegenereerde content.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<?php settings_errors( $option_key ); ?>
<form method="post" action="options.php">
<?php settings_fields( $option_key ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Rank Math integratie', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php $this->render_rankmath_module_field(); ?></td>
</tr>
</table>
<?php submit_button( __( 'Modules opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
</form>
</div>
<?php
}
public function render_logs_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$logs_table = new Groq_AI_Logs_Table( $this->plugin );
$logs_table->prepare_items();
?>
<div class="wrap">
<h1><?php esc_html_e( 'AI-logboek', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<form method="get">
<input type="hidden" name="page" value="groq-ai-product-text-logs" />
<?php $logs_table->search_box( __( 'Zoek logboek', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'groq-ai-logs' ); ?>
<?php $logs_table->display(); ?>
</form>
</div>
<?php
}
public function render_log_detail_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$log_id = isset( $_GET['log_id'] ) ? absint( $_GET['log_id'] ) : 0;
$back_url = $this->get_page_url( 'groq-ai-product-text-logs' );
$log = null;
if ( $log_id ) {
global $wpdb;
$table = $wpdb->prefix . 'groq_ai_generation_logs';
$query = $wpdb->prepare(
"SELECT l.*, p.post_title FROM {$table} l LEFT JOIN {$wpdb->posts} p ON p.ID = l.post_id WHERE l.id = %d",
$log_id
);
$log = $wpdb->get_row( $query, ARRAY_A );
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'Logdetail', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p>
<a href="<?php echo esc_url( $back_url ); ?>" class="button">&larr; <?php esc_html_e( 'Terug naar logboek', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></a>
</p>
<?php if ( ! $log ) : ?>
<p><?php esc_html_e( 'Log niet gevonden of verwijderd.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<?php else : ?>
<table class="widefat striped" style="margin-top:16px;">
<tbody>
<tr>
<th><?php esc_html_e( 'Datum', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php echo esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $log['created_at'] ) ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'Gebruiker', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<?php
if ( $log['user_id'] ) {
$user = get_userdata( $log['user_id'] );
echo $user ? esc_html( $user->display_name ) : esc_html( (string) $log['user_id'] );
} else {
echo '—';
}
?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Product', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<?php
if ( $log['post_id'] ) {
$link = get_edit_post_link( $log['post_id'] );
$title = $log['post_title'] ? $log['post_title'] : sprintf( __( 'Product #%d', GROQ_AI_PRODUCT_TEXT_DOMAIN ), (int) $log['post_id'] );
echo $link ? '<a href="' . esc_url( $link ) . '">' . esc_html( $title ) . '</a>' : esc_html( $title );
} else {
echo '—';
}
?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Provider', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php echo esc_html( $log['provider'] ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'Model', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php echo esc_html( $log['model'] ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'Status', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php echo esc_html( $log['status'] ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'Tokens', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<?php
printf(
esc_html__( 'Prompt: %1$s — Completion: %2$s — Totaal: %3$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
isset( $log['tokens_prompt'] ) ? number_format_i18n( (int) $log['tokens_prompt'] ) : '—',
isset( $log['tokens_completion'] ) ? number_format_i18n( (int) $log['tokens_completion'] ) : '—',
isset( $log['tokens_total'] ) ? number_format_i18n( (int) $log['tokens_total'] ) : '—'
);
?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Foutmelding', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php echo $log['error_message'] ? esc_html( $log['error_message'] ) : '—'; ?></td>
</tr>
</tbody>
</table>
<h2><?php esc_html_e( 'Prompt', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<pre style="background:#fff;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $log['prompt'] ); ?></pre>
<h2><?php esc_html_e( 'AI-respons', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<pre style="background:#f9f9f9;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $log['response'] ); ?></pre>
<?php endif; ?>
</div>
<?php
}
public function render_prompt_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$option_key = $this->plugin->get_option_key();
$settings = $this->plugin->get_settings();
$definitions = $this->plugin->get_context_field_definitions();
$context_vals = isset( $settings['context_fields'] ) ? (array) $settings['context_fields'] : $this->plugin->get_default_context_fields();
$image_mode = $this->plugin->get_image_context_mode( $settings );
$image_limit = $this->plugin->get_image_context_limit( $settings );
$preview = $this->plugin->build_prompt_template_preview( $settings );
?>
<div class="wrap">
<h1><?php esc_html_e( 'Prompt & context', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
<p class="description"><?php esc_html_e( 'Bepaal welke winkelcontext standaard meegestuurd wordt en hoe de prompt eruit ziet voor nieuwe generaties.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<div style="margin:16px 0; padding:16px; background:#fff; border:1px solid #dcdcde;">
<strong><?php esc_html_e( 'Voorbeeldprompt', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></strong>
<pre style="background:#f6f7f7; padding:12px; border-radius:4px; white-space:pre-wrap; overflow:auto; margin-top:8px;"><?php echo esc_html( $preview ); ?></pre>
</div>
<?php settings_errors( $option_key ); ?>
<form method="post" action="options.php">
<?php settings_fields( $option_key ); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="groq-ai-store-context"><?php esc_html_e( 'Winkelcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<textarea id="groq-ai-store-context" class="large-text" rows="4" name="<?php echo esc_attr( $option_key ); ?>[store_context]"><?php echo esc_textarea( isset( $settings['store_context'] ) ? $settings['store_context'] : '' ); ?></textarea>
<p class="description"><?php esc_html_e( 'Beschrijf je winkel, tone-of-voice en doelgroep. Wordt in system prompts gebruikt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><label for="groq-ai-default-prompt"><?php esc_html_e( 'Standaard prompt', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<textarea id="groq-ai-default-prompt" class="large-text" rows="6" name="<?php echo esc_attr( $option_key ); ?>[default_prompt]"><?php echo esc_textarea( isset( $settings['default_prompt'] ) ? $settings['default_prompt'] : '' ); ?></textarea>
<p class="description"><?php esc_html_e( 'Vult automatisch de AI-modal en termgenerator. Je kunt dit per product/term overschrijven.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Contextvelden', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td>
<div class="groq-ai-context-defaults">
<?php foreach ( $definitions as $key => $info ) :
$checked = ! empty( $context_vals[ $key ] );
$label = isset( $info['label'] ) ? $info['label'] : $key;
$description = isset( $info['description'] ) ? $info['description'] : '';
?>
<label>
<input type="checkbox" name="<?php echo esc_attr( $option_key ); ?>[context_fields][<?php echo esc_attr( $key ); ?>]" value="1" <?php checked( $checked ); ?> />
<strong><?php echo esc_html( $label ); ?></strong>
</label>
<?php if ( $description ) : ?>
<p class="description" style="margin:0 0 8px 24px;"><?php echo esc_html( $description ); ?></p>
<?php endif; ?>
<?php endforeach; ?>
</div>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Productattributen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
<td><?php $this->render_product_attribute_includes_field(); ?></td>
</tr>
<tr>
<th scope="row"><label for="groq-ai-image-mode"><?php esc_html_e( 'Afbeeldingen als context', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
<td>
<select id="groq-ai-image-mode" name="<?php echo esc_attr( $option_key ); ?>[image_context_mode]">
<option value="none" <?php selected( 'none', $image_mode ); ?>><?php esc_html_e( 'Niet meesturen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></option>
<option value="url" <?php selected( 'url', $image_mode ); ?>><?php esc_html_e( 'Alleen URL en korte beschrijving', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></option>
<option value="base64" <?php selected( 'base64', $image_mode ); ?>><?php esc_html_e( 'Inline base64 (alleen voor modellen die dit vereisen)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></option>
</select>
<p class="description"><?php esc_html_e( 'Sommige modellen (zoals Gemini) ondersteunen beeldcontext; let op je tokenverbruik.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<label>
<?php esc_html_e( 'Maximaal aantal afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
<input type="number" min="1" max="10" name="<?php echo esc_attr( $option_key ); ?>[image_context_limit]" value="<?php echo esc_attr( $image_limit ); ?>" style="width:80px;" />
</label>
</td>
</tr>
</table>
<?php submit_button( __( 'Prompt instellingen opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
</form>
</div>
<?php
}
public function render_term_generator_page() { public function render_term_generator_page() {
if ( ! current_user_can( 'manage_options' ) ) { if ( ! current_user_can( 'manage_options' ) ) {
return; return;
@@ -454,6 +993,20 @@ class Groq_AI_Product_Text_Settings_Page {
return; return;
} }
$term_notice = isset( $_GET['groq_ai_term_notice'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_term_notice'] ) ) : '';
$term_notice_status = isset( $_GET['groq_ai_term_status'] ) ? sanitize_key( wp_unslash( $_GET['groq_ai_term_status'] ) ) : 'success';
$term_notice_message = '';
if ( isset( $_GET['groq_ai_term_notice_message'] ) ) {
$term_notice_message = sanitize_text_field( rawurldecode( wp_unslash( $_GET['groq_ai_term_notice_message'] ) ) );
}
if ( $term_notice && '' === $term_notice_message ) {
if ( 'saved' === $term_notice ) {
$term_notice_message = __( 'Term succesvol opgeslagen.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
} else {
$term_notice_message = __( 'Actie voltooid.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
}
$term_label = ( 'product_cat' === $taxonomy ) ? __( 'Categorie', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Term', GROQ_AI_PRODUCT_TEXT_DOMAIN ); $term_label = ( 'product_cat' === $taxonomy ) ? __( 'Categorie', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Term', GROQ_AI_PRODUCT_TEXT_DOMAIN );
$word_count = $this->count_words( $term->description ); $word_count = $this->count_words( $term->description );
$meta_prompt = get_term_meta( $term_id, 'groq_ai_term_custom_prompt', true ); $meta_prompt = get_term_meta( $term_id, 'groq_ai_term_custom_prompt', true );
@@ -478,6 +1031,12 @@ class Groq_AI_Product_Text_Settings_Page {
<h1> <h1>
<?php echo esc_html( $term_label ); ?>: <?php echo esc_html( $term->name ); ?> <?php echo esc_html( $term_label ); ?>: <?php echo esc_html( $term->name ); ?>
</h1> </h1>
<?php if ( $term_notice ) : ?>
<?php $notice_class = ( 'error' === $term_notice_status ) ? 'notice notice-error' : 'notice notice-success'; ?>
<div class="<?php echo esc_attr( $notice_class ); ?>">
<p><?php echo esc_html( $term_notice_message ); ?></p>
</div>
<?php endif; ?>
<p> <p>
<?php <?php
printf( printf(
@@ -648,6 +1207,8 @@ class Groq_AI_Product_Text_Settings_Page {
return apply_filters( 'groq_ai_default_term_prompt', $default_prompt, $term ); return apply_filters( 'groq_ai_default_term_prompt', $default_prompt, $term );
} }
public function render_product_attribute_includes_field() { public function render_product_attribute_includes_field() {
$settings = $this->plugin->get_settings(); $settings = $this->plugin->get_settings();
$values = isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] ) $values = isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] )
@@ -815,14 +1376,25 @@ class Groq_AI_Product_Text_Settings_Page {
} }
public function enqueue_settings_assets( $hook ) { public function enqueue_settings_assets( $hook ) {
if ( ! in_array( $hook, [ $allowed_hooks = [
'settings_page_groq-ai-product-text', 'settings_page_groq-ai-product-text',
'settings_page_groq-ai-product-text-modules', 'settings_page_groq-ai-product-text-modules',
'settings_page_groq-ai-product-text-prompts', 'settings_page_groq-ai-product-text-prompts',
'settings_page_groq-ai-product-text-categories', 'settings_page_groq-ai-product-text-categories',
'settings_page_groq-ai-product-text-brands', 'settings_page_groq-ai-product-text-brands',
'settings_page_groq-ai-product-text-term', 'settings_page_groq-ai-product-text-term',
], true ) ) { 'settings_page_groq-ai-product-text-logs',
];
$matches_hook = false;
foreach ( $allowed_hooks as $allowed ) {
if ( 0 === strpos( (string) $hook, $allowed ) ) {
$matches_hook = true;
break;
}
}
if ( ! $matches_hook ) {
return; return;
} }
@@ -848,7 +1420,9 @@ class Groq_AI_Product_Text_Settings_Page {
true true
); );
if ( 'settings_page_groq-ai-product-text-term' === $hook ) { $current_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
if ( 0 === strpos( (string) $hook, 'settings_page_groq-ai-product-text-term' ) ) {
wp_enqueue_script( wp_enqueue_script(
'groq-ai-term-admin', 'groq-ai-term-admin',
plugins_url( 'assets/js/term-admin.js', GROQ_AI_PRODUCT_TEXT_FILE ), plugins_url( 'assets/js/term-admin.js', GROQ_AI_PRODUCT_TEXT_FILE ),
@@ -875,7 +1449,7 @@ class Groq_AI_Product_Text_Settings_Page {
$bulk_allow_regen = false; $bulk_allow_regen = false;
$bulk_strings = []; $bulk_strings = [];
if ( 'settings_page_groq-ai-product-text-categories' === $hook ) { if ( 0 === strpos( (string) $hook, 'settings_page_groq-ai-product-text-categories' ) ) {
$bulk_taxonomy = 'product_cat'; $bulk_taxonomy = 'product_cat';
$bulk_strings = [ $bulk_strings = [
'statusIdle' => __( 'Bulk gestart. AI werkt de geselecteerde categorieën bij…', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'statusIdle' => __( 'Bulk gestart. AI werkt de geselecteerde categorieën bij…', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
@@ -887,7 +1461,7 @@ class Groq_AI_Product_Text_Settings_Page {
'logError' => __( '%1$s mislukt: %2$s', 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 ), '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 ) { } elseif ( 0 === strpos( (string) $hook, 'settings_page_groq-ai-product-text-brands' ) ) {
$detected_taxonomy = $this->detect_brand_taxonomy(); $detected_taxonomy = $this->detect_brand_taxonomy();
if ( '' !== $detected_taxonomy ) { if ( '' !== $detected_taxonomy ) {
$bulk_taxonomy = $detected_taxonomy; $bulk_taxonomy = $detected_taxonomy;
@@ -957,4 +1531,300 @@ class Groq_AI_Product_Text_Settings_Page {
wp_localize_script( 'groq-ai-settings', 'GroqAISettingsData', $data ); wp_localize_script( 'groq-ai-settings', 'GroqAISettingsData', $data );
} }
public function handle_google_oauth_start() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Je hebt geen toestemming om deze actie uit te voeren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), '', [ 'response' => 403 ] );
}
check_admin_referer( 'groq_ai_google_oauth' );
$redirect = $this->get_request_redirect_url( 'redirect_to' );
$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'] ) : '';
if ( '' === $client_id || '' === $client_secret ) {
$this->redirect_with_google_notice( 'error', __( 'Vul eerst het Google client ID en secret in en sla de instellingen op.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$state_payload = [
'nonce' => wp_create_nonce( 'groq_ai_google_oauth_state' ),
'redirect' => $redirect,
'timestamp' => time(),
];
$state_json = wp_json_encode( $state_payload );
if ( ! is_string( $state_json ) ) {
$state_json = wp_json_encode( (object) [] );
}
$state = base64_encode( (string) $state_json );
$scopes = [
'https://www.googleapis.com/auth/webmasters.readonly',
'https://www.googleapis.com/auth/analytics.readonly',
'https://www.googleapis.com/auth/userinfo.email',
];
$auth_url = add_query_arg(
[
'response_type' => 'code',
'client_id' => $client_id,
'redirect_uri' => $this->get_google_redirect_uri(),
'scope' => implode( ' ', $scopes ),
'access_type' => 'offline',
'prompt' => 'consent',
'include_granted_scopes' => 'true',
'state' => $state,
],
'https://accounts.google.com/o/oauth2/v2/auth'
);
wp_safe_redirect( $auth_url );
exit;
}
public function handle_google_oauth_callback() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Je hebt geen toestemming om deze actie uit te voeren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), '', [ 'response' => 403 ] );
}
$state_value = isset( $_GET['state'] ) ? wp_unslash( $_GET['state'] ) : '';
$state = $this->parse_oauth_state( $state_value );
$redirect = isset( $state['redirect'] ) ? wp_validate_redirect( (string) $state['redirect'], $this->get_page_url() ) : $this->get_page_url();
if ( isset( $_GET['error'] ) ) {
$error_message = sanitize_text_field( wp_unslash( $_GET['error'] ) );
if ( isset( $_GET['error_description'] ) ) {
$error_message .= ': ' . sanitize_text_field( wp_unslash( $_GET['error_description'] ) );
}
$this->redirect_with_google_notice( 'error', $error_message, $redirect, 'error' );
}
if ( isset( $state['nonce'] ) && ! wp_verify_nonce( $state['nonce'], 'groq_ai_google_oauth_state' ) ) {
$this->redirect_with_google_notice( 'error', __( 'Ongeldige OAuth-sessie. Probeer het opnieuw.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$timestamp = isset( $state['timestamp'] ) ? absint( $state['timestamp'] ) : 0;
if ( $timestamp && ( time() - $timestamp ) > HOUR_IN_SECONDS ) {
$this->redirect_with_google_notice( 'error', __( 'OAuth-sessie verlopen. Probeer het opnieuw.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
if ( '' === $code ) {
$this->redirect_with_google_notice( 'error', __( 'Geen autorisatiecode ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$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'] ) : '';
if ( '' === $client_id || '' === $client_secret ) {
$this->redirect_with_google_notice( 'error', __( 'Google client ID en secret ontbreken.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$response = wp_remote_post(
'https://oauth2.googleapis.com/token',
[
'timeout' => 20,
'body' => [
'code' => $code,
'client_id' => $client_id,
'client_secret' => $client_secret,
'redirect_uri' => $this->get_google_redirect_uri(),
'grant_type' => 'authorization_code',
],
]
);
if ( is_wp_error( $response ) ) {
$this->redirect_with_google_notice( 'error', $response->get_error_message(), $redirect, 'error' );
}
$status_code = wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
$data = json_decode( (string) $body, true );
if ( 200 !== $status_code || ! is_array( $data ) ) {
$this->redirect_with_google_notice( 'error', __( 'Kon tokens niet ophalen bij Google.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$access_token = isset( $data['access_token'] ) ? trim( (string) $data['access_token'] ) : '';
$refresh_token = isset( $data['refresh_token'] ) ? trim( (string) $data['refresh_token'] ) : '';
if ( '' === $refresh_token ) {
$existing = isset( $settings['google_oauth_refresh_token'] ) ? (string) $settings['google_oauth_refresh_token'] : '';
$refresh_token = $existing;
}
if ( '' === $refresh_token ) {
$this->redirect_with_google_notice( 'error', __( 'Google retourneerde geen refresh token. Forceer toestemming opnieuw en probeer het nogmaals.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
if ( '' === $access_token ) {
$this->redirect_with_google_notice( 'error', __( 'Google retourneerde geen access token.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect, 'error' );
}
$email = '';
$userinfo = wp_remote_get(
'https://openidconnect.googleapis.com/v1/userinfo',
[
'timeout' => 15,
'headers' => [
'Authorization' => 'Bearer ' . $access_token,
],
]
);
if ( ! is_wp_error( $userinfo ) ) {
$userinfo_code = wp_remote_retrieve_response_code( $userinfo );
$userinfo_body = json_decode( wp_remote_retrieve_body( $userinfo ), true );
if ( 200 === $userinfo_code && is_array( $userinfo_body ) && isset( $userinfo_body['email'] ) ) {
$email = sanitize_email( (string) $userinfo_body['email'] );
}
}
$this->update_settings_partial(
[
'google_oauth_refresh_token' => sanitize_text_field( $refresh_token ),
'google_oauth_connected_email' => $email,
'google_oauth_connected_at' => current_time( 'timestamp' ),
]
);
$this->redirect_with_google_notice( 'connected', __( 'Google OAuth is verbonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect );
}
public function handle_google_oauth_disconnect() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Je hebt geen toestemming om deze actie uit te voeren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), '', [ 'response' => 403 ] );
}
check_admin_referer( 'groq_ai_google_disconnect' );
$redirect = $this->get_request_redirect_url( 'redirect_to' );
$this->update_settings_partial(
[
'google_oauth_refresh_token' => '',
'google_oauth_connected_email' => '',
'google_oauth_connected_at' => 0,
]
);
$this->redirect_with_google_notice( 'disconnected', __( 'Google OAuth is ontkoppeld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $redirect );
}
public function handle_google_test_connection() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Je hebt geen toestemming om deze actie uit te voeren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), '', [ 'response' => 403 ] );
}
check_admin_referer( 'groq_ai_google_test_connection' );
$redirect = $this->get_request_redirect_url( 'redirect_to' );
$settings = $this->plugin->get_settings();
$oauth_client = new Groq_AI_Google_OAuth_Client();
$token = $oauth_client->get_access_token( $settings );
if ( is_wp_error( $token ) ) {
$this->redirect_with_google_notice( 'test', $token->get_error_message(), $redirect, 'error' );
}
$messages = [ __( 'OAuth token opgehaald.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ];
$token_info = $oauth_client->get_access_token_info( $token );
if ( ! is_wp_error( $token_info ) && ! empty( $token_info['scope'] ) ) {
$messages[] = sprintf( __( 'Scopes: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $token_info['scope'] );
}
if ( ! empty( $settings['google_enable_gsc'] ) && ! empty( $settings['google_gsc_site_url'] ) ) {
$gsc_client = new Groq_AI_Google_Search_Console_Client( $oauth_client );
$result = $gsc_client->list_sites( $settings );
if ( is_wp_error( $result ) ) {
$this->redirect_with_google_notice( 'test', $result->get_error_message(), $redirect, 'error' );
}
$messages[] = __( 'Search Console API bereikbaar.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
if ( ! empty( $settings['google_enable_ga'] ) && ! empty( $settings['google_ga4_property_id'] ) ) {
$ga_client = new Groq_AI_Google_Analytics_Data_Client( $oauth_client );
$end_date = gmdate( 'Y-m-d' );
$start_date = gmdate( 'Y-m-d', time() - ( 7 * DAY_IN_SECONDS ) );
$summary = $ga_client->get_property_sessions_summary( $settings, $settings['google_ga4_property_id'], $start_date, $end_date );
if ( is_wp_error( $summary ) ) {
$this->redirect_with_google_notice( 'test', $summary->get_error_message(), $redirect, 'error' );
}
$sessions = isset( $summary['sessions'] ) ? absint( $summary['sessions'] ) : 0;
$messages[] = sprintf( __( 'GA4 API bereikbaar (sessies laatste 7 dagen: %d).', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $sessions );
}
$this->redirect_with_google_notice( 'test', implode( ' ', $messages ), $redirect );
}
public function handle_save_term_content() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Je hebt geen toestemming om deze actie uit te voeren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), '', [ 'response' => 403 ] );
}
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;
if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
$this->redirect_with_term_notice( $taxonomy, $term_id, 'error', __( 'Ongeldige term.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'error' );
}
$term = get_term( $term_id, $taxonomy );
if ( ! $term || is_wp_error( $term ) ) {
$this->redirect_with_term_notice( $taxonomy, $term_id, 'error', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'error' );
}
$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'] ) ) : '';
$update = wp_update_term(
$term_id,
$taxonomy,
[
'description' => $description,
]
);
if ( is_wp_error( $update ) ) {
$this->redirect_with_term_notice( $taxonomy, $term_id, 'error', $update->get_error_message(), 'error' );
}
$settings = $this->plugin->get_settings();
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
$bottom_meta_key = '' !== $bottom_meta_key ? $bottom_meta_key : 'groq_ai_term_bottom_description';
update_term_meta( $term_id, $bottom_meta_key, $bottom_description );
if ( '' === trim( $custom_prompt ) ) {
delete_term_meta( $term_id, 'groq_ai_term_custom_prompt' );
} else {
update_term_meta( $term_id, 'groq_ai_term_custom_prompt', $custom_prompt );
}
if ( isset( $_POST['groq_ai_term_bottom_description'] ) && $bottom_meta_key !== 'groq_ai_term_bottom_description' ) {
update_term_meta( $term_id, 'groq_ai_term_bottom_description', $bottom_description );
}
if ( $this->plugin->is_module_enabled( 'rankmath', $settings ) ) {
$rankmath_keys = $this->resolve_rankmath_term_meta_keys( $term, $settings );
$rankmath_title = isset( $_POST['groq_ai_rankmath_meta_title'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_meta_title'] ) ) : '';
$rankmath_description = isset( $_POST['groq_ai_rankmath_meta_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['groq_ai_rankmath_meta_description'] ) ) : '';
$rankmath_keywords = isset( $_POST['groq_ai_rankmath_focus_keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_focus_keywords'] ) ) : '';
update_term_meta( $term_id, $rankmath_keys['title'], $rankmath_title );
update_term_meta( $term_id, $rankmath_keys['description'], $rankmath_description );
update_term_meta( $term_id, $rankmath_keys['focus_keyword'], $rankmath_keywords );
}
$this->redirect_with_term_notice( $taxonomy, $term_id, 'saved', __( 'Term opgeslagen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
} }