Home / Admin / Cortex E-commerce Products SEO Meta
Duplicate Snippet

Embed Snippet on Your Site

Cortex E-commerce Products SEO Meta

<10
Code Preview
php
<?php
/**
 * Cortex E-commerce Products SEO Meta API v1.0
 * REST Endpoints for WooCommerce Product & Product Category SEO Data
 * 
 * For meta titles, descriptions, excerpts (short descriptions), focus keywords,
 * and social meta on WooCommerce products and product categories.
 * Works with RankMath SEO plugin.
 *
 * ============================================================================
 * E-COMMERCE SEO BEST PRACTICES 2025 - TOP 0.1% STANDARDS
 * ============================================================================
 *
 * PRODUCT TITLE (post_title) - The H1:
 *   - Primary keyword within first 3-4 words
 *   - Brand + Product Type + Key Attribute format
 *   - Clear, descriptive - tells user exactly what the product is
 *   - Include model numbers or key specs when relevant
 *   - Unique per product
 *
 * META TITLE (rank_math_title):
 *   - 50-60 characters max (55 is sweet spot)
 *   - Primary keyword within first 3-4 words
 *   - Include price OR "Buy" OR availability when possible
 *   - Brand name if high recognition value
 *   - Format: "[Product] [Specs] | [Price/Buy/Brand]"
 *
 * META DESCRIPTION (rank_math_description):
 *   - 150-155 characters (truncates at ~155-160)
 *   - Primary keyword in first sentence
 *   - Include: price, key specs, availability, shipping
 *   - Strong CTA: "Shop now", "Buy online", "Free shipping"
 *   - Urgency elements when applicable
 *
 * PRODUCT SHORT DESCRIPTION (post_excerpt):
 *   - 50-75 WORDS (not characters!)
 *   - Primary keyword in FIRST sentence
 *   - Entity-first format: "[Product Name] features..."
 *   - Include: key specs, primary benefits, use case
 *   - Citable facts: dimensions, capacity, compatibility
 *   - NO hedging language (may, might, could)
 *   - Price mention when stable
 *
 * FOCUS KEYWORD (rank_math_focus_keyword):
 *   ⚠️ CRITICAL FORMAT: 1 primary + 4-5 secondary variations, comma-separated
 *   
 *   CORRECT:
 *   "wireless earbuds, bluetooth earbuds, wireless headphones, earbuds with mic, best wireless earbuds"
 *   
 *   WRONG:
 *   "wireless" (single word)
 *   "wireless, earbuds, bluetooth, headphones" (random words)
 *   
 *   Rules:
 *   - Primary = highest search volume product term
 *   - Include brand + product variations
 *   - Include "buy [product]", "[product] price" variants
 *   - Check for cannibalization across product catalog
 *
 * FACEBOOK TITLE (rank_math_facebook_title):
 *   - 60-90 characters
 *   - Include price or discount when applicable
 *   - "Shop [Product]" or "[Product] - Now Available" format
 *   - Engaging for social sharing
 *
 * FACEBOOK DESCRIPTION (rank_math_facebook_description):
 *   - Up to 200 characters
 *   - Highlight key benefit + price + CTA
 *   - Social-friendly, promotional tone
 *
 * TWITTER TITLE (rank_math_twitter_title):
 *   - 60-90 characters
 *   - Can be more concise/punchy than FB
 *   - Works in Twitter card format
 *
 * TWITTER DESCRIPTION (rank_math_twitter_description):
 *   - Up to 200 characters
 *   - Quick benefit + urgency + CTA
 *
 * PRODUCT CATEGORIES:
 *   - Hierarchical structure for navigation
 *   - Keywords in category names
 *   - SEO optimized category descriptions
 *   - Max 2-3 categories per product for clarity
 *
 * E-COMMERCE SPECIFIC:
 *   - Include product schema signals in content
 *   - Price in descriptions when stable
 *   - Availability signals ("In Stock", "Ships Free")
 *   - Review/rating mentions when applicable
 *   - Comparison signals for featured snippets
 *
 * CANNIBALIZATION:
 *   - Watch for product variants targeting same keyword
 *   - Parent vs variation keyword strategy
 *   - Category vs product keyword overlap
 *
 * ============================================================================
 * ALL AUDIT FIELDS FOR PRODUCTS:
 * ============================================================================
 * 1. post_title - Product title (H1)
 * 2. rank_math_title - Meta title (50-60 chars)
 * 3. rank_math_description - Meta description (150-155 chars)
 * 4. rank_math_focus_keyword - 1 primary + 4-5 secondary, comma-separated
 * 5. rank_math_facebook_title - OG title (60-90 chars)
 * 6. rank_math_facebook_description - OG description (up to 200 chars)
 * 7. rank_math_twitter_title - Twitter title (60-90 chars)
 * 8. rank_math_twitter_description - Twitter description (up to 200 chars)
 * 9. excerpt (post_excerpt) - Short description, 50-75 WORDS
 * ============================================================================
 * 
 * ADDITIONAL PRODUCT CONTEXT FIELDS (Read-Only for Audit):
 * - price (regular price)
 * - sale_price
 * - sku
 * - stock_status
 * - product_type (simple, variable, grouped, external)
 * - total_sales
 * - average_rating
 * - review_count
 * ============================================================================
 */
