6 || ! in_array( $intent, array( 'next', 'save_draft', 'change_language' ), true ) ) { RequestGuard::reject( 400 ); } $setup = AuditRuntime::setup(); try { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Schema selects only known bounded scalar/list fields before individually unslashing them; malformed shapes fail before any write. $raw = SetupInputSchema::normalize( $_POST['lmcc'] ?? null, $step, $setup->get_settings() ); } catch ( \InvalidArgumentException $exception ) { unset( $exception ); RequestGuard::reject( 400 ); } $binding_errors = 4 === $step ? self::maybe_bind_terms_page( $raw ) : array(); $facts = AuditRuntime::environment(); // Persist a sanitized draft first so unrelated valid input survives validation errors. $setup->save_step( $step, $raw, false ); if ( ! empty( $binding_errors ) ) { self::redirect( $step, array( 'lmcc_errors' => self::encode_errors( $binding_errors, $step ) ) ); } if ( in_array( $intent, array( 'save_draft', 'change_language' ), true ) ) { self::redirect( $step, array( 'lmcc_draft_saved' => 1 ) ); } $errors = $setup->validate_step( $step, $raw, false, $facts ); if ( ! empty( $errors ) ) { self::redirect( $step, array( 'lmcc_errors' => self::encode_errors( $errors, $step ) ) ); } $setup->save_step( $step, $raw, true ); if ( 6 === $step ) { $completion_errors = $setup->validate_completion( $facts ); if ( ! empty( $completion_errors ) ) { $first_step = (int) $completion_errors[0]['step']; self::redirect( $first_step, array( 'lmcc_errors' => self::encode_errors( $completion_errors ), 'lmcc_finish_blocked' => 1, ) ); } } self::redirect( min( SetupService::TOTAL_STEPS, $step + 1 ), array( 'lmcc_saved' => 1 ) ); } public static function handle_finish(): void { self::require_capability(); RequestGuard::require_post(); if ( ! wp_verify_nonce( RequestGuard::nonce( 'lmcc_finish_setup' ), 'lmcc_finish_setup' ) ) { RequestGuard::reject( 403 ); } if ( ! isset( $_POST['lmcc_intent'] ) || ! is_string( $_POST['lmcc_intent'] ) || ! in_array( $_POST['lmcc_intent'], array( 'audit', 'finish' ), true ) ) { RequestGuard::reject( 400 ); } $setup = AuditRuntime::setup(); $facts = AuditRuntime::environment(); $errors = $setup->validate_completion( $facts ); $intent = isset( $_POST['lmcc_intent'] ) ? sanitize_key( wp_unslash( $_POST['lmcc_intent'] ) ) : 'audit'; if ( ! empty( $errors ) ) { self::redirect( 7, array( 'lmcc_errors' => self::encode_errors( $errors ), 'lmcc_finish_blocked' => 1, ) ); } if ( 'finish' === $intent ) { $report = AuditRuntime::last_report(); if ( $report instanceof AuditReport && ! $report->has_blockers() ) { $setup->mark_complete(); wp_safe_redirect( add_query_arg( array( 'page' => 'leanmerce-commerce-compliance', 'lmcc_completed' => 1, ), admin_url( 'admin.php' ) ) ); exit; } } $report = AuditRuntime::run_audit( true ); self::redirect( 7, array( 'lmcc_audited' => 1, 'lmcc_problems' => $report->counts()['blocker'], ) ); } public static function render(): void { self::require_capability(); $facts = AuditRuntime::environment(); $settings = AuditRuntime::setup()->prefill_from_environment( $facts, false ); $state = AuditRuntime::setup()->get_state(); $step = self::requested_step( (int) $state['current_step'] ); $language = self::language( $settings ); $errors = self::request_errors(); $completion = AuditRuntime::setup()->step_completion( $facts ); $last_report = AuditRuntime::last_report(); $completion[7] = $last_report instanceof AuditReport && ! $last_report->has_blockers(); ?>

· Free/Core

