WC_Admin_Duplicate_Product::generate_unique_skuprivateWC 10.5.0

Generates a unique sku for a given product.

Appends a numeric suffix to the original SKU. For example, if the original SKU is "SKU-123", the duplicate will get "SKU-123-1". This preserves hyphens in the original SKU rather than treating them as suffix delimiters.

Method of the class: WC_Admin_Duplicate_Product{}

Returns

null. Nothing (null).

Usage

// private - for code of main (parent) class only
$result = $this->generate_unique_sku( $product );
$product(WC_Product) (required)
The product to generate a sku for.

Changelog

Since 10.5.0 Introduced.

WC_Admin_Duplicate_Product::generate_unique_sku() code WC 11.0.0

private function generate_unique_sku( $product ) {
	global $wpdb;

	$original_sku = $product->get_sku();

	// If the product has no SKU, don't do anything.
	if ( '' === $original_sku ) {
		return;
	}

	// If the source SKU is not used by any saved product, use it as-is.
	// This preserves backward compatibility with the REST API duplicate endpoint,
	// which lets callers set a custom SKU on the duplicate and expects that SKU
	// to be applied directly when it is unique.
	$source_sku_in_use = (bool) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->wc_product_meta_lookup} WHERE sku = %s",
			$original_sku
		)
	);

	if ( ! $source_sku_in_use ) {
		$product->set_sku( $original_sku );
		return;
	}

	// Query existing SKUs that match the pattern: original SKU + hyphen + number.
	$existing_skus = $wpdb->get_col(
		$wpdb->prepare(
			"SELECT lookup.sku
				FROM {$wpdb->posts} as posts
				INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id
				WHERE posts.post_type IN ( 'product', 'product_variation' )
				AND lookup.sku LIKE %s",
			$wpdb->esc_like( $original_sku ) . '-%'
		)
	);

	// Find the maximum suffix.
	$max_suffix = 0;
	// +1 for the hyphen separator between original SKU and suffix.
	$prefix_len = strlen( $original_sku ) + 1;
	foreach ( $existing_skus as $existing_sku ) {
		// Check if the SKU starts with our base and has a numeric suffix.
		if ( stripos( $existing_sku, $original_sku . '-' ) === 0 ) {
			$suffix_str = substr( $existing_sku, $prefix_len );
			// Only consider pure numeric suffixes.
			if ( is_numeric( $suffix_str ) ) {
				$suffix = intval( $suffix_str );
				if ( $suffix > $max_suffix ) {
					$max_suffix = $suffix;
				}
			}
		}
	}

	// Set the new SKU with the next available suffix.
	$new_sku    = $original_sku . '-' . ( $max_suffix + 1 );
	$product_id = $product->get_id();

	/**
	 * Gives plugins an opportunity to verify SKU uniqueness themselves. Filter added to keep backwards
	 * compatibility with `wc_product_has_unique_sku()`.
	 * See: https://github.com/woocommerce/woocommerce/pull/62628
	 *
	 * @since 10.5.0
	 *
	 * @param bool|null $has_unique_sku Set to a boolean value to short-circuit the default SKU check.
	 * @param int       $product_id     The ID of the current product.
	 * @param string    $sku            The SKU to check for uniqueness.
	 */
	$pre_has_unique_sku = apply_filters( 'wc_product_pre_has_unique_sku', true, $product_id, $new_sku );

	if ( $pre_has_unique_sku ) {
		/**
		 * Gives plugins an opportunity to verify SKU uniqueness themselves. Filter added to keep backwards
		 * compatibility with `wc_product_has_unique_sku()`.
		 * See: https://github.com/woocommerce/woocommerce/pull/62628
		 *
		 * @since 10.5.0
		 *
		 * @param bool|null $sku_found Set to a boolean value to short-circuit the default SKU check.
		 * @param int       $product_id The ID of the current product.
		 * @param string    $sku       The SKU to check for uniqueness.
		 */
		$sku_found = apply_filters( 'wc_product_has_unique_sku', false, $product_id, $new_sku );

		if ( ! $sku_found ) {
			$product->set_sku( $new_sku );
			return;
		}
	}

	// Fallback: use the standard unique SKU generator which loops until it finds a unique value.
	$product->set_sku( wc_product_generate_unique_sku( $product_id, $original_sku, $max_suffix + 1 ) );
}