Преглед изворни кода

studiou-wc-mandatory-products: add files. initialize

Dalibor Votruba пре 1 година
родитељ
комит
61e042b4a7

+ 56 - 0
studiou-wc-mandatory-products/assets/css/frontend.css

@@ -0,0 +1,56 @@
+/**
+ * StudioU WC Mandatory Products Frontend Styles
+ */
+
+/* Mandatory products info on product page */
+.mandatory-products-info {
+    margin: 1.5em 0;
+    padding: 1em;
+    background-color: #f7f7f7;
+    border-left: 4px solid #2271b1;
+    box-shadow: 0 1px 1px rgba(0,0,0,.05);
+}
+
+.mandatory-products-info h4 {
+    margin-top: 0;
+    font-size: 1.1em;
+    font-weight: 600;
+    color: #333;
+}
+
+.mandatory-products-info ul {
+    margin-left: 1.5em;
+    padding-left: 0;
+}
+
+.mandatory-products-info li {
+    margin-bottom: 0.5em;
+}
+
+/* Cart item mandatory product */
+.cart_item .product-name .variation dt.variation-MandatoryItem {
+    font-weight: 600;
+    color: #2271b1;
+}
+
+/* Lock icon for mandatory products */
+.mandatory-product-info {
+    display: inline-block;
+    vertical-align: middle;
+    color: #999;
+    cursor: help;
+}
+
+.mandatory-product-info .dashicons {
+    width: 16px;
+    height: 16px;
+    font-size: 16px;
+}
+
+/* Readonly quantity field styling */
+.cart_item .quantity input[readonly] {
+    background-color: #f9f9f9;
+    border-color: #ddd;
+    color: #666;
+    cursor: not-allowed;
+}

+ 371 - 0
studiou-wc-mandatory-products/includes/class-studiou-wcmp-admin.php

