# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed
- **Filox Booking Review Links Stripped on Addon Domain**: Fixed issue where booking review URLs with credentials were stripped when accessed from addon domain
  - **Original Issue**: "https://theoliveproject.gr/review-page/?bookingCode=9aa4b6&key=...&email=...&admin=1 this link show the admin form" (shows lookup form instead of booking overview)
  - **Root Cause**: `FrontendUrlManager::get_canonical_url()` returns permalink without query parameters for singular pages, causing canonical redirect to strip all booking credentials (bookingCode, key, email)
  - **Impact**: Direct booking review links from emails failed on addon domain - users saw empty lookup form instead of their booking
  - **Solution**: Preserve query parameters when building canonical URL for singular pages
  - **Technical Changes**:
    - Modified `get_canonical_url()` to call `add_query_arg($_GET, $permalink)` for singular pages
    - Query parameters now preserved during canonical URL generation and redirect
  - **Affected Files**: `includes/classes/FrontendUrlManager.php`
  - **Backwards Compatibility**: No breaking changes - fixes broken functionality on addon domain
- **Cookie Check Failed Error on Filox Booking Review Form**: Fixed "Cookie check failed" error when submitting booking lookup form on addon domain
  - **Original Issue**: "https://theoliveproject.gr/review-page/ -> this gives us Cookie check failed" when entering booking code and email
  - **Root Cause**: `UrlManager::register()` runs on `plugins_loaded` hook before `REST_REQUEST` constant is defined, causing REST API requests to be misidentified as 'frontend' context. This prevented `BackendUrlManager::fix_rest_authentication` filter from registering, allowing WordPress core's `rest_cookie_check_errors` to fail on cross-domain REST requests
  - **Impact**: Booking review form submission via REST API failed with authentication error on addon domain
  - **Solution**: Always register CORS management and REST authentication filter globally, regardless of detected context
  - **Technical Changes**:
    - Made `BackendUrlManager::register_cors_management()` public
    - Call `register_cors_management()` globally in `UrlManager::register()` before context detection
    - Removed duplicate call from `BackendUrlManager::register()` to avoid double-registration
    - Ensures `fix_rest_authentication` filter (priority 99) runs before WordPress core's `rest_cookie_check_errors` (priority 100)
  - **Affected Files**: `includes/classes/UrlManager.php`, `includes/classes/BackendUrlManager.php`
  - **Backwards Compatibility**: No breaking changes - fixes broken REST API authentication on addon domain

### Fixed
- **WP Rocket Minified Files Showing Internal Domain**: Fixed issue where minified CSS/JS files contained internal domain URLs instead of addon domain
  - **Original Issue**: "When we enable rocket still most of the minified scripts show .motivar.dev domain"
  - **Root Cause**: WP Rocket's minification process creates combined/minified files before URL replacement filters run, resulting in cached files containing internal domain URLs
  - **Impact**: Minified CSS and JavaScript files served to users contained internal domain references (e.g., `https://10354.motivar.dev`) instead of addon domain (e.g., `https://sgpsecurity.gr`)
  - **Solution**: Added WP Rocket minification filters to replace URLs in CSS/JS content before files are cached
  - **Technical Changes**:
    - Added `register_minification_filters()` method to register 7 new WP Rocket filters
    - Added `replace_minified_css_urls()` to process CSS content and replace all domain variations
    - Added `replace_minified_js_urls()` to process JS content including escaped URLs (e.g., `https:\/\/`)
    - Added `replace_minify_url()` to replace URLs in minified file paths
    - Filters applied: `rocket_minify_css_content`, `rocket_minify_js_content`, `rocket_css_content`, `rocket_js_content`, `rocket_minify_url`, `rocket_css_url`, `rocket_js_url`
    - Handles standard URLs, escaped URLs (JavaScript/JSON), and plain domain strings
  - **Affected Files**: `includes/classes/RocketPreloader.php`
  - **Backwards Compatibility**: No breaking changes - requires WP Rocket cache clear to regenerate minified files with correct URLs
  - **Action Required**: Clear WP Rocket cache after plugin update to regenerate minified files
- **Duplicate Log Entries on Domain Save**: Fixed issue where saving a domain created two log entries (one empty, one with domain)
  - **Original Issue**: "When add a new domain I get two entries, one empty and one with the domain"
  - **Root Cause**: WordPress fires `update_option` hooks separately for `mtv_ready_to_publish` and `mtv_product_addon_domain`, causing two sequential log entries
  - **Impact**: Each option change triggered a separate log entry, resulting in duplicate/incomplete entries
  - **Solution**: Implemented transient-based batching to collect all option changes and process them together on shutdown
  - **Technical Changes**:
    - Added `BATCH_TRANSIENT` constant for storing pending changes
    - Modified `process_option_change()` to store changes in transient instead of immediate processing
    - Added `process_batched_changes()` method that runs on shutdown hook
    - All option changes within same request are now batched and processed once with complete context
    - Transient expires after 5 seconds as safety mechanism
  - **Affected Files**: `includes/classes/DomainActionLogger.php`
  - **Backwards Compatibility**: No breaking changes - improves logging accuracy