// ============================================================================
// CHECK FOR WOOCOMMERCE
// ============================================================================
if (!function_exists('cortex_ecommerce_check_woo')) {
    function cortex_ecommerce_check_woo() {
        return class_exists('WooCommerce');
    }
}
// ============================================================================
// REST API REGISTRATION
// ============================================================================
add_action('rest_api_init', function() {
    
    // Only register if WooCommerce is active
    if (!cortex_ecommerce_check_woo()) {
        // Register a single endpoint to report WooCommerce not active
        register_rest_route('cortex-ecommerce/v1', '/status', array(
            'methods' => 'GET',
            'callback' => function() {
                return new WP_Error('woo_not_active', 'WooCommerce is not active on this site', array('status' => 503));
            },
            'permission_callback' => '__return_true',
        ));
        return;
    }
    
    // GET /wp-json/cortex-ecommerce/v1/status - API status check
    register_rest_route('cortex-ecommerce/v1', '/status', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_status',
        'permission_callback' => '__return_true',
    ));
    
    // ========================================================================
    // PRODUCT ENDPOINTS
    // ========================================================================
    
    // GET /wp-json/cortex-ecommerce/v1/products - Get all products SEO data
    register_rest_route('cortex-ecommerce/v1', '/products', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_get_products',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // GET /wp-json/cortex-ecommerce/v1/products/{id} - Get single product
    register_rest_route('cortex-ecommerce/v1', '/products/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_get_product',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/products/{id} - Update single product
    register_rest_route('cortex-ecommerce/v1', '/products/(?P<id>\d+)', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_update_product',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/products/bulk - Bulk update products
    register_rest_route('cortex-ecommerce/v1', '/products/bulk', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_bulk_update_products',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/products/bulk-get - Bulk get products
    register_rest_route('cortex-ecommerce/v1', '/products/bulk-get', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_bulk_get_products',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/products/mass-categorize - Mass categorize products
    // Efficient server-side categorization with pattern matching
    register_rest_route('cortex-ecommerce/v1', '/products/mass-categorize', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_mass_categorize_products',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // ========================================================================
    // PRODUCT CATEGORY ENDPOINTS
    // ========================================================================
    
    // GET /wp-json/cortex-ecommerce/v1/product-categories - List all product categories
    register_rest_route('cortex-ecommerce/v1', '/product-categories', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_get_product_categories',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // GET /wp-json/cortex-ecommerce/v1/product-categories/{id} - Get single category
    register_rest_route('cortex-ecommerce/v1', '/product-categories/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_get_product_category',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/product-categories - Create category
    register_rest_route('cortex-ecommerce/v1', '/product-categories', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_create_product_category',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/product-categories/{id} - Update category
    register_rest_route('cortex-ecommerce/v1', '/product-categories/(?P<id>\d+)', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_update_product_category',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // DELETE /wp-json/cortex-ecommerce/v1/product-categories/{id} - Delete category
    register_rest_route('cortex-ecommerce/v1', '/product-categories/(?P<id>\d+)', array(
        'methods' => 'DELETE',
        'callback' => 'cortex_ecommerce_delete_product_category',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // POST /wp-json/cortex-ecommerce/v1/product-categories/bulk - Bulk update categories
    register_rest_route('cortex-ecommerce/v1', '/product-categories/bulk', array(
        'methods' => 'POST',
        'callback' => 'cortex_ecommerce_bulk_update_product_categories',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
    
    // ========================================================================
    // PRODUCT TAGS ENDPOINTS (Bonus)
    // ========================================================================
    
    // GET /wp-json/cortex-ecommerce/v1/product-tags - List all product tags
    register_rest_route('cortex-ecommerce/v1', '/product-tags', array(
        'methods' => 'GET',
        'callback' => 'cortex_ecommerce_get_product_tags',
        'permission_callback' => 'cortex_ecommerce_permission_check',
    ));
});
// ============================================================================
// PERMISSION CHECK
// ============================================================================
function cortex_ecommerce_permission_check() {
    return current_user_can('edit_products');
}
// ============================================================================
// STATUS ENDPOINT
// ============================================================================
function cortex_ecommerce_status() {
    $product_count = wp_count_posts('product');
    $category_count = wp_count_terms('product_cat');
    
    return array(
        'status' => 'active',
        'api_version' => '1.0',
        'woocommerce_active' => true,
        'woocommerce_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown',
        'products' => array(
            'published' => intval($product_count->publish),
            'draft' => intval($product_count->draft),
            'total' => intval($product_count->publish) + intval($product_count->draft)
        ),
        'product_categories' => is_wp_error($category_count) ? 0 : intval($category_count),
        'endpoints' => array(
            'products' => '/cortex-ecommerce/v1/products',
            'product_single' => '/cortex-ecommerce/v1/products/{id}',
            'products_bulk' => '/cortex-ecommerce/v1/products/bulk',
            'products_bulk_get' => '/cortex-ecommerce/v1/products/bulk-get',
            'products_mass_categorize' => '/cortex-ecommerce/v1/products/mass-categorize',
            'product_categories' => '/cortex-ecommerce/v1/product-categories',
            'product_category_single' => '/cortex-ecommerce/v1/product-categories/{id}',
            'product_tags' => '/cortex-ecommerce/v1/product-tags'
        )
    );
}
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
 * Get product categories for a product
 */
function cortex_ecommerce_get_product_cats($product_id) {
    $terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'all'));
    $cat_data = array();
    
    if (!is_wp_error($terms)) {
        foreach ($terms as $term) {
            $cat_data[] = array(
                'id' => $term->term_id,
                'name' => $term->name,
                'slug' => $term->slug,
                'link' => get_term_link($term)
            );
        }
    }
    
    return $cat_data;
}
/**
 * Get product tags for a product
 */
function cortex_ecommerce_get_product_tag_data($product_id) {
    $terms = wp_get_post_terms($product_id, 'product_tag', array('fields' => 'all'));
    $tag_data = array();
    
    if (!is_wp_error($terms)) {
        foreach ($terms as $term) {
            $tag_data[] = array(
                'id' => $term->term_id,
                'name' => $term->name,
                'slug' => $term->slug
            );
        }
    }
    
    return $tag_data;
}
/**
 * Build product data - ALL SEO FIELDS + WooCommerce context
 */
function cortex_ecommerce_build_product_data($post, $include_categories = true) {
    // Get WooCommerce product object
    $product = wc_get_product($post->ID);
    
    if (!$product) {
        return null;
    }
    
    // Get excerpt (short description)
    $excerpt = $post->post_excerpt;
    if (empty($excerpt)) {
        $excerpt = '';
    }
    
    // Count words in excerpt for audit
    $excerpt_word_count = str_word_count(strip_tags($excerpt));
    
    // Get focus keyword and parse
    $focus_keyword = get_post_meta($post->ID, 'rank_math_focus_keyword', true);
    $focus_keywords_array = array_map('trim', explode(',', $focus_keyword));
    $focus_keyword_count = count(array_filter($focus_keywords_array));
    $primary_keyword = !empty($focus_keywords_array[0]) ? $focus_keywords_array[0] : '';
    
    // Build item data
    $item = array(
        'id' => $post->ID,
        'slug' => $post->post_name,
        'post_type' => 'product',
        'product_type' => $product->get_type(),
        'link' => get_permalink($post->ID),
        'status' => $post->post_status,
        
        // ALL 9 SEO AUDIT FIELDS
        'post_title' => $post->post_title,
        'rank_math_title' => get_post_meta($post->ID, 'rank_math_title', true),
        'rank_math_description' => get_post_meta($post->ID, 'rank_math_description', true),
        'rank_math_focus_keyword' => $focus_keyword,
        'rank_math_facebook_title' => get_post_meta($post->ID, 'rank_math_facebook_title', true),
        'rank_math_facebook_description' => get_post_meta($post->ID, 'rank_math_facebook_description', true),
        'rank_math_twitter_title' => get_post_meta($post->ID, 'rank_math_twitter_title', true),
        'rank_math_twitter_description' => get_post_meta($post->ID, 'rank_math_twitter_description', true),
        'excerpt' => $excerpt,
        
        // WOOCOMMERCE CONTEXT (Read-Only for SEO context)
        'woo_context' => array(
            'sku' => $product->get_sku(),
            'price' => $product->get_price(),
            'regular_price' => $product->get_regular_price(),
            'sale_price' => $product->get_sale_price(),
            'on_sale' => $product->is_on_sale(),
            'stock_status' => $product->get_stock_status(),
            'stock_quantity' => $product->get_stock_quantity(),
            'total_sales' => $product->get_total_sales(),
            'average_rating' => $product->get_average_rating(),
            'review_count' => $product->get_review_count(),
            'is_featured' => $product->is_featured(),
            'is_virtual' => $product->is_virtual(),
            'is_downloadable' => $product->is_downloadable(),
        ),
        
        // AUDIT METADATA
        'audit_meta' => array(
            'post_title_chars' => strlen($post->post_title),
            'meta_title_chars' => strlen(get_post_meta($post->ID, 'rank_math_title', true)),
            'meta_desc_chars' => strlen(get_post_meta($post->ID, 'rank_math_description', true)),
            'excerpt_words' => $excerpt_word_count,
            'focus_keyword_count' => $focus_keyword_count,
            'primary_keyword' => $primary_keyword,
            'fb_title_chars' => strlen(get_post_meta($post->ID, 'rank_math_facebook_title', true)),
            'fb_desc_chars' => strlen(get_post_meta($post->ID, 'rank_math_facebook_description', true)),
            'twitter_title_chars' => strlen(get_post_meta($post->ID, 'rank_math_twitter_title', true)),
            'twitter_desc_chars' => strlen(get_post_meta($post->ID, 'rank_math_twitter_description', true)),
            'has_price_in_meta' => (
                stripos(get_post_meta($post->ID, 'rank_math_description', true), '$') !== false ||
                stripos(get_post_meta($post->ID, 'rank_math_description', true), 'price') !== false
            ),
        ),
    );
    
    if ($include_categories) {
        $item['categories'] = cortex_ecommerce_get_product_cats($post->ID);
        $item['category_ids'] = wp_get_post_terms($post->ID, 'product_cat', array('fields' => 'ids'));
        $item['tags'] = cortex_ecommerce_get_product_tag_data($post->ID);
        $item['tag_ids'] = wp_get_post_terms($post->ID, 'product_tag', array('fields' => 'ids'));
    }
    
    return $item;
}
/**
 * Process product update
 */
function cortex_ecommerce_process_product_update($product_id, $data) {
    $post = get_post($product_id);
    
    if (!$post || $post->post_type !== 'product') {
        return array('success' => false, 'error' => 'Product not found', 'id' => $product_id);
    }
    
    $updated = array();
    $post_updates = array();
    
    // Product title (H1)
    if (isset($data['post_title'])) {
        $post_updates['post_title'] = sanitize_text_field($data['post_title']);
        $updated['post_title'] = $data['post_title'];
    }
    
    // Slug
    if (isset($data['slug'])) {
        $post_updates['post_name'] = sanitize_title($data['slug']);
        $updated['slug'] = $data['slug'];
    }
    
    // Excerpt (Short Description)
    if (isset($data['excerpt'])) {
        $post_updates['post_excerpt'] = wp_kses_post($data['excerpt']);
        $updated['excerpt'] = $data['excerpt'];
    }
    
    // Also accept 'short_description' as alias for excerpt
    if (isset($data['short_description'])) {
        $post_updates['post_excerpt'] = wp_kses_post($data['short_description']);
        $updated['short_description'] = $data['short_description'];
    }
    
    if (!empty($post_updates)) {
        $post_updates['ID'] = $product_id;
        wp_update_post($post_updates);
    }
    
    // Product Category management
    // Set categories by ID
    if (isset($data['category_ids']) && is_array($data['category_ids'])) {
        $category_ids = array_map('intval', $data['category_ids']);
        wp_set_post_terms($product_id, $category_ids, 'product_cat');
        $updated['category_ids'] = $category_ids;
    }
    
    // Set categories by name (creates if not exists)
    if (isset($data['categories']) && is_array($data['categories'])) {
        $cat_ids = array();
        foreach ($data['categories'] as $cat_name) {
            $cat_name = sanitize_text_field($cat_name);
            $term = get_term_by('name', $cat_name, 'product_cat');
            if ($term) {
                $cat_ids[] = $term->term_id;
            } else {
                $new_term = wp_insert_term($cat_name, 'product_cat');
                if (!is_wp_error($new_term)) {
                    $cat_ids[] = $new_term['term_id'];
                }
            }
        }
        wp_set_post_terms($product_id, $cat_ids, 'product_cat');
        $updated['categories'] = $data['categories'];
        $updated['category_ids'] = $cat_ids;
    }
    
    // Add categories (preserves existing)
    if (isset($data['add_categories']) && is_array($data['add_categories'])) {
        $existing_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
        $new_cats = array();
        
        foreach ($data['add_categories'] as $cat) {
            if (is_numeric($cat)) {
                $new_cats[] = intval($cat);
            } else {
                $cat_name = sanitize_text_field($cat);
                $term = get_term_by('name', $cat_name, 'product_cat');
                if ($term) {
                    $new_cats[] = $term->term_id;
                } else {
                    $new_term = wp_insert_term($cat_name, 'product_cat');
                    if (!is_wp_error($new_term)) {
                        $new_cats[] = $new_term['term_id'];
                    }
                }
            }
        }
        
        $merged = array_unique(array_merge($existing_cats, $new_cats));
        wp_set_post_terms($product_id, $merged, 'product_cat');
        $updated['add_categories'] = $data['add_categories'];
    }
    
    // Remove categories
    if (isset($data['remove_categories']) && is_array($data['remove_categories'])) {
        $existing_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
        $remove_ids = array();
        
        foreach ($data['remove_categories'] as $cat) {
            if (is_numeric($cat)) {
                $remove_ids[] = intval($cat);
            } else {
                $term = get_term_by('name', sanitize_text_field($cat), 'product_cat');
                if ($term) {
                    $remove_ids[] = $term->term_id;
                }
            }
        }
        
        $remaining = array_diff($existing_cats, $remove_ids);
        wp_set_post_terms($product_id, $remaining, 'product_cat');
        $updated['remove_categories'] = $data['remove_categories'];
    }
    
    // Product Tag management
    if (isset($data['tag_ids']) && is_array($data['tag_ids'])) {
        $tag_ids = array_map('intval', $data['tag_ids']);
        wp_set_post_terms($product_id, $tag_ids, 'product_tag');
        $updated['tag_ids'] = $tag_ids;
    }
    
    if (isset($data['tags']) && is_array($data['tags'])) {
        $tag_ids = array();
        foreach ($data['tags'] as $tag_name) {
            $tag_name = sanitize_text_field($tag_name);
            $term = get_term_by('name', $tag_name, 'product_tag');
            if ($term) {
                $tag_ids[] = $term->term_id;
            } else {
                $new_term = wp_insert_term($tag_name, 'product_tag');
                if (!is_wp_error($new_term)) {
                    $tag_ids[] = $new_term['term_id'];
                }
            }
        }
        wp_set_post_terms($product_id, $tag_ids, 'product_tag');
        $updated['tags'] = $data['tags'];
    }
    
    // RankMath meta fields - ALL 7 meta fields
    $meta_fields = array(
        'rank_math_title',
        'rank_math_description',
        'rank_math_focus_keyword',
        'rank_math_facebook_title',
        'rank_math_facebook_description',
        'rank_math_twitter_title',
        'rank_math_twitter_description'
    );
    
    foreach ($meta_fields as $field) {
        if (isset($data[$field])) {
            update_post_meta($product_id, $field, sanitize_text_field($data[$field]));
            $updated[$field] = $data[$field];
        }
    }
    
    return array(
        'success' => true,
        'id' => $product_id,
        'updated_fields' => $updated,
        'fields_count' => count($updated)
    );
}
// ============================================================================
// PRODUCT ENDPOINTS
// ============================================================================
function cortex_ecommerce_get_products($request) {
    $per_page = $request->get_param('per_page') ? intval($request->get_param('per_page')) : 100;
    $page = $request->get_param('page') ? intval($request->get_param('page')) : 1;
    $status = $request->get_param('status') ? sanitize_text_field($request->get_param('status')) : 'publish';
    $category = $request->get_param('category') ? intval($request->get_param('category')) : null;
    $category_ids = $request->get_param('category_ids'); // Support multiple categories
    $uncategorized = $request->get_param('uncategorized'); // Filter for uncategorized products
    $on_sale = $request->get_param('on_sale');
    $featured = $request->get_param('featured');
    
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => min($per_page, 100),
        'paged' => $page,
        'post_status' => $status
    );
    
    // Filter by single category
    if ($category) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => $category
            )
        );
    }
    
    // Filter by multiple categories (OR logic - products in ANY of these categories)
    if ($category_ids && is_array($category_ids)) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => array_map('intval', $category_ids),
                'operator' => 'IN'
            )
        );
    }
    
    // Filter for uncategorized products (no categories assigned)
    if ($uncategorized === 'true' || $uncategorized === '1') {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'operator' => 'NOT EXISTS'
            )
        );
    }
    
    // Filter by on_sale
    if ($on_sale === 'true' || $on_sale === '1') {
        $args['meta_query'] = array(
            'relation' => 'OR',
            array(
                'key' => '_sale_price',
                'value' => 0,
                'compare' => '>',
                'type' => 'NUMERIC'
            ),
            array(
                'key' => '_min_variation_sale_price',
                'value' => 0,
                'compare' => '>',
                'type' => 'NUMERIC'
            )
        );
    }
    
    // Filter by featured
    if ($featured === 'true' || $featured === '1') {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_visibility',
                'field' => 'name',
                'terms' => 'featured',
            )
        );
    }
    
    $products = get_posts($args);
    $total = wp_count_posts('product');
    $total_count = ($status === 'publish') ? $total->publish : $total->$status;
    
    $result = array();
    $primary_keywords = array();
    
    foreach ($products as $product) {
        $item = cortex_ecommerce_build_product_data($product);
        if ($item) {
            $result[] = $item;
            
            // Track primary keywords for cannibalization
            if (!empty($item['audit_meta']['primary_keyword'])) {
                $pk = strtolower(trim($item['audit_meta']['primary_keyword']));
                if (!isset($primary_keywords[$pk])) {
                    $primary_keywords[$pk] = array();
                }
                $primary_keywords[$pk][] = array(
                    'id' => $item['id'],
                    'slug' => $item['slug'],
                    'post_title' => $item['post_title'],
                    'sku' => $item['woo_context']['sku']
                );
            }
        }
    }
    
    // Build cannibalization report
    $cannibalization = array();
    foreach ($primary_keywords as $keyword => $pages) {
        if (count($pages) > 1) {
            $cannibalization[] = array(
                'keyword' => $keyword,
                'count' => count($pages),
                'products' => $pages
            );
        }
    }
    
    return array(
        'items' => $result,
        'pagination' => array(
            'total' => intval($total_count),
            'per_page' => $per_page,
            'current_page' => $page,
            'total_pages' => ceil($total_count / $per_page)
        ),
        'cannibalization' => $cannibalization,
        'cannibalization_count' => count($cannibalization)
    );
}
function cortex_ecommerce_get_product($request) {
    $product_id = $request->get_param('id');
    $post = get_post($product_id);
    
    if (!$post || $post->post_type !== 'product') {
        return new WP_Error('not_found', 'Product not found', array('status' => 404));
    }
    
    $item = cortex_ecommerce_build_product_data($post);
    
    if (!$item) {
        return new WP_Error('not_found', 'Product not found', array('status' => 404));
    }
    
    return $item;
}
function cortex_ecommerce_update_product($request) {
    $product_id = $request->get_param('id');
    $data = $request->get_params();
    
    $result = cortex_ecommerce_process_product_update($product_id, $data);
    
    if (!$result['success']) {
        return new WP_Error('not_found', $result['error'], array('status' => 404));
    }
    
    if (empty($result['updated_fields'])) {
        return new WP_Error('no_fields', 'No valid fields provided to update', array('status' => 400));
    }
    
    $post = get_post($product_id);
    
    return array(
        'success' => true,
        'product_id' => $product_id,
        'updated_fields' => $result['updated_fields'],
        'current_data' => cortex_ecommerce_build_product_data($post),
        'message' => $result['fields_count'] . ' field(s) updated successfully'
    );
}
function cortex_ecommerce_bulk_update_products($request) {
    $items = $request->get_param('items');
    
    if (!$items || !is_array($items)) {
        return new WP_Error('invalid_payload', 'Expected "items" array in request body', array('status' => 400));
    }
    
    if (count($items) > 50) {
        return new WP_Error('too_many_items', 'Maximum 50 items per bulk request', array('status' => 400));
    }
    
    $results = array(
        'success' => array(),
        'failed' => array(),
        'summary' => array(
            'total' => count($items),
            'succeeded' => 0,
            'failed' => 0,
            'fields_updated' => 0
        )
    );
    
    foreach ($items as $item) {
        if (!isset($item['id'])) {
            $results['failed'][] = array('error' => 'Missing id field', 'item' => $item);
            $results['summary']['failed']++;
            continue;
        }
        
        $product_id = intval($item['id']);
        $result = cortex_ecommerce_process_product_update($product_id, $item);
        
        if ($result['success']) {
            $results['success'][] = $result;
            $results['summary']['succeeded']++;
            $results['summary']['fields_updated'] += $result['fields_count'];
        } else {
            $results['failed'][] = $result;
            $results['summary']['failed']++;
        }
    }
    
    return $results;
}
function cortex_ecommerce_bulk_get_products($request) {
    $body = $request->get_json_params();
    
    $ids = isset($body['ids']) ? $body['ids'] : $request->get_param('ids');
    $category = isset($body['category']) ? $body['category'] : $request->get_param('category');
    $category_ids = isset($body['category_ids']) ? $body['category_ids'] : null;
    $uncategorized = isset($body['uncategorized']) ? $body['uncategorized'] : false;
    $missing_meta = isset($body['missing_meta']) ? $body['missing_meta'] : $request->get_param('missing_meta');
    
    // Get by IDs
    if ($ids && is_array($ids)) {
        if (count($ids) > 100) {
            return new WP_Error('too_many_ids', 'Maximum 100 IDs per request', array('status' => 400));
        }
        
        $results = array();
        $not_found = array();
        $primary_keywords = array();
        
        foreach ($ids as $id) {
            $post = get_post(intval($id));
            if ($post && $post->post_type === 'product') {
                $item = cortex_ecommerce_build_product_data($post);
                if ($item) {
                    $results[] = $item;
                    
                    // Track for cannibalization
                    if (!empty($item['audit_meta']['primary_keyword'])) {
                        $pk = strtolower(trim($item['audit_meta']['primary_keyword']));
                        if (!isset($primary_keywords[$pk])) {
                            $primary_keywords[$pk] = array();
                        }
                        $primary_keywords[$pk][] = array(
                            'id' => $item['id'],
                            'slug' => $item['slug'],
                            'sku' => $item['woo_context']['sku']
                        );
                    }
                }
            } else {
                $not_found[] = intval($id);
            }
        }
        
        // Build cannibalization report
        $cannibalization = array();
        foreach ($primary_keywords as $keyword => $pages) {
            if (count($pages) > 1) {
                $cannibalization[] = array(
                    'keyword' => $keyword,
                    'count' => count($pages),
                    'products' => $pages
                );
            }
        }
        
        return array(
            'items' => $results,
            'not_found' => $not_found,
            'count' => count($results),
            'cannibalization' => $cannibalization
        );
    }
    
    // Get with filters
    $per_page = isset($body['per_page']) ? min(intval($body['per_page']), 100) : 50;
    $page = isset($body['page']) ? intval($body['page']) : 1;
    
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => $per_page,
        'paged' => $page,
        'post_status' => 'publish'
    );
    
    // Filter by single category (by ID or slug)
    if ($category) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => is_numeric($category) ? 'term_id' : 'slug',
                'terms' => $category
            )
        );
    }
    // Filter by multiple categories (OR logic - products in ANY of these)
    elseif ($category_ids && is_array($category_ids)) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => array_map('intval', $category_ids),
                'operator' => 'IN'
            )
        );
    }
    // Filter for uncategorized products
    elseif ($uncategorized) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'operator' => 'NOT EXISTS'
            )
        );
    }
    
    // Filter for products missing SEO meta
    if ($missing_meta) {
        $args['meta_query'] = array(
            'relation' => 'OR',
            array('key' => 'rank_math_title', 'compare' => 'NOT EXISTS'),
            array('key' => 'rank_math_title', 'value' => '', 'compare' => '='),
            array('key' => 'rank_math_description', 'compare' => 'NOT EXISTS'),
            array('key' => 'rank_math_description', 'value' => '', 'compare' => '=')
        );
    }
    
    $products = get_posts($args);
    
    // Get accurate total count for the current filter
    $count_args = $args;
    $count_args['posts_per_page'] = -1;
    $count_args['fields'] = 'ids';
    $total_in_filter = count(get_posts($count_args));
    
    $results = array();
    $primary_keywords = array();
    
    foreach ($products as $product) {
        $item = cortex_ecommerce_build_product_data($product);
        if ($item) {
            $results[] = $item;
            
            // Track for cannibalization
            if (!empty($item['audit_meta']['primary_keyword'])) {
                $pk = strtolower(trim($item['audit_meta']['primary_keyword']));
                if (!isset($primary_keywords[$pk])) {
                    $primary_keywords[$pk] = array();
                }
                $primary_keywords[$pk][] = array(
                    'id' => $item['id'],
                    'slug' => $item['slug']
                );
            }
        }
    }
    
    // Build cannibalization report
    $cannibalization = array();
    foreach ($primary_keywords as $keyword => $pages) {
        if (count($pages) > 1) {
            $cannibalization[] = array(
                'keyword' => $keyword,
                'count' => count($pages),
                'products' => $pages
            );
        }
    }
    
    return array(
        'items' => $results,
        'pagination' => array(
            'total' => intval($total),
            'per_page' => $per_page,
            'current_page' => $page,
            'total_pages' => ceil($total / $per_page)
        ),
        'count' => count($results),
        'cannibalization' => $cannibalization
    );
}
// ============================================================================
// PRODUCT CATEGORY ENDPOINTS
// ============================================================================
function cortex_ecommerce_get_product_categories($request) {
    $args = array(
        'taxonomy' => 'product_cat',
        'hide_empty' => false,
        'orderby' => 'name',
        'order' => 'ASC'
    );
    
    $parent = $request->get_param('parent');
    if ($parent !== null) {
        $args['parent'] = intval($parent);
    }
    
    $hide_empty = $request->get_param('hide_empty');
    if ($hide_empty === 'true' || $hide_empty === '1') {
        $args['hide_empty'] = true;
    }
    
    $categories = get_terms($args);
    
    if (is_wp_error($categories)) {
        return new WP_Error('error', 'Failed to retrieve product categories', array('status' => 500));
    }
    
    $result = array();
    foreach ($categories as $cat) {
        // Get thumbnail
        $thumbnail_id = get_term_meta($cat->term_id, 'thumbnail_id', true);
        $thumbnail_url = $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : '';
        
        $result[] = array(
            'id' => $cat->term_id,
            'name' => $cat->name,
            'slug' => $cat->slug,
            'description' => $cat->description,
            'parent' => $cat->parent,
            'count' => $cat->count,
            'link' => get_term_link($cat),
            'thumbnail_url' => $thumbnail_url,
            'rank_math_title' => get_term_meta($cat->term_id, 'rank_math_title', true),
            'rank_math_description' => get_term_meta($cat->term_id, 'rank_math_description', true),
            'rank_math_focus_keyword' => get_term_meta($cat->term_id, 'rank_math_focus_keyword', true),
            'rank_math_facebook_title' => get_term_meta($cat->term_id, 'rank_math_facebook_title', true),
            'rank_math_facebook_description' => get_term_meta($cat->term_id, 'rank_math_facebook_description', true),
            'rank_math_twitter_title' => get_term_meta($cat->term_id, 'rank_math_twitter_title', true),
            'rank_math_twitter_description' => get_term_meta($cat->term_id, 'rank_math_twitter_description', true),
        );
    }
    
    return $result;
}
function cortex_ecommerce_get_product_category($request) {
    $term_id = $request->get_param('id');
    $cat = get_term($term_id, 'product_cat');
    
    if (!$cat || is_wp_error($cat)) {
        return new WP_Error('not_found', 'Product category not found', array('status' => 404));
    }
    
    // Get thumbnail
    $thumbnail_id = get_term_meta($cat->term_id, 'thumbnail_id', true);
    $thumbnail_url = $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : '';
    
    // Get products in this category
    $products = get_posts(array(
        'post_type' => 'product',
        'posts_per_page' => -1,
        'post_status' => 'publish',
        'fields' => 'ids',
        'tax_query' => array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => $term_id
            )
        )
    ));
    
    return array(
        'id' => $cat->term_id,
        'name' => $cat->name,
        'slug' => $cat->slug,
        'description' => $cat->description,
        'parent' => $cat->parent,
        'count' => $cat->count,
        'link' => get_term_link($cat),
        'thumbnail_url' => $thumbnail_url,
        'product_ids' => $products,
        'rank_math_title' => get_term_meta($cat->term_id, 'rank_math_title', true),
        'rank_math_description' => get_term_meta($cat->term_id, 'rank_math_description', true),
        'rank_math_focus_keyword' => get_term_meta($cat->term_id, 'rank_math_focus_keyword', true),
        'rank_math_facebook_title' => get_term_meta($cat->term_id, 'rank_math_facebook_title', true),
        'rank_math_facebook_description' => get_term_meta($cat->term_id, 'rank_math_facebook_description', true),
        'rank_math_twitter_title' => get_term_meta($cat->term_id, 'rank_math_twitter_title', true),
        'rank_math_twitter_description' => get_term_meta($cat->term_id, 'rank_math_twitter_description', true),
        'audit_meta' => array(
            'name_chars' => strlen($cat->name),
            'desc_chars' => strlen($cat->description),
            'meta_title_chars' => strlen(get_term_meta($cat->term_id, 'rank_math_title', true)),
            'meta_desc_chars' => strlen(get_term_meta($cat->term_id, 'rank_math_description', true)),
        )
    );
}
function cortex_ecommerce_create_product_category($request) {
    $name = $request->get_param('name');
    
    if (empty($name)) {
        return new WP_Error('missing_name', 'Category name is required', array('status' => 400));
    }
    
    $args = array();
    
    $slug = $request->get_param('slug');
    if ($slug) $args['slug'] = sanitize_title($slug);
    
    $description = $request->get_param('description');
    if ($description) $args['description'] = sanitize_textarea_field($description);
    
    $parent = $request->get_param('parent');
    if ($parent) $args['parent'] = intval($parent);
    
    $result = wp_insert_term(sanitize_text_field($name), 'product_cat', $args);
    
    if (is_wp_error($result)) {
        return new WP_Error('create_failed', $result->get_error_message(), array('status' => 400));
    }
    
    $term_id = $result['term_id'];
    
    // Set RankMath meta
    $seo_fields = array(
        'rank_math_title', 'rank_math_description', 'rank_math_focus_keyword',
        'rank_math_facebook_title', 'rank_math_facebook_description',
        'rank_math_twitter_title', 'rank_math_twitter_description'
    );
    
    foreach ($seo_fields as $field) {
        $value = $request->get_param($field);
        if ($value !== null) {
            update_term_meta($term_id, $field, sanitize_text_field($value));
        }
    }
    
    $cat = get_term($term_id, 'product_cat');
    
    return array(
        'success' => true,
        'message' => 'Product category created successfully',
        'category' => array(
            'id' => $cat->term_id,
            'name' => $cat->name,
            'slug' => $cat->slug,
            'description' => $cat->description,
            'parent' => $cat->parent,
            'link' => get_term_link($cat),
        )
    );
}
function cortex_ecommerce_update_product_category($request) {
    $term_id = $request->get_param('id');
    $cat = get_term($term_id, 'product_cat');
    
    if (!$cat || is_wp_error($cat)) {
        return new WP_Error('not_found', 'Product category not found', array('status' => 404));
    }
    
    $updated = array();
    $term_args = array();
    
    $name = $request->get_param('name');
    if ($name !== null) { $term_args['name'] = sanitize_text_field($name); $updated['name'] = $name; }
    
    $slug = $request->get_param('slug');
    if ($slug !== null) { $term_args['slug'] = sanitize_title($slug); $updated['slug'] = $slug; }
    
    $description = $request->get_param('description');
    if ($description !== null) { $term_args['description'] = sanitize_textarea_field($description); $updated['description'] = $description; }
    
    $parent = $request->get_param('parent');
    if ($parent !== null) { $term_args['parent'] = intval($parent); $updated['parent'] = intval($parent); }
    
    if (!empty($term_args)) {
        $result = wp_update_term($term_id, 'product_cat', $term_args);
        if (is_wp_error($result)) {
            return new WP_Error('update_failed', $result->get_error_message(), array('status' => 400));
        }
    }
    
    // RankMath meta
    $seo_fields = array(
        'rank_math_title', 'rank_math_description', 'rank_math_focus_keyword',
        'rank_math_facebook_title', 'rank_math_facebook_description',
        'rank_math_twitter_title', 'rank_math_twitter_description'
    );
    
    foreach ($seo_fields as $field) {
        $value = $request->get_param($field);
        if ($value !== null) {
            update_term_meta($term_id, $field, sanitize_text_field($value));
            $updated[$field] = $value;
        }
    }
    
    if (empty($updated)) {
        return new WP_Error('no_fields', 'No valid fields provided to update', array('status' => 400));
    }
    
    $cat = get_term($term_id, 'product_cat');
    
    return array(
        'success' => true,
        'message' => count($updated) . ' field(s) updated successfully',
        'updated_fields' => $updated,
        'category' => array(
            'id' => $cat->term_id,
            'name' => $cat->name,
            'slug' => $cat->slug,
            'description' => $cat->description,
            'parent' => $cat->parent,
            'link' => get_term_link($cat),
            'rank_math_title' => get_term_meta($term_id, 'rank_math_title', true),
            'rank_math_description' => get_term_meta($term_id, 'rank_math_description', true),
        )
    );
}
function cortex_ecommerce_delete_product_category($request) {
    $term_id = $request->get_param('id');
    $cat = get_term($term_id, 'product_cat');
    
    if (!$cat || is_wp_error($cat)) {
        return new WP_Error('not_found', 'Product category not found', array('status' => 404));
    }
    
    // Check if it's the "Uncategorized" default category
    $default_cat = get_option('default_product_cat');
    if ($term_id == $default_cat) {
        return new WP_Error('cannot_delete', 'Cannot delete the default product category', array('status' => 400));
    }
    
    $cat_name = $cat->name;
    $result = wp_delete_term($term_id, 'product_cat');
    
    if (is_wp_error($result)) {
        return new WP_Error('delete_failed', $result->get_error_message(), array('status' => 400));
    }
    
    return array(
        'success' => true,
        'message' => 'Product category "' . $cat_name . '" deleted successfully',
        'deleted_id' => $term_id
    );
}
function cortex_ecommerce_bulk_update_product_categories($request) {
    $items = $request->get_param('items');
    
    if (!$items || !is_array($items)) {
        return new WP_Error('invalid_payload', 'Expected "items" array in request body', array('status' => 400));
    }
    
    if (count($items) > 50) {
        return new WP_Error('too_many_items', 'Maximum 50 items per bulk request', array('status' => 400));
    }
    
    $results = array(
        'success' => array(),
        'failed' => array(),
        'summary' => array('total' => count($items), 'succeeded' => 0, 'failed' => 0)
    );
    
    $seo_fields = array(
        'rank_math_title', 'rank_math_description', 'rank_math_focus_keyword',
        'rank_math_facebook_title', 'rank_math_facebook_description',
        'rank_math_twitter_title', 'rank_math_twitter_description'
    );
    
    foreach ($items as $item) {
        if (!isset($item['id'])) {
            $results['failed'][] = array('error' => 'Missing id field', 'item' => $item);
            $results['summary']['failed']++;
            continue;
        }
        
        $term_id = intval($item['id']);
        $cat = get_term($term_id, 'product_cat');
        
        if (!$cat || is_wp_error($cat)) {
            $results['failed'][] = array('id' => $term_id, 'error' => 'Product category not found');
            $results['summary']['failed']++;
            continue;
        }
        
        $updated = array();
        $term_args = array();
        
        if (isset($item['name'])) { $term_args['name'] = sanitize_text_field($item['name']); $updated['name'] = $item['name']; }
        if (isset($item['slug'])) { $term_args['slug'] = sanitize_title($item['slug']); $updated['slug'] = $item['slug']; }
        if (isset($item['description'])) { $term_args['description'] = sanitize_textarea_field($item['description']); $updated['description'] = $item['description']; }
        
        if (!empty($term_args)) wp_update_term($term_id, 'product_cat', $term_args);
        
        foreach ($seo_fields as $field) {
            if (isset($item[$field])) {
                update_term_meta($term_id, $field, sanitize_text_field($item[$field]));
                $updated[$field] = $item[$field];
            }
        }
        
        $results['success'][] = array('id' => $term_id, 'updated_fields' => $updated, 'fields_count' => count($updated));
        $results['summary']['succeeded']++;
    }
    
    return $results;
}
// ============================================================================
// PRODUCT TAGS ENDPOINT
// ============================================================================
function cortex_ecommerce_get_product_tags($request) {
    $args = array(
        'taxonomy' => 'product_tag',
        'hide_empty' => false,
        'orderby' => 'count',
        'order' => 'DESC'
    );
    
    $hide_empty = $request->get_param('hide_empty');
    if ($hide_empty === 'true' || $hide_empty === '1') {
        $args['hide_empty'] = true;
    }
    
    $tags = get_terms($args);
    
    if (is_wp_error($tags)) {
        return new WP_Error('error', 'Failed to retrieve product tags', array('status' => 500));
    }
    
    $result = array();
    foreach ($tags as $tag) {
        $result[] = array(
            'id' => $tag->term_id,
            'name' => $tag->name,
            'slug' => $tag->slug,
            'description' => $tag->description,
            'count' => $tag->count,
            'link' => get_term_link($tag),
        );
    }
    
    return $result;
}
// ============================================================================
// MASS CATEGORIZE ENDPOINT
// Efficient server-side categorization with pattern matching
// Single API call processes all matching products
// ============================================================================
/**
 * Mass Categorize Products
 * 
 * Efficiently categorize large numbers of products server-side with pattern matching.
 * Single API call can process thousands of products.
 * 
 * Request body:
 * {
 *   "source": {
 *     "category_id": 15,              // Products in this category
 *     "category_ids": [15, 22],       // Products in ANY of these categories
 *     "uncategorized": true,          // Only uncategorized products
 *     "all": true                     // All products site-wide
 *   },
 *   "rules": [
 *     {
 *       "match": {
 *         "title_contains": ["wire", "cable"],      // Title contains ANY of these (OR)
 *         "title_contains_all": ["red", "14awg"],   // Title contains ALL of these (AND)
 *         "sku_prefix": "ELEC-",                    // SKU starts with
 *         "sku_contains": "WIRE",                   // SKU contains
 *         "price_min": 10.00,                       // Minimum price
 *         "price_max": 100.00                       // Maximum price
 *       },
 *       "action": {
 *         "add_categories": ["Electrical"],         // Add these categories (by name)
 *         "add_category_ids": [25, 30],             // Add these categories (by ID)
 *         "remove_categories": ["Uncategorized"],   // Remove these categories
 *         "remove_category_ids": [1],               // Remove these categories (by ID)
 *         "set_categories": ["Electrical", "Wire"], // Replace ALL categories with these
 *         "set_category_ids": [25, 30]              // Replace ALL categories with these IDs
 *       }
 *     }
 *   ],
 *   "dry_run": true,                  // Preview changes without applying
 *   "limit": 1000,                    // Max products to process (safety limit)
 *   "return_matches": false           // Return matched product details (slower for large sets)
 * }
 */
