You've already forked wc-composable-product
Upgrade to PHPUnit 10, add PHPCS with WPCS compliance, add phpcs CI job
All checks were successful
All checks were successful
- Upgrade PHPUnit 9.6 → 10, update phpunit.xml.dist schema - Add PHPCS 3.13 with WordPress-Extra + PHPCompatibilityWP standards - PHPCBF auto-fix + manual fixes for full WPCS compliance - Add phpcs job to release workflow (parallel with lint) - Pin composer platform to PHP 8.3 to prevent incompatible dep locks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,211 +7,239 @@
|
||||
|
||||
namespace Magdev\WcComposableProduct\Admin;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Product Data Tab Class
|
||||
*/
|
||||
class ProductData {
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter('woocommerce_product_data_tabs', [$this, 'add_product_data_tab']);
|
||||
add_action('woocommerce_product_data_panels', [$this, 'add_product_data_panel']);
|
||||
add_action('woocommerce_process_product_meta_composable', [$this, 'save_product_data']);
|
||||
add_action('woocommerce_product_options_general_product_data', [$this, 'add_general_fields']);
|
||||
}
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_product_data_tabs', array( $this, 'add_product_data_tab' ) );
|
||||
add_action( 'woocommerce_product_data_panels', array( $this, 'add_product_data_panel' ) );
|
||||
add_action( 'woocommerce_process_product_meta_composable', array( $this, 'save_product_data' ) );
|
||||
add_action( 'woocommerce_product_options_general_product_data', array( $this, 'add_general_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add composable products tab
|
||||
*
|
||||
* @param array $tabs Product data tabs
|
||||
* @return array
|
||||
*/
|
||||
public function add_product_data_tab($tabs) {
|
||||
$tabs['composable'] = [
|
||||
'label' => __('Composable Options', 'wc-composable-product'),
|
||||
'target' => 'composable_product_data',
|
||||
'class' => ['show_if_composable'],
|
||||
'priority' => 21,
|
||||
];
|
||||
return $tabs;
|
||||
}
|
||||
/**
|
||||
* Add composable products tab
|
||||
*
|
||||
* @param array $tabs Product data tabs
|
||||
* @return array
|
||||
*/
|
||||
public function add_product_data_tab( $tabs ) {
|
||||
$tabs['composable'] = array(
|
||||
'label' => __( 'Composable Options', 'wc-composable-product' ),
|
||||
'target' => 'composable_product_data',
|
||||
'class' => array( 'show_if_composable' ),
|
||||
'priority' => 21,
|
||||
);
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields to general tab
|
||||
*/
|
||||
public function add_general_fields() {
|
||||
global $product_object;
|
||||
/**
|
||||
* Add fields to general tab
|
||||
*/
|
||||
public function add_general_fields() {
|
||||
global $product_object;
|
||||
|
||||
if ($product_object && $product_object->get_type() === 'composable') {
|
||||
echo '<div class="options_group show_if_composable">';
|
||||
if ( $product_object && $product_object->get_type() === 'composable' ) {
|
||||
echo '<div class="options_group show_if_composable">';
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_composable_selection_limit',
|
||||
'label' => __('Selection Limit', 'wc-composable-product'),
|
||||
'description' => __('Maximum number of items customers can select. Leave empty to use global default.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'type' => 'number',
|
||||
'custom_attributes' => [
|
||||
'min' => '1',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => '_composable_selection_limit',
|
||||
'label' => __( 'Selection Limit', 'wc-composable-product' ),
|
||||
'description' => __( 'Maximum number of items customers can select. Leave empty to use global default.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'type' => 'number',
|
||||
'custom_attributes' => array(
|
||||
'min' => '1',
|
||||
'step' => '1',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
woocommerce_wp_select([
|
||||
'id' => '_composable_pricing_mode',
|
||||
'label' => __('Pricing Mode', 'wc-composable-product'),
|
||||
'description' => __('How to calculate the price.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'options' => [
|
||||
'' => __('Use global default', 'wc-composable-product'),
|
||||
'sum' => __('Sum of selected products', 'wc-composable-product'),
|
||||
'fixed' => __('Fixed price', 'wc-composable-product'),
|
||||
],
|
||||
]);
|
||||
woocommerce_wp_select(
|
||||
array(
|
||||
'id' => '_composable_pricing_mode',
|
||||
'label' => __( 'Pricing Mode', 'wc-composable-product' ),
|
||||
'description' => __( 'How to calculate the price.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'options' => array(
|
||||
'' => __( 'Use global default', 'wc-composable-product' ),
|
||||
'sum' => __( 'Sum of selected products', 'wc-composable-product' ),
|
||||
'fixed' => __( 'Fixed price', 'wc-composable-product' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_regular_price',
|
||||
'label' => __('Fixed Price', 'wc-composable-product') . ' (' . get_woocommerce_currency_symbol() . ')',
|
||||
'description' => __('Enter the fixed price for this composable product.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'type' => 'text',
|
||||
'data_type' => 'price',
|
||||
'wrapper_class' => 'composable_fixed_price_field',
|
||||
]);
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => '_regular_price',
|
||||
'label' => __( 'Fixed Price', 'wc-composable-product' ) . ' (' . get_woocommerce_currency_symbol() . ')',
|
||||
'description' => __( 'Enter the fixed price for this composable product.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'type' => 'text',
|
||||
'data_type' => 'price',
|
||||
'wrapper_class' => 'composable_fixed_price_field',
|
||||
)
|
||||
);
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add product data panel
|
||||
*/
|
||||
public function add_product_data_panel() {
|
||||
global $post;
|
||||
?>
|
||||
<div id="composable_product_data" class="panel woocommerce_options_panel hidden">
|
||||
<div class="options_group">
|
||||
<?php
|
||||
woocommerce_wp_select([
|
||||
'id' => '_composable_include_unpublished',
|
||||
'label' => __('Include Non-Public Products', 'wc-composable-product'),
|
||||
'description' => __('Allow draft and private products in the selection. Useful when products should only be sold as part of a composition.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'options' => [
|
||||
'' => __('Use global default', 'wc-composable-product'),
|
||||
'yes' => __('Yes', 'wc-composable-product'),
|
||||
'no' => __('No', 'wc-composable-product'),
|
||||
],
|
||||
'value' => get_post_meta($post->ID, '_composable_include_unpublished', true) ?: '',
|
||||
]);
|
||||
/**
|
||||
* Add product data panel
|
||||
*/
|
||||
public function add_product_data_panel() {
|
||||
global $post;
|
||||
?>
|
||||
<div id="composable_product_data" class="panel woocommerce_options_panel hidden">
|
||||
<div class="options_group">
|
||||
<?php
|
||||
woocommerce_wp_select(
|
||||
array(
|
||||
'id' => '_composable_include_unpublished',
|
||||
'label' => __( 'Include Non-Public Products', 'wc-composable-product' ),
|
||||
'description' => __( 'Allow draft and private products in the selection. Useful when products should only be sold as part of a composition.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'options' => array(
|
||||
'' => __( 'Use global default', 'wc-composable-product' ),
|
||||
'yes' => __( 'Yes', 'wc-composable-product' ),
|
||||
'no' => __( 'No', 'wc-composable-product' ),
|
||||
),
|
||||
'value' => get_post_meta( $post->ID, '_composable_include_unpublished', true ) ? get_post_meta( $post->ID, '_composable_include_unpublished', true ) : '',
|
||||
)
|
||||
);
|
||||
|
||||
woocommerce_wp_select([
|
||||
'id' => '_composable_criteria_type',
|
||||
'label' => __('Selection Criteria', 'wc-composable-product'),
|
||||
'description' => __('How to select available products.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'options' => [
|
||||
'category' => __('By Category', 'wc-composable-product'),
|
||||
'tag' => __('By Tag', 'wc-composable-product'),
|
||||
'sku' => __('By SKU', 'wc-composable-product'),
|
||||
],
|
||||
'value' => get_post_meta($post->ID, '_composable_criteria_type', true) ?: 'category',
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
woocommerce_wp_select(
|
||||
array(
|
||||
'id' => '_composable_criteria_type',
|
||||
'label' => __( 'Selection Criteria', 'wc-composable-product' ),
|
||||
'description' => __( 'How to select available products.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'options' => array(
|
||||
'category' => __( 'By Category', 'wc-composable-product' ),
|
||||
'tag' => __( 'By Tag', 'wc-composable-product' ),
|
||||
'sku' => __( 'By SKU', 'wc-composable-product' ),
|
||||
),
|
||||
'value' => get_post_meta( $post->ID, '_composable_criteria_type', true ) ? get_post_meta( $post->ID, '_composable_criteria_type', true ) : 'category',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_category">
|
||||
<p class="form-field">
|
||||
<label for="_composable_categories"><?php esc_html_e('Select Categories', 'wc-composable-product'); ?></label>
|
||||
<select id="_composable_categories" name="_composable_categories[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
|
||||
<?php
|
||||
$selected_categories = get_post_meta($post->ID, '_composable_categories', true) ?: [];
|
||||
$categories = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false]);
|
||||
foreach ($categories as $category) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr($category->term_id),
|
||||
selected(in_array($category->term_id, (array) $selected_categories), true, false),
|
||||
esc_html($category->name)
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<span class="description"><?php esc_html_e('Select product categories to include.', 'wc-composable-product'); ?></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_category">
|
||||
<p class="form-field">
|
||||
<label for="_composable_categories"><?php esc_html_e( 'Select Categories', 'wc-composable-product' ); ?></label>
|
||||
<select id="_composable_categories" name="_composable_categories[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
|
||||
<?php
|
||||
$selected_categories = get_post_meta( $post->ID, '_composable_categories', true );
|
||||
$selected_categories = $selected_categories ? $selected_categories : array();
|
||||
$categories = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_cat',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
foreach ( $categories as $category ) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr( $category->term_id ),
|
||||
selected( in_array( $category->term_id, (array) $selected_categories, true ), true, false ),
|
||||
esc_html( $category->name )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<span class="description"><?php esc_html_e( 'Select product categories to include.', 'wc-composable-product' ); ?></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_tag" style="display: none;">
|
||||
<p class="form-field">
|
||||
<label for="_composable_tags"><?php esc_html_e('Select Tags', 'wc-composable-product'); ?></label>
|
||||
<select id="_composable_tags" name="_composable_tags[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
|
||||
<?php
|
||||
$selected_tags = get_post_meta($post->ID, '_composable_tags', true) ?: [];
|
||||
$tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => false]);
|
||||
foreach ($tags as $tag) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr($tag->term_id),
|
||||
selected(in_array($tag->term_id, (array) $selected_tags), true, false),
|
||||
esc_html($tag->name)
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<span class="description"><?php esc_html_e('Select product tags to include.', 'wc-composable-product'); ?></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_tag" style="display: none;">
|
||||
<p class="form-field">
|
||||
<label for="_composable_tags"><?php esc_html_e( 'Select Tags', 'wc-composable-product' ); ?></label>
|
||||
<select id="_composable_tags" name="_composable_tags[]" class="wc-enhanced-select" multiple="multiple" style="width: 50%;">
|
||||
<?php
|
||||
$selected_tags = get_post_meta( $post->ID, '_composable_tags', true );
|
||||
$selected_tags = $selected_tags ? $selected_tags : array();
|
||||
$tags = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_tag',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
foreach ( $tags as $tag ) {
|
||||
printf(
|
||||
'<option value="%s" %s>%s</option>',
|
||||
esc_attr( $tag->term_id ),
|
||||
selected( in_array( $tag->term_id, (array) $selected_tags, true ), true, false ),
|
||||
esc_html( $tag->name )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<span class="description"><?php esc_html_e( 'Select product tags to include.', 'wc-composable-product' ); ?></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_sku" style="display: none;">
|
||||
<?php
|
||||
woocommerce_wp_textarea_input([
|
||||
'id' => '_composable_skus',
|
||||
'label' => __('Product SKUs', 'wc-composable-product'),
|
||||
'description' => __('Enter product SKUs separated by commas.', 'wc-composable-product'),
|
||||
'desc_tip' => true,
|
||||
'placeholder' => __('SKU-1, SKU-2, SKU-3', 'wc-composable-product'),
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
<div class="options_group composable_criteria_group" id="composable_criteria_sku" style="display: none;">
|
||||
<?php
|
||||
woocommerce_wp_textarea_input(
|
||||
array(
|
||||
'id' => '_composable_skus',
|
||||
'label' => __( 'Product SKUs', 'wc-composable-product' ),
|
||||
'description' => __( 'Enter product SKUs separated by commas.', 'wc-composable-product' ),
|
||||
'desc_tip' => true,
|
||||
'placeholder' => __( 'SKU-1, SKU-2, SKU-3', 'wc-composable-product' ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save product data
|
||||
*
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function save_product_data($post_id) {
|
||||
// Save selection limit
|
||||
$selection_limit = isset($_POST['_composable_selection_limit']) ? absint($_POST['_composable_selection_limit']) : '';
|
||||
update_post_meta($post_id, '_composable_selection_limit', $selection_limit);
|
||||
/**
|
||||
* Save product data
|
||||
*
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function save_product_data( $post_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce in woocommerce_process_product_meta.
|
||||
|
||||
// Save pricing mode
|
||||
$pricing_mode = isset($_POST['_composable_pricing_mode']) ? sanitize_text_field($_POST['_composable_pricing_mode']) : '';
|
||||
update_post_meta($post_id, '_composable_pricing_mode', $pricing_mode);
|
||||
// Save selection limit.
|
||||
$selection_limit = isset( $_POST['_composable_selection_limit'] ) ? absint( $_POST['_composable_selection_limit'] ) : '';
|
||||
update_post_meta( $post_id, '_composable_selection_limit', $selection_limit );
|
||||
|
||||
// Save include unpublished
|
||||
$include_unpublished = isset($_POST['_composable_include_unpublished']) ? sanitize_text_field($_POST['_composable_include_unpublished']) : '';
|
||||
update_post_meta($post_id, '_composable_include_unpublished', $include_unpublished);
|
||||
// Save pricing mode.
|
||||
$pricing_mode = isset( $_POST['_composable_pricing_mode'] ) ? sanitize_text_field( $_POST['_composable_pricing_mode'] ) : '';
|
||||
update_post_meta( $post_id, '_composable_pricing_mode', $pricing_mode );
|
||||
|
||||
// Save criteria type
|
||||
$criteria_type = isset($_POST['_composable_criteria_type']) ? sanitize_text_field($_POST['_composable_criteria_type']) : 'category';
|
||||
update_post_meta($post_id, '_composable_criteria_type', $criteria_type);
|
||||
// Save include unpublished.
|
||||
$include_unpublished = isset( $_POST['_composable_include_unpublished'] ) ? sanitize_text_field( $_POST['_composable_include_unpublished'] ) : '';
|
||||
update_post_meta( $post_id, '_composable_include_unpublished', $include_unpublished );
|
||||
|
||||
// Save categories
|
||||
$categories = isset($_POST['_composable_categories']) ? array_map('absint', $_POST['_composable_categories']) : [];
|
||||
update_post_meta($post_id, '_composable_categories', $categories);
|
||||
// Save criteria type.
|
||||
$criteria_type = isset( $_POST['_composable_criteria_type'] ) ? sanitize_text_field( $_POST['_composable_criteria_type'] ) : 'category';
|
||||
update_post_meta( $post_id, '_composable_criteria_type', $criteria_type );
|
||||
|
||||
// Save tags
|
||||
$tags = isset($_POST['_composable_tags']) ? array_map('absint', $_POST['_composable_tags']) : [];
|
||||
update_post_meta($post_id, '_composable_tags', $tags);
|
||||
// Save categories.
|
||||
$categories = isset( $_POST['_composable_categories'] ) ? array_map( 'absint', $_POST['_composable_categories'] ) : array();
|
||||
update_post_meta( $post_id, '_composable_categories', $categories );
|
||||
|
||||
// Save SKUs
|
||||
$skus = isset($_POST['_composable_skus']) ? sanitize_textarea_field($_POST['_composable_skus']) : '';
|
||||
update_post_meta($post_id, '_composable_skus', $skus);
|
||||
}
|
||||
// Save tags.
|
||||
$tags = isset( $_POST['_composable_tags'] ) ? array_map( 'absint', $_POST['_composable_tags'] ) : array();
|
||||
update_post_meta( $post_id, '_composable_tags', $tags );
|
||||
|
||||
// Save SKUs.
|
||||
$skus = isset( $_POST['_composable_skus'] ) ? sanitize_textarea_field( $_POST['_composable_skus'] ) : '';
|
||||
update_post_meta( $post_id, '_composable_skus', $skus );
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user