- **Fatal Error on Domain Unpublish**: Fixed "Too few arguments to function rocket_clean_files()" error when unpublishing domain
  - **Original Issue**: "What is more when I unpublish the domain I get fatal error"
  - **Root Cause**: `rocket_clean_files()` was called without required parameter on line 333 of RocketPreloader
  - **Impact**: Unpublishing a domain caused fatal error and prevented cache clearing
  - **Solution**: Pass required parameter array `['all' => true]` to `rocket_clean_files()`
  - **Technical Changes**:
    - Changed `rocket_clean_files()` to `rocket_clean_files(['all' => true])`
    - Matches WP Rocket API requirements for clearing all cache files
  - **Affected Files**: `includes/classes/RocketPreloader.php` (handle_addon_domain_change method, line 333)
  - **Backwards Compatibility**: No breaking changes - fixes broken functionality

### Changed
- **Architectural Refactoring**: Complete restructure into specialized classes with separation of concerns
  - **Original Issue**: "The response is not a valid JSON response" error when editing widgets from addon domain; block editor errors in admin
  - **Root Cause**: 
    1. Monolithic `UrlManager` class handling too many responsibilities
    2. Cross-domain cookie/session authentication issues
    3. Difficult to debug which context was causing failures
  - **Solution**: Separated into 4 specialized classes with clear responsibilities
  - **New Architecture**:
    - **`UrlReplacer.php`** (Utility): Reusable static methods for URL replacement logic
    - **`BackendUrlManager.php`** (Backend): Handles admin/AJAX/REST/cron contexts
    - **`FrontendUrlManager.php`** (Frontend): Handles frontend URL replacement and canonical URLs
    - **`UrlManager.php`** (Orchestrator): Detects context and delegates to appropriate manager
  - **Key Insight**: When admin accessed from addon domain, ALL URLs (including AJAX/REST) must stay on addon domain to maintain cookie-based authentication
  - **Technical Changes**:
    - Created `UrlReplacer` with `normalize_domain()`, `should_skip_url()`, `replace_domain()`, `replace_html_urls()`, `replace_urls_recursive()`
    - Created `BackendUrlManager` with context-specific handlers: `handle_admin_context()`, `handle_ajax_context()`, `handle_rest_context()`, `handle_cron_context()`
    - Created `FrontendUrlManager` with all frontend filters, canonical management, and output buffering
    - Refactored `UrlManager` to orchestrator pattern with `detect_context()` and `is_backend_context()`
    - Added comprehensive WP_DEBUG logging to all classes for easier troubleshooting
  - **Affected Files**: 
    - `includes/classes/UrlReplacer.php` (new)
    - `includes/classes/BackendUrlManager.php` (new)
    - `includes/classes/FrontendUrlManager.php` (new)
    - `includes/classes/UrlManager.php` (refactored to orchestrator)
  - **Behavior**:
    - **Internal domain** (`10354.ddev.site`): Plugin completely inactive, no interference with admin operations
    - **Addon domain frontend** (`sgpsecurity.gr`): Full URL replacement via `FrontendUrlManager`
    - **Addon domain admin/AJAX/REST** (`sgpsecurity.gr/wp-admin`): All URLs stay on addon domain via `BackendUrlManager` to maintain session cookies
  - **Impact**: 
    - Widget editing now works from both internal and addon domains
    - Block editor loads correctly when accessing admin via addon domain
    - Session cookies maintained for cross-domain admin access
    - Much easier to debug with context-specific logging
  - **Debug Logging**: All classes log operations when `WP_DEBUG` is enabled:
    - `MTV UrlManager [Orchestrator]: Context detected: ajax`
    - `MTV BackendUrlManager: AJAX from addon domain - replacing URLs to maintain session`
    - `MTV FrontendUrlManager: Processing widget buffer: 1234 bytes`
    - `MTV UrlReplacer: Replaced: https://10354.ddev.site → https://sgpsecurity.gr`

### Fixed
- **Redirect Loop on Addon Domain for Specific Post Types**: Fixed infinite redirect loop (20+ redirects) when accessing certain post types via addon domain
  - **Original Issue**: "Safari can't open the page. The error is: Load cannot follow more than 20 redirections" for URLs like `https://sgpsecurity.gr/security-services/τύπος/meletes-sistimaton-pirasfalias/`
  - **Root Cause**: `handle_canonical_redirect()` was comparing URLs without proper normalization, causing mismatches due to:
    - URL-encoded vs decoded Greek characters (τύπος)
    - Trailing slash inconsistencies
    - Case sensitivity differences
    - Protocol variations (http vs https)
  - **Impact**: Specific post types with Greek characters or special URL structures became inaccessible from addon domain (worked fine from internal domain)
  - **Solution**: Implemented comprehensive URL normalization and redirect loop prevention
  - **Technical Changes**:
    - Added static `$redirect_count` variable to prevent multiple redirect attempts in single request
    - Created `normalize_url_for_comparison()` method with:
      - Trailing slash removal for consistent comparison
      - URL decoding to handle Greek/special characters
      - Protocol normalization (force https)
      - Multiple slash normalization
      - Case-insensitive comparison (lowercase conversion)
    - Added safety check to prevent redirecting to identical URLs
    - Enhanced debug logging to track canonical vs current URL comparison
    - Added early return if canonical URL cannot be determined
  - **Affected Files**: `includes/classes/UrlManager.php` (handle_canonical_redirect, normalize_url_for_comparison methods)
  - **Backwards Compatibility**: No breaking changes - only prevents redirect loops while maintaining proper canonical URL enforcement
  - **Debug Support**: Enable WP_DEBUG to see detailed URL comparison