function cortex_ecommerce_mass_categorize_products($request) {
    $body = $request->get_json_params();
    
    $source = isset($body['source']) ? $body['source'] : array();
    $rules = isset($body['rules']) ? $body['rules'] : array();
    $dry_run = isset($body['dry_run']) ? (bool)$body['dry_run'] : true; // Default to dry_run for safety
    $limit = isset($body['limit']) ? min(intval($body['limit']), 5000) : 1000; // Max 5000 per call
    $return_matches = isset($body['return_matches']) ? (bool)$body['return_matches'] : false;
    
    // Validate request
    if (empty($rules)) {
        return new WP_Error('no_rules', 'At least one categorization rule is required', array('status' => 400));
    }
    
    if (empty($source)) {
        return new WP_Error('no_source', 'Source filter is required (category_id, category_ids, uncategorized, or all)', array('status' => 400));
    }
    
    // Build source query
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => $limit,
        'post_status' => 'publish',
        'fields' => 'ids' // Only get IDs for efficiency
    );
    
    // Source: specific category
    if (!empty($source['category_id'])) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => intval($source['category_id'])
            )
        );
    }
    // Source: multiple categories
    elseif (!empty($source['category_ids']) && is_array($source['category_ids'])) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'term_id',
                'terms' => array_map('intval', $source['category_ids']),
                'operator' => 'IN'
            )
        );
    }
    // Source: uncategorized products
    elseif (!empty($source['uncategorized'])) {
        $args['tax_query'] = array(
            array(
                'taxonomy' => 'product_cat',
                'operator' => 'NOT EXISTS'
            )
        );
    }
    // Source: all products (no filter needed)
    elseif (empty($source['all'])) {
        return new WP_Error('invalid_source', 'Invalid source. Use category_id, category_ids, uncategorized, or all', array('status' => 400));
    }
    
    // Get matching product IDs
    $product_ids = get_posts($args);
    
    $results = array(
        'success' => true,
        'dry_run' => $dry_run,
        'source' => $source,
        'summary' => array(
            'products_scanned' => count($product_ids),
            'products_matched' => 0,
            'products_updated' => 0,
            'rules_applied' => count($rules)
        ),
        'rules_summary' => array(),
        'matches' => array()
    );
    
    // Pre-resolve category names to IDs for efficiency
    $resolved_categories = array();
    
    // Process each rule
    foreach ($rules as $rule_index => $rule) {
        $match_criteria = isset($rule['match']) ? $rule['match'] : array();
        $action = isset($rule['action']) ? $rule['action'] : array();
        
        $rule_matches = 0;
        $rule_updated = 0;
        
        // Process each product
        foreach ($product_ids as $product_id) {
            // Get product data for matching
            $product = wc_get_product($product_id);
            if (!$product) continue;
            
            $title = strtolower($product->get_name());
            $sku = strtolower($product->get_sku());
            $price = floatval($product->get_price());
            
            // Check if product matches this rule
            $matches = true;
            
            // Title contains ANY (OR logic)
            if (!empty($match_criteria['title_contains'])) {
                $title_match = false;
                foreach ((array)$match_criteria['title_contains'] as $term) {
                    if (strpos($title, strtolower($term)) !== false) {
                        $title_match = true;
                        break;
                    }
                }
                if (!$title_match) $matches = false;
            }
            
            // Title contains ALL (AND logic)
            if ($matches && !empty($match_criteria['title_contains_all'])) {
                foreach ((array)$match_criteria['title_contains_all'] as $term) {
                    if (strpos($title, strtolower($term)) === false) {
                        $matches = false;
                        break;
                    }
                }
            }
            
            // SKU prefix
            if ($matches && !empty($match_criteria['sku_prefix'])) {
                if (strpos($sku, strtolower($match_criteria['sku_prefix'])) !== 0) {
                    $matches = false;
                }
            }
            
            // SKU contains
            if ($matches && !empty($match_criteria['sku_contains'])) {
                if (strpos($sku, strtolower($match_criteria['sku_contains'])) === false) {
                    $matches = false;
                }
            }
            
            // Price range
            if ($matches && isset($match_criteria['price_min'])) {
                if ($price < floatval($match_criteria['price_min'])) {
                    $matches = false;
                }
            }
            if ($matches && isset($match_criteria['price_max'])) {
                if ($price > floatval($match_criteria['price_max'])) {
                    $matches = false;
                }
            }
            
            // If product matches, apply action
            if ($matches) {
                $rule_matches++;
                
                // Build match info for response
                if ($return_matches) {
                    $current_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'names'));
                    $results['matches'][] = array(
                        'id' => $product_id,
                        'title' => $product->get_name(),
                        'sku' => $product->get_sku(),
                        'price' => $price,
                        'rule_index' => $rule_index,
                        'current_categories' => $current_cats
                    );
                }
                
                // Apply category changes if not dry run
                if (!$dry_run) {
                    $updated = cortex_ecommerce_apply_category_action($product_id, $action, $resolved_categories);
                    if ($updated) {
                        $rule_updated++;
                    }
                }
            }
        }
        
        $results['rules_summary'][] = array(
            'rule_index' => $rule_index,
            'match_criteria' => $match_criteria,
            'matched' => $rule_matches,
            'updated' => $dry_run ? 0 : $rule_updated
        );
        
        $results['summary']['products_matched'] += $rule_matches;
        $results['summary']['products_updated'] += $rule_updated;
    }
    
    // Add helpful message
    if ($dry_run) {
        $results['message'] = sprintf(
            'DRY RUN: %d products would be updated. Set "dry_run": false to apply changes.',
            $results['summary']['products_matched']
        );
    } else {
        $results['message'] = sprintf(
            '%d products updated successfully.',
            $results['summary']['products_updated']
        );
    }
    
    return $results;
}
/**
 * Apply category action to a product
 * Helper function for mass categorization
 */
