I have to do the following reflection code because the WooCommerce version I have available has a bug (v4.9.2).
Please see the comments in the code below:
// checked before the existence of the class with class_exists
$rp = new ReflectionProperty('WC_Product_Variable_Data_Store_CPT', 'prices_array');
$rp->setAccessible(true);
var_dump('start'); // echoes something to the html code
$protected_prices_array = $rp->getValue($ths); // crashes the PHP script instance
var_dump('stop'); // this is not printed anymore
If requested, I can offer more code.
Currently I am attempting to inherit the given class to see if I can walk around the bug.
On staging site I have PHP 7.4.16.
Update 1
I have this code in my own function my_read_price_data( $ths, &$product, $for_display = false ) { ...
which does the same as WC's data store's read_price_data public method which accesses the prices_array property which is protected.
Update 2
/**
* Modified function from WC.
*
* @param WC_Product_Variable_Data_Store_CPT $ths
* @param WC_Product_Variable $product
* @param boolean $for_display
* @return void
*/
function my_read_price_data( $ths, &$product, $for_display = false ) {
/**
* Transient name for storing prices for this product (note: Max transient length is 45)
*
* @since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
*/
$transient_name = 'wc_var_prices_' . $product->get_id();
$transient_version = WC_Cache_Helper::get_transient_version( 'product' );
$price_hash = my_get_price_hash($ths, $product, $for_display); // with this it does not crash (*)
// NOTE: maybe inherit from WC_Product_Variable_Data_Store_CPT to not use reflection.
$rp = new ReflectionProperty('WC_Product_Variable_Data_Store_CPT', 'prices_array'); // the class exists
$rp->setAccessible(true);
var_dump('start');
$protected_prices_array = $rp->getValue($ths); // (*) until this
var_dump('stop');
// Check if prices array is stale.
if ( ! isset( $protected_prices_array['version'] ) || $protected_prices_array['version'] !== $transient_version ) {
$rp->setValue($ths, array(
'version' => $transient_version,
));
}
$protected_prices_array = $rp->getValue($ths);
/**
* $this->prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array.
* If the value has already been generated, we don't need to grab the values again so just return them. They are already filtered.
*/
if ( empty( $protected_prices_array[ $price_hash ] ) ) {
$transient_cached_prices_array = array_filter( (array) json_decode( strval( get_transient( $transient_name ) ), true ) );
// If the product version has changed since the transient was last saved, reset the transient cache.
if ( ! isset( $transient_cached_prices_array['version'] ) || $transient_version !== $transient_cached_prices_array['version'] ) {
$transient_cached_prices_array = array(
'version' => $transient_version,
);
}
// If the prices are not stored for this hash, generate them and add to the transient.
if ( empty( $transient_cached_prices_array[ $price_hash ] ) ) {
$prices_array = array(
'price' => array(),
'regular_price' => array(),
'sale_price' => array(),
);
$variation_ids = $product->get_visible_children();
if ( is_callable( '_prime_post_caches' ) ) {
_prime_post_caches( $variation_ids );
}
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
if ( $variation ) {
$price = apply_filters( 'woocommerce_variation_prices_price', $variation->get_price( 'edit' ), $variation, $product );
$regular_price = apply_filters( 'woocommerce_variation_prices_regular_price', $variation->get_regular_price( 'edit' ), $variation, $product );
$sale_price = apply_filters( 'woocommerce_variation_prices_sale_price', $variation->get_sale_price( 'edit' ), $variation, $product );
// Skip empty prices.
if ( '' === $price ) {
continue;
}
// If sale price does not equal price, the product is not yet on sale.
if ( $sale_price === $regular_price || $sale_price !== $price ) {
$sale_price = $regular_price;
}
// If we are getting prices for display, we need to account for taxes.
if ( $for_display ) {
if ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ) {
$price = '' === $price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $price,
)
);
$regular_price = '' === $regular_price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $regular_price,
)
);
$sale_price = '' === $sale_price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $sale_price,
)
);
} else {
$price = '' === $price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $price,
)
);
$regular_price = '' === $regular_price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $regular_price,
)
);
$sale_price = '' === $sale_price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $sale_price,
)
);
}
}
$prices_array['price'][ $variation_id ] = wc_format_decimal( $price, wc_get_price_decimals() );
$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
$prices_array['sale_price'][ $variation_id ] = wc_format_decimal( $sale_price, wc_get_price_decimals() );
$prices_array = apply_filters( 'woocommerce_variation_prices_array', $prices_array, $variation, $for_display );
}
}
// Add all pricing data to the transient array.
foreach ( $prices_array as $key => $values ) {
$transient_cached_prices_array[ $price_hash ][ $key ] = $values;
}
set_transient( $transient_name, wp_json_encode( $transient_cached_prices_array ), DAY_IN_SECONDS * 30 );
}
/**
* Give plugins one last chance to filter the variation prices array which has been generated and store locally to the class.
* This value may differ from the transient cache. It is filtered once before storing locally.
*/
$protected_prices_array = $rp->getValue($ths);
$protected_prices_array[$price_hash] = apply_filters( 'woocommerce_variation_prices', $transient_cached_prices_array[ $price_hash ], $product, $for_display );
$rp->setValue($ths, $protected_prices_array);
}
return $rp->getValue($ths)[ $price_hash ];
}
Update 3
The function above, my_read_price_data, is called by:
/**
* Function modified from WC.
*
* @param WC_Product_Variable $p
* @param boolean $for_display
* @return void
*/
function my_get_variation_prices( $p, $for_display = false ) {
$ds = $p->get_data_store(); // $p->data_store;
$prices = my_read_price_data($ds, $p, $for_display);
foreach ( $prices as $price_key => $variation_prices ) {
$prices[ $price_key ] = asort( $variation_prices );
}
return $prices;
}
This is called by the following function which is a modified version of a WC function, but this time the modification is done to change the output to something the client wants:
function my_get_price_html( $price = '' ) {
global $product;
$prices = my_get_variation_prices($product, true);
if ( empty( $prices['price'] ) ) {
$price = apply_filters( 'woocommerce_variable_empty_price_html', '', $product );
} else {
$min_price = current( $prices['price'] );
$max_price = end( $prices['price'] );
$min_reg_price = current( $prices['regular_price'] );
$max_reg_price = end( $prices['regular_price'] );
if ( $min_price !== $max_price ) {
$price = wc_format_price_range( $min_price, $max_price );
} elseif ( $product->is_on_sale() && $min_reg_price === $max_reg_price ) {
$price = my_wc_format_sale_price( $prices['regular_price'] , $prices['price'] );
} else {
$price = wc_price( $min_price );
}
$price = apply_filters( 'woocommerce_variable_price_html', $price . $product->get_price_suffix(), $product );
}
return apply_filters( 'woocommerce_get_price_html', $price, $product );
}
As you can see above, I use my_wc_format_sale_price which is here:
/**
* Format a sale price for display.
*
* @since 3.0.0
* @param float $regular_price Regular price.
* @param float $sale_price Sale price.
* @return string
*/
function my_wc_format_sale_price( $regular_price, $sale_price ) {
$price = '<span>' . get_my_percent($regular_price, $sale_price) . '</span> <ins>' . ( is_numeric( $sale_price ) ? wc_price( $sale_price ) : $sale_price ) . '</ins>';
return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price );
}
Here is the last function that matters, I think (it has a doc comment that says it returns a string):
function get_my_percent($regular_price, $sale_price) {
$a = ($regular_price - $sale_price) / $regular_price * 100;
return "$a% reducere";
}
Update 4
I discovered the following through https://stackoverflow.com/a/21429652/258462.
It seems that the object given to the reflection mechanism is of a different type than the expected type.
From the source code of WooCommerce 4.9.2:
/**
* WC Variable Product Data Store: Stored in CPT.
*
* @version 3.0.0
*/
class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store_Interface, WC_Product_Variable_Data_Store_Interface {
/**
* Cached & hashed prices array for child variations.
*
* @var array
*/
protected $prices_array = array()
...
So the question is how to convert the WC_Data_Store
into something that has the $prices_array
property?