# GGM Commerce Plugin - Development Rules

## ⚠️ DOCUMENTATION RELOCATED

**This file has been superseded by comprehensive documentation.**

Please refer to:
- **[STYLING-ARCHITECTURE.md](STYLING-ARCHITECTURE.md)** - Complete styling system guide

This file is kept for backwards compatibility but is no longer the primary reference.

---

## 📋 WordPress Coding Standards (WPCS)

**All code must pass WordPress Plugin Check (CPC) compliance checks.**

### Input Sanitization & Validation

When accessing superglobal variables (`$_POST`, `$_GET`, `$_REQUEST`, `$_SERVER`):

```php
// ✅ CORRECT: Use isset(), wp_unslash(), and appropriate sanitization
$value = isset($_POST['field_name']) ? sanitize_text_field(wp_unslash($_POST['field_name'])) : '';

// ✅ CORRECT: For email fields
$email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';

// ✅ CORRECT: For textarea content
$content = isset($_POST['content']) ? sanitize_textarea_field(wp_unslash($_POST['content'])) : '';

// ✅ CORRECT: For numeric values
$id = isset($_POST['id']) ? absint($_POST['id']) : 0;
$price = isset($_POST['price']) ? floatval(wp_unslash($_POST['price'])) : 0.0;

// ❌ WRONG: Direct access without sanitization
$value = $_POST['field_name'];
```

### Nonce Verification

For AJAX handlers and form processing:

```php
// ✅ CORRECT: Nonce verification with phpcs ignore for the nonce value itself
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce is verified, not used as data
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'action_name')) {
    wp_send_json_error(['message' => 'Security check failed.']);
}
```

For context detection (not form processing), use phpcs:ignore:

```php
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Context detection, not form processing
if (isset($_GET['page']) && $_GET['page'] === 'ggmc-products') {
    // Context-specific code
}
```

### Database Queries

Use `$wpdb->prepare()` for all user-supplied values:

```php
// ✅ CORRECT: Prepared statements
$results = $wpdb->get_results($wpdb->prepare(
    "SELECT * FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
    $product_id
));

// For safe table name interpolation, use phpcs:ignore with explanation
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table_name is safely constructed from $wpdb->prefix
$results = $wpdb->get_results("SELECT * FROM {$table_name} WHERE status = 'active'");
```

### Output Escaping

Always escape output, or use phpcs:ignore with explanation when output is pre-escaped:

```php
// ✅ CORRECT: Escape output
echo esc_html($text);
echo esc_attr($attribute);
echo esc_url($url);
echo wp_kses_post($html_content);

// For pre-escaped content from trusted internal functions:
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- format_price() returns pre-escaped HTML
echo GGM_Commerce_Helpers::format_price($price);
```

### Common Replacements

| Avoid | Use Instead |
|-------|-------------|
| `strip_tags()` | `wp_strip_all_tags()` |
| `date()` | `gmdate()` or `wp_date()` |
| `json_encode()` | `wp_json_encode()` |
| Direct `$_POST` access | `isset()` + `wp_unslash()` + sanitization |

### phpcs:ignore Comments

When using phpcs:ignore, always include an explanation:

```php
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Commerce plugin requires custom table queries
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Elementor handles nonce verification
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Function returns pre-escaped HTML
```

---

## 🚀 PRODUCTION STATUS - No Breaking Changes

**This plugin is in PRODUCTION and actively used on live sites.**

- **Breaking changes are NOT allowed** unless explicitly approved by the user
- Maintain backwards compatibility with all existing features, settings, and data
- Database schema changes MUST use `ALTER TABLE` migrations, not drop/recreate
- Existing Elementor widget control IDs must not be renamed (saved widget data depends on them)
- CSS class names used in render files must not change (custom CSS may depend on them)
- Function signatures must remain compatible (new parameters must have defaults)
- AJAX action names must not change (frontend JS depends on them)

### Database Schema Changes

When adding new columns to existing tables in `class-database.php`:

1. Add the column to the `CREATE TABLE` schema definition (for fresh installs)
2. **Also add an `ALTER TABLE` migration** in the plugin's upgrade routine to add the column to existing installations
3. New columns MUST have sensible defaults so existing data continues to work

