<?php
/**
 * Name: Media Replacement
 * Description: Replace any media file while preserving the same attachment ID, URL, and all references to update images or documents without breaking existing links
 * Category: Media
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Update Product Photos",
 *     "description": "Replace outdated product image used in 50+ posts without re-uploading and manually updating each post or page."
 *   },
 *   {
 *     "title": "Fix Incorrect Images",
 *     "description": "Swap wrong logo or team photo embedded across site while keeping same URL and all references intact automatically."
 *   },
 *   {
 *     "title": "Brand Asset Updates",
 *     "description": "Replace old brand colors in header image used site-wide without breaking existing page layouts or shortcodes."
 *   }
 * ]
 * Added: 2025-09-12
 * Tags: media, replace, update, attachment, image
 * WhereToFind: {
 *   "location": "Media Library (Edit Media)",
 *   "url": "upload.php",
 *   "instructions": "Edit any media item to see 'Replace Media' button",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardMediaReplacement {
    
    private $user_cap = false;
    private $general_cap = false;
    
    public function __construct() {
        // Set up capabilities
        if (!current_user_can('upload_files')) {
            return;
        }
        
        add_filter('media_row_actions', [$this, 'add_replace_link'], 10, 2);
        add_action('admin_footer-upload.php', [$this, 'add_modal_html']);
        add_action('admin_footer-post.php', [$this, 'add_modal_html']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
        add_action('wp_ajax_get_replace_form', [$this, 'get_replace_form']);
        add_action('wp_ajax_replace_media', [$this, 'replace_media']);
        
        // Add meta box on attachment edit screen
        add_action('add_meta_boxes_attachment', [$this, 'add_meta_boxes'], 10, 2);
        
        // Show replaced date info
        add_action('attachment_submitbox_misc_actions', [$this, 'show_replaced_info'], 91);
        
        // Add cache busting for just-replaced images
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only cache busting check after redirect
        if (isset($_GET['emr_replaced']) && intval($_GET['emr_replaced']) == 1) {
            add_filter('wp_get_attachment_image_src', [$this, 'attempt_uncache_image'], 10, 4);
        }
    }
    
    /**
     * Check if user has permission to replace this attachment
     */
    private function check_permission($post) {
        if (!is_object($post) || $post->post_type !== 'attachment') {
            return false;
        }
        
        $post_id = $post->ID;
        $author_id = $post->post_author;
        
        if ($this->general_cap === false && $this->user_cap === false) {
            if (current_user_can('edit_post', $post_id) === true) {
                return true;
            }
        } elseif (current_user_can($this->general_cap)) {
            return true;
        } elseif (current_user_can($this->user_cap) && $author_id == get_current_user_id()) {
            return true;
        }
        
        return false;
    }
    
    public function add_replace_link($actions, $post) {
        if (!$this->check_permission($post)) {
            return $actions;
        }
        
        $actions['replace_media'] = '<a href="#" class="replace-media-link" data-id="' . $post->ID . '" aria-label="' . esc_attr__('Replace media', 'wpswitchboard') . '">' . esc_html__('Replace Media', 'wpswitchboard') . '</a>';
        
        return $actions;
    }
    
    /**
     * Add meta box on attachment edit screen
     */
    public function add_meta_boxes($post) {
        if (!is_object($post) || !$this->check_permission($post)) {
            return;
        }
        
        add_meta_box(
            'switchboard-replace-box',
            __('Replace Media', 'wpswitchboard') . ' <span class="switchboard-badge">SWITCHBOARD</span>',
            [$this, 'replace_meta_box'],
            'attachment',
            'side',
            'low'
        );
    }
    
    /**
     * Render meta box content
     */
    public function replace_meta_box($post) {
        $nonce = wp_create_nonce('replace_media_' . $post->ID);
        ?>
        <p>
            <button type="button" class="button button-secondary replace-media-link" data-id="<?php echo esc_attr($post->ID); ?>">
                <?php esc_html_e('Upload a new file', 'wpswitchboard'); ?>
            </button>
        </p>
        <p class="description">
            <?php esc_html_e('To replace the current file, click the link and upload a replacement file.', 'wpswitchboard'); ?>
        </p>
        <?php
    }
    
    /**
     * Show replaced date and author info
     */
    public function show_replaced_info($post) {
        if (!is_object($post)) {
            global $post;
            if (!is_object($post)) {
                return;
            }
        }
        
        $post_id = $post->ID;
        
        // Show modified date if different from created date
        if ($post->post_modified !== $post->post_date) {
            $modified = date_i18n(__('M j, Y @ H:i', 'wpswitchboard'), strtotime($post->post_modified));
            ?>
            <div class="misc-pub-section curtime">
                <span id="timestamp"><?php echo esc_html__('Revised', 'wpswitchboard'); ?>: <b><?php echo esc_html($modified); ?></b></span>
            </div>
            <?php
        }
        
        // Show who replaced it if different from author
        $author_id = get_post_meta($post_id, '_emr_replace_author', true);
        if ($author_id) {
            $display_name = get_the_author_meta('display_name', $author_id);
            ?>
            <div class="misc-pub-section replace_author">
                <span><?php echo esc_html__('Replaced By', 'wpswitchboard'); ?>: <b><?php echo esc_html($display_name); ?></b></span>
            </div>
            <?php
        }
    }
    
    /**
     * Add timestamp to image URL for cache busting
     */
    public function attempt_uncache_image($image, $attachment_id, $size, $icon) {
        if ($image === false) {
            return $image;
        }
        
        // Add timestamp to URL to bust browser cache
        $image[0] = add_query_arg('time', time(), $image[0]);
        return $image;
    }
    
    public function add_modal_html() {
        ?>
        <div id="replace-modal" style="display:none; position:fixed; z-index:160000; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,0.75); overflow-y:auto;">
            <div style="background:#fff; margin:40px auto; padding:0; width:90%; max-width:700px; border-radius:4px; box-shadow:0 5px 25px rgba(0,0,0,0.5);">
                <div style="padding:20px 24px; border-bottom:1px solid #ddd; background:#f9f9f9;">
                    <span onclick="closeReplaceModal()" style="float:right; font-size:28px; font-weight:bold; cursor:pointer; color:#666; line-height:1; transition:color 0.2s;" onmouseover="this.style.color='#000'" onmouseout="this.style.color='#666'">&times;</span>
                    <h2 style="margin:0; font-size:20px; color:#23282d;">
                        <span class="dashicons dashicons-update" style="font-size:20px; margin-right:8px;"></span>
                        <?php esc_html_e('Replace Media', 'wpswitchboard'); ?>
                    </h2>
                </div>
                <div id="replace-modal-body" style="padding:24px; max-height:calc(100vh - 250px); overflow-y:auto;"></div>
                <div style="padding:16px 24px; border-top:1px solid #ddd; text-align:right; background:#f9f9f9;">
                    <button type="button" class="button button-large" onclick="closeReplaceModal()" style="margin-right:8px;"><?php esc_html_e('Cancel', 'wpswitchboard'); ?></button>
                    <button type="button" class="button button-primary button-large" onclick="submitReplace()">
                        <span class="dashicons dashicons-upload" style="font-size:16px; margin-top:4px;"></span>
                        <?php esc_html_e('Replace Media', 'wpswitchboard'); ?>
                    </button>
                </div>
            </div>
        </div>
        <style>
            #replace-modal .button-primary:disabled {
                opacity: 0.6;
                cursor: not-allowed;
            }
            #replace-modal input[type="file"] {
                padding: 8px;
                border: 2px dashed #ddd;
                background: #fafafa;
                cursor: pointer;
            }
            #replace-modal input[type="file"]:hover {
                border-color: #2271b1;
                background: #f0f6fc;
            }
        </style>
        <?php
    }
    
    public function enqueue_scripts($hook) {
        // Load on media library and attachment edit pages
        if ($hook !== 'upload.php' && $hook !== 'post.php') {
            return;
        }
        
        $max_upload_size = wp_max_upload_size();
        
        // Make sure jQuery is loaded
        wp_enqueue_script('jquery');

        // Register and enqueue our script
        wp_register_script('switchboard-media-replace', false, ['jquery'], '1.0', true);
        wp_enqueue_script('switchboard-media-replace');

        // Localize script with translatable strings
        wp_localize_script('switchboard-media-replace', 'switchboardMediaReplaceI18n', [
            'error' => __('Error', 'wpswitchboard'),
            'unknownError' => __('Unknown error', 'wpswitchboard'),
            'failedToLoad' => __('Failed to load form', 'wpswitchboard'),
            'pleaseSelectFile' => __('Please select a file to upload', 'wpswitchboard'),
            'fileSizeExceeds' => __('File size exceeds maximum upload size of', 'wpswitchboard'),
            'uploading' => __('Uploading...', 'wpswitchboard'),
            'mediaReplacedSuccess' => __('Media replaced successfully!', 'wpswitchboard'),
            'failedToReplace' => __('Failed to replace media', 'wpswitchboard'),
            'replaceMedia' => __('Replace Media', 'wpswitchboard'),
        ]);

        wp_add_inline_script('switchboard-media-replace', '
(function($) {
    "use strict";
    
    var SwitchboardMediaReplace = {
        currentMediaId: 0,
        currentNonce: "",
        maxFileSize: ' . $max_upload_size . ',
        
        init: function() {
            this.bindEvents();
        },
        
        bindEvents: function() {
            var self = this;
            
            // Use event delegation for dynamically added links
            $(document).on("click", ".replace-media-link", function(e) {
                e.preventDefault();
                e.stopPropagation();
                var mediaId = $(this).data("id");
                if (mediaId) {
                    self.openModal(mediaId);
                }
                return false;
            });
            
            // Close on escape
            $(document).on("keydown", function(e) {
                if (e.key === "Escape" && $("#replace-modal").is(":visible")) {
                    self.closeModal();
                }
            });
            
            // Close on background click
            $(document).on("click", "#replace-modal", function(e) {
                if (e.target === this) {
                    self.closeModal();
                }
            });
        },
        
        openModal: function(mediaId) {
            var self = this;
            this.currentMediaId = mediaId;
            this.currentNonce = "";
            
            $.ajax({
                url: ajaxurl,
                type: "POST",
                data: {
                    action: "get_replace_form",
                    media_id: mediaId,
                    _ajax_nonce: "' . esc_js(wp_create_nonce('get_replace_form')) . '"
                },
                success: function(response) {
                    if (response.success) {
                        self.currentNonce = response.data.nonce;
                        $("#replace-modal-body").html(response.data.html);
                        $("#replace-modal").fadeIn(200);
                    } else {
                        alert(switchboardMediaReplaceI18n.error + ": " + (response.data && response.data.message ? response.data.message : switchboardMediaReplaceI18n.unknownError));
                    }
                },
                error: function(xhr, status, error) {
                    alert(switchboardMediaReplaceI18n.failedToLoad + ": " + error);
                }
            });
        },
        
        closeModal: function() {
            $("#replace-modal").fadeOut(200);
            setTimeout(function() {
                $("#replace-modal-body").html("");
            }, 200);
            this.currentMediaId = 0;
            this.currentNonce = "";
        },
        
        submitReplace: function() {
            var self = this;
            var fileInput = document.getElementById("replace-file");

            if (!fileInput || !fileInput.files.length) {
                alert(switchboardMediaReplaceI18n.pleaseSelectFile);
                return;
            }

            var file = fileInput.files[0];

            if (file.size > this.maxFileSize) {
                alert(switchboardMediaReplaceI18n.fileSizeExceeds + " " + (this.maxFileSize / 1024 / 1024).toFixed(2) + " MB");
                return;
            }
            
            var formData = new FormData();
            formData.append("action", "replace_media");
            formData.append("media_id", this.currentMediaId);
            formData.append("file", file);
            formData.append("keep_filename", $("#keep-filename").is(":checked") ? "yes" : "no");
            formData.append("keep_date", $("#keep-date").is(":checked") ? "yes" : "no");
            formData.append("_ajax_nonce", this.currentNonce);
            
            $("#replace-progress").show();
            $("#replace-progress-bar").css("width", "0%");
            $("#replace-modal .button-primary").prop("disabled", true).html(switchboardMediaReplaceI18n.uploading);

            $.ajax({
                url: ajaxurl,
                type: "POST",
                data: formData,
                processData: false,
                contentType: false,
                xhr: function() {
                    var xhr = new window.XMLHttpRequest();
                    xhr.upload.addEventListener("progress", function(evt) {
                        if (evt.lengthComputable) {
                            var percentComplete = (evt.loaded / evt.total) * 100;
                            $("#replace-progress-bar").css("width", percentComplete + "%");
                        }
                    }, false);
                    return xhr;
                },
                success: function(response) {
                    if (response.success) {
                        alert(switchboardMediaReplaceI18n.mediaReplacedSuccess);
                        location.reload();
                    } else {
                        alert(switchboardMediaReplaceI18n.error + ": " + (response.data && response.data.message ? response.data.message : switchboardMediaReplaceI18n.unknownError));
                        $("#replace-modal .button-primary").prop("disabled", false).html(switchboardMediaReplaceI18n.replaceMedia);
                        $("#replace-progress").hide();
                    }
                },
                error: function(xhr, status, error) {
                    alert(switchboardMediaReplaceI18n.failedToReplace + ": " + error);
                    $("#replace-modal .button-primary").prop("disabled", false).html(switchboardMediaReplaceI18n.replaceMedia);
                    $("#replace-progress").hide();
                }
            });
        }
    };
    
    // Make functions globally available
    window.openReplaceModal = function(id) { SwitchboardMediaReplace.openModal(id); };
    window.closeReplaceModal = function() { SwitchboardMediaReplace.closeModal(); };
    window.submitReplace = function() { SwitchboardMediaReplace.submitReplace(); };
    
    // Initialize when DOM is ready
    $(document).ready(function() {
        SwitchboardMediaReplace.init();
    });
    
})(jQuery);
        ');
        
        // Add CSS for spinner animation
        wp_add_inline_style('wp-admin', '
            @keyframes rotation {
                from { transform: rotate(0deg); }
                to { transform: rotate(359deg); }
            }
        ');
    }
    
    public function get_replace_form() {
        check_ajax_referer('get_replace_form');
        
        $media_id = isset($_POST['media_id']) ? intval($_POST['media_id']) : 0;
        
        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => __('Permission denied', 'wpswitchboard')]);
        }

        $attachment = get_post($media_id);

        if (!$attachment || $attachment->post_type !== 'attachment') {
            wp_send_json_error(['message' => __('Invalid media ID', 'wpswitchboard')]);
        }

        if (!$this->check_permission($attachment)) {
            wp_send_json_error(['message' => __('You do not have permission to replace this file', 'wpswitchboard')]);
        }
        
        // Get file info - handle WP 5.3+ scaled images
        $file_path = get_attached_file($media_id, true);
        if (function_exists('wp_get_original_image_path')) {
            $original_path = wp_get_original_image_path($media_id, true);
            if ($original_path && $original_path !== $file_path) {
                $file_path = $original_path;
            }
        }
        
        $file_url = wp_get_attachment_url($media_id);
        $file_type = get_post_mime_type($media_id);
        $file_name = basename($file_path);
        $file_size = file_exists($file_path) ? size_format(filesize($file_path)) : 'Unknown';
        
        // Get image dimensions if it's an image
        $dimensions = '';
        if (wp_attachment_is_image($media_id)) {
            $meta = wp_get_attachment_metadata($media_id);
            if (isset($meta['width']) && isset($meta['height'])) {
                $dimensions = $meta['width'] . ' × ' . $meta['height'] . ' pixels';
            }
        }
        
        // Create nonce for the upload
        $upload_nonce = wp_create_nonce('replace_media_' . $media_id);
        
        ob_start();
        ?>
        <div class="sb-sp-text-center sb-sp-my-5">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('Current File', 'wpswitchboard'); ?></h4>
            <?php if (wp_attachment_is_image($media_id)): ?>
                <img src="<?php echo esc_url($file_url); ?>" class="sb-sp-upload-image" style="max-height:300px;">
            <?php else: ?>
                <div class="sb-sp-upload-preview">
                    <span class="dashicons dashicons-media-default sb-sp-icon-lg"></span>
                </div>
            <?php endif; ?>
            <p class="sb-sp-my-2"><strong><?php echo esc_html($file_name); ?></strong></p>
            <p class="sb-sp-my-1"><small><?php echo esc_html($file_type); ?> • <?php echo esc_html($file_size); ?></small></p>
            <?php if ($dimensions): ?>
                <p class="sb-sp-my-1"><small><?php echo esc_html($dimensions); ?></small></p>
            <?php endif; ?>
        </div>

        <div class="sb-sp-section sb-sp-my-5">
            <label for="replace-file" class="sb-sp-label"><strong><?php esc_html_e('Choose New File:', 'wpswitchboard'); ?></strong></label>
            <input type="file" id="replace-file" class="sb-sp-input">
            <p class="sb-sp-description">
                <?php
                // translators: %s is the maximum upload file size (e.g., "64 MB")
                printf(esc_html__('Maximum upload size: %s', 'wpswitchboard'), esc_html(size_format(wp_max_upload_size()))); ?>
            </p>
        </div>

        <div class="sb-sp-section sb-sp-my-5">
            <p class="sb-sp-label"><?php esc_html_e('Options:', 'wpswitchboard'); ?></p>
            <label class="sb-sp-checkbox-label">
                <input type="checkbox" id="keep-filename" checked>
                <?php esc_html_e('Keep original filename (recommended)', 'wpswitchboard'); ?>
            </label>
            <label class="sb-sp-checkbox-label">
                <input type="checkbox" id="keep-date" checked>
                <?php esc_html_e('Keep original upload date', 'wpswitchboard'); ?>
            </label>
        </div>

        <div id="replace-progress" class="sb-sp-my-4 hidden">
            <p class="sb-sp-label"><?php esc_html_e('Uploading and processing...', 'wpswitchboard'); ?></p>
            <div class="sb-sp-progress-bar-container">
                <div id="replace-progress-bar" class="sb-sp-progress-bar" style="width:0%;"></div>
            </div>
        </div>
        <?php
        
        $html = ob_get_clean();
        wp_send_json_success([
            'html' => $html,
            'nonce' => $upload_nonce
        ]);
    }
    
    public function replace_media() {
        $media_id = isset($_POST['media_id']) ? intval($_POST['media_id']) : 0;
        
        // Verify nonce with media ID
        check_ajax_referer('replace_media_' . $media_id);
        
        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => __('Permission denied', 'wpswitchboard')]);
        }

        $attachment = get_post($media_id);
        if (!$attachment || $attachment->post_type !== 'attachment') {
            wp_send_json_error(['message' => __('Invalid media ID', 'wpswitchboard')]);
        }

        if (!$this->check_permission($attachment)) {
            wp_send_json_error(['message' => __('You do not have permission to replace this file', 'wpswitchboard')]);
        }
        
        // Get parameters
        $keep_filename = isset($_POST['keep_filename']) && $_POST['keep_filename'] === 'yes';
        $keep_date = isset($_POST['keep_date']) && $_POST['keep_date'] === 'yes';
        
        // Validate file upload
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File tmp_name is a server path, not user input
        if (!isset($_FILES['file']) || !isset($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
            wp_send_json_error(['message' => __('No file uploaded', 'wpswitchboard')]);
        }

        // Check for upload errors
        if (isset($_FILES['file']['error']) && intval($_FILES['file']['error']) > 0) {
            $error_messages = [
                UPLOAD_ERR_INI_SIZE => __('File exceeds upload_max_filesize', 'wpswitchboard'),
                UPLOAD_ERR_FORM_SIZE => __('File exceeds MAX_FILE_SIZE', 'wpswitchboard'),
                UPLOAD_ERR_PARTIAL => __('File was only partially uploaded', 'wpswitchboard'),
                UPLOAD_ERR_NO_FILE => __('No file was uploaded', 'wpswitchboard'),
                UPLOAD_ERR_NO_TMP_DIR => __('Missing temporary folder', 'wpswitchboard'),
                UPLOAD_ERR_CANT_WRITE => __('Failed to write file to disk', 'wpswitchboard'),
                UPLOAD_ERR_EXTENSION => __('File upload stopped by extension', 'wpswitchboard')
            ];
            $upload_error_code = intval($_FILES['file']['error']);
            $error_msg = isset($error_messages[$upload_error_code])
                ? $error_messages[$upload_error_code]
                : __('Unknown upload error', 'wpswitchboard');
            wp_send_json_error(['message' => $error_msg]);
        }

        // Validate file type and extension
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- tmp_name is a server path used for file type checking
        $file_tmp_name = isset($_FILES['file']['tmp_name']) ? $_FILES['file']['tmp_name'] : '';
        $file_name = isset($_FILES['file']['name']) ? sanitize_file_name(wp_unslash($_FILES['file']['name'])) : '';
        $filedata = wp_check_filetype_and_ext($file_tmp_name, $file_name);
        if ($filedata['ext'] === false && !current_user_can('unfiltered_upload')) {
            wp_send_json_error(['message' => __('File type not allowed for security reasons', 'wpswitchboard')]);
        }
        
        // Get the source file - handle WP 5.3+ scaled images
        $source_file = get_attached_file($media_id, true);
        $source_file_main = $source_file;
        
        if (function_exists('wp_get_original_image_path')) {
            $original_file = wp_get_original_image_path($media_id, true);
            if ($original_file && $original_file !== $source_file) {
                $source_file = $original_file;
            }
        }
        
        if (!file_exists($source_file)) {
            wp_send_json_error(['message' => __('Original file not found on server', 'wpswitchboard')]);
        }
        
        // Store old file info
        $old_filename = basename($source_file);
        $old_permissions = fileperms($source_file);
        $old_url = wp_get_attachment_url($media_id);
        
        // Determine new filename
        if ($keep_filename) {
            $new_filename = $old_filename;
        } else {
            $new_filename = isset($_FILES['file']['name']) ? sanitize_file_name(wp_unslash($_FILES['file']['name'])) : '';
            // Make filename unique if needed
            $upload_dir = dirname($source_file);
            $new_filename = wp_unique_filename($upload_dir, $new_filename);
        }
        
        $new_file_path = dirname($source_file) . '/' . $new_filename;
        
        // Get old metadata before deletion
        $old_metadata = wp_get_attachment_metadata($media_id);
        $backup_sizes = get_post_meta($media_id, '_wp_attachment_backup_sizes', true);
        
        // Delete old files and thumbnails
        wp_delete_attachment_files($media_id, $old_metadata, $backup_sizes, $source_file);
        
        // Also delete the attached file if it still exists (for -scaled images)
        $attached_file = get_attached_file($media_id);
        if ($attached_file && file_exists($attached_file) && $attached_file !== $source_file) {
            wp_delete_file($attached_file);
        }
        
        // Move uploaded file to target location
        // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- Required for media replacement, wp_handle_upload not suitable here
        if (!move_uploaded_file($file_tmp_name, $new_file_path)) {
            wp_send_json_error(['message' => __('Failed to move uploaded file to destination', 'wpswitchboard')]);
        }
        
        // Restore permissions
        if ($old_permissions && $old_permissions > 0) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Setting correct file permissions
            @chmod($new_file_path, $old_permissions);
        }
        
        // Update attached file path
        $updated = update_attached_file($media_id, $new_file_path);
        
        // Update mime type (only use validated type from WordPress)
        $new_filetype = $filedata['type'];
        if (!$new_filetype) {
            // If WordPress can't determine file type, reject the upload
            wp_delete_file($new_file_path);
            wp_send_json_error(['message' => esc_html__('Unable to determine file type. File may be corrupted or unsupported.', 'wpswitchboard')]);
        }

        if ($new_filetype !== get_post_mime_type($media_id)) {
            wp_update_post([
                'ID' => $media_id,
                'post_mime_type' => $new_filetype
            ], true);
        }
        
        // Regenerate metadata and thumbnails
        require_once(ABSPATH . 'wp-admin/includes/image.php');
        $new_metadata = wp_generate_attachment_metadata($media_id, $new_file_path);
        wp_update_attachment_metadata($media_id, $new_metadata);
        
        // Get new URL
        $new_url = wp_get_attachment_url($media_id);
        
        // Update dates
        global $wpdb;
        $update_data = [];
        
        if ($keep_date) {
            // Keep original upload date, only update modified
            $update_data['post_modified'] = current_time('mysql');
            $update_data['post_modified_gmt'] = current_time('mysql', 1);
        } else {
            // Update all dates to current time
            $current_time = current_time('mysql');
            $current_time_gmt = current_time('mysql', 1);
            $update_data['post_date'] = $current_time;
            $update_data['post_date_gmt'] = $current_time_gmt;
            $update_data['post_modified'] = $current_time;
            $update_data['post_modified_gmt'] = $current_time_gmt;
        }
        
        if (!empty($update_data)) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Updating post dates for replaced media
            $wpdb->update($wpdb->posts, $update_data, ['ID' => $media_id], null, ['%d']);
        }
        
        // Clear all caches
        wp_cache_delete($media_id, 'posts');
        wp_cache_delete($media_id, 'post_meta');
        clean_attachment_cache($media_id);
        
        // Track who replaced it (if different from author)
        $post_author = get_post_field('post_author', $media_id);
        if (intval($post_author) !== get_current_user_id()) {
            update_post_meta($media_id, '_emr_replace_author', get_current_user_id());
        } else {
            delete_post_meta($media_id, '_emr_replace_author');
        }
        
        // Fire action hook for other plugins
        do_action('switchboard_media_replaced', $media_id, $source_file, $new_file_path);
        do_action('switchboard_media_replace_upload_done', $new_url, $old_url, $media_id);
        
        wp_send_json_success([
            'message' => __('Media replaced successfully', 'wpswitchboard'),
            'media_id' => $media_id,
            'old_url' => $old_url,
            'new_url' => $new_url
        ]);
    }

    /**
     * Register data locations for cleanup system
     */
    public function register_data_locations() {
        return [
            'options' => [],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [
                '_emr_replace_author',
            ],
            'user_meta' => [],
        ];
    }
}

$switchboard_instance = new SwitchboardMediaReplacement();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