1 ) : ?>
$settings @param array $facts @param array $errors */ private static function render_step( int $step, array $settings, array $facts, string $language, array $errors ): void { switch ( $step ) { case 1: self::step_languages( $settings, $facts, $language, $errors ); break; case 2: self::step_business( $settings, $language, $errors ); break; case 3: self::step_sales( $settings, $language, $errors ); break; case 4: self::step_terms( $settings, $facts, $language, $errors ); break; case 5: self::step_delivery( $settings, $facts, $language, $errors ); break; case 6: self::step_documents( $settings, $facts, $language, $errors ); break; } } private static function step_languages( array $settings, array $facts, string $language, array $errors ): void { $languages = (array) $settings['languages']; $state = AuditRuntime::setup()->get_state(); $is_new = '' === (string) ( $state['updated_at'] ?? '' ); self::section_heading( $language, 'language_title', 'language_intro' ); ?>
'; } echo '
' . esc_html( self::t( $language, 'document_language' ) ) . '
'; self::language_select( 'default_document_language', 'document_language', (string) $languages['default_document_language'], $language, $errors, false, 'document_language_help' ); echo '
' . esc_html( self::t( $language, 'customer_languages' ) ) . ' ' . esc_html( self::t( $language, 'required' ) ) . '
'; foreach ( LanguageCatalog::supported_codes() as $code ) { echo ''; } echo '

' . esc_html( self::t( $language, 'customer_languages_help' ) ) . '

'; self::inline_error( 'enabled_customer_languages', $language, $errors ); echo '
'; ?>
order_storage
checkout_mode
currency
site_locale
'; self::field( 'legal_name', 'legal_name', $m['legal_name'], $language, $errors, true, 'text', 'legal_name_help' ); self::field( 'registered_name', 'registered_name', $m['registered_name'], $language, $errors, false, 'text', 'registered_name_help' ); self::field( 'registration_number', 'registration_number', $m['registration_number'], $language, $errors, true, 'text', 'registration_number_help' ); self::field( 'registration_authority', 'registration_authority', $m['registration_authority'], $language, $errors, true, 'text', 'registration_authority_help' ); self::field( 'tax_id', 'tax_id', $m['tax_id'], $language, $errors, true, 'text', 'tax_id_help' ); self::checkbox( 'vat_registered', 'vat_registered', $m['vat_registered'], $language, $errors ); self::field( 'vat_number', 'vat_number', $m['vat_number'], $language, $errors, false ); self::field( 'address', 'address', $m['address'], $language, $errors, true ); self::field( 'city', 'city', $m['city'], $language, $errors, true ); self::field( 'postcode', 'postcode', $m['postcode'], $language, $errors, true ); self::field( 'phone', 'phone', $m['phone'], $language, $errors, true, 'tel', 'phone_help', 'phone_placeholder' ); self::field( 'email', 'email', $m['email'], $language, $errors, true, 'email' ); self::field( 'site', 'site', $m['site'], $language, $errors, false, 'url' ); self::select_field( 'establishment', 'establishment', (string) $m['establishment'], array( '' => self::t( $language, 'select' ), 'FBIH' => 'Federacija BiH', 'RS' => 'Republika Srpska', 'BD' => 'Brčko distrikt', ), $language, $errors, true, 'establishment_help' ); echo ''; } private static function step_sales( array $settings, string $language, array $errors ): void { $s = (array) $settings['sales']; $m = (array) $settings['merchant']; self::section_heading( $language, 'sales_title', 'sales_intro' ); echo '

' . esc_html( self::t( $language, 'scope_confirmations_title' ) ) . '

' . esc_html( self::t( $language, 'self_declared' ) ) . '
'; $registration_help = array( 'FBIH' => 'distance_registration_help_fbih', 'RS' => 'distance_registration_help_rs', 'BD' => 'distance_registration_help_bd', )[ (string) ( $m['establishment'] ?? '' ) ] ?? 'distance_registration_help'; self::checkbox( 'distance_sale_registration_confirmed', 'distance_registration', $s['distance_sale_registration_confirmed'], $language, $errors, $registration_help, true ); self::checkbox( 'physical_goods_scope_confirmed', 'physical_scope', $s['physical_goods_scope_confirmed'], $language, $errors, 'physical_scope_help', true ); echo '

' . esc_html( self::t( $language, 'customer_contacts_title' ) ) . '

' . esc_html( self::t( $language, 'customer_contacts_intro' ) ) . '

'; $complaints_email = '' !== (string) $s['complaints_email'] ? $s['complaints_email'] : self::legacy_email( (string) $s['complaints_contact'] ); self::field( 'complaints_email', 'complaints_email', $complaints_email, $language, $errors, false, 'email', 'complaints_email_help', '', 'conditional' ); self::field( 'complaints_phone', 'complaints_phone', $s['complaints_phone'], $language, $errors, false, 'tel', 'complaints_phone_help', 'phone_placeholder' ); echo '
'; self::checkbox( 'returns_same_as_complaints', 'returns_same', $s['returns_same_as_complaints'], $language, $errors ); echo '
'; $returns_email = '' !== (string) $s['returns_email'] ? $s['returns_email'] : self::legacy_email( (string) $s['returns_contact'] ); self::field( 'returns_email', 'returns_email', $returns_email, $language, $errors, false, 'email', '', '', 'conditional' ); self::field( 'returns_phone', 'returns_phone', $s['returns_phone'], $language, $errors, false, 'tel', '', 'phone_placeholder' ); self::field( 'returns_address', 'returns_address', $s['returns_address'], $language, $errors, false, 'text', 'returns_address_help' ); echo '

' . esc_html( self::t( $language, 'returns_postal_title' ) ) . '

' . esc_html( self::t( $language, 'returns_postal_help' ) ) . '

'; self::checkbox( 'returns_use_merchant_address', 'returns_use_merchant_address', $s['returns_use_merchant_address'], $language, $errors ); echo '
'; self::field( 'returns_recipient', 'returns_recipient', $s['returns_recipient'], $language, $errors, false, 'text', '', '', 'conditional' ); self::field( 'returns_street', 'returns_street', $s['returns_street'], $language, $errors, false, 'text', '', '', 'conditional' ); self::field( 'returns_postcode', 'returns_postcode', $s['returns_postcode'], $language, $errors, false, 'text', '', '', 'conditional' ); self::field( 'returns_city', 'returns_city', $s['returns_city'], $language, $errors, false, 'text', '', '', 'conditional' ); self::field( 'returns_country', 'returns_country', $s['returns_country'], $language, $errors, false, 'text', '', '', 'conditional' ); echo '

' . esc_html( self::t( $language, 'customer_information_title' ) ) . '

' . esc_html( self::t( $language, 'self_declared' ) ) . '
'; self::checkbox( 'offer_validity_configured', 'offer_validity', $s['offer_validity_configured'], $language, $errors, 'offer_validity_help', true ); self::checkbox( 'warranty_information_configured', 'warranty_information', $s['warranty_information_configured'], $language, $errors, 'warranty_information_help', true ); self::checkbox( 'sells_across_bih', 'sells_across_bih', $s['sells_across_bih'], $language, $errors, 'sells_across_bih_help' ); echo '
' . esc_html( self::t( $language, 'why_cross_entity' ) ) . '

' . esc_html( self::t( $language, 'cross_entity_legal_review' ) ) . '

'; echo '

' . esc_html( self::t( $language, 'supplier_title' ) ) . '

' . esc_html( self::t( $language, 'supplier_intro' ) ) . '

'; $merchant_name = trim( (string) $m['legal_name'] ); self::select_field( 'supplier_relationship', 'supplier_relationship', (string) $m['supplier_relationship'], array( 'SAME_AS_MERCHANT' => '' !== $merchant_name ? $merchant_name : self::t( $language, 'supplier_same_fallback' ), 'SEPARATE_CONTRACT_SUPPLIER' => self::t( $language, 'supplier_separate' ), ), $language, $errors, true, 'supplier_separate_help', 'data-lmcc-supplier-select' ); echo '
'; self::field( 'separate_supplier_name', 'supplier_name', $m['separate_supplier_name'], $language, $errors, false ); self::field( 'separate_supplier_email', 'supplier_email', $m['separate_supplier_email'], $language, $errors, false, 'email' ); echo '
'; } private static function step_terms( array $settings, array $facts, string $language, array $errors ): void { $t = (array) $settings['terms']; self::section_heading( $language, 'terms_title', 'terms_intro' ); echo '

' . esc_html( self::t( $language, 'terms_page_heading' ) ) . '

'; $terms_ready = ! empty( $facts['terms_page_id'] ) && 'publish' === (string) ( $facts['terms_page_status'] ?? '' ) && ! empty( $facts['terms_page_url_present'] ); echo '' . esc_html( self::t( $language, $terms_ready ? 'connected' : 'needs_setup' ) ) . '
'; if ( $terms_ready ) { echo '
' . esc_html( (string) ( $facts['terms_page_title'] ?? self::t( $language, 'terms_page_heading' ) ) ) . '' . esc_html( self::t( $language, 'published' ) ) . ''; if ( ! empty( $facts['terms_page_url'] ) ) { echo '' . esc_html( self::t( $language, 'open_page' ) ) . ' (' . esc_html( self::t( $language, 'opens_new_tab' ) ) . ')'; } echo '
'; } else { echo ''; } self::inline_error( 'terms_page', $language, $errors ); $pages = array( '' => self::t( $language, 'select_published_terms' ) ); $current = (int) ( $facts['terms_page_option_id'] ?? 0 ) > 0 ? (string) (int) $facts['terms_page_option_id'] : ''; foreach ( (array) ( $facts['published_pages'] ?? array() ) as $page ) { if ( is_array( $page ) && ! empty( $page['id'] ) ) { $pages[ (string) (int) $page['id'] ] = (string) ( $page['title'] ?? '#' . (int) $page['id'] ); } } self::select_field( 'terms_page_id', 'terms_page_select', $current, $pages, $language, $errors, false, 'terms_page_select_help' ); if ( ! empty( $facts['terms_page_id_mismatch'] ) ) { echo '
' . esc_html( self::t( $language, 'terms_resolution_details' ) ) . '

' . esc_html( self::t( $language, 'terms_resolution_mismatch', array( (string) ( $facts['terms_page_option_id'] ?? 0 ), (string) ( $facts['terms_page_wc_id'] ?? 0 ) ) ) ) . '

'; } echo '

' . esc_html( self::t( $language, 'contract_acceptance_heading' ) ) . '

' . esc_html( self::t( $language, 'contract_acceptance_help' ) ) . '

'; self::select_field( 'contract_formation_mode', 'contract_mode', (string) $t['contract_formation_mode'], array( 'ORDER_CREATED' => self::t( $language, 'mode_order_created' ), 'PAYMENT_COMPLETED' => self::t( $language, 'mode_payment_completed' ), 'ORDER_STATUS' => self::t( $language, 'mode_order_status' ), 'CUSTOM_PROVIDER' => self::t( $language, 'mode_custom_provider' ), ), $language, $errors, true, '', 'data-lmcc-contract-mode' ); echo '
'; $statuses = (array) ( $facts['order_statuses'] ?? array() ); $current_status = (string) $t['contract_formation_status']; if ( '' !== $current_status && ! isset( $statuses[ $current_status ] ) ) { $statuses = array( $current_status => self::t( $language, 'saved_status_missing', array( $current_status ) ) ) + $statuses; } self::select_field( 'contract_formation_status', 'contract_status', $current_status, array( '' => self::t( $language, 'select' ) ) + $statuses, $language, $errors, false, 'contract_status_help' ); echo '
'; echo '
'; self::field( 'contract_formation_provider', 'contract_provider', $t['contract_formation_provider'] ?? '', $language, $errors, false ); echo '
'; self::checkbox( 'order_process_information_confirmed', 'order_process', $t['order_process_information_confirmed'], $language, $errors, 'order_process_help', true ); self::checkbox( 'terms_accessible_saveable_confirmed', 'terms_accessible', $t['terms_accessible_saveable_confirmed'], $language, $errors, 'terms_accessible_help', true ); self::checkbox( 'rs_payment_obligation_confirmed', 'rs_payment', $t['rs_payment_obligation_confirmed'], $language, $errors, 'rs_payment_help', true ); echo '

' . wp_kses_post( self::brand_text( self::t( $language, 'legal_confirmation_help' ) ) ) . '

' . esc_html( self::t( $language, 'legal_review_details' ) ) . '

' . esc_html( self::t( $language, 'cross_entity_legal_review' ) ) . '

'; } private static function step_delivery( array $settings, array $facts, string $language, array $errors ): void { $d = (array) $settings['delivery']; $s = (array) $settings['sales']; self::section_heading( $language, 'delivery_title', 'delivery_intro' ); echo '

' . esc_html( self::t( $language, 'payment_methods_heading' ) ) . '

' . esc_html( self::t( $language, 'automatically_checked' ) ) . '
'; self::method_list( (array) ( $facts['payment_gateways'] ?? array() ), $language ); echo '' . esc_html( self::t( $language, 'manage_payment_methods' ) ) . '
'; echo '

' . esc_html( self::t( $language, 'shipping_methods_heading' ) ) . '

' . esc_html( self::t( $language, 'automatically_checked' ) ) . '
'; self::method_list( (array) ( $facts['shipping_methods'] ?? array() ), $language ); echo '' . esc_html( self::t( $language, 'manage_shipping_methods' ) ) . '

' . esc_html( self::t( $language, 'method_detection_limit' ) ) . '

'; echo '

' . esc_html( self::t( $language, 'checkout_review_heading' ) ) . '

' . esc_html( self::t( $language, 'self_declared' ) ) . '
'; self::checkbox( 'payment_delivery_information_confirmed', 'payment_delivery', $s['payment_delivery_information_confirmed'], $language, $errors, 'payment_delivery_help', true ); self::checkbox( 'completed_means_dispatched', 'completed_dispatched', $d['completed_means_dispatched'], $language, $errors, 'completed_help' ); $email_help = ! empty( $facts['pro_active'] ) ? 'customer_email_help_pro' : 'customer_email_help_free'; self::email_select( 'customer_email_id', 'customer_email', (string) $d['customer_email_id'], (array) ( $facts['customer_emails'] ?? array() ), $language, $errors, $email_help ); echo '
'; } private static function step_documents( array $settings, array $facts, string $language, array $errors ): void { $d = (array) $settings['documents']; $l = (array) $settings['languages']; self::section_heading( $language, 'documents_title', 'documents_intro' ); echo '
' . esc_html( self::t( $language, 'pdf_contents_title' ) ) . '

' . esc_html( self::t( $language, 'pdf_contents' ) ) . '

' . esc_html( self::t( $language, 'basic_pdf_notice' ) ) . '

'; self::checkbox( 'basic_pdf_enabled', 'basic_pdf_enabled', $d['basic_pdf_enabled'], $language, $errors ); echo '
'; self::email_select( 'basic_pdf_email_id', 'basic_pdf_email', (string) $d['basic_pdf_email_id'], (array) ( $facts['customer_emails'] ?? array() ), $language, $errors, 'basic_pdf_email_help' ); echo '
' . esc_html( self::t( $language, 'delivery_preview' ) ) . '

' . esc_html( self::t( $language, 'delivery_preview_body' ) ) . '

' . esc_html( self::t( $language, 'pdf_off' ) ) . '

'; echo '

' . esc_html( self::t( $language, 'no_side_effects' ) ) . '

'; $enabled_languages = array_values( array_intersect( LanguageCatalog::supported_codes(), array_map( 'strval', (array) ( $l['enabled_customer_languages'] ?? array() ) ) ) ); $preview_language = in_array( (string) ( $l['default_document_language'] ?? '' ), $enabled_languages, true ) ? (string) $l['default_document_language'] : ( $enabled_languages[0] ?? LanguageCatalog::BOSNIAN ); echo '

' . esc_html( self::t( $language, 'pdf_preview_title' ) ) . '

' . esc_html( self::t( $language, 'pdf_preview_help' ) ) . '

' . esc_html( self::t( $language, 'preview_language_help' ) ) . '

' . esc_html( self::t( $language, 'view_pdf' ) ) . ' ' . esc_html( self::t( $language, 'download_sample' ) ) . '

'; echo '
' . esc_html( self::t( $language, 'pro_boundary' ) ) . '

' . esc_html( self::t( $language, ! empty( $facts['pro_active'] ) ? 'pro_active_pdf_note' : 'pro_available_pdf_note' ) ) . '

'; } private static function render_audit_step( string $language ): void { $report = AuditRuntime::last_report(); self::section_heading( $language, 'audit_title', 'audit_intro' ); echo '
'; if ( $report instanceof AuditReport ) { self::render_audit_report( $report, $language ); } else { echo '

' . esc_html( self::t( $language, 'audit_not_run' ) ) . '

'; } $can_finish = $report instanceof AuditReport && ! $report->has_blockers(); if ( $can_finish ) { echo '
' . esc_html( self::t( $language, 'setup_ready_title' ) ) . '

' . esc_html( self::t( $language, 'setup_ready_body' ) ) . '

'; } echo '
'; wp_nonce_field( 'lmcc_finish_setup' ); echo '

' . esc_html( self::t( $language, 'free_boundary' ) ) . '

'; } public static function render_audit_report( AuditReport $report, string $language ): void { $counts = $report->counts(); echo '

' . esc_html( self::t( $language, 'audit_counts', array( (string) $counts['pass'], (string) $counts['warning'], (string) $counts['blocker'] ) ) ) . '

'; $key = $report->has_blockers() ? 'audit_blocker' : ( $counts['warning'] > 0 ? 'audit_warning' : 'audit_pass' ); echo '

' . esc_html( self::t( $language, $key ) ) . '

'; $groups = array( 'blocker' => array( 'audit_fixes', false ), 'warning' => array( 'audit_warnings', false ), 'pass' => array( 'audit_passed', true ), 'not_applicable' => array( 'audit_not_applicable', true ), ); foreach ( $groups as $status => $group ) { $results = array_filter( $report->results(), static fn( $result ): bool => $status === $result->get_status() ); if ( empty( $results ) ) { continue; } echo '
' . esc_html( self::t( $language, $group[0] ) ) . ' ' . esc_html( (string) count( $results ) ) . '
'; foreach ( $results as $result ) { self::audit_finding( $result, $language ); } echo '
'; } } private static function audit_finding( $result, string $language ): void { $mapping = AuditPresentationMap::for_code( $result->get_code() ); $title = $mapping ? self::t( $language, $mapping['title_key'] ) : self::t( $language, 'unmapped_finding' ); $status = $result->get_status(); $reason = $mapping && 'pass' === $status ? WizardTextCatalog::audit_pass( $language, $result->get_code() ) : ( $mapping && in_array( $status, array( 'blocker', 'warning' ), true ) ? self::t( $language, $mapping['issue_key'] ) : self::t( $language, 'finding_' . $status ) ); echo '
' . esc_html( $title ) . '

' . esc_html( $reason ) . '

'; if ( $mapping && in_array( $status, array( 'blocker', 'warning' ), true ) ) { echo '

' . esc_html( self::t( $language, 'what_to_do' ) ) . ' ' . esc_html( self::t( $language, $mapping['fix_key'] ) ) . '

'; } echo '
'; if ( $mapping ) { $target = self::url( $mapping['step'] ); if ( '' !== $mapping['field'] ) { $target .= '#lmcc-field-' . rawurlencode( $mapping['field'] ); } echo '' . esc_html( self::t( $language, in_array( $status, array( 'blocker', 'warning' ), true ) ? 'go_fix' : 'view_setting' ) ) . ''; } else { echo '' . esc_html( self::t( $language, 'mapping_unavailable' ) ) . ''; } $source_rule = '' !== $result->get_source_rule() ? $result->get_source_rule() : '—'; echo '
' . esc_html( self::t( $language, 'diagnostic_details' ) ) . '
' . esc_html( self::t( $language, 'check_id' ) ) . '
' . esc_html( $result->get_code() ) . '
' . esc_html( self::t( $language, 'source_rule' ) ) . '
' . esc_html( $source_rule ) . '
' . esc_html( self::t( $language, 'engine_message' ) ) . '
' . esc_html( $reason ) . '
'; if ( $mapping && in_array( $status, array( 'blocker', 'warning' ), true ) ) { echo '
' . esc_html( self::t( $language, 'engine_remediation' ) ) . '
' . esc_html( self::t( $language, $mapping['fix_key'] ) ) . '
'; } echo '
'; } public static function current_language(): string { return self::language( AuditRuntime::setup()->get_settings() ); } public static function text( string $language, string $key, array $values = array() ): string { return self::t( $language, $key, $values ); } private static function section_heading( string $language, string $title_key, string $intro_key ): void { echo '

' . esc_html( self::t( $language, $title_key ) ) . '

'; if ( '' !== $intro_key ) { $intro = self::t( $language, $intro_key ); echo '

' . ( 'terms_intro' === $intro_key ? wp_kses_post( self::brand_text( $intro ) ) : esc_html( $intro ) ) . '

'; } echo '
'; } private static function field( string $name, string $label_key, $value, string $language, array $errors, bool $required, string $type = 'text', string $help_key = '', string $placeholder_key = '', string $badge_key = '' ): void { $error = self::field_error( $name, $errors ); $id = 'lmcc-field-' . $name; $description_id = $id . '-description'; $badge = '' !== $badge_key ? $badge_key : ( $required ? 'required' : 'optional' ); echo '
'; echo '
'; if ( '' !== $help_key ) { echo '

' . esc_html( self::t( $language, $help_key ) ) . '

'; } self::inline_error( $name, $language, $errors ); echo '
'; } private static function checkbox( string $name, string $label_key, $value, string $language, array $errors, string $help_key = '', bool $completion_required = false ): void { $id = 'lmcc-field-' . $name; echo '
'; if ( '' !== $help_key ) { echo '

' . esc_html( self::t( $language, $help_key ) ) . '

'; } self::inline_error( $name, $language, $errors ); echo '
'; } private static function select_field( string $name, string $label_key, string $current, array $options, string $language, array $errors, bool $required, string $help_key = '', string $extra_attribute = '' ): void { $id = 'lmcc-field-' . $name; echo '
'; if ( '' !== $help_key ) { echo '

' . esc_html( self::t( $language, $help_key ) ) . '

'; } self::inline_error( $name, $language, $errors ); echo '
'; } private static function language_select( string $name, string $label_key, string $current, string $language, array $errors, bool $auto_submit, string $help_key = '' ): void { $options = array(); foreach ( LanguageCatalog::supported_codes() as $code ) { $options[ $code ] = LanguageCatalog::label( $code, $language ); } self::select_field( $name, $label_key, $current, $options, $language, $errors, true, $help_key, $auto_submit ? 'data-lmcc-language-select' : '' ); } private static function checkout_label( string $mode, string $language ): string { if ( 'classic' === $mode ) { return self::t( $language, 'checkout_classic' ); } if ( 'blocks' === $mode ) { return self::t( $language, 'checkout_blocks' ); } return '' !== $mode ? $mode : '—'; } private static function currency_label( string $code, string $language ): string { $code = strtoupper( trim( $code ) ); if ( 'BAM' === $code ) { return self::t( $language, 'currency_bam' ); } if ( '' === $code ) { return '—'; } if ( function_exists( 'get_woocommerce_currencies' ) ) { $currencies = get_woocommerce_currencies(); if ( isset( $currencies[ $code ] ) && '' !== trim( (string) $currencies[ $code ] ) ) { return (string) $currencies[ $code ] . ' (' . $code . ')'; } } return $code; } private static function locale_label( string $locale, string $language ): string { $prefix = strtolower( substr( $locale, 0, 2 ) ); if ( in_array( $prefix, array( 'bs', 'hr', 'sr', 'en' ), true ) ) { return self::t( $language, 'locale_' . $prefix ); } return '' !== $locale ? $locale : '—'; } public static function brand_text( string $text ): string { $parts = explode( 'Leanmerce', $text, 2 ); if ( 2 !== count( $parts ) ) { return esc_html( $text ); } return esc_html( $parts[0] ) . 'Leanmerce' . esc_html( $parts[1] ); } private static function email_select( string $name, string $label_key, string $current, array $emails, string $language, array $errors, string $help_key = '' ): void { $options = array( '' => self::t( $language, 'select_eligible_email' ) ); $current_label = ''; $current_valid = false; foreach ( $emails as $email ) { if ( ! is_array( $email ) || empty( $email['id'] ) ) { continue; } $id = (string) $email['id']; $title = self::email_label( $id, (string) ( $email['title'] ?? $id ), $language ); $eligible = ! empty( $email['eligible'] ) || ( ! array_key_exists( 'eligible', $email ) && ! empty( $email['enabled'] ) && in_array( $id, array( 'customer_processing_order', 'customer_completed_order', 'customer_on_hold_order' ), true ) ); if ( $id === $current ) { $current_label = $title; $current_valid = $eligible; } if ( $eligible ) { $options[ $id ] = $title . ' — ' . self::t( $language, 'enabled' ); } } if ( '' !== $current && ! $current_valid ) { $options = array( $current => ( '' !== $current_label ? $current_label : $current ) . ' — ' . self::t( $language, 'email_needs_replacement' ) ) + $options; } self::select_field( $name, $label_key, $current, $options, $language, $errors, true, $help_key ); } private static function email_label( string $id, string $fallback, string $language ): string { $key = 'email_' . $id; $text = self::t( $language, $key ); return '[' . $key . ']' === $text ? $fallback : $text; } private static function render_edition_note( array $facts, string $language, bool $compact = false ): void { $key = ! empty( $facts['pro_active'] ) ? 'edition_pro_active' : 'edition_free_only'; $class = $compact ? ' lmcc-edition-note--compact' : ''; echo '
' . esc_html( self::t( $language, ! empty( $facts['pro_active'] ) ? 'edition_pro_title' : 'edition_free_title' ) ) . '

' . esc_html( self::t( $language, $key ) ) . '

'; } /** @return array */ private static function maybe_bind_terms_page( array $input ): array { $page_id = isset( $input['terms_page_id'] ) ? absint( $input['terms_page_id'] ) : 0; if ( $page_id <= 0 ) { return array(); } // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce registers this capability. if ( ! current_user_can( 'manage_woocommerce' ) ) { return array( array( 'field' => 'terms_page_id', 'code' => 'terms_page_permission', ), ); } if ( ! function_exists( 'get_post_type' ) || 'page' !== get_post_type( $page_id ) || ! function_exists( 'get_post_status' ) || 'publish' !== get_post_status( $page_id ) ) { return array( array( 'field' => 'terms_page_id', 'code' => 'terms_page_selection', ), ); } if ( function_exists( 'update_option' ) ) { update_option( 'woocommerce_terms_page_id', $page_id, false ); AuditRuntime::repository()->clear_audit_cache(); } return array(); } private static function render_navigation( int $current, array $completion, string $language ): void { echo ''; } private static function render_notices( string $language ): void { $map = array( 'lmcc_saved' => 'step_saved', 'lmcc_draft_saved' => 'draft_saved', 'lmcc_finish_blocked' => 'finish_blocked', 'lmcc_completed' => 'setup_completed', ); foreach ( $map as $query => $text ) { if ( ! empty( $_GET[ $query ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect notice. $type = 'lmcc_finish_blocked' === $query ? 'error' : 'success'; echo '

' . esc_html( self::t( $language, $text ) ) . '

'; } } if ( ! empty( $_GET['lmcc_audited'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect notice. $problems = isset( $_GET['lmcc_problems'] ) ? absint( wp_unslash( $_GET['lmcc_problems'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only count. echo '

' . esc_html( self::t( $language, 'audit_executed', array( (string) $problems ) ) ) . '

'; } } private static function render_error_summary( array $errors, string $language ): void { if ( empty( $errors ) ) { return; } echo ''; } private static function inline_error( string $field, string $language, array $errors ): void { $error = self::field_error( $field, $errors ); if ( $error ) { echo ''; } } private static function field_error( string $field, array $errors ): ?array { foreach ( $errors as $error ) { if ( $field === $error['field'] ) { return $error; } } return null; } private static function error_text( string $language, string $code ): string { return self::t( $language, 'error_' . $code ); } private static function t( string $language, string $key, array $values = array() ): string { return WizardTextCatalog::text( $language, $key, $values ); } private static function language( array $settings ): string { $language = (string) ( $settings['languages']['admin_language'] ?? LanguageCatalog::BOSNIAN ); return in_array( $language, LanguageCatalog::supported_codes(), true ) ? $language : LanguageCatalog::BOSNIAN; } private static function request_errors(): array { $raw = isset( $_GET['lmcc_errors'] ) ? sanitize_text_field( wp_unslash( $_GET['lmcc_errors'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only redirect state. $errors = array(); foreach ( array_filter( explode( ',', $raw ) ) as $encoded ) { $parts = explode( '.', $encoded ); if ( 3 === count( $parts ) ) { $errors[] = array( 'step' => absint( $parts[0] ), 'field' => sanitize_key( $parts[1] ), 'code' => sanitize_key( $parts[2] ), ); } } return $errors; } private static function encode_errors( array $errors, int $default_step = 0 ): string { $encoded = array(); foreach ( $errors as $error ) { $encoded[] = (int) ( $error['step'] ?? $default_step ) . '.' . sanitize_key( $error['field'] ) . '.' . sanitize_key( $error['code'] ); } return implode( ',', $encoded ); } private static function redirect( int $step, array $args = array() ): void { $args['page'] = self::PAGE_SLUG; $args['step'] = $step; wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) ); exit; } private static function url( int $step ): string { return add_query_arg( array( 'page' => self::PAGE_SLUG, 'step' => max( 1, min( SetupService::TOTAL_STEPS, $step ) ), ), admin_url( 'admin.php' ) ); } private static function requested_step( int $fallback ): int { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation. $step = isset( $_GET['step'] ) ? absint( wp_unslash( $_GET['step'] ) ) : $fallback; return max( 1, min( SetupService::TOTAL_STEPS, $step ) ); } private static function require_capability(): void { if ( ! current_user_can( Capabilities::MANAGE_SETTINGS ) ) { wp_die( esc_html( self::t( self::current_language(), 'error_permission' ) ), '', array( 'response' => 403 ) ); } } private static function count_enabled( array $items ): int { $count = 0; foreach ( $items as $item ) { if ( is_array( $item ) && ! empty( $item['enabled'] ) ) { ++$count; } } return $count; } private static function method_list( array $items, string $language ): void { if ( empty( $items ) ) { echo '

' . esc_html( self::t( $language, 'none_detected' ) ) . '

'; return; } echo '
    '; foreach ( $items as $item ) { if ( ! is_array( $item ) ) { continue; } echo '
  • ' . esc_html( (string) ( $item['title'] ?? $item['id'] ?? '—' ) ) . '' . esc_html( self::t( $language, empty( $item['enabled'] ) ? 'disabled' : 'enabled' ) ) . '
  • '; } echo '
'; } private static function legacy_email( string $value ): string { $value = trim( $value ); return false !== filter_var( $value, FILTER_VALIDATE_EMAIL ) ? $value : ''; } private function __construct() {} }