**Claude: When modifying database schema, you MUST provide both the CREATE TABLE change and an ALTER TABLE migration. Never ask users to drop tables on production sites.**

### Versioning

- Bump the `Version:` header in `goose-commerce.php` with every release zip
- Version numbers are sequential: 1.0.NNN
- Every commit that changes functionality should bump the version when creating a zip

### Testing Considerations

- Changes must work on both Elementor and Gutenberg sites (shared render functions)
- Render files must not assume Elementor is installed (use `ggmc_render_icon()`, `ggmc_get_placeholder_image_src()`, `ggmc_is_editor_mode()`)
- New widget settings must have defaults so existing widgets don't break
- The block adapter auto-converts camelCase to snake_case, but new settings should still use `??` fallbacks

---

## ⚠️ CRITICAL: Data Architecture - Source of Truth

### The Plugin Database Tables are ALWAYS the Source of Truth

**This plugin uses its OWN custom database tables - NOT WordPress native structures.**

❌ **DO NOT USE:**
- WordPress Custom Post Types (CPT) for products
- WordPress taxonomies for categories/tags
- WooCommerce tables or structures
- The `wp_posts` or `wp_terms` tables

✅ **ALWAYS USE:**
- Custom `wp_ggmc_*` tables for all data
- `GGM_Commerce_Taxonomy` class for category/tag operations
- Direct queries to plugin tables via `$wpdb`

### Key Tables:
| Table | Purpose |
|-------|---------|
| `wp_ggmc_products` | Products (core product data) |
| `wp_ggmc_categories` | Product categories |
| `wp_ggmc_product_categories` | Product-category relationships (many-to-many) |
| `wp_ggmc_tags` | Product tags |
| `wp_ggmc_product_tags` | Product-tag relationships (many-to-many) |
| `wp_ggmc_orders` | Customer orders |
| `wp_ggmc_customers` | Customer accounts |
| `wp_ggmc_product_reviews` | Product reviews/ratings |

### Current Implementation:
- **Categories**: Many-to-many via `wp_ggmc_product_categories` junction table (products can have multiple categories)
- **Tags**: Many-to-many via `wp_ggmc_product_tags` junction table (products can have multiple tags)
- **Always use `GGM_Commerce_Taxonomy` class methods** for category/tag operations to ensure junction tables are used correctly

---

## 🔄 Version Control Requirements

### IMPORTANT: Regular Commits Required

**Claude MUST commit changes to the repository regularly without being asked.**

#### When to Commit:
1. **After completing a feature or fix** - Don't wait for the user to ask
2. **After making significant changes** to multiple files
3. **Before starting a new task** if there are uncommitted changes
4. **At natural breakpoints** in the work session

#### Commit Guidelines:
- Use descriptive commit messages that explain what was changed and why
- Group related changes into single commits
- Don't commit broken or incomplete code
- The repository is at: `wp-content/plugins/ggm-commerce` (submodule)

#### Commit Message Format:
```
Brief description of what changed and why

Detailed explanation if needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
```

#### Push to Remote:
- Only push when explicitly requested by the user
- The branch is ahead of origin - commits are local until pushed

---

## Quick Reference

### The Three-Layer Styling System

1. **widgets.css** (Always Loaded)
   - Structural/functional CSS only
   - No colors, typography, or decorative styles

2. **professional-defaults.css** (Conditionally Loaded)
   - Design CSS with variables
   - Only loads when defaults are enabled

3. **default-widget-styles.php** (Elementor Defaults)
   - PHP array of Elementor control defaults
   - Applied to new widgets only

### The Fundamental Rule

**Every styled property MUST have a corresponding Elementor widget control**

If styling exists in CSS or PHP defaults, users must be able to override it via Elementor's Style tab.

### Before Making Changes

1. Read the renderer file to verify HTML structure
2. Check widget controls exist for properties you'll style
3. Add missing controls if needed
4. Add CSS using variables only
5. Add PHP defaults matching the CSS

---

For complete documentation, see [STYLING-ARCHITECTURE.md](../docs/STYLING-ARCHITECTURE.md)
