|
<?php |
|
class SingleProduct implements DependencyInterface |
|
{ |
|
|
|
private const ATTRIBUTE_PREFIX = 'attribute_'; |
|
|
|
/** |
|
* @param array $product_variation |
|
* @param array $filter_criteria |
|
* @param array $product_atts |
|
* @param array $filtered_options |
|
* @param array $options_order |
|
*/ |
|
private function filterProductVariationOptions( |
|
array $product_variation, |
|
array $filter_criteria, |
|
array &$product_atts, |
|
array &$filtered_options, |
|
array &$options_order |
|
) { |
|
$att_prefix_len = strlen(self::ATTRIBUTE_PREFIX); |
|
extract($product_variation); |
|
|
|
/** |
|
* @var array $attributes |
|
* @var bool $is_in_stock |
|
* @var bool $variation_is_active |
|
* @var bool $variation_is_visible |
|
*/ |
|
|
|
$variation_atts = $attributes; |
|
unset($attributes); |
|
|
|
if ( |
|
! ($variation_is_active && $variation_is_visible && $is_in_stock) || |
|
! $variation_atts |
|
) { |
|
return; |
|
} |
|
|
|
$matched_all = true; |
|
|
|
foreach ((array)$filter_criteria as $att_key => $att_val) { |
|
if ($att_val !== ($variation_atts[ $att_key ] ?? '')) { |
|
$matched_all = false; |
|
break; |
|
} |
|
} |
|
|
|
if (! $matched_all) { |
|
return; |
|
} |
|
|
|
foreach ((array)$variation_atts as $att_key => $att_val) { |
|
$att_name = substr($att_key, $att_prefix_len); |
|
|
|
if ( |
|
! (empty($filter_criteria[ $att_key ]) && |
|
isset($product_atts[ $att_name ])) |
|
) { |
|
continue; |
|
} |
|
|
|
if (isset($filtered_options[ $att_key ])) { |
|
$filtered_options_values = |
|
wp_list_pluck($filtered_options[ $att_key ], 'value'); |
|
|
|
if (in_array($att_val, $filtered_options_values)) { |
|
continue; |
|
} |
|
} |
|
|
|
/** @var WC_Product_Attribute $product_att */ |
|
$product_att = &$product_atts[ $att_name ]; |
|
|
|
if ($product_att->is_taxonomy()) { |
|
$taxonomy = $product_att->get_taxonomy(); |
|
$term = get_term_by('slug', $att_val, $taxonomy); |
|
|
|
if ($term instanceof WP_Term) { |
|
/** @var WP_Term $term */ |
|
$filtered_options[ $att_key ][] = [ |
|
'value' => $term->slug, |
|
'name' => $term->name |
|
]; |
|
} |
|
} else { |
|
$filtered_options[ $att_key ][] = [ |
|
'value' => $att_val, |
|
'name' => $att_val |
|
]; |
|
} |
|
|
|
if (! isset($options_order[ $att_key ])) { |
|
$product_att_options = $product_att->get_options(); |
|
$options_order[ $att_key ] = [ |
|
'attribute' => &$product_att, |
|
'options' => $product_att_options |
|
]; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @param WC_Product|WP_Post|int|bool $product |
|
* @param string $att_name |
|
* |
|
* @return WC_Product_Attribute|null |
|
*/ |
|
private function getProductAttribute( |
|
$product, |
|
string $att_name |
|
): ?WC_Product_Attribute { |
|
$product = ($product instanceof WC_Product) ? $product : |
|
wc_get_product($product); |
|
|
|
if (! $product instanceof WC_Product) { |
|
return null; |
|
} |
|
|
|
$att_prefix_len = strlen(self::ATTRIBUTE_PREFIX); |
|
|
|
if (self::ATTRIBUTE_PREFIX === substr($att_name, 0, $att_prefix_len)) { |
|
$att_name = substr($att_name, $att_prefix_len); |
|
} |
|
|
|
$atts = $product->get_attributes(); |
|
|
|
return $atts[ $att_name ] ?? null; |
|
} |
|
|
|
/** |
|
* @param array $options |
|
* @param array $options_sorted |
|
* @param string $taxonomy |
|
*/ |
|
private function sortProductAttributeOptions( |
|
array &$options, |
|
array $options_sorted, |
|
string $taxonomy |
|
) { |
|
usort( |
|
$options, |
|
function ($a, $b) use ($options_sorted, $taxonomy) { |
|
$return = 0; |
|
|
|
if ($taxonomy) { |
|
$a = get_term_by('slug', $a, $taxonomy); |
|
$b = get_term_by('slug', $b, $taxonomy); |
|
|
|
if (($a instanceof WP_Term) && ($b instanceof WP_Term)) { |
|
/** |
|
* @var WP_Term $a |
|
* @var WP_Term $b |
|
*/ |
|
$a = $a->term_id; |
|
$b = $b->term_id; |
|
} else { |
|
$a = $b = null; |
|
} |
|
} |
|
|
|
$a_index = array_search($a, $options_sorted); |
|
$b_index = array_search($b, $options_sorted); |
|
|
|
if (! in_array(false, [$a_index, $b_index], true)) { |
|
$return = $a_index <=> $b_index; |
|
} |
|
|
|
return $return; |
|
} |
|
); |
|
} |
|
|
|
public function ajaxFilterProductAttributeOptions(): void |
|
{ |
|
$filtered_options = $options_order = $product_atts = []; |
|
$input_vars = wp_parse_args($_POST, ['product_id' => 0]); |
|
$product_id = absint($input_vars[ 'product_id' ]); |
|
$product = wc_get_product($product_id); |
|
$att_prefix = 'attribute_'; |
|
$att_prefix_len = strlen($att_prefix); |
|
|
|
$input_atts = []; |
|
foreach ((array)$input_vars as $att_key => $val) { |
|
if (substr($att_key, 0, $att_prefix_len) === $att_prefix) { |
|
$input_atts[ $att_key ] = $val; |
|
} |
|
} |
|
|
|
if ($product instanceof WC_Product_Variable) { |
|
$variations = $product->get_available_variations(); |
|
$product_atts = $product->get_attributes(); |
|
|
|
foreach ((array)$variations as $variation) { |
|
$this->filterProductVariationOptions( |
|
$variation, |
|
$input_atts, |
|
$product_atts, |
|
$filtered_options, |
|
$options_order |
|
); |
|
} |
|
}; |
|
|
|
foreach ((array)$input_vars as $att_key => $value) { |
|
if (substr($att_key, 0, $att_prefix_len) === $att_prefix) { |
|
$product_att = $this->getProductAttribute($product, $att_key); |
|
|
|
if (! $product_att) { |
|
continue; |
|
} |
|
|
|
$name = $value; |
|
|
|
if ($product_att->is_taxonomy()) { |
|
$taxonomy = $product_att->get_taxonomy(); |
|
$term = get_term_by('slug', $value, $taxonomy); |
|
|
|
if ($term instanceof WP_Term) { |
|
$name = $term->name; |
|
} |
|
} |
|
|
|
$selected = true; |
|
$filtered_options[ $att_key ][] = |
|
compact('name', 'selected', 'value'); |
|
} |
|
} |
|
|
|
foreach (array_keys($filtered_options) as $att_key) { |
|
if (! isset($options_order[ $att_key ])) { |
|
continue; |
|
} |
|
|
|
/** @var WC_Product_Attribute $product_att */ |
|
$product_att = $options_order[ $att_key ][ 'attribute' ]; |
|
$options_sorted = $options_order[ $att_key ][ 'options' ]; |
|
$taxonomy = $product_att->get_taxonomy(); |
|
$this->sortProductAttributeOptions( |
|
$filtered_options[ $att_key ], |
|
$options_sorted, |
|
$taxonomy |
|
); |
|
} |
|
|
|
die(json_encode(['options' => $filtered_options])); |
|
} |
|
|
|
public function ajaxResetProductAttributeOptions(): void |
|
{ |
|
$input_vars = wp_parse_args($_POST, ['product_id' => 0]); |
|
$product_id = absint($input_vars[ 'product_id' ]); |
|
$product = wc_get_product($product_id); |
|
$options = []; |
|
|
|
if ($product instanceof WC_Product_Variable) { |
|
/** |
|
* @var WC_Product_Variable $product |
|
* @var WC_Product_Attribute[] $atts |
|
*/ |
|
$atts = $product->get_attributes(); |
|
|
|
foreach ((array)$atts as $att_name => $att) { |
|
/** @var WC_Product_Attribute $att */ |
|
$att_key = self::ATTRIBUTE_PREFIX . $att_name; |
|
|
|
if ($att->is_taxonomy()) { |
|
$terms = $att->get_terms(); |
|
|
|
foreach ($terms as $term) { |
|
/** @var WP_Term $term */ |
|
$options[ $att_key ][] = [ |
|
'value' => $term->slug, |
|
'name' => $term->name |
|
]; |
|
} |
|
} else { |
|
$att_options = $att->get_options(); |
|
|
|
foreach ($att_options as $att_option) { |
|
$options[ $att_key ][] = [ |
|
'value' => $att_option, |
|
'name' => $att_option |
|
]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
die(json_encode(compact('options'))); |
|
} |
|
|
|
/** |
|
* @param WP $wp |
|
*/ |
|
public function mainQueryReady(WP $wp) |
|
{ |
|
if (! is_singular('product')) { |
|
return; |
|
} |
|
|
|
$product = wc_get_product(); |
|
|
|
if ($product instanceof WC_Product_Variable) { |
|
add_action( |
|
'wp_print_footer_scripts', |
|
[$this, 'productVariationFooterScript'] |
|
); |
|
} |
|
|
|
unset($wp); |
|
} |
|
|
|
public function productVariationFooterScript(): void |
|
{ |
|
?> |
|
<!--suppress JSNonStrictModeUsed --> |
|
<script> |
|
(function($) { |
|
'use strict'; |
|
var ajax_url = <?= json_encode(admin_url('admin-ajax.php')) ?>, |
|
$variations_form = $(".variations_form"), |
|
$reset_button = $variations_form.find("a.reset_variations"), |
|
$selectors = $variations_form.find(".variations select"), |
|
product_id = $variations_form.data("product_id"); |
|
|
|
/** |
|
* |
|
* @param {Object} options |
|
* @param {String|JQuery} selectors |
|
*/ |
|
function repopulate_options(options, selectors) { |
|
var $selectors = $(selectors); |
|
|
|
$selectors.each(function() { |
|
var $selector = $(this), |
|
att_name = $selector.data("attribute_name"), |
|
att_options; |
|
|
|
if ('undefined' === typeof options[ att_name ]) { |
|
return true; |
|
} |
|
|
|
att_options = options[ att_name ]; |
|
|
|
$selector.find("option[value!='']").remove(); |
|
$.each(att_options, function(key, option) { |
|
var $option = $("<option><" + "/option>").attr( |
|
"value", option.value |
|
).text(option.name); |
|
|
|
if ('undefined' !== typeof option.selected) { |
|
$option.prop('selected', !!option.selected); |
|
} |
|
|
|
$selector.append($option); |
|
}); |
|
|
|
return true; |
|
}); |
|
} |
|
|
|
$selectors.on("change", function() { |
|
var post_data = { |
|
action : "filter_product_attribute_options", |
|
product_id : product_id |
|
}, |
|
values_selected = false; |
|
|
|
$selectors.each(function() { |
|
var $this = $(this), |
|
att_name = $this.data("attribute_name"), |
|
val = $this.val(); |
|
|
|
if ('' !== val) { |
|
values_selected = true; |
|
post_data[ att_name ] = val; |
|
} |
|
}); |
|
|
|
if (values_selected) { |
|
$.post(ajax_url, post_data, function(response) { |
|
var options = response.options; |
|
repopulate_options(options, $selectors); |
|
}, "json"); |
|
} |
|
}); |
|
|
|
$reset_button.on("click", function() { |
|
var post_data = { |
|
action : "reset_product_attribute_options", |
|
product_id : product_id |
|
}; |
|
$.post(ajax_url, post_data, function(response) { |
|
var options = response.options; |
|
repopulate_options(options, $selectors); |
|
}, "json"); |
|
}); |
|
})(jQuery); |
|
</script> |
|
<?php } |
|
|
|
public function setup(): void |
|
{ |
|
if (wp_doing_ajax()) { |
|
add_action( |
|
'wp_ajax_nopriv_filter_product_attribute_options', |
|
[$this, 'ajaxFilterProductAttributeOptions'] |
|
); |
|
add_action( |
|
'wp_ajax_filter_product_attribute_options', |
|
[$this, 'ajaxFilterProductAttributeOptions'] |
|
); |
|
|
|
add_action( |
|
'wp_ajax_nopriv_reset_product_attribute_options', |
|
[$this, 'ajaxResetProductAttributeOptions'] |
|
); |
|
add_action( |
|
'wp_ajax_reset_product_attribute_options', |
|
[$this, 'ajaxResetProductAttributeOptions'] |
|
); |
|
} elseif (! is_admin()) { |
|
add_action('wp', [$this, 'mainQueryReady']); |
|
} |
|
} |
|
} |