=== $b->get_date_created() ) {
return 0;
}
return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1;
}
/**
* Sort by modified.
*
* @since 3.0.0
* @param WC_Product $a First WC_Product object.
* @param WC_Product $b Second WC_Product object.
* @return int
*/
function wc_products_array_orderby_modified( $a, $b ) {
if ( $a->get_date_modified() === $b->get_date_modified() ) {
return 0;
}
return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1;
}
/**
* Sort by menu order.
*
* @since 3.0.0
* @param WC_Product $a First WC_Product object.
* @param WC_Product $b Second WC_Product object.
* @return int
*/
function wc_products_array_orderby_menu_order( $a, $b ) {
if ( $a->get_menu_order() === $b->get_menu_order() ) {
return 0;
}
return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1;
}
/**
* Sort by price low to high.
*
* @since 3.0.0
* @param WC_Product $a First WC_Product object.
* @param WC_Product $b Second WC_Product object.
* @return int
*/
function wc_products_array_orderby_price( $a, $b ) {
if ( $a->get_price() === $b->get_price() ) {
return 0;
}
return ( $a->get_price() < $b->get_price() ) ? -1 : 1;
}
/**
* Queue a product for syncing at the end of the request.
*
* @param int $product_id Product ID.
*/
function wc_deferred_product_sync( $product_id ) {
global $wc_deferred_product_sync;
if ( empty( $wc_deferred_product_sync ) ) {
$wc_deferred_product_sync = array();
}
$wc_deferred_product_sync[] = $product_id;
}
/**
* See if the lookup table is being generated already.
*
* @since 3.6.0
* @return bool
*/
function wc_update_product_lookup_tables_is_running() {
$table_updates_pending = WC()->queue()->search(
array(
'status' => 'pending',
'group' => 'wc_update_product_lookup_tables',
'per_page' => 1,
)
);
return (bool) count( $table_updates_pending );
}
/**
* Populate lookup table data for products.
*
* @since 3.6.0
*/
function wc_update_product_lookup_tables() {
global $wpdb;
$is_cli = Constants::is_true( 'WP_CLI' );
if ( ! $is_cli ) {
WC_Admin_Notices::add_notice( 'regenerating_lookup_table' );
}
// Note that the table is not yet generated.
update_option( 'woocommerce_product_lookup_table_is_generating', true );
// Make a row per product in lookup table.
$wpdb->query(
"
INSERT IGNORE INTO {$wpdb->wc_product_meta_lookup} (`product_id`)
SELECT
posts.ID
FROM {$wpdb->posts} posts
WHERE
posts.post_type IN ('product', 'product_variation')
"
);
// List of column names in the lookup table we need to populate.
$columns = array(
'min_max_price',
'stock_quantity',
'sku',
'global_unique_id',
'stock_status',
'average_rating',
'total_sales',
'downloadable',
'virtual',
'onsale',
'tax_class',
'tax_status', // When last column is updated, woocommerce_product_lookup_table_is_generating is updated.
);
foreach ( $columns as $index => $column ) {
if ( $is_cli ) {
wc_update_product_lookup_tables_column( $column );
} else {
WC()->queue()->schedule_single(
time() + $index,
'wc_update_product_lookup_tables_column',
array(
'column' => $column,
),
'wc_update_product_lookup_tables'
);
}
}
// Rating counts are serialised so they have to be unserialised before populating the lookup table.
if ( $is_cli ) {
$rating_count_rows = $wpdb->get_results(
"
SELECT post_id, meta_value FROM {$wpdb->postmeta}
WHERE meta_key = '_wc_rating_count'
AND meta_value != ''
AND meta_value != 'a:0:{}'
",
ARRAY_A
);
wc_update_product_lookup_tables_rating_count( $rating_count_rows );
} else {
WC()->queue()->schedule_single(
time() + 10,
'wc_update_product_lookup_tables_rating_count_batch',
array(
'offset' => 0,
'limit' => 50,
),
'wc_update_product_lookup_tables'
);
}
}
/**
* Populate lookup table column data.
*
* @since 3.6.0
* @param string $column Column name to set.
*/
function wc_update_product_lookup_tables_column( $column ) {
if ( empty( $column ) ) {
return;
}
global $wpdb;
switch ( $column ) {
case 'min_max_price':
$wpdb->query(
"
UPDATE
{$wpdb->wc_product_meta_lookup} lookup_table
INNER JOIN (
SELECT lookup_table.product_id, MIN( meta_value+0 ) as min_price, MAX( meta_value+0 ) as max_price
FROM {$wpdb->wc_product_meta_lookup} lookup_table
LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_price'
WHERE
meta1.meta_value <> ''
GROUP BY lookup_table.product_id
) as source on source.product_id = lookup_table.product_id
SET
lookup_table.min_price = source.min_price,
lookup_table.max_price = source.max_price
"
);
break;
case 'stock_quantity':
$wpdb->query(
"
UPDATE
{$wpdb->wc_product_meta_lookup} lookup_table
LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_manage_stock'
LEFT JOIN {$wpdb->postmeta} meta2 ON lookup_table.product_id = meta2.post_id AND meta2.meta_key = '_stock'
SET
lookup_table.stock_quantity = meta2.meta_value
WHERE
meta1.meta_value = 'yes'
"
);
break;
case 'sku':
case 'global_unique_id':
case 'stock_status':
case 'average_rating':
case 'total_sales':
case 'tax_class':
case 'tax_status':
if ( 'total_sales' === $column ) {
$meta_key = 'total_sales';
} elseif ( 'average_rating' === $column ) {
$meta_key = '_wc_average_rating';
} else {
$meta_key = '_' . $column;
}
$column = esc_sql( $column );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
UPDATE
{$wpdb->wc_product_meta_lookup} lookup_table
LEFT JOIN {$wpdb->postmeta} meta ON lookup_table.product_id = meta.post_id AND meta.meta_key = %s
SET
lookup_table.`{$column}` = meta.meta_value
",
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
break;
case 'downloadable':
case 'virtual':
$column = esc_sql( $column );
$meta_key = '_' . $column;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
UPDATE
{$wpdb->wc_product_meta_lookup} lookup_table
LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = %s
SET
lookup_table.`{$column}` = IF ( meta1.meta_value = 'yes', 1, 0 )
",
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
break;
case 'onsale':
$column = esc_sql( $column );
$decimals = absint( wc_get_price_decimals() );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
UPDATE
{$wpdb->wc_product_meta_lookup} lookup_table
LEFT JOIN {$wpdb->postmeta} meta1 ON lookup_table.product_id = meta1.post_id AND meta1.meta_key = '_price'
LEFT JOIN {$wpdb->postmeta} meta2 ON lookup_table.product_id = meta2.post_id AND meta2.meta_key = '_sale_price'
LEFT JOIN {$wpdb->postmeta} meta3 ON lookup_table.product_id = meta3.post_id AND meta3.meta_key = '_regular_price'
SET
lookup_table.`{$column}` = IF (
CAST( meta1.meta_value AS DECIMAL ) >= 0
AND CAST( meta2.meta_value AS CHAR ) != ''
AND CAST( meta1.meta_value AS DECIMAL( 10, %d ) ) = CAST( meta2.meta_value AS DECIMAL( 10, %d ) )
AND CAST( meta3.meta_value AS DECIMAL( 10, %d ) ) > CAST( meta2.meta_value AS DECIMAL( 10, %d ) )
, 1, 0 )
",
$decimals,
$decimals,
$decimals,
$decimals
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
break;
}
// Final column - mark complete.
if ( 'tax_status' === $column ) {
delete_option( 'woocommerce_product_lookup_table_is_generating' );
}
}
add_action( 'wc_update_product_lookup_tables_column', 'wc_update_product_lookup_tables_column' );
/**
* Populate rating count lookup table data for products.
*
* @since 3.6.0
* @param array $rows Rows of rating counts to update in lookup table.
*/
function wc_update_product_lookup_tables_rating_count( $rows ) {
if ( ! $rows || ! is_array( $rows ) ) {
return;
}
global $wpdb;
foreach ( $rows as $row ) {
$count = array_sum( (array) maybe_unserialize( $row['meta_value'] ) );
$wpdb->update(
$wpdb->wc_product_meta_lookup,
array(
'rating_count' => absint( $count ),
),
array(
'product_id' => absint( $row['post_id'] ),
)
);
}
}
/**
* Populate a batch of rating count lookup table data for products.
*
* @since 3.6.2
* @param array $offset Offset to query.
* @param array $limit Limit to query.
*/
function wc_update_product_lookup_tables_rating_count_batch( $offset = 0, $limit = 0 ) {
global $wpdb;
if ( ! $limit ) {
return;
}
$rating_count_rows = $wpdb->get_results(
$wpdb->prepare(
"
SELECT post_id, meta_value FROM {$wpdb->postmeta}
WHERE meta_key = '_wc_rating_count'
AND meta_value != ''
AND meta_value != 'a:0:{}'
ORDER BY post_id ASC
LIMIT %d, %d
",
$offset,
$limit
),
ARRAY_A
);
if ( $rating_count_rows ) {
wc_update_product_lookup_tables_rating_count( $rating_count_rows );
WC()->queue()->schedule_single(
time() + 1,
'wc_update_product_lookup_tables_rating_count_batch',
array(
'offset' => $offset + $limit,
'limit' => $limit,
),
'wc_update_product_lookup_tables'
);
}
}
add_action( 'wc_update_product_lookup_tables_rating_count_batch', 'wc_update_product_lookup_tables_rating_count_batch', 10, 2 );
/**
* Attach product featured image. Use image filename to match a product sku when product is not provided.
*
* @since 8.5.0
* @param int $attachment_id Media attachment ID.
* @param WC_Product $product Optional product object.
* @param bool $save_product If true, the changes in the product will be saved before the method returns.
* @return void
*/
function wc_product_attach_featured_image( $attachment_id, $product = null, $save_product = true ) {
$attachment_post = get_post( $attachment_id );
if ( ! $attachment_post ) {
return;
}
if ( null === $product && wc_get_container()->get( MatchImageBySKU::class )->is_enabled() ) {
// On upload the attachment post title is the uploaded file's filename.
$file_name = pathinfo( $attachment_post->post_title, PATHINFO_FILENAME );
if ( ! $file_name ) {
return;
}
$product_id = wc_get_product_id_by_sku( $file_name );
$product = wc_get_product( $product_id );
}
if ( ! $product ) {
return;
}
$product->set_image_id( $attachment_id );
if ( $save_product ) {
$product->save();
}
if ( 0 === $attachment_post->post_parent ) {
wp_update_post(
array(
'ID' => $attachment_id,
'post_parent' => $product->get_id(),
)
);
}
}
add_action( 'add_attachment', 'wc_product_attach_featured_image' );