@@ -0,0 +1,371 @@
+<?php
+/**
+ * StudioU WC Mandatory Products Admin
+ * 
+ * @package StudioU_WC_Mandatory_Products
+ */
+
+// Exit if accessed directly
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+class StudioU_WCMP_Admin {
+
+    /**
+     * Constructor
+     */
+    public function __construct() {
+        // Nothing to do at constructor
+    }
+
+    /**
+     * Initialize hooks
+     */
+    public function init() {
+        // Add fields to product category edit page
+        add_action('product_cat_edit_form_fields', array($this, 'add_category_fields'), 20);
+        
+        // Add fields to product category add page
+        add_action('product_cat_add_form_fields', array($this, 'add_category_fields_new'), 20);
+        
+        // Save category fields
+        add_action('edited_product_cat', array($this, 'save_category_fields'), 20);
+        add_action('created_product_cat', array($this, 'save_category_fields'), 20);
+        
+        // Add admin scripts and styles
+        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
+        
+        // Add product meta box to show mandatory products info
+        add_action('add_meta_boxes', array($this, 'add_product_meta_boxes'));
+    }
+
+    /**
+     * Add fields to product category edit form
+     * 
+     * @param object $term The term object
+     */
+    public function add_category_fields($term) {
+        // Get current value
+        $mandatory_products = get_term_meta($term->term_id, 'mandatory_products', true);
+        
+        // If not array, initialize it
+        if (!is_array($mandatory_products)) {
+            $mandatory_products = array();
+        }
+        ?>
+        <tr class="form-field">
+            <th scope="row" valign="top">
+                <label><?php _e('Mandatory Products', 'studiou-wc-product-cat-manage'); ?></label>
+            </th>
+            <td>
+                <div id="studiou_mandatory_products_container">
+                    <?php if (!empty($mandatory_products)) : ?>
+                        <?php foreach ($mandatory_products as $index => $mandatory_product) : 
+                            $product_id = isset($mandatory_product['product_id']) ? absint($mandatory_product['product_id']) : 0;
+                            $quantity = isset($mandatory_product['quantity']) ? absint($mandatory_product['quantity']) : 1;
+                            $type = isset($mandatory_product['type']) ? sanitize_text_field($mandatory_product['type']) : 'OnePerCart';
+                            $product_title = '';
+                            
+                            if ($product_id > 0) {
+                                $product = wc_get_product($product_id);
+                                if ($product) {
+                                    $product_title = $product->get_formatted_name();
+                                }
+                            }
+                        ?>
+                            <div class="mandatory-product-row" style="margin-bottom: 10px; padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">
+                                <p>
+                                    <label><?php _e('Product:', 'studiou-wc-product-cat-manage'); ?></label>
+                                    <select 
+                                        class="wc-product-search" 
+                                        style="width: 50%;" 
+                                        name="mandatory_product_id[]" 
+                                        data-placeholder="<?php esc_attr_e('Search for a product&hellip;', 'studiou-wc-product-cat-manage'); ?>" 
+                                        data-action="woocommerce_json_search_products_and_variations" 
+                                        data-allow_clear="true"
+                                    >
+                                        <?php if ($product_id && $product_title) : ?>
+                                            <option value="<?php echo esc_attr($product_id); ?>" selected="selected"><?php echo esc_html($product_title); ?></option>
+                                        <?php endif; ?>
+                                    </select>
+                                </p>
+                                <p>
+                                    <label><?php _e('Quantity:', 'studiou-wc-product-cat-manage'); ?></label>
+                                    <input type="number" name="mandatory_product_quantity[]" value="<?php echo esc_attr($quantity); ?>" min="1" step="1" style="width: 80px;" />
+                                </p>
+                                <p>
+                                    <label><?php _e('Type:', 'studiou-wc-product-cat-manage'); ?></label>
+                                    <select name="mandatory_product_type[]">
+                                        <option value="OnePerCart" <?php selected($type, 'OnePerCart'); ?>><?php _e('One Per Cart', 'studiou-wc-product-cat-manage'); ?></option>
+                                        <option value="OnePerProduct" <?php selected($type, 'OnePerProduct'); ?>><?php _e('One Per Product', 'studiou-wc-product-cat-manage'); ?></option>
+                                    </select>
+                                </p>
+                                <p>
+                                    <button type="button" class="button remove-mandatory-product"><?php _e('Remove', 'studiou-wc-product-cat-manage'); ?></button>
+                                </p>
+                            </div>
+                        <?php endforeach; ?>
+                    <?php endif; ?>
+                </div>
+                <p>
+                    <button type="button" class="button add-mandatory-product"><?php _e('Add Mandatory Product', 'studiou-wc-mandatory-products'); ?></button>
+                </p>
+                <p class="description">
+                    <?php _e('These products will be automatically added to the cart when a product from this category is added.', 'studiou-wc-product-cat-manage'); ?>
+                    <br>
+                    <?php _e('Note: Child categories will inherit these settings.', 'studiou-wc-product-cat-manage'); ?>
+                </p>
+            </td>
+        </tr>
+        
+        <script type="text/template" id="tmpl-mandatory-product-row">
+            <div class="mandatory-product-row" style="margin-bottom: 10px; padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">
+                <p>
+                    <label><?php _e('Product:', 'studiou-wc-mandatory-products'); ?></label>
+                    <select 
+                        class="wc-product-search" 
+                        style="width: 50%;" 
+                        name="mandatory_product_id[]" 
+                        data-placeholder="<?php esc_attr_e('Search for a product&hellip;', 'studiou-wc-mandatory-products'); ?>" 
+                        data-action="woocommerce_json_search_products_and_variations" 
+                        data-allow_clear="true"
+                    ></select>
+                </p>
+                <p>
+                    <label><?php _e('Quantity:', 'studiou-wc-mandatory-products'); ?></label>
+                    <input type="number" name="mandatory_product_quantity[]" value="1" min="1" step="1" style="width: 80px;" />
+                </p>
+                <p>
+                    <label><?php _e('Type:', 'studiou-wc-mandatory-products'); ?></label>
+                    <select name="mandatory_product_type[]">
+                        <option value="OnePerCart"><?php _e('One Per Cart', 'studiou-wc-mandatory-products'); ?></option>
+                        <option value="OnePerProduct"><?php _e('One Per Product', 'studiou-wc-mandatory-products'); ?></option>
+                    </select>
+                </p>
+                <p>
+                    <button type="button" class="button remove-mandatory-product"><?php _e('Remove', 'studiou-wc-mandatory-products'); ?></button>
+                </p>
+            </div>
+        </script>
+        
+        <script>
+            jQuery(function($) {
+                // Initialize select2
+                $(document).ready(function() {
+                    $('.wc-product-search').selectWoo({
+                        width: '100%'
+                    });
+                });
+                
+                // Add new row
+                $('.add-mandatory-product').on('click', function() {
+                    var template = wp.template('mandatory-product-row');
+                    $('#studiou_mandatory_products_container').append(template());
+                    
+                    // Re-initialize select2 for the new row
+                    $('#studiou_mandatory_products_container .wc-product-search:last').selectWoo({
+                        width: '100%'
+                    });
+                });
+                
+                // Remove row
+                $(document).on('click', '.remove-mandatory-product', function() {
+                    $(this).closest('.mandatory-product-row').remove();
+                });
+            });
+        </script>
+        <?php
+    }
+
+    /**
+     * Add fields to product category add form
+     */
+    public function add_category_fields_new() {
+        ?>
+        <div class="form-field">
+            <label><?php _e('Mandatory Products', 'studiou-wc-mandatory-products'); ?></label>
+            <div id="studiou_mandatory_products_container">
+                <!-- Empty container for new category -->
+            </div>
+            <p>
+                <button type="button" class="button add-mandatory-product"><?php _e('Add Mandatory Product', 'studiou-wc-mandatory-products'); ?></button>
+            </p>
+            <p class="description">
+                <?php _e('These products will be automatically added to the cart when a product from this category is added.', 'studiou-wc-mandatory-products'); ?>
+                <br>
+                <?php _e('Note: Child categories will inherit these settings.', 'studiou-wc-mandatory-products'); ?>
+            </p>
+        </div>
+        
+        <script type="text/template" id="tmpl-mandatory-product-row">
+            <div class="mandatory-product-row" style="margin-bottom: 10px; padding: 10px; border: 1px solid #ddd; background: #f9f9f9;">
+                <p>
+                    <label><?php _e('Product:', 'studiou-wc-mandatory-products'); ?></label>
+                    <select 
+                        class="wc-product-search" 
+                        style="width: 50%;" 
+                        name="mandatory_product_id[]" 
+                        data-placeholder="<?php esc_attr_e('Search for a product&hellip;', 'studiou-wc-mandatory-products'); ?>" 
+                        data-action="woocommerce_json_search_products_and_variations" 
+                        data-allow_clear="true"
+                    ></select>
+                </p>
+                <p>
+                    <label><?php _e('Quantity:', 'studiou-wc-mandatory-products'); ?></label>
+                    <input type="number" name="mandatory_product_quantity[]" value="1" min="1" step="1" style="width: 80px;" />
+                </p>
+                <p>
+                    <label><?php _e('Type:', 'studiou-wc-mandatory-products'); ?></label>
+                    <select name="mandatory_product_type[]">
+                        <option value="OnePerCart"><?php _e('One Per Cart', 'studiou-wc-mandatory-products'); ?></option>
+                        <option value="OnePerProduct"><?php _e('One Per Product', 'studiou-wc-mandatory-products'); ?></option>
+                    </select>
+                </p>
+                <p>
+                    <button type="button" class="button remove-mandatory-product"><?php _e('Remove', 'studiou-wc-mandatory-products'); ?></button>
+                </p>
+            </div>
+        </script>
+        
+        <script>
+            jQuery(function($) {
+                // Add new row
+                $('.add-mandatory-product').on('click', function() {
+                    var template = wp.template('mandatory-product-row');
+                    $('#studiou_mandatory_products_container').append(template());
+                    
+                    // Initialize select2 for the new row
+                    $('#studiou_mandatory_products_container .wc-product-search:last').selectWoo({
+                        width: '100%'
+                    });
+                });
+                
+                // Remove row
+                $(document).on('click', '.remove-mandatory-product', function() {
+                    $(this).closest('.mandatory-product-row').remove();
+                });
+            });
+        </script>
+        <?php
+    }
+
+    /**
+     * Save category fields
+     * 
+     * @param int $term_id The term ID
+     */
+    public function save_category_fields($term_id) {
+        $mandatory_products = array();
+        
+        if (isset($_POST['mandatory_product_id']) && is_array($_POST['mandatory_product_id'])) {
+            $product_ids = array_map('absint', $_POST['mandatory_product_id']);
+            $quantities = isset($_POST['mandatory_product_quantity']) ? array_map('absint', $_POST['mandatory_product_quantity']) : array();
+            $types = isset($_POST['mandatory_product_type']) ? array_map('sanitize_text_field', $_POST['mandatory_product_type']) : array();
+            
+            foreach ($product_ids as $index => $product_id) {
+                // Skip if product ID is empty
+                if (empty($product_id)) {
+                    continue;
+                }
+                
+                $quantity = isset($quantities[$index]) ? max(1, $quantities[$index]) : 1;
+                $type = isset($types[$index]) ? $types[$index] : 'OnePerCart';
+                
+                // Validate type
+                if (!in_array($type, array('OnePerCart', 'OnePerProduct'))) {
+                    $type = 'OnePerCart';
+                }
+                
+                $mandatory_products[] = array(
+                    'product_id' => $product_id,
+                    'quantity' => $quantity,
+                    'type' => $type
+                );
+            }
+        }
+        
+        update_term_meta($term_id, 'mandatory_products', $mandatory_products);
+    }
+
+    /**
+     * Enqueue admin scripts and styles
+     * 
+     * @param string $hook Current admin page
+     */
+    public function enqueue_admin_scripts($hook) {
+        // Only on category edit page
+        if ('edit-tags.php' !== $hook && 'term.php' !== $hook) {
+            return;
+        }
+        
+        // Check if we're on the product category screen
+        $screen = get_current_screen();
+        if (!$screen || 'product_cat' !== $screen->taxonomy) {
+            return;
+        }
+        
+        // WooCommerce admin scripts
+        wp_enqueue_script('wc-enhanced-select');
+        wp_enqueue_style('woocommerce_admin_styles');
+    }
+
+    /**
+     * Add product meta boxes
+     */
+    public function add_product_meta_boxes() {
+        add_meta_box(
+            'studiou-wcmp-mandatory-products',
+            __('Mandatory Products Info', 'studiou-wc-product-cat-manage'),
+            array($this, 'render_product_meta_box'),
+            'product',
+            'side',
+            'default'
+        );
+    }
+
+    /**
+     * Render product meta box
+     * 
+     * @param WP_Post $post The post object
+     */
+    public function render_product_meta_box($post) {
+        $product_id = $post->ID;
+        
+        // Get plugin instance
+        $plugin = new StudioU_WC_Mandatory_Products();
+        
+        // Get mandatory products
+        $mandatory_products = $plugin->get_mandatory_products($product_id);
+        
+        if (empty($mandatory_products)) {
+            echo '<p>' . __('No mandatory products set for this product\'s categories.', 'studiou-wc-mandatory-products') . '</p>';
+            return;
+        }
+        
+        echo '<p>' . __('This product has mandatory products set in its categories:', 'studiou-wc-mandatory-products') . '</p>';
+        echo '<ul>';
+        
+        foreach ($mandatory_products as $mandatory_product) {
+            $product_id = absint($mandatory_product['product_id']);
+            $quantity = absint($mandatory_product['quantity']);
+            $type = sanitize_text_field($mandatory_product['type']);
+            
+            $mandatory_product_obj = wc_get_product($product_id);
+            
+            if ($mandatory_product_obj) {
+                echo '<li>';
+                echo sprintf(
+                    '%s x %d (%s)',
+                    $mandatory_product_obj->get_name(),
+                    $quantity,
+                    $type === 'OnePerCart' ? __('One per cart', 'studiou-wc-mandatory-products') : __('One per product', 'studiou-wc-mandatory-products')
+                );
+                echo '</li>';
+            }
+        }
+        
+        echo '</ul>';
+        echo '<p class="description">' . __('These products will be automatically added to the customer\'s cart when this product is added.', 'studiou-wc-mandatory-products') . '</p>';
+    }
+}

