marketplace category ID. * * @var array */ private $map = array(); /** * Constructor. * * @param array $map WooCommerce category ID => marketplace category ID. */ public function __construct( array $map = array() ) { foreach ( $map as $wc => $remote ) { if ( (int) $wc > 0 && (int) $remote > 0 ) { $this->map[ (int) $wc ] = (int) $remote; } } } /** * The map of one channel (or account) out of the stored setting (key => map). * * @param array $setting Stored setting. * @param int|string $key Channel ID, or an account ID. */ public static function from_setting( array $setting, $key = 0 ): self { return new self( (array) ( $setting[ $key ] ?? $setting[ (string) $key ] ?? array() ) ); } /** * The stored setting with this map for one channel (or account). * * @param array $setting Stored setting. * @param int|string $key Channel ID, or an account ID. */ public function to_setting( array $setting, $key = 0 ): array { $setting[ $key ] = $this->map; return $setting; } /** * Maps a WooCommerce category (0 as marketplace category removes the mapping). * * @param int $wc_id WooCommerce category ID. * @param int $remote_id marketplace category ID. */ public function set( int $wc_id, int $remote_id ): void { if ( $remote_id > 0 && $wc_id > 0 ) { $this->map[ $wc_id ] = $remote_id; } else { unset( $this->map[ $wc_id ] ); } } /** * The marketplace category this WooCommerce category is mapped to directly. * * @param int $wc_id WooCommerce category ID. */ public function get( int $wc_id ): ?int { return $this->map[ $wc_id ] ?? null; } /** * All mappings. * * @return array */ public function all(): array { return $this->map; } /** * The marketplace category for a product: the closest mapped category among its categories, looking at each * category and then its parents. A direct mapping beats an inherited one. Among equally close ones the most specific * shop category (the one deepest in the tree) wins, so "Men's jackets" beats a general "Jackets" the product is also * in; any remaining tie goes to the lowest category ID. * * @param int[] $product_category_ids WooCommerce category IDs of the product. * @param callable $ancestors callable( int $id ): int[] parents, nearest first. * @return int|null marketplace category ID, or null when nothing is mapped ("not mapped"). */ public function resolve( array $product_category_ids, callable $ancestors ): ?int { $all = $this->candidates( $product_category_ids, $ancestors ); return $all ? $all[0] : null; } /** * Every marketplace category the product's shop categories lead to, best first (the first one is what resolve() * returns). More than one means the product sits in shop categories that are mapped to different marketplace * categories, so the choice is a guess the owner may want to check. * * @param int[] $product_category_ids WooCommerce category IDs of the product. * @param callable $ancestors callable( int $id ): int[] parents, nearest first. * @return int[] Distinct marketplace category IDs. */ public function candidates( array $product_category_ids, callable $ancestors ): array { $found = array(); foreach ( $product_category_ids as $start ) { $start = (int) $start; $parents = array_map( 'intval', (array) call_user_func( $ancestors, $start ) ); $chain = array_merge( array( $start ), $parents ); foreach ( $chain as $distance => $id ) { if ( isset( $this->map[ $id ] ) ) { // Closest first, then deepest start category, then lowest ID. $found[] = array( $distance, -count( $parents ), $start, $this->map[ $id ] ); break; } } } sort( $found ); return array_values( array_unique( array_column( $found, 3 ) ) ); } }