get_currency(); $source = Options::getString(Options::SOURCE) ?: 'woocommerce'; $payload = new ProductInput(); $payload->setTitle($this->resolveTitle($product)); $payload->setSource($source); $payload->setSourceProductId((string) $product->get_id()); $payload->setCurrency($currency); $payload->setProductType($this->resolveProductTypeAlias($product)); $sourceUrl = $this->resolveSourceUrl($product); if ('' !== $sourceUrl) { $payload->setSourceUrl($sourceUrl); } $sku = $product->get_sku(); if ('' !== (string) $sku) { $payload->setSku((string) $sku); } $status = $this->normalizeStatus($this->callString($product, 'get_status')); if ('' !== $status) { $payload->setStatus($status); } $description = $this->resolveDescription($product); if ('' !== $description) { $payload->setDescription($description); } $regularPrice = $this->callString($product, 'get_regular_price'); if ('' !== $regularPrice) { $payload->setRegularPrice(MoneyAmount::toMinorUnits($regularPrice, $currency)); } $salePrice = $this->callString($product, 'get_sale_price'); if ('' !== $salePrice) { $payload->setSalePrice(MoneyAmount::toMinorUnits($salePrice, $currency)); } $unit = apply_filters('aivie_wc_product_unit', 'piece', $product, $order); if (is_string($unit) && '' !== trim($unit)) { $payload->setUnit(trim($unit)); } $virtual = $this->callNullableBool($product, 'is_virtual'); if (null !== $virtual) { $payload->setVirtual($virtual); } $stockStatus = $this->normalizeStockStatus($this->callString($product, 'get_stock_status')); if ('' !== $stockStatus) { $payload->setStockStatus($stockStatus); } $taxTreatment = $this->resolveTaxTreatment($product); if ('' !== $taxTreatment) { $payload->setTaxTreatment($taxTreatment); } $categoryId = apply_filters('aivie_wc_product_category_id', null, $product, $order); if (is_int($categoryId) && $categoryId > 0) { $payload->setCategoryId($categoryId); } $attributes = $this->buildAttributes($product); $attributes = apply_filters('aivie_wc_product_attributes', $attributes, $product, $order); if (is_array($attributes) && ! empty($attributes)) { $payload->setAttributes($attributes); } $filtered = apply_filters('aivie_wc_product_input', $payload, $product, $order); return $filtered instanceof ProductInput ? $filtered : $payload; } private function resolveTitle(\WC_Product $product): string { $title = $this->callString($product, 'get_name'); if ('' !== $title) { return $title; } $sku = $product->get_sku(); if ('' !== (string) $sku) { return (string) $sku; } return 'WooCommerce product ' . $product->get_id(); } private function resolveDescription(\WC_Product $product): string { $description = $this->cleanText($product->get_short_description()); if ('' !== $description) { return $description; } return $this->cleanText($this->callString($product, 'get_description')); } private function resolveSourceUrl(\WC_Product $product): string { $permalink = method_exists($product, 'get_permalink') ? $product->get_permalink() : false; if (is_string($permalink) && '' !== $permalink) { return $permalink; } $postId = $product instanceof \WC_Product_Variation ? $product->get_parent_id() : $product->get_id(); $url = get_permalink($postId); return is_string($url) ? $url : ''; } private function resolveProductTypeAlias(\WC_Product $product): string { $type = $this->callString($product, 'get_type'); $default = match ($type) { 'subscription', 'subscription_variation', 'variable-subscription' => 'woocommerce_subscription', 'external' => 'woocommerce_external_product', default => 'woocommerce_product', }; $alias = apply_filters('aivie_wc_product_type_alias', $default, $product); if (is_string($alias) && 1 === preg_match('/^[a-z][a-z0-9_]*$/', $alias)) { return $alias; } return $default; } private function normalizeStatus(string $status): string { return match ($status) { 'publish' => 'active', 'trash' => 'archived', 'draft', 'pending', 'private' => 'inactive', default => $status, }; } private function normalizeStockStatus(string $status): string { return match ($status) { 'instock' => 'in_stock', 'outofstock' => 'out_of_stock', 'onbackorder' => 'on_backorder', default => $status, }; } private function resolveTaxTreatment(\WC_Product $product): string { return match ($product->get_tax_class()) { '' => TaxTreatments::DEFAULT, 'zero-rate' => 'zero_rated', 'reduced-rate' => 'domestic_reduced', default => '', }; } private function buildAttributes(\WC_Product $product): array { $productId = $product->get_id(); $variationId = $product instanceof \WC_Product_Variation ? $productId : 0; $parentId = $product instanceof \WC_Product_Variation ? $product->get_parent_id() : $productId; $attributes = [ 'woocommerce' => $this->removeEmpty([ 'product_id' => $parentId, 'variation_id' => $variationId, 'type' => $this->callString($product, 'get_type'), ]), 'catalog' => $this->removeEmpty([ 'categories' => $this->getTermNames($parentId, 'product_cat'), 'tags' => $this->getTermNames($parentId, 'product_tag'), ]), 'inventory' => $this->removeEmpty([ 'manage_stock' => $this->callNullableBool($product, 'managing_stock'), 'stock_quantity' => $this->callNullableInt($product, 'get_stock_quantity'), ]), 'shipping' => $this->removeEmpty([ 'weight' => $this->callString($product, 'get_weight'), 'length' => $this->callString($product, 'get_length'), 'width' => $this->callString($product, 'get_width'), 'height' => $this->callString($product, 'get_height'), ]), 'delivery' => $this->removeEmpty([ 'downloadable' => $this->callNullableBool($product, 'is_downloadable'), ]), ]; if ($product instanceof \WC_Product_Variation) { $attributes['variation'] = $this->removeEmpty($product->get_variation_attributes()); } return $this->removeEmpty($attributes); } private function getTermNames(int $productId, string $taxonomy): array { $terms = get_the_terms($productId, $taxonomy); if (! is_array($terms)) { return []; } return array_map(static fn(\WP_Term $term): string => $term->name, $terms); } private function cleanText(string $value): string { return trim(html_entity_decode(wp_strip_all_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8')); } private function callString(object $object, string $method): string { if (! method_exists($object, $method)) { return ''; } $value = $object->{$method}(); return is_scalar($value) ? trim((string) $value) : ''; } private function callNullableBool(object $object, string $method): ?bool { if (! method_exists($object, $method)) { return null; } return (bool) $object->{$method}(); } private function callNullableInt(object $object, string $method): ?int { if (! method_exists($object, $method)) { return null; } $value = $object->{$method}(); return null === $value ? null : (int) $value; } private function removeEmpty(array $values): array { return array_filter( $values, static fn(mixed $value): bool => null !== $value && '' !== $value && [] !== $value ); } }