- **Widget Editor Breaking on Internal Domain**: Fixed "The response is not a valid JSON response" error when editing widgets
  - **Issue**: Widget editing failed completely when plugin was activated, even when accessing from internal domain
  - **Root Cause**: Plugin was registering URL filters and CORS management even when accessed from internal domain, interfering with admin operations
  - **Solution**: Complete bypass - plugin now only activates when accessed via addon domain
  - **Technical Changes**:
    - Added early return in `register()` method when `current_host === internal_domain`
    - Plugin completely skips all URL replacement, canonical management, and CORS when on internal domain
    - Disabled `rest_post_dispatch` filter to prevent REST API interference
    - Added `is_admin()` and `wp_doing_ajax()` checks to widget buffer hooks
  - **Affected Files**: `includes/classes/UrlManager.php` (register, register_url_filters, start_widget_buffer, end_widget_buffer methods)
  - **Impact**: 
    - Internal domain access: Plugin is completely inactive, no interference with admin operations
    - Addon domain access: Plugin activates and performs URL replacement as designed
  - **Behavior**: Plugin now only processes URLs when site is accessed via addon domain (`sgpsecurity.gr`), not internal domain (`10354.ddev.site`)
- **Production CORS Issues**: Enhanced CORS header handling to work reliably in production environments
  - **Issue**: Widget editing worked in DDEV but failed in production with same error
  - **Root Cause**: Production environments have stricter header timing requirements; CORS headers were being sent too late
  - **Solution**: Send CORS headers earlier using `send_headers` hook and add dedicated admin-ajax CORS handling
  - **Technical Changes**:
    - Changed from `init` to `send_headers` hook (priority 1) for earlier CORS header delivery
    - Added `handle_admin_ajax_cors()` method specifically for admin-ajax.php requests
    - Added `headers_sent()` check to prevent errors
    - Added debug logging when WP_DEBUG is enabled to troubleshoot CORS issues
    - Added `status_header(200)` for OPTIONS preflight requests
    - Added HTTP_REFERER fallback for origin detection
    - Added comprehensive debug logging for troubleshooting
  - **Affected Files**: `includes/classes/UrlManager.php` (register_cors_management, handle_cors_headers, handle_admin_ajax_cors methods)
  - **Debugging**: Enable WP_DEBUG to see CORS origin matching in error logs
- **Widget Editing and AJAX Failure on Addon Domain**: Fixed "The response is not a valid JSON response" error when editing widgets or using Customizer while accessing site via addon domain
  - **Original Issue**: "In all projects using this plugin, where domain mapping and addon email is registered from both links original and addon we cannot make changes to widgets"
  - **Root Cause**: All URLs including REST API (`/wp-json/`) and admin AJAX (`admin-ajax.php`) were being replaced with addon domain, breaking cross-domain communication
  - **Impact**: Widget editing, Customizer saves, REST API calls, and all admin AJAX operations failed when site was accessed via addon domain
  - **Solution**: Selective URL replacement - REST API and admin-ajax endpoints always point to internal domain, while frontend URLs use addon domain
  - **Technical Changes**:
    - Removed `rest_url` filter entirely - REST API endpoints never get replaced
    - Modified `replace_with_addon_domain()` to skip URLs containing `admin-ajax.php`
    - These critical endpoints now always point to internal domain where WordPress admin is hosted
    - CORS headers already configured to whitelist both internal and addon domains
    - Users stay logged in on their access domain while AJAX/REST calls work cross-domain
    - Frontend display correctly shows addon domain URLs for all other resources
    - Only `rest_post_dispatch` filter remains to replace URLs in REST API response data
  - **User Experience**:
    - User accesses site via addon domain → stays on addon domain
    - All frontend URLs (images, links, etc.) → addon domain
    - All AJAX/REST API calls → internal domain (with CORS headers)
    - No domain switching, no login issues, no CORS errors
  - **Affected Files**: `includes/classes/UrlManager.php` (replace_with_addon_domain method)
  - **Backwards Compatibility**: No breaking changes - only fixes broken functionality
  - **CORS Configuration**: Existing CORS management already whitelists both domains for cross-origin requests
