modules = $modules; } public function register(): void { add_action('admin_menu', [$this, 'addMenuPage']); add_action('admin_init', [$this, 'registerSettings']); add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']); add_action('admin_notices', [$this, 'adminNotices']); } // Branding private function getMenuIcon(): string { $svg = '' . '' . ''; return 'data:image/svg+xml;base64,' . base64_encode($svg); } // Menu & settings registration public function addMenuPage(): void { add_menu_page( __('Aikanen', 'aikanen'), __('Aikanen', 'aikanen'), 'manage_woocommerce', 'aikanen', [$this, 'render'], $this->getMenuIcon(), 56 ); add_submenu_page( 'aikanen', __('Settings', 'aikanen'), __('Settings', 'aikanen'), 'manage_woocommerce', 'aikanen', [$this, 'render'], ); add_submenu_page( 'aikanen', __('Products', 'aikanen'), __('Products', 'aikanen'), 'manage_woocommerce', 'aikanen-products', [$this, 'renderProducts'], ); add_submenu_page( 'aikanen', __('Categories', 'aikanen'), __('Categories', 'aikanen'), 'manage_woocommerce', 'aikanen-categories', [$this, 'renderCategories'], ); } public function registerSettings(): void { $requiresToken = fn($v) => get_option('aikanen_api_token') ? rest_sanitize_boolean($v) : false; // Connection (API and Webhook) register_setting('aikanen', 'aikanen_api_token', [ 'type' => 'string', 'sanitize_callback' => function ($value) { $value = sanitize_text_field($value); return $value !== '' ? $value : get_option('aikanen_api_token', ''); }, ]); register_setting('aikanen', 'aikanen_webhook_secret', [ 'type' => 'string', 'sanitize_callback' => function ($value) { $value = trim($value); return $value !== '' ? $value : get_option('aikanen_webhook_secret', ''); }, ]); // VAT mapping register_setting('aikanen', ProductImportMapper::OPTION_VAT_MAP, [ 'type' => 'array', 'sanitize_callback' => [$this, 'sanitizeVatMap'], ]); // Automatic sync toggles foreach ([ 'aikanen_auto_export_category', 'aikanen_auto_export_product', 'aikanen_auto_import_category', 'aikanen_auto_import_product', 'aikanen_auto_stock_sync', ] as $option) { register_setting('aikanen', $option, [ 'type' => 'boolean', 'default' => false, 'sanitize_callback' => $requiresToken, ]); } register_setting('aikanen', 'aikanen_stock_source_of_truth', [ 'type' => 'string', 'default' => 'aikanen', 'sanitize_callback' => fn ($v) => in_array($v, ['aikanen', 'woocommerce', 'bidirectional']) ? $v : 'aikanen', ]); register_setting('aikanen', 'aikanen_stock_full_sync_interval', [ 'type' => 'string', 'default' => 'disabled', 'sanitize_callback' => fn($v) => in_array($v, ['disabled', 'hourly', 'daily'], true) ? $v : 'disabled', ]); } public function sanitizeVatMap(mixed $input): array { if (!is_array($input)) { return ProductImportMapper::DEFAULT_VAT_MAP; } $validClasses = array_merge([''], \WC_Tax::get_tax_class_slugs()); $map = []; foreach (['1', '2', '3', '4'] as $vatId) { $value = (string) ($input[$vatId] ?? ''); $map[$vatId] = in_array($value, $validClasses, true) ? $value : (ProductImportMapper::DEFAULT_VAT_MAP[$vatId] ?? ''); } return $map; } // Assets public function enqueueAssets(string $hook): void { if (!in_array($hook, ['toplevel_page_aikanen', 'aikanen_page_aikanen-products', 'aikanen_page_aikanen-categories'], true)) return; wp_enqueue_style('aikanen-admin', plugins_url('assets/css/admin.css', AIKANEN_PLUGIN_FILE), [], AIKANEN_VERSION); wp_enqueue_script('aikanen-admin', plugins_url('assets/js/admin.js', AIKANEN_PLUGIN_FILE), [], AIKANEN_VERSION, true); if ($hook === 'toplevel_page_aikanen') { wp_add_inline_script('aikanen-admin', ' (function () { var interval = document.querySelector("[name=\"aikanen_stock_full_sync_interval\"]"); var sourceRow = document.getElementById("aikanen-stock-source-row"); if (!interval || !sourceRow) return; interval.addEventListener("change", function () { sourceRow.hidden = this.value === "disabled"; }); })(); '); } if ($hook === 'aikanen_page_aikanen-products') { wp_add_inline_script('aikanen-admin', ' document.getElementById("aikanen-select-all")?.addEventListener("change", function () { document.querySelectorAll("input[name=\"services[]\"]").forEach(function (cb) { cb.checked = this.checked; }, this); }); '); } } // Admin notices public function adminNotices(): void { $notice = get_transient('aikanen_notice'); if (!$notice) { return; } delete_transient('aikanen_notice'); printf( '

%s

', esc_attr($notice['type']), esc_html($notice['message']) ); } // Render settings page public function render(): void { if (!current_user_can('manage_woocommerce')) { wp_die(esc_html(__('You do not have permission to access this page.', 'aikanen'))); } $connected = $this->testConnection(); include __DIR__ . '/views/settings.php'; } // Render products page public function renderProducts(): void { if (!current_user_can('manage_woocommerce')) { wp_die(esc_html(__('You do not have permission to access this page.', 'aikanen'))); } $importModule = $this->modules['product_import'] ?? null; $previewData = $importModule ? get_transient('aikanen_preview') : false; $importedCount = $importModule ? $importModule->getImportedCount() : 0; include __DIR__ . '/views/products.php'; } // Render categories page public function renderCategories(): void { if (!current_user_can('manage_woocommerce')) { wp_die(esc_html(__('You do not have permission to access this page.', 'aikanen'))); } $categoryMappingData = get_transient('aikanen_fetched_groups') ?: null; include __DIR__ . '/views/categories.php'; } // Test connection/credentials to Aikanen private function testConnection(): array { $token = get_option('aikanen_api_token'); if (!$token) { return ['status' => 'disconnected', 'company' => null]; } try { (new ApiClient($token))->get('/test'); return ['status' => 'connected']; } catch (\Exception $e) { return ['status' => 'error', 'message' => $e->getMessage()]; } } }