Automattic\WooCommerce\Internal\ProductAttributesLookup

LookupDataStore::create_data_for_product_cpt_core()privateWC 1.0

Core version of create_data_for_product_cpt (doesn't catch exceptions).

Метод класса: LookupDataStore{}

Хуков нет.

Возвращает

null. Ничего (null).

Использование

// private - только в коде основоного (родительского) класса
$result = $this->create_data_for_product_cpt_core( $product_id );
$product_id(int) (обязательный)
Product or variation id.

Код LookupDataStore::create_data_for_product_cpt_core() WC 9.3.3

private function create_data_for_product_cpt_core( int $product_id ) {
	global $wpdb;

	// phpcs:disable WordPress.DB.PreparedSQL
	$sql = $wpdb->prepare(
		"delete from {$this->lookup_table_name} where product_or_parent_id=%d",
		$product_id
	);
	$wpdb->query( $sql );
	// phpcs:enable WordPress.DB.PreparedSQL

	// * Obtain list of product variations, together with stock statuses; also get the product type.
	// For a variation this will return just one entry, with type 'variation'.
	// Output: $product_ids_with_stock_status = associative array where 'id' is the key and values are the stock status (1 for "in stock", 0 otherwise).
	// $variation_ids = raw list of variation ids.
	// $is_variable_product = true or false.
	// $is_variation = true or false.

	$sql = $wpdb->prepare(
		"(select p.ID as id, null parent, m.meta_value as stock_status, t.name as product_type from {$wpdb->posts} p
		left join {$wpdb->postmeta} m on p.id=m.post_id and m.meta_key='_stock_status'
		left join {$wpdb->term_relationships} tr on tr.object_id=p.id
		left join {$wpdb->term_taxonomy} tt on tt.term_taxonomy_id=tr.term_taxonomy_id
		left join {$wpdb->terms} t on t.term_id=tt.term_id
		where p.post_type = 'product'
		and p.post_status in ('publish', 'draft', 'pending', 'private')
		and tt.taxonomy='product_type'
		and t.name != 'exclude-from-search'
		and p.id=%d
		limit 1)
			union
		(select p.ID as id, p.post_parent as parent, m.meta_value as stock_status, 'variation' as product_type from {$wpdb->posts} p
		left join {$wpdb->postmeta} m on p.id=m.post_id and m.meta_key='_stock_status'
		where p.post_type = 'product_variation'
		and p.post_status in ('publish', 'draft', 'pending', 'private')
		and (p.ID=%d or p.post_parent=%d));
	",
		$product_id,
		$product_id,
		$product_id
	);

	// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	$product_ids_with_stock_status = $wpdb->get_results( $sql, ARRAY_A );

	$main_product_row = array_filter( $product_ids_with_stock_status, fn( $item ) => 'variation' !== $item['product_type'] );
	$is_variation     = empty( $main_product_row );

	$main_product_id =
		$is_variation ?
		current( $product_ids_with_stock_status )['parent'] :
		$product_id;

	$is_variable_product = ! $is_variation && ( 'variable' === current( $main_product_row )['product_type'] );

	$product_ids_with_stock_status = ArrayUtil::group_by_column( $product_ids_with_stock_status, 'id', true );
	$variation_ids                 = $is_variation ? array( $product_id ) : array_keys( array_diff_key( $product_ids_with_stock_status, array( $product_id => null ) ) );
	$product_ids_with_stock_status = ArrayUtil::select( $product_ids_with_stock_status, 'stock_status' );

	$product_ids_with_stock_status = array_map( fn( $item ) => 'instock' === $item ? 1 : 0, $product_ids_with_stock_status );

	// * Obtain the list of attributes used for variations and not.
	// Output: two lists of attribute slugs, all starting with 'pa_'.

	$sql = $wpdb->prepare(
		"select meta_value from {$wpdb->postmeta} where post_id=%d and meta_key=%s",
		$main_product_id,
		'_product_attributes'
	);

	// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	$temp = $wpdb->get_var( $sql );

	if ( is_null( $temp ) ) {
		// The product has no attributes, thus there's no attributes lookup data to generate.
		return;
	}

	// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
	$temp = unserialize( $temp );
	if ( false === $temp ) {
		throw new \WC_Data_Exception( 0, 'The product attributes metadata row is not properly serialized' );
	}

	$temp = array_filter( $temp, fn( $item, $slug ) => StringUtil::starts_with( $slug, 'pa_' ) && '' === $item['value'], ARRAY_FILTER_USE_BOTH );

	$attributes_not_for_variations =
		$is_variation || $is_variable_product ?
		array_keys( array_filter( $temp, fn( $item ) => 0 === $item['is_variation'] ) ) :
		array_keys( $temp );

	// * Obtain the terms used for each attribute.
	// Output: $terms_used_per_attribute =
	// [
	// 'pa_...' => [
	// [
	// 'term_id' => <term id>,
	// 'attribute' => 'pa_...'
	// 'slug' => <term slug>
	// ],...
	// ],...
	// ]

	$sql = $wpdb->prepare(
		"select tt.term_id, tt.taxonomy as attribute, t.slug from {$wpdb->prefix}term_relationships tr
		join {$wpdb->term_taxonomy} tt on tt.term_taxonomy_id = tr.term_taxonomy_id
		join {$wpdb->terms} t on t.term_id=tt.term_id
		where tr.object_id=%d and taxonomy like %s;",
		$main_product_id,
		'pa_%'
	);

	// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	$terms_used_per_attribute = $wpdb->get_results( $sql, ARRAY_A );
	foreach ( $terms_used_per_attribute as &$term ) {
		$term['attribute'] = strtolower( rawurlencode( $term['attribute'] ) );
	}
	$terms_used_per_attribute = ArrayUtil::group_by_column( $terms_used_per_attribute, 'attribute' );

	// * Obtain the actual variations defined (only if variations exist).
	// Output: $variations_defined =
	// [
	// <variation id> => [
	// [
	// 'variation_id' => <variation id>,
	// 'attribute' => 'pa_...'
	// 'slug' => <term slug>
	// ],...
	// ],...
	// ]
	//
	// Note that this does NOT include "any..." attributes!

	if ( ! $is_variation && ( ! $is_variable_product || empty( $variation_ids ) ) ) {
		$variations_defined = array();
	} else {
		$sql = $wpdb->prepare(
			"select post_id as variation_id, substr(meta_key,11) as attribute, meta_value as slug from {$wpdb->postmeta}
			where post_id in (select ID from {$wpdb->posts} where (id=%d or post_parent=%d) and post_type = 'product_variation')
			and meta_key like %s
			and meta_value != ''",
			$product_id,
			$product_id,
			'attribute_pa_%'
		);
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		$variations_defined = $wpdb->get_results( $sql, ARRAY_A );
		$variations_defined = ArrayUtil::group_by_column( $variations_defined, 'variation_id' );
	}

	// Now we'll fill an array with all the data rows to be inserted in the lookup table.

	$insert_data = array();

	// * Insert data for the main product

	if ( ! $is_variation ) {
		foreach ( $attributes_not_for_variations as $attribute_name ) {
			foreach ( ( $terms_used_per_attribute[ $attribute_name ] ?? array() ) as $attribute_data ) {
				$insert_data[] = array( $product_id, $main_product_id, $attribute_name, $attribute_data['term_id'], 0, $product_ids_with_stock_status[ $product_id ] );
			}
		}
	}

	// * Insert data for the variations defined

	// Remove the non-variation attributes data first.
	$terms_used_per_attribute = array_diff_key( $terms_used_per_attribute, array_flip( $attributes_not_for_variations ) );

	$used_attributes_per_variation = array();
	foreach ( $variations_defined as $variation_id => $variation_data ) {
		$used_attributes_per_variation[ $variation_id ] = array();
		foreach ( $variation_data as $variation_attribute_data ) {
			$attribute_name                                   = $variation_attribute_data['attribute'];
			$used_attributes_per_variation[ $variation_id ][] = $attribute_name;
			$term_id = current( array_filter( ( $terms_used_per_attribute[ $attribute_name ] ?? array() ), fn( $item ) => $item['slug'] === $variation_attribute_data['slug'] ) )['term_id'] ?? null;
			if ( is_null( $term_id ) ) {
				continue;
			}
			$insert_data[] = array( $variation_id, $main_product_id, $attribute_name, $term_id, 1, $product_ids_with_stock_status[ $variation_id ] ?? false );
		}
	}

	// * Insert data for variations that have "any..." attributes and at least one defined attribute

	foreach ( $used_attributes_per_variation as $variation_id => $attributes_list ) {
		$any_attributes = array_diff_key( $terms_used_per_attribute, array_flip( $attributes_list ) );
		foreach ( $any_attributes as $attributes_data ) {
			foreach ( $attributes_data as $attribute_data ) {
				$insert_data[] = array( $variation_id, $main_product_id, $attribute_data['attribute'], $attribute_data['term_id'], 1, $product_ids_with_stock_status[ $variation_id ] ?? false );
			}
		}
	}

	// * Insert data for variations that have all their attributes defined as "any..."

	$variations_with_all_any = array_keys( array_diff_key( array_flip( $variation_ids ), $used_attributes_per_variation ) );
	foreach ( $variations_with_all_any as $variation_id ) {
		foreach ( $terms_used_per_attribute as $attribute_name => $attribute_terms ) {
			foreach ( $attribute_terms as $attribute_term ) {
				$insert_data[] = array( $variation_id, $main_product_id, $attribute_name, $attribute_term['term_id'], 1, $product_ids_with_stock_status[ $variation_id ] ?? false );
			}
		}
	}

	// * We have all the data to insert, let's go and insert it.

	$insert_data_chunks = array_chunk( $insert_data, 100 );
	foreach ( $insert_data_chunks as $insert_data_chunk ) {
		$sql = 'INSERT INTO ' . $this->lookup_table_name . ' (
				  product_id,
				  product_or_parent_id,
				  taxonomy,
				  term_id,
				  is_variation_attribute,
				  in_stock)
				VALUES (';

		$values_strings = array();
		foreach ( $insert_data_chunk as $dataset ) {
			$attribute_name   = esc_sql( $dataset[2] );
			$values_strings[] = "{$dataset[0]},{$dataset[1]},'{$attribute_name}',{$dataset[3]},{$dataset[4]},{$dataset[5]}";
		}

		$sql .= implode( '),(', $values_strings ) . ')';

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		$result = $wpdb->query( $sql );
		if ( false === $result ) {
			throw new \WC_Data_Exception(
				0,
				'INSERT statement failed',
				0,
				array(
					'db_error' => esc_html( $wpdb->last_error ),
					'db_query' => esc_html( $wpdb->last_query ),
				)
			);
		}
	}
}