function cortex_ecommerce_apply_category_action($product_id, $action, &$resolved_categories) {
    $updated = false;
    
    // SET categories (replace all) - by ID
    if (!empty($action['set_category_ids'])) {
        $cat_ids = array_map('intval', (array)$action['set_category_ids']);
        wp_set_post_terms($product_id, $cat_ids, 'product_cat');
        $updated = true;
    }
    // SET categories (replace all) - by name
    elseif (!empty($action['set_categories'])) {
        $cat_ids = array();
        foreach ((array)$action['set_categories'] as $cat_name) {
            $cat_id = cortex_ecommerce_resolve_category($cat_name, $resolved_categories);
            if ($cat_id) $cat_ids[] = $cat_id;
        }
        if (!empty($cat_ids)) {
            wp_set_post_terms($product_id, $cat_ids, 'product_cat');
            $updated = true;
        }
    }
    else {
        $existing_cats = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
        $new_cats = $existing_cats;
        
        // ADD categories - by ID
        if (!empty($action['add_category_ids'])) {
            $add_ids = array_map('intval', (array)$action['add_category_ids']);
            $new_cats = array_unique(array_merge($new_cats, $add_ids));
            $updated = true;
        }
        
        // ADD categories - by name
        if (!empty($action['add_categories'])) {
            foreach ((array)$action['add_categories'] as $cat_name) {
                $cat_id = cortex_ecommerce_resolve_category($cat_name, $resolved_categories, true);
                if ($cat_id && !in_array($cat_id, $new_cats)) {
                    $new_cats[] = $cat_id;
                    $updated = true;
                }
            }
        }
        
        // REMOVE categories - by ID
        if (!empty($action['remove_category_ids'])) {
            $remove_ids = array_map('intval', (array)$action['remove_category_ids']);
            $new_cats = array_diff($new_cats, $remove_ids);
            $updated = true;
        }
        
        // REMOVE categories - by name
        if (!empty($action['remove_categories'])) {
            foreach ((array)$action['remove_categories'] as $cat_name) {
                $cat_id = cortex_ecommerce_resolve_category($cat_name, $resolved_categories);
                if ($cat_id) {
                    $new_cats = array_diff($new_cats, array($cat_id));
                    $updated = true;
                }
            }
        }
        
        if ($updated) {
            wp_set_post_terms($product_id, array_values($new_cats), 'product_cat');
        }
    }
    
    return $updated;
}
/**
 * Resolve category name to ID with caching
 * Creates category if it doesn't exist and $create is true
 */
function cortex_ecommerce_resolve_category($cat_name, &$cache, $create = false) {
    $cat_name = sanitize_text_field($cat_name);
    $cache_key = strtolower($cat_name);
    
    // Check cache first
    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }
    
    // Look up category
    $term = get_term_by('name', $cat_name, 'product_cat');
    if ($term) {
        $cache[$cache_key] = $term->term_id;
        return $term->term_id;
    }
    
    // Create if requested and not found
    if ($create) {
        $new_term = wp_insert_term($cat_name, 'product_cat');
        if (!is_wp_error($new_term)) {
            $cache[$cache_key] = $new_term['term_id'];
            return $new_term['term_id'];
        }
    }
    
    return null;
}

Comments

Add a Comment