- **CORS Error in Admin Notice Dismiss Script**: Fixed XMLHttpRequest CORS error when dismissing domain action notices
  - **Original Issue**: "XMLHttpRequest cannot load https://appsecurity.gr/wp/wp-admin/admin-ajax.php due to access control checks"
  - **Root Cause**: Inline JavaScript was using global `ajaxurl` variable which may not be properly defined or may point to wrong domain in addon domain context
  - **Impact**: Admin notices couldn't be dismissed via AJAX, causing JavaScript console errors
  - **Solution**: Explicitly define AJAX URL using `admin_url('admin-ajax.php')` and pass it to JavaScript as local variable
  - **Technical Changes**:
    - Changed from relying on global `ajaxurl` to explicitly defined `ajaxUrl` variable
    - Added PHP variable `$ajax_url = admin_url('admin-ajax.php')` before script output
    - Properly escaped URL with `esc_js()` for security
    - JavaScript now uses local `ajaxUrl` variable instead of global `ajaxurl`
  - **Affected Files**: `includes/classes/DomainActionNotifier.php` (enqueue_dismiss_script method)
  - **Backwards Compatibility**: No breaking changes - improves reliability of notice dismissal
- **Redirect Loop with Special Query Parameters**: Fixed infinite redirect loop when accessing URLs with special query parameters like Complianz scan tokens
  - **Original Issue**: "Too many redirects occurred trying to open https://sgpsecurity.gr/wp?complianz_scan_token=...&complianz_id=home"
  - **Root Cause**: `handle_canonical_redirect()` was redirecting all URLs that didn't match the canonical URL, including special tool URLs with query parameters
  - **Impact**: External tools like Complianz cookie scanner couldn't access the site due to redirect loops
  - **Solution**: Skip canonical redirects for URLs with special query parameters
  - **Technical Changes**:
    - Added whitelist of query parameters that should skip canonical redirects
    - Includes: `complianz_scan_token`, `complianz_id`, `preview_id`, `preview`, `p`
    - Prevents redirect loops for external scanning/preview tools
  - **Affected Files**: `includes/classes/UrlManager.php`
  - **Backwards Compatibility**: No breaking changes - only prevents redirects for specific query parameters
- **REST API URL Replacement**: Fixed internal domain URLs appearing in REST API JSON responses
  - **Original Issue**: "wp-json endpoint returns internal domain (10354.motivar.dev) in 'url' field instead of addon domain (sgpsecurity.gr)"
  - **Root Cause**: Shutdown HTML replacement explicitly skipped REST API requests, and `rest_url` filter only affected endpoint URLs, not response body content
  - **Impact**: REST API responses contained internal domain URLs in fields like `url`, `home`, `link`, breaking API consumers expecting addon domain
  - **Solution**: Added `rest_post_dispatch` filter to recursively replace URLs in REST API response data
  - **Technical Changes**:
    - Added `replace_rest_response_urls()` method to intercept and modify REST API responses
    - Added `replace_urls_recursive()` helper to recursively process arrays, objects, and strings
    - Handles both regular and escaped URLs (e.g., `https:\/\/` in JSON)
    - Only processes when accessed via addon domain
    - Uses `rest_post_dispatch` filter (not `rest_pre_serve_request`) to avoid infinite recursion
    - Simply modifies and returns response without calling `serve_request()`
  - **Bug Fix**: Initial implementation caused memory exhaustion due to infinite recursion when calling `$server->serve_request()`
  - **Affected Files**: `includes/classes/UrlManager.php`
  - **Backwards Compatibility**: No breaking changes - only affects REST API responses when accessed via addon domain
- **Duplicate Log Entries Causing Repeated Notifications**: Fixed race condition in log file writes that created duplicate entries with the same ID
  - **Original Issue**: "Receiving failure notification every 5 minutes - same log entry appearing multiple times in processed log"
  - **Root Cause**: `write_log_entry()` used `FILE_APPEND` without proper duplicate checking, allowing concurrent cron runs to create duplicate entries with identical IDs
  - **Impact**: Each duplicate entry triggered separate notification checks, causing repeated emails every 5 minutes
  - **Solution**: Implemented atomic file operations with exclusive locking to prevent duplicates
  - **Technical Changes**:
    - Changed from `FILE_APPEND` to atomic read-modify-write pattern with file locking
    - Added `flock(LOCK_EX)` to prevent concurrent writes during cron execution
    - Automatically removes existing entry with same ID before writing new one
    - Uses temp file + atomic rename for safe file replacement
    - Maintains entry uniqueness by ID across all operations
  - **Affected Files**: `includes/classes/DomainActionManager.php` (write_log_entry method)
  - **Cleanup Tool**: Created `cli/cleanup-duplicate-logs.php` to remove existing duplicates
  - **Backwards Compatibility**: Existing duplicates must be cleaned manually using cleanup script
