1

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?

silviubogan
  • 2,416
  • 3
  • 21
  • 38

0 Answers0