'Merk', 'supplier' => 'Leverancier', 'custom_taxonomy' => 'Custom', 'ignore' => 'Negeren', ); /** * Labels for meta field mapping targets. * * @var array */ private array $meta_labels = array( 'ean' => 'EAN Code', 'purchase_price' => 'Inkoopprijs', 'weight' => 'Gewicht', 'length' => 'Lengte', 'width' => 'Breedte', 'height' => 'Hoogte', 'hs_code' => 'HS Code', 'lead_time_days' => 'Levertijd (dagen)', 'custom_field' => 'Custom', 'ignore' => 'Negeren', ); /** * Constructor. * * Note: Menu registration has been removed. The mapping UI is now * rendered as a subtab within WooCommerce > Settings > Genius Commerce. */ public function __construct() { // AJAX handler for saving mapping. add_action( 'wp_ajax_genius_commerce_save_mapping', array( $this, 'ajax_save_mapping' ) ); } /** * Render the mapping content for the settings subtab. * * This method is called from GC_Settings when rendering the mapping subtab. * It renders the content without the wrap div (provided by WooCommerce). * * @return void */ public function render_content(): void { // Get discovery data. $discovery = Genius_Commerce::instance()->get_field_discovery(); $discovery_data = $discovery ? $discovery->discover() : array(); // Get current mapping (with defaults applied). $mapping = GC_Sync_Mapping::apply_defaults( $discovery_data ); // Extract scanner data. $taxonomies = $discovery_data['scanners']['taxonomy'] ?? array(); $meta_fields = $discovery_data['scanners']['meta_field'] ?? array(); $this->render_subtab_header( $discovery_data ); $this->render_notices(); // Render mapping UI (no form wrapper - uses AJAX). echo '
'; echo esc_html__( 'Configureer hoe WooCommerce velden en taxonomieën worden gemapped naar Genius Commerce.', 'genius-commerce' ); echo '
'; echo ''; echo esc_html__( 'Mapping configuratie opgeslagen.', 'genius-commerce' ); echo '
'; echo esc_html__( 'Discovery vernieuwd.', 'genius-commerce' ); echo '
'; echo esc_html__( 'Er is een fout opgetreden bij het opslaan.', 'genius-commerce' ); echo '
'; echo '' . esc_html__( 'Mapping conflict:', 'genius-commerce' ) . ' '; if ( isset( $_GET['msg'] ) ) { echo esc_html( rawurldecode( sanitize_text_field( wp_unslash( $_GET['msg'] ) ) ) ); } else { echo esc_html__( 'Meerdere velden zijn naar hetzelfde target gemapped.', 'genius-commerce' ); } echo '
' . esc_html__( 'Geen taxonomies gedetecteerd.', 'genius-commerce' ) . '
'; return; } echo '| ' . esc_html__( 'Taxonomy', 'genius-commerce' ) . ' | '; echo '' . esc_html__( 'Mapping', 'genius-commerce' ) . ' | '; echo '' . esc_html__( 'Info', 'genius-commerce' ) . ' | '; echo '
|---|---|---|
' . esc_html( $label ) . ' ' . esc_html( $slug ) . ' | ';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- render_taxonomy_dropdown() returns pre-escaped HTML.
echo '' . $this->render_taxonomy_dropdown( $slug, $current_map ) . ' | '; echo ''; printf( /* translators: 1: term count, 2: fill rate percentage */ esc_html__( '%1$d terms, %2$s%%', 'genius-commerce' ), intval( $term_count ), number_format( $fill_rate, 1 ) ); if ( $is_suggested ) { echo ' '; } echo ' | '; echo '
' . esc_html__( 'Geen meta fields gedetecteerd.', 'genius-commerce' ) . '
'; return; } echo '| ' . esc_html__( 'Veld', 'genius-commerce' ) . ' | '; echo '' . esc_html__( 'Mapping', 'genius-commerce' ) . ' | '; echo '' . esc_html__( 'Info', 'genius-commerce' ) . ' | '; echo '
|---|---|---|
' . esc_html( $label ) . ' ' . esc_html( $key ) . '';
if ( $is_acf ) {
echo ' (ACF)';
}
echo ' | ';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- render_meta_dropdown() returns pre-escaped HTML.
echo '' . $this->render_meta_dropdown( $key, $current_map ) . ' | '; echo ''; printf( /* translators: 1: product count, 2: fill rate percentage */ esc_html__( '%1$d producten, %2$s%%', 'genius-commerce' ), intval( $product_count ), number_format( $fill_rate, 1 ) ); if ( $is_suggested ) { echo ' '; } echo ' | '; echo '
'; echo ' '; echo esc_html__( '= Aanbevolen mapping (automatisch gedetecteerd)', 'genius-commerce' ); echo '
'; } /** * Render a taxonomy mapping dropdown. * * @param string $slug The taxonomy slug. * @param string $current_map The current mapping value. * @return string The HTML for the dropdown. */ private function render_taxonomy_dropdown( string $slug, string $current_map ): string { $html = ''; return $html; } /** * Render a meta field mapping dropdown. * * @param string $key The meta key. * @param string $current_map The current mapping value. * @return string The HTML for the dropdown. */ private function render_meta_dropdown( string $key, string $current_map ): string { $html = ''; return $html; } /** * AJAX handler for saving mapping configuration. * * @return void */ public function ajax_save_mapping(): void { // Verify nonce. if ( ! check_ajax_referer( 'genius_commerce_mapping_action', 'nonce', false ) ) { wp_send_json_error( array( 'message' => __( 'Ongeldige beveiligingstoken', 'genius-commerce' ) ) ); } // Check permissions. if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Geen rechten', 'genius-commerce' ) ) ); } // Get mapping data from POST. // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified above. $taxonomy_input = isset( $_POST['taxonomy_mapping'] ) && is_array( $_POST['taxonomy_mapping'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['taxonomy_mapping'] ) ) : array(); $meta_input = isset( $_POST['meta_mapping'] ) && is_array( $_POST['meta_mapping'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['meta_mapping'] ) ) : array(); // phpcs:enable WordPress.Security.NonceVerification.Missing // Build taxonomy mappings. $taxonomy_mappings = array(); foreach ( $taxonomy_input as $slug => $map_to ) { if ( ! empty( $map_to ) && in_array( $map_to, GC_Sync_Mapping::TAXONOMY_TARGETS, true ) ) { $taxonomy_mappings[ $slug ] = array( 'map_to' => $map_to, 'auto_suggested' => false, ); } } // Build meta field mappings. Only keys from the last Discovery scan are // accepted, so a Shop Manager cannot map an arbitrary (secret) meta key // into the sync payload. $allowed_meta_keys = GC_Sync_Mapping::get_allowed_meta_keys(); $meta_field_mappings = array(); $rejected_keys = array(); foreach ( $meta_input as $key => $map_to ) { if ( empty( $map_to ) || ! in_array( $map_to, GC_Sync_Mapping::META_TARGETS, true ) ) { continue; } $sanitized_key = GC_Sync_Mapping::sanitize_meta_key( (string) $key ); if ( '' === $sanitized_key || ! isset( $allowed_meta_keys[ $sanitized_key ] ) ) { $rejected_keys[] = (string) $key; continue; } // Persist the exact discovered key so the resolver reads the right meta. $canonical_key = $allowed_meta_keys[ $sanitized_key ]; $meta_field_mappings[ $canonical_key ] = array( 'map_to' => $map_to, 'auto_suggested' => false, ); } if ( ! empty( $rejected_keys ) ) { Genius_Commerce::log( 'Sync mapping opslaan geweigerd: meta-key(s) niet aanwezig in de laatste Discovery-scan.', 'warning', array( 'rejected_keys' => $rejected_keys ) ); wp_send_json_error( array( 'message' => sprintf( /* translators: %s: comma-separated list of rejected meta keys */ __( 'Deze veld(en) kunnen niet gemapped worden omdat ze niet in de laatste Discovery-scan voorkomen: %s. Vernieuw de Discovery-scan en probeer opnieuw.', 'genius-commerce' ), implode( ', ', $rejected_keys ) ), ) ); } // Validate unique targets. $unique_targets = array( 'ean', 'purchase_price', 'weight', 'length', 'width', 'height', 'hs_code', 'lead_time_days' ); $used_targets = array(); foreach ( $meta_field_mappings as $key => $config ) { $map_to = $config['map_to']; if ( ! in_array( $map_to, $unique_targets, true ) ) { continue; } if ( isset( $used_targets[ $map_to ] ) ) { wp_send_json_error( array( 'message' => sprintf( /* translators: 1: target label, 2: meta field key */ __( 'Conflict: "%1$s" is al toegewezen aan "%2$s"', 'genius-commerce' ), $this->meta_labels[ $map_to ] ?? $map_to, $used_targets[ $map_to ] ), ) ); } $used_targets[ $map_to ] = $key; } // Save mapping. $mapping = array( 'taxonomy_mappings' => $taxonomy_mappings, 'meta_field_mappings' => $meta_field_mappings, ); $saved = GC_Sync_Mapping::save( $mapping ); if ( $saved ) { wp_send_json_success( array( 'message' => __( 'Mapping configuratie opgeslagen', 'genius-commerce' ), ) ); } else { wp_send_json_error( array( 'message' => __( 'Kon mapping niet opslaan', 'genius-commerce' ), ) ); } } }