- **Cloudflare DNS Detection**: DNS monitoring now correctly handles domains behind Cloudflare proxy
  - **Original Issue**: "Site is working but DNS check fails because domain is in Cloudflare"
  - **Root Cause**: DNS lookups return Cloudflare IPs (104.x.x.x, 172.x.x.x) instead of origin server IP
  - **Solution**: Dynamic fetching of Cloudflare IP ranges from official endpoint (https://www.cloudflare.com/ips-v4/)
  - **Behavior**: Domains resolving to Cloudflare IPs are now accepted as valid configuration
  - **Technical Changes**:
    - Enhanced `ip_matches()` to accept Cloudflare proxy IPs
    - Added `get_cloudflare_ip_ranges()` to fetch latest ranges from Cloudflare's official endpoint
    - Added `get_fallback_cloudflare_ranges()` with static ranges (as of 2024-12-08) for when fetch fails
    - Added `is_cloudflare_ip()` to check against fetched IPv4 ranges
    - Added `ip_in_range()` for CIDR subnet matching
    - Uses WordPress transient caching (24 hours) to avoid excessive HTTP requests
    - Logs Cloudflare detection and fetch operations for debugging
  - **Caching Strategy**: Fetches once per 24 hours, falls back to static ranges on failure
  - **Affected Files**: `includes/classes/DomainActionCronManager.php`
  - **Backwards Compatibility**: No breaking changes - direct IP matching still works as before
- **CLI Access Restriction in Cron**: Fixed "Only CLI access" error (exit code 255) when running from cron
  - **Root Cause**: Cron runs with minimal environment - missing PATH and HOME variables needed by WP-CLI
  - **Impact**: Domain actions stuck in "Queued" status because cron couldn't execute PHP processor (worked fine when run manually)
  - **Solution**: Updated wrapper script to set explicit PATH and HOME before sourcing `.bashrc`
  - **Technical Changes**:
    - Set `PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"`
    - Set `HOME="/root"` for WP-CLI config
    - Added validation that function loaded correctly
    - Removed subshell and `2>&1` redirection from function itself
  - **Deployment**: Update both `/usr/local/bin/run_mtv_domain_actions.sh` and `/root/.bashrc` from bash-scripts repo

### Changed
- **Domain Action Log Viewer**: Transformed from card-based layout to terminal-style code viewer with dark theme
  - **Monospace Font**: Uses Monaco, Menlo, Consolas for authentic terminal appearance
  - **Color-Coded Status**: Status levels use distinct colors (info=blue, success=green, warning=orange, error=red, processing=purple)
  - **Dark Theme**: Terminal background (#1e1e1e) with light text (#d4d4d4) for reduced eye strain
  - **Real-Time Filter**: Live search/filter functionality with regex support
  - **Compact Format**: Single-line log entries with timestamp, status, ID, action, and domain
  - **Inline Metadata**: DNS checks, errors, and client actions shown inline with color coding
  - **Increased Capacity**: Shows last 50 entries (up from 10) with scroll functionality
  - **Auto-Scroll**: Automatically scrolls to bottom on load and after filtering

### Added


- **Domain Action Management System**: Complete lifecycle tracking for addon domain configuration changes
  - **DomainActionManager**: State machine with unique log IDs (format: `mtv-DA-YYYYMMDD-NNNN`)
  - **DomainActionNotifier**: Admin notices (non-dismissible for pending, dismissible for completed/failed) and email notifications
  - **DomainActionCronManager**: Hourly DNS monitoring with automatic nameserver validation
  - **DomainActionLogger**: Structured JSON logging with unique IDs and state tracking
  - **PHP CLI Processor**: Server-side processing via WP-CLI with cPanel UAPI integration
  - **Minimal Bash Wrapper**: `mtv_domain_actions_cron` finds sites and invokes PHP processor
  - **Two Log Files**: `domain_actions.log` (pending) and `domain_actions_wp.log` (processed)
  - **Admin Log Viewer**: Last 10 processed actions displayed in AdminSettings with status icons
  - **State Machine**: `created → queued → processing → server_done → dns_pending → completed`
  - **Email Notifications**: Sent to editors, flx_bookings_admin, and administrators at key lifecycle events
  - **DNS Monitoring**: Automatic hourly checks with 7-day timeout for nameserver changes
  - **Client Action Tracking**: Identifies when user action is required (e.g., update nameservers)

### Changed
- **Plugin.php**: Registered DomainActionNotifier and DomainActionCronManager components
- **AdminSettings**: Added domain action log viewer section (admin-only visibility)
- **Bash Scripts**: Refactored `mtv_domain_actions_cron` to be minimal wrapper calling PHP
- **Architecture**: Moved domain processing logic from bash to PHP for single source of truth
- **cPanel API**: Changed from UAPI to cpapi2 for addon domain operations (UAPI has no equivalent)

### Fixed
- **WP-CLI Root Execution**: Added `--allow-root` flag to WP-CLI command in bash wrapper (cron runs as root)
- **WP-CLI Bedrock Support**: Changed to `cd` into directory before running WP-CLI (respects `wp-cli.yml` config)
- **Unnecessary Processing**: Added check for `domain_actions.log` existence before attempting to process site
- **Performance**: Sites without pending actions are now skipped immediately, reducing execution time
- **DNS Checking Logic**: Changed from nameserver validation to A record/IP resolution checking
- **Client Instructions**: Updated to instruct users to add A record or CNAME instead of changing nameservers entirely
- **Server-Level Verification**: Added `verify_addon_domain_exists()` to confirm domain creation/removal at cPanel level
- **Domain Action Reliability**: PHP CLI processor now verifies UAPI operations succeeded before marking as complete
- **DNS Records Display**: Fixed missing server IP in DNS configuration by adding `get_server_ip()` method to DomainActionLogger
- **Unpublish Action Detection**: Fixed DomainActionLogger to correctly detect unpublish actions by using hook parameter values instead of re-reading from database
- **Domain Change Handling**: Added automatic removal of old addon domain when changing to a new domain or clearing the domain field
- **Email Notifications**: Fixed server_done emails to include client action instructions by re-reading entry after metadata update
- **Admin Notices**: Updated DNS pending notice to show A record/CNAME instructions instead of nameservers
- **DNS Instructions**: Simplified and clarified all DNS instructions to focus on A record requirement with step-by-step guidance
- **DNS Completion Notifications**: Fixed email notifications for DNS completion by re-reading entry after status update
- **Unpublish Action Logging**: Fixed unpublish and stop actions to use old domain instead of empty new domain for target config
- **Unpublish/Change Email Notifications**: Improved email messaging for unpublish and domain change actions with action-specific content
- **Duplicate Log Entries**: Fixed duplicate log entries by creating entries directly with QUEUED status and removing old entries before writing updates
- **Missing DNS Instructions in Emails**: Fixed server_done emails missing DNS instructions by reading entry from processed log after moving it there
- **Log File Permissions**: Improved directory and file creation with better error handling and automatic permission fixes
- **Pending Log Cleanup**: CLI processor now deletes pending log file after processing all entries to prevent permission conflicts between WordPress and CLI contexts
- **File Permission Management**: New log files are created with 0644 permissions (rw-r--r--) for security
- **CLI Permission Fix**: CLI processor now explicitly sets 0644 permissions on processed log file after processing to ensure WordPress can write new entries
- **Undefined Variable Fix**: Fixed undefined `$success` and `$error_message` variables in CLI processor by initializing them before the try-catch block
- **DNS Records Cleanup Order**: Fixed unpublish action to only clear DNS records after successful verification that domain was removed at server level
- **Failed Action Error Messages**: Fixed email notifications for failed actions to display the actual error message by re-reading entry after status update
- **Failed Actions Stay in Pending**: Failed domain actions now remain in pending log (`domain_actions.log`) instead of being moved to processed, allowing them to be retried on next cron run
- **Pending Log Preservation**: CLI processor now only deletes pending log file if ALL actions succeeded; keeps it if any failed so entries remain visible
- **Pending Log Permissions**: When failures occur, pending log permissions are fixed to 0644 so WordPress can update entries
- **cpapi2 deladdondomain Fix**: Fixed addon domain removal to pass both `subdomain` and `domain` parameters as required by cpapi2 API
- **cpapi2 Result Checking**: Fixed success detection to check `data[0].result` instead of `event.result` for actual operation status
- **Subdomain Lookup**: Fixed addon domain removal to query cPanel for the actual subdomain created instead of extracting from domain name
- **Full Subdomain Usage**: Fixed addon domain removal to use `fullsubdomain` (e.g., `motivar.artisan-single.motivar.dev`) instead of `subdomain` for cpapi2 deladdondomain command
- **cpapi2 JSON Parsing**: Fixed JSON parsing to extract only JSON from cpapi2 output, ignoring warning/log lines that precede the JSON response

### Added
- **Debug Logging**: Added comprehensive debug logging to DomainActionLogger and DomainActionManager to troubleshoot unpublish action detection and log write failures
- **Unpublish Action Debug Logging**: Added detailed step-by-step debug logging to `process_unpublish_action()` function to track cPanel username retrieval, UAPI calls, domain verification, status updates, and DNS cleanup
- **UAPI Removal Debug Logging**: Added comprehensive logging to `remove_addon_domain_via_uapi()` including command execution, return codes, raw output, JSON parsing, and cpapi2 response details
- **CORS Errors on Addon Domain**: Fixed CORS errors for CSS files and fonts when accessing site via addon domain
- **WP Rocket Multi-Domain Support**: Implemented proper multi-domain configuration using WP Rocket's official approach
- **Separate Cache Files**: Each domain (internal + addon) now gets its own WP Rocket config file for proper cache separation
- **Cache Clearing**: Both domains are included in cache clearing operations to maintain consistency

### Added
- **DNS Records Cleanup**: Automatically delete DNS records and addon domain options when unpublishing or changing domains
- **Processing Notifications**: Send email notifications to admins when domain actions start processing
- **Old DNS Monitoring Cancellation**: Automatically cancel DNS monitoring for previous actions when a new action is initiated
- **WP Rocket Multi-Domain Helper**: Functional implementation based on WP Rocket's official multiple-config-files helper
- **Automatic Config Generation**: Separate config files automatically generated for internal and addon domains
- **Domain Change Handling**: Config files automatically regenerated when addon domain changes
- **Dynamic Domain Detection**: `define_urls()` function dynamically retrieves addon domain from WordPress options
- **RocketPreloader Class**: Comprehensive WP Rocket preloader integration for addon domains with HTML buffer processing

### Changed
- **Dual WP Rocket Integration**: Using both functional helper (for config files) and RocketPreloader class (for cache preloading)
- **Plugin Architecture**: Added RocketPreloader instance to Plugin class, registered when WP Rocket is active
- **HTML Buffer Processing**: Process HTML before caching to replace internal domain URLs with addon domain

### Added
- **Read-Only Admin Access via Addon Domain**: Users can now view Product Configuration settings when accessing admin via the public/addon domain
- **Disabled Critical Fields**: `mtv_ready_to_publish` and `mtv_product_addon_domain` fields are automatically disabled when accessed via addon domain for safety
- **Warning Notice System**: Prominent warning message displayed at top of configuration page when accessed via addon domain
- **Inline Field Warnings**: Individual disabled fields show inline warning messages explaining they must be modified via internal domain
- **Contextual Help Message**: Inline warning box within settings form with direct link to switch to internal domain

### Changed
- **AdminSettings Class**: Enhanced `get_configuration_settings()` to dynamically disable fields based on access domain
- **Domain Detection**: Added `is_accessing_via_addon_domain()` method to detect when admin is accessed via public domain
- **Admin Notice Hook**: Added `show_addon_domain_notice()` to display warning banner on configuration page
- **Configuration Array Structure**: Refactored settings array to use dynamic `$settings` variable for conditional field attributes
- **DRY Refactoring**: Created `get_switch_to_internal_domain_url()` method as single source of truth for building switch URLs
- **Code Reusability**: Both admin notice and inline warning now use the same method for URL generation

### Fixed
- **Critical Domain Detection Bug**: Fixed issue where `get_site_url()` was returning addon domain due to UrlManager filters
- **Direct Database Query**: Now uses `$wpdb->get_var()` to query `siteurl` option directly from database, bypassing all WordPress filters
- **Filter Bypass**: Ensures internal domain detection works correctly even when UrlManager has already replaced URLs in memory
- **Consistent Behavior**: All three methods (`is_accessing_via_addon_domain()`, `show_addon_domain_notice()`, `get_configuration_settings()`) now use direct database queries
- **Login Flow**: Switch domain links now point to `wp-login.php` with `redirect_to` parameter to handle domain-specific authentication cookies
- **Seamless Redirect**: After logging in on internal domain, users are automatically redirected back to the Product Configuration page

### Technical Details
- **Original User Question**: "We are in a site which is motivar.dev site. We have enabled the addon. We have visited the page via motivar.digital (the addon domain). But when logging to the plugin configuration, we cannot see the options. What alternatives we have?"
- **Solution Chosen**: Option 2 - Show options as read-only with warnings (rejected Option 1: redirect, and Option 3: allow full editing due to URL/session conflicts)
- **Safety Rationale**: Prevents accidental misconfiguration and URL conflicts while maintaining visibility of current settings
- **User Experience**: Users can view all settings but must switch to internal domain (*.motivar.dev) to make changes

### Affected Files
- `includes/classes/AdminSettings.php`: Added domain detection, admin notices, and conditional field disabling
- `includes/wp-rocket-multi-domain.php`: Functional helper for WP Rocket multi-domain config file generation
- `includes/classes/RocketPreloader.php`: Comprehensive WP Rocket preloader class with HTML buffer processing
- `includes/classes/Plugin.php`: Added RocketPreloader instance and registration
- `mtv-product-configuration.php`: Loads WP Rocket helper and includes RocketPreloader helper function
- `includes/classes/DomainActionLogger.php`: Added flush_wp_rocket() call when logging domain actions

### Backwards Compatibility
- No breaking changes - existing functionality preserved
- New behavior only activates when accessing admin via addon domain
- Internal domain access remains fully functional with no changes

## [1.1.0] - 2024-10-22

### Changed
- **Restructured Architecture**: Created clean Plugin class while maintaining simplicity
- **Composer Integration**: Restored Composer autoloading with PSR-4 namespace
- **Class-Based Approach**: Moved functionality into Plugin class with proper separation of concerns
- **Fixed 502 Errors**: Eliminated complex service initialization that was causing gateway errors
- **Backward Compatibility**: Added global functions for existing integrations

### Removed
- **Complex Classes**: Removed Plugin, OptionsProvider, DomainRouter, SeoGuards, SMTPManager, LocaleManager, HeadersManager classes
- **Service Architecture**: Removed dependency injection and service orchestration
- **SMTP Manager**: Removed complex SMTP configuration (kept simple email from/name override)
- **Headers Manager**: Removed HTTP headers management
- **Complex Domain Routing**: Simplified to basic domain detection

### Added
- **New Plugin Class**: Clean, simple Plugin class with organized methods
- **PipelineManager Class**: Automatic GitLab pipeline triggering on configuration changes
- **Pipeline Triggers**: Watches Site Language, Ready to Publish, and Public Domain changes
- **Admin Notices**: Non-dismissible pending notice and dismissible completion notice
- **REST API Endpoint**: `/wp-json/mtv-product-configuration/v1/pipeline/complete` for pipeline completion
- **Async Pipeline Calls**: Non-blocking HTTP requests to GitLab API
- **Composer Autoloading**: PSR-4 autoloading with fallback for non-Composer environments
- **Backward Compatibility Functions**: `mtv_is_dev_environment()`, `mtv_get_internal_domain()`, `mtv_get_public_domain()`
- **Global Instance Access**: `mtv_product_configuration_get_instance()` for external access
- **Method Organization**: Separated SEO, email, and language functionality into dedicated methods

### Changed
- **Internal Domain Detection**: Now uses `site_url` as primary fallback for internal domain detection
- **Domain Pattern**: Updated to match `*.motivar.dev` pattern instead of `*.domain.com`
- **Conflict Prevention**: Added domain conflict detection to prevent addon and internal domains from being identical
- **SEO Options Logic**: Changed from "enable" to "disable" toggles - SEO protections (noindex, robots disallow) are now enabled by default
- **Option Names**: `mtv_product_send_noindex_on_internal` → `mtv_product_disable_noindex_on_internal` (inverted logic)
- **Option Names**: `mtv_product_disallow_robots_on_internal` → `mtv_product_disable_robots_disallow_on_internal` (inverted logic)
- **Internal Domain Source**: Removed `mtv_product_internal_domain` option - now uses only WordPress `site_url` as source of truth
- **Environment-Based Loading**: Plugin functionality completely disabled on non-MTV development domains to prevent conflicts

### Removed
- **Redirect Functionality**: Removed all redirect-related code and `mtv_product_force_redirect_public` option
- **DomainRouter Redirect Methods**: Removed `handle_redirect()`, `build_redirect_url()`, and `get_current_scheme()` methods
- **Redirect Hook Registration**: DomainRouter no longer registers any hooks (domain detection only)
- **Internal Domain Option**: Removed `mtv_product_internal_domain` option - internal domain now auto-detected from `site_url` only

### Added
- `has_domain_conflict()` method in OptionsProvider to detect domain conflicts
- Enhanced domain detection logic with multiple fallback strategies
- Improved error handling when domains conflict
- **AdminSettings class** for AWM options framework integration
- **Complete admin interface** for all plugin configuration options
- **DNS configuration fields** for DKIM, SPF, CNAME, and A records (server-posted)
- **System information display** with real-time configuration status
- **Language selection dropdown** with common locales
- **Brand configuration fields** for email and branding settings
- **Environment Detection**: Plugin only functions on MTV development domains (.motivar.dev, .ddev.site)
- **Development Environment Check**: Shows notice and disables options on non-development domains
- **Auto Internal Domain Detection**: Automatically detects .ddev.site domains in addition to .motivar.dev

### Technical Details
- Internal domain detection: Uses only WordPress `site_url` as source of truth
- Domain conflict protection: treats all requests as public when addon and internal domains are identical
- Pattern validation: ensures internal domains match `^\d+\.motivar\.dev$` or contain `.ddev.site`

## [1.0.2] - 2024-10-21

### Added
- Complete MTV Product Configuration MU plugin implementation
- OptionsProvider class for safe option reads with normalization and defaults
- DomainRouter class for automatic domain detection and optional redirects
- SeoGuards class for noindex/canonical/robots.txt handling on internal domains
- SMTPManager class for complete email configuration with PHPMailer integration
- LocaleManager class for frontend-only language management
- HeadersManager class for HTTP headers (Vary: Host, diagnostic headers)
- Plugin orchestrator class for service coordination
- Comprehensive README.md with installation and usage instructions
- Support for Bedrock and vanilla WordPress installations
- Yoast SEO compatibility for robots and canonical URL overrides

### Features
- **Domain Routing**: Automatic detection of internal vs public domains
- **SEO Protection**: Noindex meta tags and canonical URLs for internal domains
- **Email Configuration**: Complete SMTP setup with authentication
- **Language Management**: Site locale configuration independent of admin
- **Cache Control**: Proper HTTP headers to prevent cache pollution
- **Fail-safe Operation**: Graceful degradation when options are missing

### Technical Details
- PHP 7.4+ compatibility
- PSR-4 autoloading with Composer
- Object-oriented architecture with dependency injection
- Low cyclomatic complexity following DRY principles
- Comprehensive error handling and validation
- WordPress coding standards compliance

### Configuration Options
- `mtv_product_addon_domain`: Public domain configuration
- `mtv_product_internal_domain`: Internal domain configuration
- `mtv_product_site_language`: Site language code
- `mtv_product_brand_name`: Email from name
- `mtv_product_from_email`: Email from address
- SMTP configuration options (host, port, user, pass, secure)
- Behavior toggles for redirects, noindex, and robots.txt

### Affected Files
- `product-configuration.php`: Plugin bootstrap
- `includes/classes/OptionsProvider.php`: Option management
- `includes/classes/DomainRouter.php`: Domain routing logic
- `includes/classes/SeoGuards.php`: SEO protection features
- `includes/classes/SMTPManager.php`: Email configuration
- `includes/classes/LocaleManager.php`: Language management
- `includes/classes/HeadersManager.php`: HTTP headers
- `includes/classes/Plugin.php`: Service orchestrator
- `README.md`: Comprehensive documentation
- `composer.json`: Package configuration

### Backwards Compatibility
- First release - no backwards compatibility concerns
- All features are opt-in via WordPress options
- No database modifications required
- Safe to activate/deactivate without data loss