+ 238 - 0
studiou-wc-mandatory-products/includes/class-studiou-wcmp-frontend.php

@@ -0,0 +1,238 @@
+<?php
+/**
+ * StudioU WC Mandatory Products Frontend
+ * 
+ * @package StudioU_WC_Mandatory_Products
+ */
+
+// Exit if accessed directly
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+class StudioU_WCMP_Frontend {
+
+    /**
+     * Constructor
+     */
+    public function __construct() {
+        // Nothing to do at constructor
+    }
+
+    /**
+     * Initialize hooks
+     */
+    public function init() {
+        // Add CSS for frontend
+        add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
+        
+        // Add custom item data to cart item
+        add_filter('woocommerce_get_item_data', array($this, 'add_cart_item_data'), 10, 2);
+        
+        // Prevent quantity changes for mandatory products
+        add_filter('woocommerce_cart_item_quantity', array($this, 'adjust_cart_item_quantity'), 10, 3);
+        
+        // Make mandatory products non-removable if needed
+        add_filter('woocommerce_cart_item_remove_link', array($this, 'adjust_cart_item_remove_link'), 10, 2);
+        
+        // Validate cart to ensure mandatory products are present
+        add_action('woocommerce_check_cart_items', array($this, 'validate_cart_has_mandatory_products'));
+    }
+
+    /**
+     * Enqueue frontend styles
+     */
+    public function enqueue_styles() {
+        if (is_product() || is_cart()) {
+            wp_enqueue_style(
+                'studiou-wcmp-styles',
+                STUDIOU_WCMP_PLUGIN_URL . 'assets/css/frontend.css',
+                array(),
+                STUDIOU_WCMP_VERSION
+            );
+        }
+    }
+
+    /**
+     * Add custom item data to cart item
+     * 
+     * @param array $item_data Cart item data
+     * @param array $cart_item Cart item
+     * @return array Modified item data
+     */
+    public function add_cart_item_data($item_data, $cart_item) {
+        if (isset($cart_item['mandatory_product']) && $cart_item['mandatory_product']) {
+            $item_data[] = array(
+                'key'   => __('Mandatory Item', 'studiou-wc-product-cat-manage'),
+                'value' => __('This item was automatically added', 'studiou-wc-product-cat-manage')
+            );
+        }
+        
+        return $item_data;
+    }
+    
+    /**
+     * Adjust cart item quantity field for mandatory products
+     * 
+     * @param string $product_quantity HTML of quantity field
+     * @param string $cart_item_key Cart item key
+     * @param array $cart_item Cart item
+     * @return string Modified quantity field HTML
+     */
+    public function adjust_cart_item_quantity($product_quantity, $cart_item_key, $cart_item) {
+        // If this is a mandatory product, make quantity field readonly
+        if (isset($cart_item['mandatory_product']) && $cart_item['mandatory_product']) {
+            // Get current quantity
+            $quantity = $cart_item['quantity'];
+            
+            // Return readonly quantity field
+            return sprintf(
+                '<div class="quantity"><input type="text" value="%s" class="input-text qty text" readonly="readonly" /></div>',
+                esc_attr($quantity)
+            );
+        }
+        
+        return $product_quantity;
+    }
+    
+    /**
+     * Adjust remove link for mandatory products
+     * 
+     * @param string $link The cart item remove link
+     * @param string $cart_item_key The cart item key
+     * @return string Modified remove link
+     */
+    public function adjust_cart_item_remove_link($link, $cart_item_key) {
+        $cart_item = WC()->cart->get_cart()[$cart_item_key];
+        
+        // If this is a mandatory product, remove the ability to remove it
+        if (isset($cart_item['mandatory_product']) && $cart_item['mandatory_product']) {
+            return '<span class="mandatory-product-info" title="' . 
+                esc_attr__('This is a mandatory product and cannot be removed directly', 'studiou-wc-product-cat-manage') . 
+                '"><i class="dashicons dashicons-lock"></i></span>';
+        }
+        
+        return $link;
+    }
+    
+    /**
+     * Validate cart to ensure mandatory products are present
+     */
+    public function validate_cart_has_mandatory_products() {
+        if (is_admin()) {
+            return;
+        }
+        
+        $cart_contents = WC()->cart->get_cart();
+        $plugin = new StudioU_WC_Mandatory_Products();
+        $required_mandatory_products = array();
+        
+        // First, get all mandatory products that should be in the cart
+        foreach ($cart_contents as $cart_item) {
+            // Skip if this is already a mandatory product
+            if (isset($cart_item['mandatory_product']) && $cart_item['mandatory_product']) {
+                continue;
+            }
+            
+            $product_id = $cart_item['product_id'];
+            $mandatory_products = $plugin->get_mandatory_products($product_id);
+            
+            foreach ($mandatory_products as $mandatory_product) {
+                $mandatory_product_id = $mandatory_product['product_id'];
+                $mandatory_product_type = $mandatory_product['type'];
+                $mandatory_product_quantity = $mandatory_product['quantity'];
+                
+                // For OnePerCart, add once to requirements
+                if ($mandatory_product_type === 'OnePerCart') {
+                    $key = 'cart_' . $mandatory_product_id;
+                    if (!isset($required_mandatory_products[$key])) {
+                        $required_mandatory_products[$key] = array(
+                            'product_id' => $mandatory_product_id,
+                            'quantity' => $mandatory_product_quantity,
+                            'type' => $mandatory_product_type
+                        );
+                    }
+                }
+                // For OnePerProduct, add for each product
+                else if ($mandatory_product_type === 'OnePerProduct') {
+                    $key = 'product_' . $product_id . '_' . $mandatory_product_id;
+                    if (!isset($required_mandatory_products[$key])) {
+                        $required_mandatory_products[$key] = array(
+                            'product_id' => $mandatory_product_id,
+                            'quantity' => $mandatory_product_quantity,
+                            'type' => $mandatory_product_type,
+                            'for_product_id' => $product_id
+                        );
+                    }
+                }
+            }
+        }
+        
+        // Now check if all required mandatory products are in the cart
+        $missing_products = array();
+        
+        foreach ($required_mandatory_products as $key => $required_product) {
+            $found = false;
+            
+            foreach ($cart_contents as $cart_item) {
+                if (isset($cart_item['product_id']) && $cart_item['product_id'] == $required_product['product_id']) {
+                    // For OnePerCart, just check it exists
+                    if ($required_product['type'] === 'OnePerCart') {
+                        $found = true;
+                        break;
+                    }
+                    // For OnePerProduct, check it exists for the specific product
+                    else if ($required_product['type'] === 'OnePerProduct' && 
+                             isset($cart_item['mandatory_for']) && 
+                             $cart_item['mandatory_for'] == $required_product['for_product_id']) {
+                        $found = true;
+                        break;
+                    }
+                }
+            }
+            
+            if (!$found) {
+                $missing_products[] = $required_product;
+            }
+        }
+        
+        // If we have missing products, add them and show notice
+        if (!empty($missing_products)) {
+            foreach ($missing_products as $missing_product) {
+                $product_id = $missing_product['product_id'];
+                $quantity = $missing_product['quantity'];
+                $type = $missing_product['type'];
+                
+                $extra_data = array(
+                    'mandatory_product' => true
+                );
+                
+                // For OnePerProduct, add the for_product_id
+                if ($type === 'OnePerProduct' && isset($missing_product['for_product_id'])) {
+                    $extra_data['mandatory_for'] = $missing_product['for_product_id'];
+                }
+                
+                // Add the product to cart
+                WC()->cart->add_to_cart(
+                    $product_id,
+                    $quantity,
+                    0, // No variation
+                    array(), // No variation data
+                    $extra_data
+                );
+                
+                // Get product name
+                $product = wc_get_product($product_id);
+                $product_name = $product ? $product->get_name() : __('A product', 'studiou-wc-mandatory-products');
+                
+                // Add notice
+                wc_add_notice(
+                    sprintf(
+                        __('%s has been automatically added to your cart as it is required.', 'studiou-wc-mandatory-products'),
+                        $product_name
+                    ),
+                    'notice'
+                );
+            }
+        }
+    }

+ 124 - 0
studiou-wc-mandatory-products/readme.md

@@ -0,0 +1,124 @@
+# QDR - Studiou WC Mandatory Products
+
+Version: 1.0.0
+
+A WordPress plugin that extends WooCommerce to add mandatory products to cart based on product categories.
+
+## Description
+
+StudioU WC Mandatory Products allows store administrators to define mandatory products at the category level. When a customer adds a product from a configured category to their cart, the mandatory products are automatically added as well.
+
+### Key Features
+
+- Extend WooCommerce Product Categories with mandatory product settings
+- Automatic addition of mandatory products to cart
+- Support for parent-child category inheritance
+- Two types of mandatory product behavior:
+  - OnePerCart: Add once per cart
+  - OnePerProduct: Add once for each product that requires it
+- Visual indicators for mandatory products in cart
+- Non-removable mandatory products in cart
+- Admin meta box showing mandatory products for a given product
+
+## Installation
+
+1. Upload the `studiou-wc-mandatory-products` folder to the `/wp-content/plugins/` directory
+2. Activate the plugin through the 'Plugins' menu in WordPress
+3. Ensure WooCommerce is installed and activated
+
+## Requirements
+
+- WordPress 6.7.2 or higher
+- WooCommerce 3.0 or higher
+- PHP 8.2 or higher
+
+## Usage
+
+### Setting Up Mandatory Products
+
+1. Navigate to **Products > Categories** in your WordPress admin
+2. Edit an existing category or create a new one
+3. Scroll down to the **Mandatory Products** section
+4. Click the "Add Mandatory Product" button
+5. Search for and select a product that should be mandatory
+6. Set the quantity and type:
+   - **OnePerCart**: Add only once per cart
+   - **OnePerProduct**: Add once for each product from this category
+7. Save the category
+
+### Inheritance
+
+Child categories automatically inherit the mandatory product settings from their parent categories. This makes it easy to set up mandatory products at a high level and have them apply to all subcategories.
+
+### Example Scenario
+
+**Product categories tree:**
+- Main Category
+  - SubCategory1
+  - SubCategory2
+
+**Products:**
+- MandatoryProduct1 (In Main Category)
+- ProductA (In SubCategory1)
+- ProductB (In SubCategory2)
+
+**Settings:**
+- Main Category
+  - Mandatory Products: MandatoryProduct1, amount: 1, type: OnePerCart
+
+**User Ordering:**
+1. Add to cart ProductA (10pcs)
+2. Add to cart ProductB (2pcs)
+3. Cart will contain:
+   - ProductA (10pcs)
+   - ProductB (2pcs)
+   - MandatoryProduct1 (1pc) - Automatically added
+
+## How It Works
+
+When a customer adds a product to their cart, the plugin checks if the product's category (or any parent category) has mandatory products configured. If it does, those mandatory products are automatically added to the cart based on the specified rules.
+
+Mandatory products in the cart are:
+- Clearly marked as mandatory
+- Cannot be removed directly (customer must remove the original product)
+- Cannot have their quantity changed
+
+## Customization
+
+### CSS
+
+The plugin includes basic styles for the frontend, which you can override in your theme's stylesheet.
+
+### Hooks and Filters
+
+Developers can use the following hooks to extend or modify the plugin's functionality:
+
+- `studiou_wcmp_before_add_mandatory_products`: Fires before mandatory products are added to cart
+- `studiou_wcmp_after_add_mandatory_products`: Fires after mandatory products are added to cart
+- `studiou_wcmp_get_mandatory_products`: Filter to modify the list of mandatory products for a given product
+
+## Frequently Asked Questions
+
+### Can I make a variable product mandatory?
+
+Yes, you can select any product type as a mandatory product, including variable products. However, the plugin will add the default variation if no specific variation is selected.
+
+### Can customers remove mandatory products from cart?
+
+No, mandatory products cannot be removed directly. They will be removed automatically when the product that triggered their addition is removed.
+
+### Will mandatory products inherit quantity from the main product?
+
+No, mandatory products will be added with the quantity specified in the category settings, regardless of how many of the main product are added to the cart.
+
+## Support
+
+For support requests, please contact us at https://www.quadarax.com or open an issue on our GitHub repository.
+
+## License
+
+This plugin is licensed under the GPL v2 or later.
+
+## Credits
+
+Developed by Dalibor Votruba (Quadarax)

+ 297 - 0
studiou-wc-mandatory-products/studiou-wc-mandatory-products.php

@@ -0,0 +1,297 @@
+<?php
+/**
+ * Plugin Name: QDR - Studiou WC Mandatory Products
+ * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-mandatory-products
+ * Description: Extends WooCommerce to add mandatory products to cart based on product categories.
+ * Version: 1.0.0
+ * Requires at least: 6.7.2
+ * Requires PHP: 8.2
+ * Author: Dalibor Votruba
+ * Author URI: https://www.quadarax.com
+ * License: GPL v2 or later
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
+ * Text Domain: studiou-wc-product-cat-manage
+ * Domain Path: /languages
+ * WC requires at least: 3.0
+ * WC tested up to: 8.0
+ */
+
+// Exit if accessed directly
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+// Check if WooCommerce is active
+if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
+    return;
+}
+
+if (!class_exists('StudioU_WC_Mandatory_Products')) {
+
+    class StudioU_WC_Mandatory_Products {
+
+        /**
+         * Constructor
+         */
+        public function __construct() {
+            // Define constants
+            $this->define_constants();
+
+            // Include required files
+            $this->includes();
+
+            // Initialize hooks
+            $this->init_hooks();
+        }
+
+        /**
+         * Define plugin constants
+         */
+        private function define_constants() {
+            define('STUDIOU_WCMP_VERSION', '1.0.0');
+            define('STUDIOU_WCMP_PLUGIN_DIR', plugin_dir_path(__FILE__));
+            define('STUDIOU_WCMP_PLUGIN_URL', plugin_dir_url(__FILE__));
+        }
+
+        /**
+         * Include required files
+         */
+        private function includes() {
+            // Admin classes
+            require_once STUDIOU_WCMP_PLUGIN_DIR . 'includes/class-studiou-wcmp-admin.php';
+            
+            // Frontend classes
+            require_once STUDIOU_WCMP_PLUGIN_DIR . 'includes/class-studiou-wcmp-frontend.php';
+        }
+
+        /**
+         * Initialize hooks
+         */
+        private function init_hooks() {
+            // Activation hook
+            register_activation_hook(__FILE__, array($this, 'activate'));
+            
+            // Admin hooks
+            if (is_admin()) {
+                add_action('init', array($this, 'init_admin'), 10);
+            }
+            
+            // Frontend hooks
+            add_action('init', array($this, 'init_frontend'), 10);
+            
+            // Add mandatory products to cart
+            add_action('woocommerce_add_to_cart', array($this, 'add_mandatory_products_to_cart'), 10, 6);
+            
+            // Display mandatory products info on product page
+            add_action('woocommerce_before_add_to_cart_button', array($this, 'display_mandatory_products_info'), 10);
+        }
+        
+        /**
+         * Plugin activation
+         */
+        public function activate() {
+            // Nothing to do at activation for now
+        }
+        
+        /**
+         * Initialize admin
+         */
+        public function init_admin() {
+            $admin = new StudioU_WCMP_Admin();
+            $admin->init();
+        }
+        
+        /**
+         * Initialize frontend
+         */
+        public function init_frontend() {
+            $frontend = new StudioU_WCMP_Frontend();
+            $frontend->init();
+        }
+        
+        /**
+         * Get all mandatory products for a product
+         * 
+         * @param int $product_id The product ID
+         * @return array Array of mandatory products
+         */
+        public function get_mandatory_products($product_id) {
+            $product_categories = get_the_terms($product_id, 'product_cat');
+            
+            if (!$product_categories || is_wp_error($product_categories)) {
+                return array();
+            }
+            
+            $mandatory_products = array();
+            $processed_categories = array();
+            
+            // Get category lineage including parent categories
+            foreach ($product_categories as $category) {
+                $this->get_mandatory_products_from_category_lineage($category, $mandatory_products, $processed_categories);
+            }
+            
+            return $mandatory_products;
+        }
+        
+        /**
+         * Recursive function to get mandatory products from a category and its parents
+         * 
+         * @param object $category The category object
+         * @param array &$mandatory_products Array to store mandatory products
+         * @param array &$processed_categories Array to track processed categories to avoid duplicates
+         */
+        private function get_mandatory_products_from_category_lineage($category, &$mandatory_products, &$processed_categories) {
+            // Skip if we've already processed this category
+            if (in_array($category->term_id, $processed_categories)) {
+                return;
+            }
+            
+            // Mark as processed
+            $processed_categories[] = $category->term_id;
+            
+            // Get mandatory products for this category
+            $category_mandatory_products = get_term_meta($category->term_id, 'mandatory_products', true);
+            
+            if (!empty($category_mandatory_products) && is_array($category_mandatory_products)) {
+                foreach ($category_mandatory_products as $mandatory_product) {
+                    $mandatory_products[] = $mandatory_product;
+                }
+            }
+            
+            // Process parent category if exists
+            if ($category->parent > 0) {
+                $parent_category = get_term($category->parent, 'product_cat');
+                if ($parent_category && !is_wp_error($parent_category)) {
+                    $this->get_mandatory_products_from_category_lineage($parent_category, $mandatory_products, $processed_categories);
+                }
+            }
+        }
+        
+        /**
+         * Add mandatory products to cart
+         * 
+         * @param string $cart_item_key Cart item key
+         * @param int $product_id Product ID
+         * @param int $quantity Quantity
+         * @param int $variation_id Variation ID
+         * @param array $variation Variation data
+         * @param array $cart_item_data Cart item data
+         */
+        public function add_mandatory_products_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
+            // Don't execute for mandatory products themselves to avoid infinite loops
+            if (isset($cart_item_data['mandatory_product'])) {
+                return;
+            }
+            
+            // Get mandatory products for this product
+            $mandatory_products = $this->get_mandatory_products($product_id);
+            
+            if (empty($mandatory_products)) {
+                return;
+            }
+            
+            // Get cart contents
+            $cart_contents = WC()->cart->get_cart();
+            
+            foreach ($mandatory_products as $mandatory_product) {
+                $mandatory_product_id = absint($mandatory_product['product_id']);
+                $mandatory_product_quantity = absint($mandatory_product['quantity']);
+                $mandatory_product_type = sanitize_text_field($mandatory_product['type']);
+                
+                // Skip if product doesn't exist
+                $product = wc_get_product($mandatory_product_id);
+                if (!$product || !$product->is_purchasable() || !$product->is_in_stock()) {
+                    continue;
+                }
+                
+                $add_to_cart = true;
+                
+                // Check if we need to add based on type
+                if ($mandatory_product_type === 'OnePerCart') {
+                    // Check if product already exists in cart
+                    foreach ($cart_contents as $cart_item) {
+                        if ($cart_item['product_id'] == $mandatory_product_id) {
+                            $add_to_cart = false;
+                            break;
+                        }
+                    }
+                } elseif ($mandatory_product_type === 'OnePerProduct') {
+                    // Check if this mandatory product has been added for this specific product already
+                    foreach ($cart_contents as $cart_item) {
+                        if (isset($cart_item['mandatory_for']) && 
+                            $cart_item['mandatory_for'] == $product_id && 
+                            $cart_item['product_id'] == $mandatory_product_id) {
+                            $add_to_cart = false;
+                            break;
+                        }
+                    }
+                }
+                
+                // Add the mandatory product to cart if needed
+                if ($add_to_cart) {
+                    // Add metadata to identify this as a mandatory product
+                    $extra_data = array(
+                        'mandatory_product' => true,
+                        'mandatory_for' => $product_id
+                    );
+                    
+                    // Add to cart
+                    WC()->cart->add_to_cart(
+                        $mandatory_product_id,
+                        $mandatory_product_quantity,
+                        0, // No variation
+                        array(), // No variation data
+                        $extra_data
+                    );
+                }
+            }
+        }
+        
+        /**
+         * Display mandatory products info on product page
+         */
+        public function display_mandatory_products_info() {
+            global $product;
+            
+            if (!$product) {
+                return;
+            }
+            
+            $mandatory_products = $this->get_mandatory_products($product->get_id());
+            
+            if (empty($mandatory_products)) {
+                return;
+            }
+            
+            echo '<div class="mandatory-products-info">';
+            echo '<h4>' . __('This product includes the following mandatory items:', 'studiou-wc-mandatory-products') . '</h4>';
+            echo '<ul>';
+            
+            foreach ($mandatory_products as $mandatory_product) {
+                $product_id = absint($mandatory_product['product_id']);
+                $quantity = absint($mandatory_product['quantity']);
+                $type = sanitize_text_field($mandatory_product['type']);
+                
+                $mandatory_product_obj = wc_get_product($product_id);
+                
+                if ($mandatory_product_obj) {
+                    echo '<li>';
+                    echo sprintf(
+                        '<a href="%s">%s</a> x %d (%s)',
+                        get_permalink($product_id),
+                        $mandatory_product_obj->get_name(),
+                        $quantity,
+                        $type === 'OnePerCart' ? __('One per cart', 'studiou-wc-mandatory-products') : __('One per product', 'studiou-wc-mandatory-products')
+                    );
+                    echo '</li>';
+                }
+            }
+            
+            echo '</ul>';
+            echo '</div>';
+        }
+    }
+    
+    // Initialize the plugin
+    new StudioU_WC_Mandatory_Products();
+}