<?php

/**
 * WUClean main plugin file.
 *
 * @package WUClean
 */
declare (strict_types = 1);
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
/**
* Plugin Name: Wuclean → Database Cleaner for WooCommerce
* Plugin URI: https://paythem.net/wuclean-the-woocommerce-cleanup-assistant-plugin/
* Description: Clean, optimize and maintain your WooCommerce database by removing orphaned data, unused attributes, and product issues.
* Version: 1.1.2
* Requires at least: 6.2
* Requires PHP: 7.4
* Requires Plugins: woocommerce
* Author: PayThem
* Author URI: https://paythem.net/
* Text Domain: wuclean-database-cleaner-for-woocommerce
* Domain Path: /languages
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* IMPORTANT: This plugin follows WordPress i18n best practices.
*
* All translation strings use the text domain 'wuclean-database-cleaner-for-woocommerce' and follow WordPress.org guidelines:
* - Ordered placeholders for translations with multiple variables (%1$d, %2$s, etc.)
* - Translator comments for all strings with placeholders
* - Single string literals (no concatenation) in translation functions
*
* Note: For plugins hosted on WordPress.org, you no longer need to manually load the text domain.
* WordPress will automatically load translations as needed (since WordPress 4.6).
*
* DESIGN DECISIONS & PCP WARNINGS:
*
* This is a specialized database cleanup and maintenance plugin for WooCommerce.
* Several WordPress Coding Standards warnings are intentional design decisions:
*
* 1. DIRECT DATABASE QUERIES (WordPress.DB.DirectDatabaseQuery):
*    - Required for finding orphaned data across multiple tables
*    - Needed for bulk cleanup operations that WP_Query cannot handle
*    - Performance-critical operations that would be too slow with WP APIs
*    - All queries use proper $wpdb->prepare() for security
*
* 2. NO CACHING (WordPress.DB.DirectDatabaseQuery.NoCaching):
*    - Cleanup operations require real-time, accurate data
*    - Cached results would defeat the purpose of finding current orphaned records
*    - Operations are admin-only and not performance-sensitive for front-end
*
* 3. SET_TIME_LIMIT / INI_SET (Squiz.PHP.DiscouragedFunctions.Discouraged):
*    - Large cleanup operations may exceed default PHP execution limits
*    - These functions fail gracefully on restricted hosts
*    - Necessary for processing thousands of records in bulk operations
*
* 4. SLOW QUERIES (WordPress.DB.SlowDBQuery):
*    - Intentional meta_key/meta_value scanning to find orphaned metadata
*    - Operations are admin-only and run on-demand or scheduled
*    - Alternative approaches would be equally slow or impossible
*
* All security-critical operations use proper nonce verification, capability checks,
* and data sanitization. Direct database access is used responsibly and only where
* necessary for the plugin's core functionality.
*
* @package WUClean
*/
if ( function_exists( 'pt_wuclean_fs' ) ) {
    pt_wuclean_fs()->set_basename( false, __FILE__ );
    // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found -- Required by Freemius SDK for auto-deactivate free when premium activates.
} else {
    /*
     * DO NOT REMOVE THIS IF, IT IS ESSENTIAL FOR THE
     * `function_exists` CALL ABOVE TO PROPERLY WORK.
     */
    if ( !function_exists( 'pt_wuclean_fs' ) ) {
        /**
         * Freemius SDK accessor.
         *
         * @return \Freemius
         */
        function pt_wuclean_fs() {
            global $pt_wuclean_fs;
            if ( !isset( $pt_wuclean_fs ) ) {
                // Include Freemius SDK.
                require_once __DIR__ . '/vendor/freemius/start.php';
                $pt_wuclean_fs = fs_dynamic_init( array(
                    'id'               => '21596',
                    'slug'             => 'wuclean-database-cleaner-for-woocommerce',
                    'premium_slug'     => 'wuclean-pro',
                    'type'             => 'plugin',
                    'public_key'       => 'pk_190edc4aac52e3b0064225d9805db',
                    'is_premium'       => false,
                    'premium_suffix'   => 'Pro',
                    'has_addons'       => false,
                    'has_paid_plans'   => true,
                    'is_org_compliant' => true,
                    'menu'             => array(
                        'slug'       => 'wuclean-database-cleaner-for-woocommerce',
                        'first-path' => 'admin.php?page=wuclean-database-cleaner-for-woocommerce',
                        'support'    => false,
                    ),
                    'is_live'          => true,
                ) );
            }
            return $pt_wuclean_fs;
        }

        // Init Freemius.
        pt_wuclean_fs();
        // Signal that SDK was initiated.
        do_action( 'pt_wuclean_fs_loaded' );
    }
}
/**
 * Plugin version (for upgrade checks).
 */
if ( !defined( 'PT_WUCLEAN_VERSION' ) ) {
    define( 'PT_WUCLEAN_VERSION', '1.1.1' );
}
/**
 * Remove deprecated scan_thumbnails from stored image settings (thumbnails are always collected; use results filters instead).
 *
 * @return void
 */
function pt_wuclean_migrate_remove_scan_thumbnails_setting() : void {
    $settings = get_option( 'pt_wuclean_image_settings', array() );
    if ( !is_array( $settings ) || !array_key_exists( 'scan_thumbnails', $settings ) ) {
        return;
    }
    unset($settings['scan_thumbnails']);
    if ( empty( $settings ) ) {
        delete_option( 'pt_wuclean_image_settings' );
        return;
    }
    update_option( 'pt_wuclean_image_settings', $settings, false );
}

add_action( 'plugins_loaded', 'pt_wuclean_migrate_remove_scan_thumbnails_setting', 20 );
/**
 * Legacy plugin slugs used for detection and migration/deactivation.
 * Previous  plugins may use main file wuclean-pro.php or wuclean.php; check both.
 * Defined before PT_WUCLEAN_DB_VERSION so pt_wuclean_legacy_pro_is_active() can use them.
 */
if ( !defined( 'PT_WUCLEAN_OLD_PLUGIN_SLUG' ) ) {
    define( 'PT_WUCLEAN_OLD_PLUGIN_SLUG', 'wuclean-pro/wuclean-pro.php' );
}
if ( !defined( 'PT_WUCLEAN_OLD_PLUGIN_SLUG_ALT' ) ) {
    define( 'PT_WUCLEAN_OLD_PLUGIN_SLUG_ALT', 'wuclean-pro/wuclean.php' );
}
if ( !defined( 'PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG' ) ) {
    define( 'PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG', 'wuclean/wuclean.php' );
}
if ( !function_exists( 'pt_wuclean_legacy_pro_is_active' ) ) {
    /**
     * Whether a *separate* legacy Wuclean Pro plugin is active alongside this build.
     *
     * Freemius premium is often installed as `wuclean-pro/wuclean.php`, same basename as
     * 1.0.5 Pro. That entry in `active_plugins` refers to *this* file when we are that
     * build — it must NOT be treated as legacy or this plugin skips defining core symbols
     * while hooks still reference them (fatal on `plugins_loaded`).
     *
     * Legacy = old slug is active and its basename is not the main file currently loading.
     *
     * @return bool
     */
    function pt_wuclean_legacy_pro_is_active() : bool {
        // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- __FILE__ is this main plugin file; required for basename comparison.
        $current_basename = plugin_basename( __FILE__ );
        $slugs = array(PT_WUCLEAN_OLD_PLUGIN_SLUG, PT_WUCLEAN_OLD_PLUGIN_SLUG_ALT);
        $active = (array) get_option( 'active_plugins', array() );
        foreach ( $slugs as $slug ) {
            if ( $slug === $current_basename ) {
                continue;
            }
            if ( in_array( $slug, $active, true ) ) {
                return true;
            }
        }
        if ( is_multisite() ) {
            $network = (array) get_site_option( 'active_sitewide_plugins', array() );
            foreach ( $slugs as $slug ) {
                if ( $slug === $current_basename ) {
                    continue;
                }
                if ( isset( $network[$slug] ) ) {
                    return true;
                }
            }
        }
        return false;
    }

}
if ( !function_exists( 'pt_wuclean_legacy_free_is_active' ) ) {
    /**
     * Whether a *separate* legacy Wuclean Free plugin (1.0.5) is active alongside this build.
     *
     * Same basename rule as Pro: if this main file is `wuclean/wuclean.php`, do not treat
     * that active entry as a distinct legacy install.
     *
     * @return bool
     */
    function pt_wuclean_legacy_free_is_active() : bool {
        // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- __FILE__ is this main plugin file; required for basename comparison.
        if ( PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG === plugin_basename( __FILE__ ) ) {
            return false;
        }
        $active = (array) get_option( 'active_plugins', array() );
        if ( in_array( PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG, $active, true ) ) {
            return true;
        }
        if ( is_multisite() ) {
            $network = (array) get_site_option( 'active_sitewide_plugins', array() );
            if ( isset( $network[PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG] ) ) {
                return true;
            }
        }
        return false;
    }

}
/**
 * DB schema version for Wuclean custom tables.
 */
if ( !defined( 'PT_WUCLEAN_DB_VERSION' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    define( 'PT_WUCLEAN_DB_VERSION', '1.0.2' );
}
if ( !function_exists( 'pt_wuclean_migration_activation_handler' ) ) {
    /**
     * Plugin activation handler.
     *
     * Deactivates the legacy plugin if it's still active to prevent conflicts.
     * Wrapped in function_exists so when both are loaded, only one defines this.
     *
     * @return void
     */
    function pt_wuclean_migration_activation_handler() : void {
        if ( !current_user_can( 'activate_plugins' ) ) {
            return;
        }
        if ( !function_exists( 'is_plugin_active' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $legacy_slugs = array(PT_WUCLEAN_OLD_PLUGIN_SLUG, PT_WUCLEAN_OLD_PLUGIN_SLUG_ALT, PT_WUCLEAN_OLD_FREE_PLUGIN_SLUG);
        foreach ( $legacy_slugs as $slug ) {
            if ( function_exists( 'is_plugin_active' ) && is_plugin_active( $slug ) ) {
                deactivate_plugins( $slug, true );
                set_transient( 'pt_wuclean_old_pro_deactivated', 1, 10 * MINUTE_IN_SECONDS );
            }
        }
    }

}
register_activation_hook( __FILE__, 'pt_wuclean_migration_activation_handler' );
if ( !function_exists( 'pt_wuclean_old_pro_admin_notice' ) ) {
    /**
     * Admin notice after deactivating the legacy premium plugin.
     * Wrapped in function_exists so when both are loaded, only one defines this.
     *
     * @return void
     */
    function pt_wuclean_old_pro_admin_notice() : void {
        if ( !get_transient( 'pt_wuclean_old_pro_deactivated' ) ) {
            return;
        }
        delete_transient( 'pt_wuclean_old_pro_deactivated' );
        echo '<div class="notice notice-warning is-dismissible"><p>' . esc_html__( 'We detected the old Wuclean plugin and deactivated it to prevent conflicts. The new "Wuclean Database Cleaner for WooCommerce" plugin is now active.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p></div>';
    }

}
add_action( 'admin_notices', 'pt_wuclean_old_pro_admin_notice' );
if ( !function_exists( 'pt_wuclean_get_required_capability' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Capability required to access Wuclean admin pages/actions.
     *
     * Defaults to `manage_options` (Administrators). Shop Managers and other roles do not receive
     * access unless a site explicitly maps it via the `pt_wuclean_required_capability` filter.
     *
     * After the filter, only known capabilities are accepted; unknown strings fall back to the default
     * so a misconfigured filter cannot widen access to arbitrary caps (e.g. `read`).
     *
     * @return string
     */
    function pt_wuclean_get_required_capability() {
        $default = 'manage_options';
        $capability = apply_filters( 'pt_wuclean_required_capability', $default );
        if ( !is_string( $capability ) || '' === $capability ) {
            $capability = $default;
        }
        $allowed_caps = array('manage_options', 'manage_woocommerce');
        /**
         * Allow sites to register one additional capability for Wuclean access control.
         *
         * @param string[] $allowed_caps Capabilities accepted after `pt_wuclean_required_capability`.
         */
        $allowed_caps = apply_filters( 'pt_wuclean_allowed_capabilities', $allowed_caps );
        $allowed_caps = ( is_array( $allowed_caps ) ? $allowed_caps : array($default) );
        $allowed_caps = array_values( array_unique( array_filter( array_map( 'strval', $allowed_caps ) ) ) );
        if ( empty( $allowed_caps ) ) {
            return $default;
        }
        if ( !in_array( $capability, $allowed_caps, true ) ) {
            return $default;
        }
        return $capability;
    }

}
if ( !function_exists( 'pt_wuclean_fs_after_uninstall_cleanup' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Freemius uninstall cleanup.
     *
     * IMPORTANT: Do NOT use `register_uninstall_hook()` or an `uninstall.php` script because WordPress only supports
     * a single static uninstall handler and it would prevent Freemius from reporting the uninstall event + feedback.
     *
     * @return void
     */
    function pt_wuclean_fs_after_uninstall_cleanup() : void {
        // Safety: only allow privileged users to run cleanup.
        if ( !current_user_can( 'activate_plugins' ) ) {
            return;
        }
        global $wpdb;
        // Delete all plugin options.
        delete_option( 'pt_wuclean_settings' );
        delete_option( 'pt_wuclean_image_settings' );
        delete_option( 'pt_wuclean_notification_email' );
        delete_option( 'pt_wuclean_db_version' );
        delete_option( 'pt_wuclean_product_issues' );
        delete_option( 'pt_wuclean_product_issues_count' );
        delete_option( 'pt_wuclean_product_scan_timestamps' );
        delete_option( 'pt_wuclean_last_scan_check' );
        delete_option( 'pt_wuclean_orphaned_data' );
        delete_option( 'pt_wuclean_orphaned_data_count' );
        delete_option( 'pt_wuclean_unused_attributes' );
        delete_option( 'pt_wuclean_unused_attributes_count' );
        delete_option( 'pt_wuclean_orphaned_order_data' );
        delete_option( 'pt_wuclean_orphaned_order_data_count' );
        delete_option( 'pt_wuclean_order_logs' );
        delete_option( 'pt_wuclean_order_scan_progress' );
        delete_option( 'pt_wuclean_orphaned_meta_scan_progress' );
        delete_option( 'pt_wuclean_auto_deleted_orphaned_order_notice' );
        delete_option( 'pt_wuclean_orphaned_data_scan_time' );
        delete_option( 'pt_wuclean_orphaned_customer_data' );
        delete_option( 'pt_wuclean_orphaned_customer_scan_time' );
        delete_option( 'pt_wuclean_orphaned_customer_scan_progress' );
        delete_option( 'pt_wuclean_auto_deleted_orphaned_customer_notice' );
        delete_option( 'pt_wuclean_unused_images' );
        delete_option( 'pt_wuclean_trashed_images' );
        delete_option( 'pt_wuclean_uploads_scan_results' );
        delete_option( 'pt_wuclean_last_uploads_scan_image_file_count' );
        delete_option( 'pt_wuclean_scan_results' );
        delete_option( 'pt_wuclean_product_search_progress' );
        delete_option( 'pt_wuclean_orphaned_search_progress' );
        delete_option( 'pt_wuclean_orphaned_data_temp' );
        delete_option( 'pt_wuclean_user_logs' );
        delete_option( 'pt_wuclean_user_scan_offset' );
        delete_option( 'pt_wuclean_user_scan_progress' );
        delete_option( 'pt_wuclean_user_log_limit' );
        delete_option( 'pt_wuclean_order_log_limit' );
        delete_option( 'pt_wuclean_pending_email_orphaned_data' );
        delete_option( 'pt_wuclean_pending_email_unused_attributes' );
        delete_option( 'pt_wuclean_pending_email_deleted_products' );
        delete_option( 'pt_wuclean_pending_email_enabled' );
        delete_option( 'pt_wuclean_unused_attributes_search_progress' );
        delete_option( 'pt_wuclean_unused_attributes_temp' );
        delete_option( 'pt_wuclean_scan_behind_info' );
        delete_option( 'pt_wuclean_protected_images' );
        // Delete all plugin transients (including pattern-based ones).
        $transient_keys = array(
            'pt_wuclean_orphaned_order_data',
            'pt_wuclean_product_issues',
            'pt_wuclean_orphaned_data',
            'pt_wuclean_unused_attributes',
            'pt_wuclean_orphaned_customer_data',
            'pt_wuclean_image_scan_lock',
            'pt_wuclean_scheduled_actions_cleanup_lock',
            'pt_wuclean_scheduled_product_scan_progress'
        );
        foreach ( $transient_keys as $key ) {
            delete_transient( $key );
        }
        // Delete transients by pattern (site and network transients).
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_pt_wuclean_' ) . '%', $wpdb->esc_like( '_transient_timeout_pt_wuclean_' ) . '%' ) );
        // Delete custom database tables.
        $runs_table = $wpdb->prefix . 'wuclean_image_scan_runs';
        $items_table = $wpdb->prefix . 'wuclean_image_scan_items';
        $attach_table = $wpdb->prefix . 'wuclean_attachment_files';
        $runs_table_escaped = esc_sql( $runs_table );
        $items_table_escaped = esc_sql( $items_table );
        $attach_table_escaped = esc_sql( $attach_table );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
        $wpdb->query( "DROP TABLE IF EXISTS {$items_table_escaped}" );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
        $wpdb->query( "DROP TABLE IF EXISTS {$runs_table_escaped}" );
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
        $wpdb->query( "DROP TABLE IF EXISTS {$attach_table_escaped}" );
        // Clear all scheduled cron events.
        $crons = _get_cron_array();
        if ( !empty( $crons ) ) {
            $wuclean_hooks = array(
                'pt_wuclean_scheduled_actions_cleanup_event',
                'pt_wuclean_other_cleanup_event',
                'pt_wuclean_product_cleanup_event',
                'pt_wuclean_flag_orders_event',
                'pt_wuclean_flag_users_event',
                'pt_wuclean_process_flagged_users_event',
                'pt_wuclean_image_scan_event',
                'pt_wuclean_process_image_scan_batch',
                'pt_wuclean_image_scan_watchdog_event',
                'pt_wuclean_attachment_index_backfill',
                'pt_wuclean_process_product_batch',
                'pt_wuclean_process_product_search_batch'
            );
            $modified = false;
            foreach ( $crons as $timestamp => $cron ) {
                foreach ( $wuclean_hooks as $hook ) {
                    if ( isset( $cron[$hook] ) ) {
                        unset($crons[$timestamp][$hook]);
                        $modified = true;
                    }
                }
                if ( empty( $crons[$timestamp] ) ) {
                    unset($crons[$timestamp]);
                }
            }
            if ( $modified ) {
                _set_cron_array( $crons );
            }
        }
        // Delete user meta keys (user protection flags).
        $user_meta_keys = array('_pt_wuclean_protect_user', '_pt_wuclean_protect_orders', '_pt_wuclean_flagged_at');
        foreach ( $user_meta_keys as $meta_key ) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->delete( $wpdb->usermeta, array(
                'meta_key' => $meta_key,
            ), array('%s') );
        }
        // Delete order meta keys (order protection and flagging).
        // Check if HPOS is enabled and use appropriate table.
        $is_hpos_enabled = 'yes' === get_option( 'woocommerce_custom_orders_table_enabled', 'no' );
        $order_meta_keys = array('_pt_wuclean_protected', '_pt_wuclean_flagged_at', '_pt_wuclean_marked_for_trash');
        if ( $is_hpos_enabled ) {
            // HPOS: Delete from wc_orders_meta table.
            $orders_meta_table = $wpdb->prefix . 'wc_orders_meta';
            foreach ( $order_meta_keys as $meta_key ) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->delete( $orders_meta_table, array(
                    'meta_key' => $meta_key,
                ), array('%s') );
            }
        } else {
            // Legacy: Delete from postmeta table.
            foreach ( $order_meta_keys as $meta_key ) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $wpdb->delete( $wpdb->postmeta, array(
                    'meta_key' => $meta_key,
                ), array('%s') );
            }
        }
    }

}
// Run cleanup after Freemius reports the uninstall event (so feedback is not lost).
pt_wuclean_fs()->add_action( 'after_uninstall', 'pt_wuclean_fs_after_uninstall_cleanup' );
if ( !function_exists( 'pt_wuclean_install_db_tables' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Install/upgrade Wuclean custom DB tables (idempotent).
     *
     * @return void
     */
    function pt_wuclean_install_db_tables() {
        $current = get_option( 'pt_wuclean_db_version', '' );
        if ( PT_WUCLEAN_DB_VERSION === $current ) {
            return;
        }
        global $wpdb;
        if ( !function_exists( 'dbDelta' ) ) {
            require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        }
        $charset_collate = $wpdb->get_charset_collate();
        $runs_table = $wpdb->prefix . 'wuclean_image_scan_runs';
        $items_table = $wpdb->prefix . 'wuclean_image_scan_items';
        $attach_table = $wpdb->prefix . 'wuclean_attachment_files';
        $sql_runs = "CREATE TABLE {$runs_table} (\n        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n        status varchar(20) NOT NULL,\n        mode varchar(20) NOT NULL,\n        created_at datetime NOT NULL,\n        updated_at datetime NOT NULL,\n        completed_at datetime NULL,\n        settings_json longtext NULL,\n        progress_json longtext NULL,\n        counts_json longtext NULL,\n        PRIMARY KEY  (id),\n        KEY status (status),\n        KEY mode (mode),\n        KEY completed_at (completed_at)\n    ) {$charset_collate};";
        $sql_items = "CREATE TABLE {$items_table} (\n        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n        run_id bigint(20) unsigned NOT NULL,\n        item_key char(32) NOT NULL,\n        type varchar(20) NOT NULL,\n        object_id bigint(20) unsigned NOT NULL DEFAULT 0,\n        subtype varchar(50) NULL,\n        path text NULL,\n        filename varchar(255) NULL,\n        parent_filename varchar(255) NULL,\n        file_type varchar(20) NULL,\n        sibling_used tinyint(1) NULL,\n        size_bytes bigint(20) unsigned NULL,\n        data_json longtext NULL,\n        PRIMARY KEY  (id),\n        UNIQUE KEY run_item (run_id, item_key),\n        KEY run_id (run_id),\n        KEY type (type),\n        KEY subtype (subtype),\n        KEY object_id (object_id),\n        KEY parent_filename (parent_filename),\n        KEY file_type (file_type),\n        KEY sibling_used (sibling_used)\n    ) {$charset_collate};";
        $sql_attach = "CREATE TABLE {$attach_table} (\n        attachment_id bigint(20) unsigned NOT NULL,\n        relpath_hash char(32) NOT NULL,\n        relpath text NOT NULL,\n        updated_at datetime NOT NULL,\n        PRIMARY KEY  (attachment_id),\n        KEY relpath_hash (relpath_hash)\n    ) {$charset_collate};";
        // dbDelta can execute multiple statements when passed separately.
        dbDelta( $sql_runs );
        dbDelta( $sql_items );
        dbDelta( $sql_attach );
        update_option( 'pt_wuclean_db_version', PT_WUCLEAN_DB_VERSION, false );
    }

}
if ( !function_exists( 'pt_wuclean_migrate_trash_from_content_to_uploads_do_work' ) ) {
    /**
     * One-time legacy: move image trash from wp-content to uploads (internal implementation).
     *
     * WordPress.org / reviewer note:
     * - Wuclean previously stored trashed images under `wp-content/wuclean-trash/images`. Current releases store
     *   writable data only under `wp_upload_dir()/wuclean-trash/images`.
     * - WordPress exposes no `wp_content_dir()` helper; `WP_CONTENT_DIR` is the correct way to locate a custom
     *   or relocated `wp-content` directory. This constant is used only to read/migrate the old path once.
     * - `pt_wuclean_trash_migrated_1_0_6` marks completion: no legacy folder, or migration finished, or nothing to do.
     * - Fast-fail: if uploads path is unavailable or mkdir/scandir fails, we do not set the flag so a later
     *   admin load or activation can retry.
     *
     * @return void
     */
    function pt_wuclean_migrate_trash_from_content_to_uploads_do_work() {
        if ( get_option( 'pt_wuclean_trash_migrated_1_0_6', '' ) === 'yes' ) {
            return;
        }
        // Legacy source: only installations from before ~1.0.6. No new writes use this path.
        $old_trash = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'wuclean-trash' . DIRECTORY_SEPARATOR . 'images';
        $old_trash = wp_normalize_path( $old_trash );
        if ( !is_dir( $old_trash ) ) {
            update_option( 'pt_wuclean_trash_migrated_1_0_6', 'yes', false );
            return;
        }
        // Resolve destination directly so migration does not depend on image module load order.
        $uploads = wp_upload_dir( null, false );
        if ( !empty( $uploads['error'] ) ) {
            return;
        }
        $new_dir = '';
        if ( isset( $uploads['basedir'] ) && is_string( $uploads['basedir'] ) && '' !== $uploads['basedir'] ) {
            $new_dir = wp_normalize_path( trailingslashit( (string) $uploads['basedir'] ) . 'wuclean-trash/images' );
            $new_dir = rtrim( $new_dir, '/\\' );
        }
        if ( '' === $new_dir ) {
            return;
        }
        if ( !wp_mkdir_p( $new_dir ) ) {
            return;
        }
        $old_base = trailingslashit( $old_trash );
        $new_base = trailingslashit( $new_dir );
        $files_migrated = 0;
        // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.rename_rename, WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- One-time migration; direct PHP filesystem calls with error suppression for compatibility.
        $entries = @scandir( $old_trash );
        if ( !is_array( $entries ) ) {
            return;
        }
        foreach ( $entries as $entry ) {
            if ( '.' === $entry || '..' === $entry ) {
                continue;
            }
            $old_path = $old_base . $entry;
            if ( !is_file( $old_path ) ) {
                continue;
            }
            $new_path = $new_base . $entry;
            if ( @rename( $old_path, $new_path ) ) {
                ++$files_migrated;
            } elseif ( @copy( $old_path, $new_path ) && @unlink( $old_path ) ) {
                ++$files_migrated;
            }
        }
        // Remove empty old trash directory (and parent if empty).
        $old_images_dir = wp_normalize_path( $old_trash );
        if ( is_dir( $old_images_dir ) ) {
            $remaining = array_diff( (array) @scandir( $old_images_dir ), array('.', '..') );
            if ( empty( $remaining ) ) {
                @rmdir( $old_images_dir );
                $old_parent = dirname( $old_images_dir );
                if ( is_dir( $old_parent ) ) {
                    $parent_remaining = array_diff( (array) @scandir( $old_parent ), array('.', '..') );
                    if ( empty( $parent_remaining ) ) {
                        @rmdir( $old_parent );
                    }
                }
            }
        }
        // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.rename_rename, WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
        // Update post meta: replace old base path with new base path for both current and legacy keys.
        global $wpdb;
        $meta_keys = array('_pt_wuclean_trash_path', '_wuclean_trash_path');
        $old_like = $wpdb->esc_like( $old_base ) . '%';
        $post_meta = $wpdb->postmeta;
        $post_meta_escaped = esc_sql( $post_meta );
        foreach ( $meta_keys as $meta_key ) {
            $results = $wpdb->get_results( $wpdb->prepare( 
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $post_meta_escaped from esc_sql
                "SELECT post_id, meta_value FROM {$post_meta_escaped} WHERE meta_key = %s AND meta_value LIKE %s",
                $meta_key,
                $old_like
             ), ARRAY_A );
            if ( is_array( $results ) ) {
                foreach ( $results as $row ) {
                    $old_val = ( isset( $row['meta_value'] ) ? $row['meta_value'] : '' );
                    if ( '' === $old_val || 0 !== strpos( $old_val, $old_base ) ) {
                        continue;
                    }
                    $new_val = $new_base . substr( $old_val, strlen( $old_base ) );
                    update_post_meta( (int) $row['post_id'], $meta_key, $new_val );
                }
            }
        }
        update_option( 'pt_wuclean_trash_migrated_1_0_6', 'yes', false );
    }

}
if ( !function_exists( 'pt_wuclean_migrate_trash_from_content_to_uploads' ) ) {
    /**
     * Public entry point: run legacy trash migration (e.g. register_activation_hook).
     *
     * No capability check: activation may run without a logged-in user context.
     *
     * @return void
     */
    function pt_wuclean_migrate_trash_from_content_to_uploads() {
        pt_wuclean_migrate_trash_from_content_to_uploads_do_work();
    }

}
if ( !function_exists( 'pt_wuclean_migrate_trash_from_content_to_uploads_maybe_admin' ) ) {
    /**
     * Run legacy trash migration on admin load only, with capability check (reviewer-friendly).
     *
     * Covers Freemius/zip updates where activation may not run; avoids filesystem work on front-end requests.
     *
     * @return void
     */
    function pt_wuclean_migrate_trash_from_content_to_uploads_maybe_admin() {
        if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
            return;
        }
        pt_wuclean_migrate_trash_from_content_to_uploads_do_work();
    }

}
add_action( 'admin_init', 'pt_wuclean_migrate_trash_from_content_to_uploads_maybe_admin', 1 );
if ( !function_exists( 'pt_wuclean_migrate_image_settings_remove_legacy_batch_size_do_work' ) ) {
    /**
     * One-time: drop legacy `batch_size` from image settings option.
     *
     * Older releases saved a UI batch size in `pt_wuclean_image_settings`; the image scan engine now uses a
     * resolver default (25) plus the `pt_wuclean_image_scan_batch_size` filter. Persisted values caused confusion
     * (e.g. 100) after the setting was removed from the admin form.
     *
     * @return void
     */
    function pt_wuclean_migrate_image_settings_remove_legacy_batch_size_do_work() {
        if ( get_option( 'pt_wuclean_image_settings_legacy_batch_size_removed', '' ) === 'yes' ) {
            return;
        }
        $settings = get_option( 'pt_wuclean_image_settings', array() );
        if ( !is_array( $settings ) || !array_key_exists( 'batch_size', $settings ) ) {
            update_option( 'pt_wuclean_image_settings_legacy_batch_size_removed', 'yes', false );
            return;
        }
        unset($settings['batch_size']);
        update_option( 'pt_wuclean_image_settings', $settings, false );
        update_option( 'pt_wuclean_image_settings_legacy_batch_size_removed', 'yes', false );
    }

}
if ( !function_exists( 'pt_wuclean_migrate_image_settings_remove_legacy_batch_size' ) ) {
    /**
     * Public entry point: legacy image batch_size cleanup (e.g. register_activation_hook).
     *
     * @return void
     */
    function pt_wuclean_migrate_image_settings_remove_legacy_batch_size() {
        pt_wuclean_migrate_image_settings_remove_legacy_batch_size_do_work();
    }

}
if ( !function_exists( 'pt_wuclean_migrate_image_settings_remove_legacy_batch_size_maybe_admin' ) ) {
    /**
     * Run legacy image settings batch_size cleanup on admin load (covers updates without activation).
     *
     * @return void
     */
    function pt_wuclean_migrate_image_settings_remove_legacy_batch_size_maybe_admin() {
        if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
            return;
        }
        pt_wuclean_migrate_image_settings_remove_legacy_batch_size_do_work();
    }

}
add_action( 'admin_init', 'pt_wuclean_migrate_image_settings_remove_legacy_batch_size_maybe_admin', 1 );
// Ensure DB tables exist even if activation hook was skipped (defensive). Only if defined (legacy guard must match hook).
if ( function_exists( 'pt_wuclean_install_db_tables' ) ) {
    add_action( 'plugins_loaded', 'pt_wuclean_install_db_tables', 5 );
}
// Backfill attachment-path index in the background (safe for large sites).
add_action( 'plugins_loaded', function () {
    if ( !wp_next_scheduled( 'pt_wuclean_attachment_index_backfill' ) ) {
        if ( get_option( 'pt_wuclean_attachment_index_complete', '' ) !== 'yes' ) {
            wp_schedule_single_event( time() + 30, 'pt_wuclean_attachment_index_backfill' );
        }
    }
}, 6 );
if ( !function_exists( 'pt_wuclean_email_header_bridge' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Bridge custom email header hook to WooCommerce email header.
     *
     * @param string        $email_heading Email heading.
     * @param WC_Email|null $email         Email object.
     */
    function pt_wuclean_email_header_bridge(  $email_heading, $email = null  ) {
        if ( function_exists( 'woocommerce_email_header' ) ) {
            woocommerce_email_header( $email_heading, $email );
            return;
        }
        if ( function_exists( 'WC' ) ) {
            $mailer = WC()->mailer();
            if ( $mailer && method_exists( $mailer, 'email_header' ) ) {
                $mailer->email_header( $email_heading, $email );
            }
        }
    }

}
add_action(
    'wuclean_woocommerce_email_header',
    'pt_wuclean_email_header_bridge',
    10,
    2
);
if ( !function_exists( 'pt_wuclean_email_footer_bridge' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Bridge custom email footer hook to WooCommerce email footer.
     *
     * @param WC_Email|null $email Email object.
     */
    function pt_wuclean_email_footer_bridge(  $email = null  ) {
        if ( function_exists( 'woocommerce_email_footer' ) ) {
            woocommerce_email_footer( $email );
            return;
        }
        if ( function_exists( 'WC' ) ) {
            $mailer = WC()->mailer();
            if ( $mailer && method_exists( $mailer, 'email_footer' ) ) {
                $mailer->email_footer( $email );
            }
        }
    }

}
add_action(
    'wuclean_woocommerce_email_footer',
    'pt_wuclean_email_footer_bridge',
    10,
    1
);
if ( !function_exists( 'pt_wuclean_apply_wc_email_footer_text' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Bridge custom footer text filter to WooCommerce's default footer text.
     *
     * @param string $text Original footer text.
     * @return string Filtered footer text.
     */
    function pt_wuclean_apply_wc_email_footer_text(  $text  ) {
        // Only apply WooCommerce filter if WooCommerce is fully loaded and translations are ready
        // This prevents early translation loading warnings (WordPress 6.7+).
        if ( !did_action( 'init' ) || !class_exists( 'WooCommerce' ) ) {
            return $text;
        }
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- 'woocommerce_email_footer_text' is a WooCommerce core hook
        return apply_filters( 'woocommerce_email_footer_text', $text );
    }

}
add_filter(
    'wuclean_woocommerce_email_footer_text',
    'pt_wuclean_apply_wc_email_footer_text',
    10,
    1
);
if ( !function_exists( 'pt_wuclean_check_woocommerce_dependency' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Check if WooCommerce is active and show admin notice if not.
     *
     * @return bool True if WooCommerce is active, false otherwise.
     */
    function pt_wuclean_check_woocommerce_dependency() {
        if ( !class_exists( 'WooCommerce' ) ) {
            add_action( 'admin_notices', function () {
                echo '<div class="notice notice-error"><p>';
                echo '<strong>' . esc_html__( 'Wuclean', 'wuclean-database-cleaner-for-woocommerce' ) . ':</strong> ';
                echo esc_html__( 'This plugin requires WooCommerce to be installed and activated.', 'wuclean-database-cleaner-for-woocommerce' );
                echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?s=woocommerce&tab=search&type=term' ) ) . '">';
                echo esc_html__( 'Install WooCommerce', 'wuclean-database-cleaner-for-woocommerce' );
                echo '</a>';
                echo '</p></div>';
            } );
            return false;
        }
        return true;
    }

}
add_action( 'admin_init', 'pt_wuclean_check_woocommerce_dependency' );
if ( !function_exists( 'pt_wuclean_check_hpos_requirement' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Check if WooCommerce HPOS is enabled and show admin notice on order pages only.
     *
     * @return void
     */
    function pt_wuclean_check_hpos_requirement() {
        // Only check if WooCommerce is active.
        if ( !class_exists( 'WooCommerce' ) ) {
            return;
        }
        // Only show notice on Wuclean Orders page
        // Verify nonce - required for security (CSRF protection).
        if ( isset( $_GET['page'] ) ) {
            if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wuclean-admin-page' ) ) {
                $current_page = sanitize_text_field( wp_unslash( $_GET['page'] ) );
            } else {
                // Nonce verification failed - default to empty for security.
                $current_page = '';
            }
        } else {
            $current_page = '';
        }
        if ( 'wuclean-orders' !== $current_page ) {
            return;
        }
        // Check if HPOS is enabled - use plugins_loaded to ensure WooCommerce is fully loaded.
        add_action( 'admin_notices', function () {
            if ( !pt_wuclean_is_hpos_enabled() ) {
                echo '<div class="notice notice-error"><p>';
                echo '<strong>' . esc_html__( 'Wuclean Orders Module', 'wuclean-database-cleaner-for-woocommerce' ) . ':</strong> ';
                echo esc_html__( 'Order cleanup requires WooCommerce High-Performance Order Storage (HPOS) to be enabled.', 'wuclean-database-cleaner-for-woocommerce' );
                echo ' ';
                echo esc_html__( 'Please enable HPOS in', 'wuclean-database-cleaner-for-woocommerce' ) . ' ';
                echo '<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced&section=features' ) ) . '" target="_blank" rel="noopener noreferrer">';
                echo esc_html__( 'WooCommerce → Settings → Advanced → Features', 'wuclean-database-cleaner-for-woocommerce' );
                echo '</a>';
                echo ' ';
                echo esc_html__( 'and enable "High-Performance Order Storage". Other Wuclean modules (Products, Images, Users) work without HPOS.', 'wuclean-database-cleaner-for-woocommerce' );
                echo '</p></div>';
            }
        } );
    }

}
add_action( 'admin_init', 'pt_wuclean_check_hpos_requirement' );
if ( !function_exists( 'pt_wuclean_process_product_search_batch_cron_handler' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Backward-compatible cron hook handler (no longer used).
     *
     * Some older builds may have scheduled the `pt_wuclean_process_product_search_batch` hook.
     * Product search batching is now AJAX-driven, so this handler just clears any lingering cron.
     *
     * @return void
     */
    function pt_wuclean_process_product_search_batch_cron_handler() {
        wp_clear_scheduled_hook( 'pt_wuclean_process_product_search_batch' );
    }

}
// Declare HPOS compatibility.
add_action( 'before_woocommerce_init', function () {
    if ( class_exists( '\\Automattic\\WooCommerce\\Utilities\\FeaturesUtil' ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
    }
} );
if ( !function_exists( 'pt_wuclean_is_hpos_enabled' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Check if WooCommerce HPOS (High-Performance Order Storage) is enabled as PRIMARY storage.
     *
     * This checks if HPOS is AUTHORITATIVE (the primary data source), not just enabled.
     * During migration or compatibility mode, HPOS tables may exist but wp_posts is still primary.
     *
     * @return bool True if HPOS is authoritative (primary storage), false otherwise.
     */
    function pt_wuclean_is_hpos_enabled() {
        // Ensure WooCommerce is loaded before accessing WooCommerce options
        // This prevents early translation loading warnings (WordPress 6.7+).
        if ( !did_action( 'init' ) || !class_exists( 'WooCommerce' ) ) {
            return false;
        }
        // Check if HPOS is set as the authoritative (primary) data source.
        // This is the WooCommerce-recommended way to check.
        $is_hpos_authoritative = 'yes' === get_option( 'woocommerce_custom_orders_table_enabled', 'no' );
        if ( !$is_hpos_authoritative ) {
            return false;
            // HPOS not primary storage.
        }
        // Verify HPOS tables actually exist (safety check).
        global $wpdb;
        $orders_table = $wpdb->prefix . 'wc_orders';
        $orders_meta_table = $wpdb->prefix . 'wc_orders_meta';
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $tables_exist = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $orders_table ) ) === $orders_table && $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $orders_meta_table ) ) === $orders_meta_table;
        // Both conditions must be true: HPOS is authoritative AND tables exist.
        return $is_hpos_authoritative && $tables_exist;
    }

}
if ( !function_exists( 'pt_wuclean_get_common_script_strings' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Provide localized strings for shared admin JavaScript.
     *
     * @return array<string, string> Map of script handle keys to translated strings.
     */
    function pt_wuclean_get_common_script_strings() {
        /* translators: %d: number of seconds remaining */
        $redirect_countdown_singular = __( 'Redirecting in %d second...', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: %d: number of seconds remaining */
        $redirect_countdown_plural = __( 'Redirecting in %d seconds...', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: %s: error message */
        $error_processing_batch_with_message = __( 'Error processing batch: %s', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: 1: processed count, 2: total items, 3: issues found, 4: completed batches, 5: total batches */
        $processed_items_status = __( 'Processed %1$d / %2$d items. Issues: %3$d (%4$d/%5$d batches)', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: 1: orphaned meta count, 2: orphaned variations count */
        $found_orphaned_meta_status = __( 'Found %1$d orphaned meta records and %2$d orphaned variations', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: %d: number of unused attributes */
        $found_unused_attributes_status = __( 'Found %d unused attributes', 'wuclean-database-cleaner-for-woocommerce' );
        /* translators: %d: number of selected items */
        $selected_count_text = __( '%d selected', 'wuclean-database-cleaner-for-woocommerce' );
        return array(
            'searchNow'                       => __( 'Search Now', 'wuclean-database-cleaner-for-woocommerce' ),
            'starting'                        => __( 'Starting...', 'wuclean-database-cleaner-for-woocommerce' ),
            'stopScan'                        => __( 'Stop Scan', 'wuclean-database-cleaner-for-woocommerce' ),
            'initializingSearch'              => __( 'Initializing search...', 'wuclean-database-cleaner-for-woocommerce' ),
            'errorStartingSearch'             => __( 'Error starting search.', 'wuclean-database-cleaner-for-woocommerce' ),
            'searchStoppedByUser'             => __( 'Search stopped by user.', 'wuclean-database-cleaner-for-woocommerce' ),
            'processing'                      => __( 'Processing...', 'wuclean-database-cleaner-for-woocommerce' ),
            'searchCompletedRedirecting'      => __( 'Search completed! Redirecting...', 'wuclean-database-cleaner-for-woocommerce' ),
            'searchCompletedNoResults'        => __( 'Search completed! No results found.', 'wuclean-database-cleaner-for-woocommerce' ),
            'noResultsFound'                  => __( 'No results found.', 'wuclean-database-cleaner-for-woocommerce' ),
            'redirectCountdownSingular'       => $redirect_countdown_singular,
            'redirectCountdownPlural'         => $redirect_countdown_plural,
            'errorProcessingBatchWithMessage' => $error_processing_batch_with_message,
            'errorProcessingBatch'            => __( 'Error processing batch.', 'wuclean-database-cleaner-for-woocommerce' ),
            'unknownError'                    => __( 'Unknown error', 'wuclean-database-cleaner-for-woocommerce' ),
            'searchTimedOut'                  => __( 'Search timed out after 5 minutes. Please try again.', 'wuclean-database-cleaner-for-woocommerce' ),
            'stopping'                        => __( 'Stopping...', 'wuclean-database-cleaner-for-woocommerce' ),
            'errorStoppingSearch'             => __( 'Error stopping search.', 'wuclean-database-cleaner-for-woocommerce' ),
            'processedItemsStatus'            => $processed_items_status,
            'foundOrphanedMetaStatus'         => $found_orphaned_meta_status,
            'foundUnusedAttributesStatus'     => $found_unused_attributes_status,
            'selectedCountText'               => $selected_count_text,
            'running'                         => __( 'Running...', 'wuclean-database-cleaner-for-woocommerce' ),
            'runSingleBatch'                  => __( 'Run Single Batch', 'wuclean-database-cleaner-for-woocommerce' ),
            'consoleDetails'                  => __( 'Please check the console for details.', 'wuclean-database-cleaner-for-woocommerce' ),
            'errorCheckingProgress'           => __( 'Error checking progress.', 'wuclean-database-cleaner-for-woocommerce' ),
        );
    }

}
/**
 * Main plugin bootstrap: menu, assets, crons, and dashboard.
 */
if ( !class_exists( 'pt_wuclean_Plugin' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    // phpcs:ignore PEAR.NamingConventions.ValidClassName.StartWithCapital, PEAR.NamingConventions.ValidClassName.Invalid, Squiz.Commenting.ClassComment.Missing -- Legacy class name; docblock directly above.
    class pt_wuclean_Plugin {
        const USER_PROTECT_META = '_pt_wuclean_protect_user';

        const USER_PROTECT_ORDERS_META = '_pt_wuclean_protect_orders';

        const ORDER_PROTECT_META = '_pt_wuclean_protected';

        const ORDER_FLAGGED_TIMESTAMP_META = '_pt_wuclean_flagged_at';

        const ORDER_MARKED_FOR_TRASH_META = '_pt_wuclean_marked_for_trash';

        const USER_FLAGGED_TIMESTAMP_META = '_pt_wuclean_flagged_at';

        const ORDER_LOG_OPTION = 'pt_wuclean_order_logs';

        const ORDER_LOG_LIMIT_OPTION = 'pt_wuclean_order_log_limit';

        const DEFAULT_ORDER_LOG_LIMIT = 100;

        const USER_LOG_OPTION = 'pt_wuclean_user_logs';

        const USER_LOG_LIMIT_OPTION = 'pt_wuclean_user_log_limit';

        const DEFAULT_USER_LOG_LIMIT = 5000;

        const ADMIN_EMAIL_OPTION = 'admin_email';

        const SETTINGS_OPTION = 'pt_wuclean_settings';

        /**
         * Bootstrap: include files, hooks, and crons.
         */
        public function __construct() {
            // Include all plugin files AFTER text domain is loaded.
            $this->include_plugin_files();
            add_action( 'admin_menu', array($this, 'register_admin_menu') );
            add_action( 'admin_enqueue_scripts', array($this, 'maybe_enqueue_admin_assets') );
            add_action( 'show_user_profile', 'pt_wuclean_add_user_protect_section' );
            add_action( 'edit_user_profile', 'pt_wuclean_add_user_protect_section' );
            add_action( 'personal_options_update', 'pt_wuclean_save_user_protect_section' );
            add_action( 'edit_user_profile_update', 'pt_wuclean_save_user_protect_section' );
            // Cron events.
            add_action( 'pt_wuclean_flag_users_event', 'pt_wuclean_flag_users_for_cleanup' );
            if ( function_exists( 'pt_wuclean_process_flagged_users' ) ) {
                add_action( 'pt_wuclean_process_flagged_users_event', 'pt_wuclean_process_flagged_users' );
            }
            add_action( 'pt_wuclean_process_product_search_batch', 'pt_wuclean_process_product_search_batch_cron_handler' );
            // Register license deactivation hook to reset premium settings.
            pt_wuclean_fs()->add_action( 'after_license_deactivation', 'pt_wuclean_reset_premium_settings_on_downgrade' );
            // Register license change hook to detect downgrades during sync (remote expiration/cancellation).
            pt_wuclean_fs()->add_action(
                'after_license_change',
                'pt_wuclean_reset_premium_settings_on_license_change',
                10,
                2
            );
            add_action( 'init', array($this, 'clear_disabled_product_crons') );
            // Clear any existing product cleanup crons immediately if disabled.
            add_action( 'plugins_loaded', array($this, 'clear_disabled_product_crons') );
            // Clear product cleanup crons immediately on plugin load if disabled.
            add_action( 'init', array($this, 'force_clear_disabled_product_crons'), 1 );
            // Add admin action for CSV export (product issues).
            // Product / orphaned AJAX actions: registered in products.php after include_plugin_files().
            add_action( 'admin_action_pt_export_product_issues', 'pt_wuclean_handle_export_product_issues' );
        }

        /**
         * Include all plugin files AFTER text domain is loaded
         */
        private function include_plugin_files() {
            // Include order-related functionality.
            require_once plugin_dir_path( __FILE__ ) . 'orders.php';
            // Include scheduled actions functionality.
            require_once plugin_dir_path( __FILE__ ) . 'scheduled.php';
            // Include other cleanup functionality.
            require_once plugin_dir_path( __FILE__ ) . 'other.php';
            // Include products functionality.
            require_once plugin_dir_path( __FILE__ ) . 'products.php';
            // Include users functionality.
            require_once plugin_dir_path( __FILE__ ) . 'users.php';
            // Include images functionality.
            require_once plugin_dir_path( __FILE__ ) . 'images.php';
        }

        /**
         * Register Wuclean admin menu and submenus.
         */
        public function register_admin_menu() {
            $capability = pt_wuclean_get_required_capability();
            // Main menu.
            add_menu_page(
                __( 'Wuclean', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Wuclean', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-database-cleaner-for-woocommerce',
                array($this, 'render_admin_page'),
                'dashicons-trash',
                76
            );
            // Products submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Products', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Products', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-products',
                'pt_wuclean_render_products_page'
            );
            // Orders submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Orders', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Orders', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-orders',
                'pt_wuclean_render_orders_page'
            );
            // Users submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Customers', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Customers', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-users',
                'pt_wuclean_render_users_page'
            );
            // Images submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Images', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Images', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-images',
                'pt_wuclean_render_images_page'
            );
            // Scheduled Actions submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Scheduled Actions', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Scheduled Actions', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-scheduled-actions',
                'pt_wuclean_render_scheduled_actions_page'
            );
            // Other submenu.
            add_submenu_page(
                'wuclean-database-cleaner-for-woocommerce',
                __( 'Other', 'wuclean-database-cleaner-for-woocommerce' ),
                __( 'Other', 'wuclean-database-cleaner-for-woocommerce' ),
                $capability,
                'wuclean-other',
                'pt_wuclean_render_other_page'
            );
        }

        /**
         * Enqueue admin styles and scripts on Wuclean pages.
         */
        public function maybe_enqueue_admin_assets() {
            $screen = get_current_screen();
            if ( $screen && isset( $screen->id ) && is_string( $screen->id ) && false !== strpos( $screen->id, 'wuclean-database-cleaner-for-woocommerce' ) ) {
                wp_enqueue_style(
                    'pt-wuclean-admin',
                    plugins_url( 'assets/css/admin.css', __FILE__ ),
                    array(),
                    '1.0.5'
                );
                // Ensure jQuery and common.js are enqueued for all wuclean pages.
                wp_enqueue_script( 'jquery' );
                wp_enqueue_script(
                    'pt-wuclean-common',
                    plugins_url( 'assets/js/common.js', __FILE__ ),
                    array('jquery'),
                    '1.0.0',
                    true
                );
                wp_localize_script( 'pt-wuclean-common', 'pt_wuclean_common_vars', pt_wuclean_get_common_script_strings() );
            }
            wp_enqueue_style(
                'pt-wuclean-admin-menu',
                plugins_url( 'assets/css/admin.css', __FILE__ ),
                array(),
                '1.0.5'
            );
        }

        /**
         * Render main Wuclean dashboard (welcome, HPOS alert, auto-delete warnings, module cards).
         */
        public function render_admin_page() {
            if ( !current_user_can( pt_wuclean_get_required_capability() ) ) {
                wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wuclean-database-cleaner-for-woocommerce' ) );
            }
            $settings = get_option( self::SETTINGS_OPTION, array() );
            /* translators: Link label for opening a related admin screen from the dashboard. */
            $wuclean_dashboard_view_label = __( '[View]', 'wuclean-database-cleaner-for-woocommerce' );
            echo '<div class="wrap">';
            echo '<h1>📊 Wuclean Dashboard</h1>';
            // 1. HPOS: order tools unavailable when WooCommerce uses legacy order storage.
            $hpos_enabled = class_exists( 'Automattic\\WooCommerce\\Utilities\\OrderUtil' ) && method_exists( 'Automattic\\WooCommerce\\Utilities\\OrderUtil', 'custom_orders_table_usage_is_enabled' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
            if ( !$hpos_enabled ) {
                $hpos_settings_url = admin_url( 'admin.php?page=wc-settings&tab=advanced&section=features' );
                echo '<div class="notice notice-warning" style="max-width: 1600px; margin: 20px 0;"><p><strong>' . esc_html__( 'Orders module unavailable', 'wuclean-database-cleaner-for-woocommerce' ) . '</strong></p>';
                echo '<p>' . esc_html__( 'Order cleanup tools in this plugin require WooCommerce High-Performance order storage (HPOS). Other Wuclean features are unchanged.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
                echo '<p>' . esc_html__( 'When HPOS is enabled for orders, these order tools will become available again without changing Wuclean settings.', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
                echo '<p><a href="' . esc_url( $hpos_settings_url ) . '" class="button button-secondary">' . esc_html__( 'Open WooCommerce order storage settings', 'wuclean-database-cleaner-for-woocommerce' ) . '</a></p></div>';
            }
            // 2. Active Auto-Delete Warnings Banner.
            // Only show auto-deletes that will ACTUALLY RUN (master enabled + scheduled).
            $auto_delete_settings = array();
            // Check product auto-deletes (requires master + scheduled).
            $product_master_enabled = !empty( $settings['enable_product_cleanup_master'] );
            $product_scheduled = ($settings['product_scan_frequency'] ?? 'unscheduled') !== 'unscheduled';
            if ( $product_master_enabled && $product_scheduled ) {
                if ( !empty( $settings['enable_delete_orphaned_data'] ) ) {
                    $auto_delete_settings[] = esc_html__( 'Products: Delete orphaned data (100 items/scheduled run)', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_delete_trashed_products'] ) ) {
                    $product_issues_enabled = !empty( $settings['enable_product_cleanup'] );
                    if ( $product_issues_enabled ) {
                        $auto_delete_settings[] = esc_html__( 'Products: Delete trashed products after 24 hours (2-3/cycle/scheduled run)', 'wuclean-database-cleaner-for-woocommerce' );
                    } else {
                        $auto_delete_settings[] = esc_html__( 'Products: Delete trashed products (10 per scheduled run, 24+ hours in trash)', 'wuclean-database-cleaner-for-woocommerce' );
                    }
                }
            }
            // Check order auto-deletes (requires master + not unscheduled).
            $order_master_enabled = !empty( $settings['enable_order_cleanup'] );
            $order_scheduled = ($settings['order_cleanup_frequency'] ?? 'unscheduled') !== 'unscheduled';
            if ( $order_master_enabled && $order_scheduled ) {
                if ( !empty( $settings['trash_flagged_orders_after_24h'] ) ) {
                    $auto_delete_settings[] = esc_html__( 'Orders: Delete trashed orders after 24H (max 10 items/scheduled run)', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_detect_orphaned_order_meta'] ) && !empty( $settings['enable_delete_orphaned_order_meta'] ) ) {
                    $auto_delete_settings[] = esc_html__( 'Orders: Delete orphaned order meta after reassignment (scheduled run)', 'wuclean-database-cleaner-for-woocommerce' );
                }
            }
            // Check customer auto-deletes (requires master + scheduled).
            $user_master_enabled = !empty( $settings['enable_user_cleanup'] );
            $user_scheduled = ($settings['user_cleanup_frequency'] ?? 'daily') !== 'unscheduled';
            $delete_flagged_days = intval( $settings['user_delete_flagged_days'] ?? 0 );
            if ( $user_master_enabled && $user_scheduled ) {
                if ( $delete_flagged_days > 0 ) {
                    /* translators: %d: number of days before auto-deletion. */
                    $auto_delete_settings[] = sprintf( esc_html__( 'Customers: Delete flagged customers after %d days (5-20 users/scheduled run)', 'wuclean-database-cleaner-for-woocommerce' ), $delete_flagged_days );
                }
                if ( !empty( $settings['user_detect_orphaned_data'] ) && !empty( $settings['user_delete_orphaned_data'] ) ) {
                    $auto_delete_settings[] = esc_html__( 'Customers: Delete orphaned customer data after reassignment (scheduled run)', 'wuclean-database-cleaner-for-woocommerce' );
                }
            }
            // Check other auto-deletes (requires master + always daily).
            $other_master_enabled = !empty( $settings['enable_other_cleanup_master'] );
            if ( $other_master_enabled ) {
                $other_auto_deletes = array();
                if ( !empty( $settings['enable_orphaned_usermeta_cleanup'] ) ) {
                    $other_auto_deletes[] = esc_html__( 'orphaned user meta', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_orphaned_postmeta_cleanup'] ) ) {
                    $other_auto_deletes[] = esc_html__( 'orphaned post meta', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_orphaned_commentmeta_cleanup'] ) ) {
                    $other_auto_deletes[] = esc_html__( 'orphaned comment meta', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_orphaned_term_relationships_cleanup'] ) ) {
                    $other_auto_deletes[] = esc_html__( 'orphaned term relationships', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $settings['enable_expired_transients_cleanup'] ) ) {
                    $other_auto_deletes[] = esc_html__( 'expired transients', 'wuclean-database-cleaner-for-woocommerce' );
                }
                if ( !empty( $other_auto_deletes ) ) {
                    $auto_delete_settings[] = esc_html__( 'Other: Delete/scheduled run - ', 'wuclean-database-cleaner-for-woocommerce' ) . implode( ', ', $other_auto_deletes );
                }
            }
            if ( !empty( $auto_delete_settings ) ) {
                echo '<div style="max-width: 1600px;background: #fff3cd; border-left: 4px solid #ff9800; padding: 15px; margin: 20px 0;">';
                echo '<h3 style="margin: 0 0 10px 0; color: #ff9800;">⚠️ ' . esc_html__( 'ACTIVE AUTO-DELETE SETTINGS', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
                echo '<p style="margin: 0 0 10px 0;">' . esc_html__( 'The following automated deletions are enabled:', 'wuclean-database-cleaner-for-woocommerce' ) . '</p>';
                echo '<ul style="margin: 0; padding-left: 20px;">';
                foreach ( $auto_delete_settings as $setting ) {
                    echo '<li>' . wp_kses_post( $setting ) . '</li>';
                }
                echo '</ul>';
                echo '</div>';
            }
            // 3. Items Requiring Attention Panel
            $attention_items = array();
            // Orders.
            $flagged_orders_count = 0;
            $_t = get_transient( 'pt_wuclean_orphaned_order_data' );
            $orphaned_order_data = ( false !== $_t ? $_t : get_option( 'pt_wuclean_orphaned_order_data', array() ) );
            $orphaned_orders_count = ( !empty( $orphaned_order_data ) ? count( $orphaned_order_data ) : 0 );
            // Get flagged orders count using direct query for performance.
            // Exclude protected orders to match the Flagged Orders tab display.
            global $wpdb;
            if ( $hpos_enabled ) {
                $orders_table = $wpdb->prefix . 'wc_orders';
                $orders_meta_table = $wpdb->prefix . 'wc_orders_meta';
                $usermeta_table = $wpdb->prefix . 'usermeta';
                $orders_table_escaped = esc_sql( $orders_table );
                $orders_meta_table_escaped = esc_sql( $orders_meta_table );
                $usermeta_table_escaped = esc_sql( $usermeta_table );
                // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names from esc_sql(); cannot be placeholders.
                // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $flagged_orders_count = $wpdb->get_var( "SELECT COUNT(DISTINCT om_flagged.order_id) \n                FROM {$orders_meta_table_escaped} om_flagged\n                INNER JOIN {$orders_table_escaped} o ON om_flagged.order_id = o.id\n                LEFT JOIN {$orders_meta_table_escaped} om_protected ON om_flagged.order_id = om_protected.order_id AND om_protected.meta_key = '_pt_wuclean_protected' AND om_protected.meta_value = '1'\n                LEFT JOIN {$usermeta_table_escaped} um_protected ON o.customer_id = um_protected.user_id AND um_protected.meta_key = '_pt_wuclean_protect_orders' AND um_protected.meta_value = '1' AND o.customer_id > 0\n                WHERE om_flagged.meta_key = '_pt_wuclean_flagged_at'\n                AND o.status != 'trash'\n                AND om_protected.order_id IS NULL\n                AND um_protected.user_id IS NULL" );
                // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $flagged_orders_count = intval( $flagged_orders_count );
            }
            // Products.
            $_t = get_transient( 'pt_wuclean_product_issues' );
            $product_issues = ( false !== $_t ? $_t : get_option( 'pt_wuclean_product_issues', array() ) );
            $product_issues_count = ( !empty( $product_issues ) ? count( $product_issues ) : 0 );
            $_t = get_transient( 'pt_wuclean_orphaned_data' );
            $orphaned_product_data = ( false !== $_t ? $_t : get_option( 'pt_wuclean_orphaned_data', array() ) );
            $orphaned_products_count = ( !empty( $orphaned_product_data ) ? count( $orphaned_product_data ) : 0 );
            $_t = get_transient( 'pt_wuclean_unused_attributes' );
            $unused_attributes = ( false !== $_t ? $_t : get_option( 'pt_wuclean_unused_attributes', array() ) );
            $unused_attributes_count = ( !empty( $unused_attributes ) ? count( $unused_attributes ) : 0 );
            // Customers.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $flagged_users_count = $wpdb->get_var( "SELECT COUNT(DISTINCT user_id) \n            FROM {$wpdb->usermeta} \n            WHERE meta_key = '_pt_wuclean_flagged_at'" );
            $flagged_users_count = intval( $flagged_users_count );
            $_t = get_transient( 'pt_wuclean_orphaned_customer_data' );
            $orphaned_customer_data = ( false !== $_t ? $_t : get_option( 'pt_wuclean_orphaned_customer_data', array() ) );
            $orphaned_customers_count = ( !empty( $orphaned_customer_data['items'] ) ? count( $orphaned_customer_data['items'] ) : 0 );
            // Images.
            $image_latest_completed = pt_wuclean_image_scan_db_get_latest_run( 'completed' );
            $image_latest_run_id = ( is_array( $image_latest_completed ) && !empty( $image_latest_completed['id'] ) ? absint( $image_latest_completed['id'] ) : 0 );
            $unused_images_count = 0;
            if ( $image_latest_run_id > 0 ) {
                // Match the Results tab combined-results view with default optional filters (no search; siblings of used images, thumbnails, and originals of used scaled images hidden by default).
                $unused_images_count = pt_wuclean_image_scan_db_count_filtered_results( $image_latest_run_id, '', array(
                    'hide_orphaned'            => 0,
                    'show_orphans_only'        => 0,
                    'hide_safe_siblings'       => 1,
                    'hide_jpg'                 => 0,
                    'hide_png'                 => 0,
                    'hide_webp'                => 0,
                    'hide_avif'                => 0,
                    'hide_thumbnails'          => 1,
                    'hide_originals_of_scaled' => 1,
                ) );
            }
            $trashed_images = get_posts( array(
                'post_type'      => 'attachment',
                'post_mime_type' => 'image',
                'post_status'    => 'trash',
                'posts_per_page' => -1,
                'fields'         => 'ids',
            ) );
            $trashed_images_count = ( is_array( $trashed_images ) ? count( $trashed_images ) : 0 );
            $protected_images = pt_wuclean_get_protected_images();
            $protected_images_count = ( !empty( $protected_images ) ? count( $protected_images ) : 0 );
            // Get trashed orders count.
            $trashed_orders_count = 0;
            if ( $hpos_enabled ) {
                $orders_table = $wpdb->prefix . 'wc_orders';
                $orders_table_escaped = esc_sql( $orders_table );
                // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table from esc_sql(); cannot be placeholder.
                // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $trashed_orders_count = $wpdb->get_var( "SELECT COUNT(*) \n                FROM {$orders_table_escaped}\n                WHERE status = 'trash'" );
                // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $trashed_orders_count = intval( $trashed_orders_count );
            }
            // Build attention items array.
            if ( $flagged_orders_count > 0 ) {
                $attention_items['orders'][] = array(
                    'count' => $flagged_orders_count,
                    'label' => esc_html__( 'Flagged Orders', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-orders',
                        'tab'      => 'flagged',
                        '_wpnonce' => wp_create_nonce( 'wuclean-orders-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $trashed_orders_count > 0 ) {
                $attention_items['orders'][] = array(
                    'count' => $trashed_orders_count,
                    'label' => esc_html__( 'Trashed Orders', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => admin_url( 'admin.php?page=wc-orders&status=trash' ),
                );
            }
            if ( $orphaned_orders_count > 0 ) {
                $attention_items['orders'][] = array(
                    'count' => $orphaned_orders_count,
                    'label' => esc_html__( 'Orphaned Order Meta', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-orders',
                        'tab'      => 'orphaned_data',
                        '_wpnonce' => wp_create_nonce( 'wuclean-orders-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $product_issues_count > 0 ) {
                $attention_items['products'][] = array(
                    'count' => $product_issues_count,
                    'label' => esc_html__( 'Product Issues', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-products',
                        'tab'      => 'issues',
                        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $orphaned_products_count > 0 ) {
                $attention_items['products'][] = array(
                    'count' => $orphaned_products_count,
                    'label' => esc_html__( 'Orphaned Product Data', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-products',
                        'tab'      => 'orphaned_data',
                        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $unused_attributes_count > 0 ) {
                $attention_items['products'][] = array(
                    'count' => $unused_attributes_count,
                    'label' => esc_html__( 'Unused Attributes', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-products',
                        'tab'      => 'unused_attributes',
                        '_wpnonce' => wp_create_nonce( 'wuclean-products-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $flagged_users_count > 0 ) {
                $attention_items['customers'][] = array(
                    'count' => $flagged_users_count,
                    'label' => esc_html__( 'Flagged Customers', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-users',
                        'tab'      => 'flagged',
                        '_wpnonce' => wp_create_nonce( 'wuclean-users-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            if ( $orphaned_customers_count > 0 ) {
                $attention_items['customers'][] = array(
                    'count' => $orphaned_customers_count,
                    'label' => esc_html__( 'Orphaned Customer Data', 'wuclean-database-cleaner-for-woocommerce' ),
                    'url'   => add_query_arg( array(
                        'page'     => 'wuclean-users',
                        'tab'      => 'orphaned',
                        '_wpnonce' => wp_create_nonce( 'wuclean-users-tab' ),
                    ), admin_url( 'admin.php' ) ),
                );
            }
            // Module Status Grid - 3 columns x 2 rows (matching menu order)
            // Attention items are now integrated into each module card below.
            echo '<div style="max-width: 1600px;display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 30px;">';
            // Row 1, Cell 1: Products Module.
            $product_enabled = ( !empty( $settings['enable_product_cleanup_master'] ) ? $settings['enable_product_cleanup_master'] : 0 );
            $product_schedule = ( isset( $settings['product_scan_frequency'] ) && is_string( $settings['product_scan_frequency'] ) ? $settings['product_scan_frequency'] : 'unscheduled' );
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #9c27b0;">📦 ' . esc_html__( 'Products', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Get protected products count.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            // Show auto-delete warning if enabled.
            if ( $product_enabled && 'unscheduled' !== $product_schedule && !empty( $settings['enable_delete_orphaned_data'] ) ) {
                echo '<div style="margin-bottom: 10px; font-size: 13px;">';
                echo '<span style="color: #ff9800;">⚠️ ' . esc_html__( 'Auto-delete: ON', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
                echo '</div>';
            }
            // Show summary section (attention items only - products don't have protection).
            if ( !empty( $attention_items['products'] ) ) {
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                foreach ( $attention_items['products'] as $item ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $item['count'] ) . '</strong> ' . esc_html( $item['label'] ) . ' → <a href="' . esc_url( $item['url'] ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-products' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            // Row 1, Cell 2: Orders Module.
            $order_enabled = ( !empty( $settings['enable_order_cleanup'] ) ? $settings['enable_order_cleanup'] : 0 );
            $order_schedule = ( isset( $settings['order_cleanup_frequency'] ) && is_string( $settings['order_cleanup_frequency'] ) ? $settings['order_cleanup_frequency'] : 'unscheduled' );
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #ff9800;">🛒 ' . esc_html__( 'Orders', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Show auto-delete warning if enabled.
            if ( $order_enabled && 'unscheduled' !== $order_schedule && !empty( $settings['trash_flagged_orders_after_24h'] ) ) {
                echo '<div style="margin-bottom: 10px; font-size: 13px;">';
                echo '<span style="color: #ff9800;">⚠️ ' . esc_html__( 'Auto-delete: ON', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
                echo '</div>';
            }
            // Show summary section (protection + attention items).
            $protected_orders_count = 0;
            if ( $hpos_enabled ) {
                $orders_meta_table = $wpdb->prefix . 'wc_orders_meta';
                $orders_table = $wpdb->prefix . 'wc_orders';
                $usermeta_table = $wpdb->prefix . 'usermeta';
                $orders_table_escaped = esc_sql( $orders_table );
                $orders_meta_table_escaped = esc_sql( $orders_meta_table );
                $usermeta_table_escaped = esc_sql( $usermeta_table );
                // Count both individually protected and user-level protected orders.
                // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names are escaped with esc_sql() before use.
                // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names from esc_sql(); cannot be placeholders.
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                $protected_orders_count = $wpdb->get_var( "SELECT COUNT(DISTINCT o.id)\n                FROM {$orders_table_escaped} o\n                LEFT JOIN {$orders_meta_table_escaped} om ON o.id = om.order_id AND om.meta_key = '_pt_wuclean_protected' AND om.meta_value = '1'\n                LEFT JOIN {$usermeta_table_escaped} um ON o.customer_id = um.user_id AND um.meta_key = '_pt_wuclean_protect_orders' AND um.meta_value = '1' AND o.customer_id > 0\n                WHERE o.status != 'trash'\n                AND (om.order_id IS NOT NULL OR um.user_id IS NOT NULL)" );
                // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
                $protected_orders_count = intval( $protected_orders_count );
            }
            if ( $protected_orders_count > 0 || !empty( $attention_items['orders'] ) ) {
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                // Protection count.
                if ( $protected_orders_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $protected_orders_count ) . '</strong> ' . esc_html__( 'Protected Orders', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( add_query_arg( array(
                        'page'           => 'wuclean-orders',
                        'tab'            => 'flagged',
                        'show_protected' => '1',
                        '_wpnonce'       => wp_create_nonce( 'wuclean-orders-tab' ),
                    ), admin_url( 'admin.php' ) ) ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                // Attention items.
                if ( !empty( $attention_items['orders'] ) ) {
                    foreach ( $attention_items['orders'] as $item ) {
                        echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                        echo '<strong>' . absint( $item['count'] ) . '</strong> ' . esc_html( $item['label'] ) . ' → <a href="' . esc_url( $item['url'] ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                        echo '</div>';
                    }
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-orders' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            // Row 1, Cell 3: Customers Module.
            $user_enabled = ( !empty( $settings['enable_user_cleanup'] ) ? $settings['enable_user_cleanup'] : 0 );
            $user_schedule = ( isset( $settings['user_cleanup_frequency'] ) && is_string( $settings['user_cleanup_frequency'] ) ? $settings['user_cleanup_frequency'] : 'daily' );
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #673ab7;">👥 ' . esc_html__( 'Customers', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Show auto-delete warning if enabled.
            $delete_flagged_days = intval( $settings['user_delete_flagged_days'] ?? 0 );
            if ( $user_enabled && 'unscheduled' !== $user_schedule && $delete_flagged_days > 0 ) {
                echo '<div style="margin-bottom: 10px; font-size: 13px;">';
                /* translators: %s: number of days before auto-deletion. */
                $days_display = number_format_i18n( absint( $delete_flagged_days ) );
                /* translators: %s: number of days before flagged customers are deleted */
                echo '<span style="color: #ff9800;">⚠️ ' . sprintf( esc_html__( 'Auto-delete: After %s days', 'wuclean-database-cleaner-for-woocommerce' ), esc_html( $days_display ) ) . '</span>';
                echo '</div>';
            }
            // Show summary section (protection + attention items).
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $protected_users_count = intval( $wpdb->get_var( "SELECT COUNT(DISTINCT user_id) \n            FROM {$wpdb->usermeta} \n            WHERE meta_key = '_pt_wuclean_protect_user' AND meta_value = '1'" ) );
            if ( $protected_users_count > 0 || !empty( $attention_items['customers'] ) ) {
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                // Protection count.
                if ( $protected_users_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $protected_users_count ) . '</strong> ' . esc_html__( 'Protected Customers', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( add_query_arg( array(
                        'page'           => 'wuclean-users',
                        'tab'            => 'flagged',
                        'show_protected' => '1',
                        '_wpnonce'       => wp_create_nonce( 'wuclean-users-tab' ),
                    ), admin_url( 'admin.php' ) ) ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                // Attention items.
                if ( !empty( $attention_items['customers'] ) ) {
                    foreach ( $attention_items['customers'] as $item ) {
                        echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                        echo '<strong>' . absint( $item['count'] ) . '</strong> ' . esc_html( $item['label'] ) . ' → <a href="' . esc_url( $item['url'] ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                        echo '</div>';
                    }
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-users' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            // Row 2, Cell 1: Images Module.
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #2196f3;">🖼️ ' . esc_html__( 'Images', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Show summary section: Potentially Unused → Protected → Trashed.
            if ( $unused_images_count > 0 || $protected_images_count > 0 || $trashed_images_count > 0 ) {
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                if ( $unused_images_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $unused_images_count ) . '</strong> ' . esc_html__( 'Potentially Unused Images', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( add_query_arg( array(
                        'page'     => 'wuclean-images',
                        'tab'      => 'results',
                        '_wpnonce' => wp_create_nonce( 'wuclean-images-tab' ),
                    ), admin_url( 'admin.php' ) ) ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $protected_images_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $protected_images_count ) . '</strong> ' . esc_html__( 'Protected Images', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( add_query_arg( array(
                        'page'     => 'wuclean-images',
                        'tab'      => 'protected',
                        '_wpnonce' => wp_create_nonce( 'wuclean-images-tab' ),
                    ), admin_url( 'admin.php' ) ) ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $trashed_images_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $trashed_images_count ) . '</strong> ' . esc_html__( 'Trashed Images', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( add_query_arg( array(
                        'page'     => 'wuclean-images',
                        'tab'      => 'trash',
                        '_wpnonce' => wp_create_nonce( 'wuclean-images-tab' ),
                    ), admin_url( 'admin.php' ) ) ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-images' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            // Row 2, Cell 2: Scheduled Actions Module.
            $scheduled_enabled = ( !empty( $settings['enable_scheduled_actions_cleanup'] ) ? $settings['enable_scheduled_actions_cleanup'] : 0 );
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #4caf50;">⏱️ ' . esc_html__( 'Scheduled Actions', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Show auto-delete info if any action deletion is enabled.
            $delete_completed = !empty( $settings['delete_completed_actions_days'] ) && $settings['delete_completed_actions_days'] > 0;
            $delete_failed = !empty( $settings['delete_failed_actions_days'] ) && $settings['delete_failed_actions_days'] > 0;
            if ( $scheduled_enabled && ($delete_completed || $delete_failed) ) {
                echo '<div style="margin-bottom: 10px; font-size: 13px;">';
                echo '<span style="color: #ff9800;">⚠️ ' . esc_html__( 'Auto-delete: ON', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
                echo '</div>';
            }
            // Summary: Action Scheduler queue + logs (free + pro; hide rows when count is zero).
            $as_failed_count = 0;
            $as_completed_count = 0;
            $as_logs_count = 0;
            $as_logs_size_mb = 0.0;
            if ( function_exists( 'pt_wuclean_get_scheduled_actions_counts' ) ) {
                $as_counts = pt_wuclean_get_scheduled_actions_counts();
                $as_failed_count = ( isset( $as_counts['failed'] ) ? absint( $as_counts['failed'] ) : 0 );
                $as_completed_count = ( isset( $as_counts['completed'] ) ? absint( $as_counts['completed'] ) : 0 );
            }
            if ( function_exists( 'pt_wuclean_get_action_scheduler_db_info' ) ) {
                $as_db_stats = pt_wuclean_get_action_scheduler_db_info();
                if ( is_array( $as_db_stats ) && !empty( $as_db_stats['count'] ) ) {
                    $as_logs_count = absint( $as_db_stats['count'] );
                    $as_logs_size_mb = ( isset( $as_db_stats['size_mb'] ) ? (float) $as_db_stats['size_mb'] : 0.0 );
                }
            }
            if ( $as_failed_count > 0 || $as_completed_count > 0 || $as_logs_count > 0 ) {
                $url_failed = admin_url( 'admin.php?page=wc-status&tab=action-scheduler&status=failed' );
                $url_complete = admin_url( 'admin.php?page=wc-status&tab=action-scheduler&status=complete' );
                $url_as_tab = admin_url( 'admin.php?page=wc-status&tab=action-scheduler' );
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                if ( $as_failed_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $as_failed_count ) . '</strong> ' . esc_html__( 'Failed', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( $url_failed ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $as_completed_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $as_completed_count ) . '</strong> ' . esc_html__( 'Completed', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( $url_complete ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $as_logs_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $as_logs_count ) . '</strong> ';
                    echo esc_html__( 'Action Logs', 'wuclean-database-cleaner-for-woocommerce' );
                    if ( $as_logs_size_mb > 0 ) {
                        echo ' (';
                        printf( 
                            /* translators: %s: formatted size in megabytes (Action Scheduler logs table). */
                            esc_html__( '%s MB', 'wuclean-database-cleaner-for-woocommerce' ),
                            esc_html( number_format_i18n( $as_logs_size_mb, 2 ) )
                         );
                        echo ')';
                    }
                    echo ' → <a href="' . esc_url( $url_as_tab ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-scheduled-actions' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            // Row 2, Cell 3: Other Cleanup Module.
            $other_enabled = ( !empty( $settings['enable_other_cleanup_master'] ) ? $settings['enable_other_cleanup_master'] : 0 );
            pt_wuclean_render_dashboard_module_card_open();
            echo '<h3 style="margin: 0 0 15px 0; color: #00bcd4;">🔧 ' . esc_html__( 'Other Cleanup', 'wuclean-database-cleaner-for-woocommerce' ) . '</h3>';
            // Show auto-delete warning if any cleanup is enabled.
            $has_auto_delete = false;
            if ( $other_enabled ) {
                if ( !empty( $settings['enable_orphaned_usermeta_cleanup'] ) || !empty( $settings['enable_orphaned_postmeta_cleanup'] ) || !empty( $settings['enable_orphaned_commentmeta_cleanup'] ) || !empty( $settings['enable_orphaned_term_relationships_cleanup'] ) || !empty( $settings['enable_expired_transients_cleanup'] ) ) {
                    $has_auto_delete = true;
                }
            }
            if ( $has_auto_delete ) {
                echo '<div style="margin-bottom: 10px; font-size: 13px;">';
                echo '<span style="color: #ff9800;">⚠️ ' . esc_html__( 'Auto-delete: ON', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
                echo '</div>';
            }
            // Summary: orphaned data + comments/reviews moderation counts (free + pro).
            $other_orphaned_total = 0;
            $other_spam_comments_count = 0;
            $other_trashed_comments_count = 0;
            $other_spam_reviews_count = 0;
            $other_trashed_reviews_count = 0;
            if ( function_exists( 'pt_wuclean_other_get_orphaned_data' ) ) {
                $other_orphaned_data = pt_wuclean_other_get_orphaned_data();
                if ( is_array( $other_orphaned_data ) ) {
                    foreach ( $other_orphaned_data as $items ) {
                        if ( is_array( $items ) ) {
                            $other_orphaned_total += count( $items );
                        }
                    }
                }
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $other_spam_comments_count = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = %s AND comment_approved = %s", 'comment', 'spam' ) ) );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $other_trashed_comments_count = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = %s AND comment_approved = %s", 'comment', 'trash' ) ) );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $other_spam_reviews_count = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = %s AND comment_approved = %s", 'review', 'spam' ) ) );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $other_trashed_reviews_count = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_type = %s AND comment_approved = %s", 'review', 'trash' ) ) );
            if ( $other_orphaned_total > 0 || $other_spam_comments_count > 0 || $other_trashed_comments_count > 0 || $other_spam_reviews_count > 0 || $other_trashed_reviews_count > 0 ) {
                $url_other_orphaned = add_query_arg( array(
                    'page'     => 'wuclean-other',
                    'tab'      => 'orphaned_data',
                    '_wpnonce' => wp_create_nonce( 'wuclean-other-tab' ),
                ), admin_url( 'admin.php' ) );
                $url_comments = admin_url( 'edit-comments.php' );
                $url_reviews = admin_url( 'edit.php?post_type=product&page=product-reviews' );
                echo '<div style="border-top: 1px solid #e0e0e0; margin: 15px 0; padding-top: 15px;">';
                echo '<div style="font-size: 12px; font-weight: bold; margin-bottom: 8px; color: #666;">' . esc_html__( 'Summary:', 'wuclean-database-cleaner-for-woocommerce' ) . '</div>';
                if ( $other_orphaned_total > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    echo '<strong>' . absint( $other_orphaned_total ) . '</strong> ' . esc_html__( 'Orphaned Data Detected', 'wuclean-database-cleaner-for-woocommerce' ) . ' → <a href="' . esc_url( $url_other_orphaned ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $other_spam_comments_count > 0 || $other_trashed_comments_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    /* translators: 1: spam comments count, 2: trashed comments count. */
                    echo '<strong>' . esc_html( sprintf( esc_html__( '%1$s Spam & %2$s Trashed Comments', 'wuclean-database-cleaner-for-woocommerce' ), number_format_i18n( $other_spam_comments_count ), number_format_i18n( $other_trashed_comments_count ) ) ) . '</strong> → <a href="' . esc_url( $url_comments ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                if ( $other_spam_reviews_count > 0 || $other_trashed_reviews_count > 0 ) {
                    echo '<div style="margin-bottom: 6px; font-size: 12px;">';
                    /* translators: 1: spam product reviews count, 2: trashed product reviews count. */
                    echo '<strong>' . esc_html( sprintf( esc_html__( '%1$s Spam & %2$s Trashed Reviews', 'wuclean-database-cleaner-for-woocommerce' ), number_format_i18n( $other_spam_reviews_count ), number_format_i18n( $other_trashed_reviews_count ) ) ) . '</strong> → <a href="' . esc_url( $url_reviews ) . '" style="text-decoration: none;">' . esc_html( $wuclean_dashboard_view_label ) . '</a>';
                    echo '</div>';
                }
                echo '</div>';
            }
            echo '<a href="' . esc_url( admin_url( 'admin.php?page=wuclean-other' ) ) . '" class="button button-primary" style="width: 100%; text-align: center; margin-top: auto;">' . esc_html__( 'Configure', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
            echo '</div>';
            echo '</div>';
            // Free version: dashboard-wide Pro automation callout (collapsed by default).
            pt_wuclean_render_dashboard_pro_automation_callout();
            echo '</div>';
            // Close wrap.
        }

        /**
         * Schedule processing of flagged items (should be called by cron).
         */
        public function maybe_schedule_crons() {
            // Only run this on admin pages to avoid triggering on every page load.
            if ( !is_admin() ) {
                return;
            }
            $settings = get_option( self::SETTINGS_OPTION, array() );
            // Check if any crons need to be scheduled.
            $needs_scheduling = false;
            // Only schedule if needed.
            if ( $needs_scheduling ) {
                $this->schedule_crons();
            }
        }

        /**
         * Schedule crons on activation.
         */
        public function schedule_crons() {
            $settings = get_option( self::SETTINGS_OPTION, array() );
        }

        /**
         * Clear product cleanup cron if all scans are disabled.
         */
        public function clear_disabled_product_crons() {
            $settings = get_option( self::SETTINGS_OPTION, array() );
            $enable_product_cleanup_master = $settings['enable_product_cleanup_master'] ?? 0;
            $enable_product_cleanup = $settings['enable_product_cleanup'] ?? 0;
            $enable_orphaned_data_cleanup = $settings['enable_orphaned_data_cleanup'] ?? 0;
            $enable_unused_attributes_cleanup = $settings['enable_unused_attributes_cleanup'] ?? 0;
            $enable_delete_trashed_products = $settings['enable_delete_trashed_products'] ?? 0;
            // Clear cron if master is disabled OR all scans are disabled.
            $should_clear = !$enable_product_cleanup_master || !$enable_product_cleanup && !$enable_orphaned_data_cleanup && !$enable_unused_attributes_cleanup && !$enable_delete_trashed_products;
            if ( $should_clear ) {
                wp_clear_scheduled_hook( 'pt_wuclean_product_cleanup_event' );
                // Also clear any pending cron events.
                $crons = _get_cron_array();
                if ( !empty( $crons ) ) {
                    foreach ( $crons as $timestamp => $cron ) {
                        if ( isset( $cron['pt_wuclean_product_cleanup_event'] ) ) {
                            unset($crons[$timestamp]['pt_wuclean_product_cleanup_event']);
                            if ( empty( $crons[$timestamp] ) ) {
                                unset($crons[$timestamp]);
                            }
                        }
                    }
                    _set_cron_array( $crons );
                }
            }
        }

        /**
         * Force clear product cleanup crons immediately.
         */
        public function force_clear_disabled_product_crons() {
            $settings = get_option( self::SETTINGS_OPTION, array() );
            $enable_product_cleanup_master = $settings['enable_product_cleanup_master'] ?? 0;
            $enable_product_cleanup = $settings['enable_product_cleanup'] ?? 0;
            $enable_orphaned_data_cleanup = $settings['enable_orphaned_data_cleanup'] ?? 0;
            $enable_unused_attributes_cleanup = $settings['enable_unused_attributes_cleanup'] ?? 0;
            $enable_delete_trashed_products = $settings['enable_delete_trashed_products'] ?? 0;
            // Clear cron if master is disabled OR all scans are disabled.
            $should_clear = !$enable_product_cleanup_master || !$enable_product_cleanup && !$enable_orphaned_data_cleanup && !$enable_unused_attributes_cleanup && !$enable_delete_trashed_products;
            if ( $should_clear ) {
                wp_clear_scheduled_hook( 'pt_wuclean_product_cleanup_event' );
                // Force clear any pending cron events.
                $crons = _get_cron_array();
                if ( !empty( $crons ) ) {
                    foreach ( $crons as $timestamp => $cron ) {
                        if ( isset( $cron['pt_wuclean_product_cleanup_event'] ) ) {
                            unset($crons[$timestamp]['pt_wuclean_product_cleanup_event']);
                            if ( empty( $crons[$timestamp] ) ) {
                                unset($crons[$timestamp]);
                            }
                        }
                    }
                    _set_cron_array( $crons );
                }
            }
        }

        /**
         * Plugin activation: DB tables, defaults, crons.
         */
        public function activate_plugin() {
            // Install/upgrade DB tables used by scan engines.
            pt_wuclean_install_db_tables();
            // Legacy trash path migration (see pt_wuclean_migrate_trash_from_content_to_uploads_do_work() docblock).
            if ( function_exists( 'pt_wuclean_migrate_trash_from_content_to_uploads' ) ) {
                pt_wuclean_migrate_trash_from_content_to_uploads();
            }
            if ( function_exists( 'pt_wuclean_migrate_image_settings_remove_legacy_batch_size' ) ) {
                pt_wuclean_migrate_image_settings_remove_legacy_batch_size();
            }
            // Initialize default settings if they don't exist.
            $existing_settings = get_option( self::SETTINGS_OPTION, array() );
            // Set default order cleanup frequency to 'unscheduled' for safety.
            if ( !isset( $existing_settings['order_cleanup_frequency'] ) ) {
                $existing_settings['order_cleanup_frequency'] = 'unscheduled';
            }
            // Set default order cleanup to disabled for safety.
            if ( !isset( $existing_settings['enable_order_cleanup'] ) ) {
                $existing_settings['enable_order_cleanup'] = 0;
            }
            // Set default product cleanup master to disabled for safety.
            if ( !isset( $existing_settings['enable_product_cleanup_master'] ) ) {
                $existing_settings['enable_product_cleanup_master'] = 0;
            }
            // Set default user cleanup frequency to 'unscheduled' for safety.
            if ( !isset( $existing_settings['user_cleanup_frequency'] ) ) {
                $existing_settings['user_cleanup_frequency'] = 'unscheduled';
            }
            // Set default user cleanup to disabled for safety.
            if ( !isset( $existing_settings['enable_user_cleanup'] ) ) {
                $existing_settings['enable_user_cleanup'] = 0;
            }
            // Set default user target roles to customer only (excluding admin/shop_manager for safety).
            if ( !isset( $existing_settings['user_target_roles'] ) ) {
                $existing_settings['user_target_roles'] = array('customer');
            }
            // Set default user log limit.
            if ( !isset( $existing_settings['user_log_limit'] ) ) {
                $existing_settings['user_log_limit'] = self::DEFAULT_USER_LOG_LIMIT;
            }
            // Only update if we made changes.
            if ( count( $existing_settings ) > 0 ) {
                update_option( self::SETTINGS_OPTION, $existing_settings, false );
            }
            $this->schedule_crons();
        }

        /**
         * Plugin deactivation: clear all scheduled events.
         */
        public function deactivate_plugin() {
            // Clear all scheduled events.
            wp_clear_scheduled_hook( 'pt_wuclean_scheduled_actions_cleanup_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_other_cleanup_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_product_cleanup_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_flag_orders_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_flag_users_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_process_flagged_users_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_image_scan_event' );
            wp_clear_scheduled_hook( 'pt_wuclean_process_image_scan_batch' );
            wp_clear_scheduled_hook( 'pt_wuclean_image_scan_watchdog_event' );
        }

    }

}
if ( !function_exists( 'pt_wuclean_get_notification_email' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Get notification email address.
     *
     * Returns custom email if set (premium), otherwise WordPress admin email.
     *
     * @return string Email address for notifications.
     */
    function pt_wuclean_get_notification_email() {
        // Use WordPress default admin email.
        return get_option( 'admin_email' );
    }

}
if ( !function_exists( 'pt_wuclean_reset_premium_settings_on_downgrade' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Reset all premium settings when license is deactivated or downgraded.
     * Called by Freemius hooks or defensive checks.
     *
     * @param object|null $license License object from Freemius (null for defensive checks).
     * @return void
     */
    function pt_wuclean_reset_premium_settings_on_downgrade(  $license = null  ) {
        // If called from hook with license object, validate it.
        if ( null !== $license && empty( $license->id ) ) {
            return;
        }
        // Get current settings.
        $settings = get_option( 'pt_wuclean_settings', array() );
        // Premium-only settings keys to remove.
        $premium_keys = array(
            // User module.
            'enable_user_cleanup',
            'user_cleanup_frequency',
            'user_delete_flagged_days',
            'user_detect_orphaned_data',
            'user_delete_orphaned_data',
            'user_cleanup_batch_size',
            'user_email_notification',
            'user_new_customer_email_notification',
            'user_customer_email_notification',
            'user_log_limit',
            // Orders module.
            'enable_order_cleanup',
            'enable_detect_orphaned_order_meta',
            'enable_delete_orphaned_order_meta',
            'email_notification',
            'trash_flagged_orders_after_24h',
            'delete_trashed_orders_after_24h',
            'trash_expired_coupons_after_24h',
            'delete_trashed_coupons_after_24h',
            'enable_delete_order_notes',
            'delete_order_notes_days',
            'order_cleanup_batch_size',
            'order_cleanup_frequency',
            'order_log_limit',
            // Products module.
            'enable_product_cleanup_master',
            'enable_product_cleanup',
            'enable_orphaned_data_cleanup',
            'enable_delete_orphaned_data',
            'enable_unused_attributes_cleanup',
            'enable_delete_unused_attributes',
            'enable_delete_trashed_products',
            'product_scan_frequency',
            'product_cleanup_batch_size',
            'product_email_notification',
            // Scheduled Actions module.
            'enable_scheduled_actions_cleanup',
            'scheduled_actions_email_notification',
            // Other Cleanup module.
            'enable_other_cleanup_master',
            'other_cleanup_frequency',
            'other_email_notification',
        );
        // Remove all premium settings.
        foreach ( $premium_keys as $key ) {
            unset($settings[$key]);
        }
        // Save modified settings.
        update_option( 'pt_wuclean_settings', $settings, false );
        // Delete premium-only options.
        delete_option( 'pt_wuclean_notification_email' );
        // Partially clear image settings: preserve free-tier UI prefs on downgrade.
        $image_settings = get_option( 'pt_wuclean_image_settings', array() );
        if ( is_array( $image_settings ) ) {
            $premium_image_keys = array(
                'enable_image_scanning',
                'enable_delete_images',
                'scan_frequency',
                'email_notification',
                'scan_thumbnails'
            );
            foreach ( $premium_image_keys as $key ) {
                unset($image_settings[$key]);
            }
            if ( empty( $image_settings ) ) {
                delete_option( 'pt_wuclean_image_settings' );
            } else {
                update_option( 'pt_wuclean_image_settings', $image_settings, false );
            }
        }
        // Clear all premium-only cron jobs.
        wp_clear_scheduled_hook( 'pt_wuclean_flag_users_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_process_flagged_users_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_image_scan_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_product_cleanup_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_flag_orders_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_process_image_scan_batch' );
        wp_clear_scheduled_hook( 'pt_wuclean_image_scan_watchdog_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_scheduled_actions_cleanup_event' );
        wp_clear_scheduled_hook( 'pt_wuclean_other_cleanup_event' );
        // Clear premium-only transients that may block functionality in free version
        // Image scan lock: if a scheduled scan was running during downgrade, the lock would prevent manual scans.
        delete_transient( 'pt_wuclean_image_scan_lock' );
        // Clear scheduled image scan runs from database (scheduled scans are premium-only)
        // If a scheduled scan was running/stopped during downgrade, it would block manual scans.
        global $wpdb;
        $runs_table = $wpdb->prefix . 'wuclean_image_scan_runs';
        $items_table = $wpdb->prefix . 'wuclean_image_scan_items';
        // Check if tables exist before attempting to delete.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $table_exists = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = %s AND table_name = %s', $wpdb->dbname, $runs_table ) );
        if ( $table_exists ) {
            // Get all scheduled scan run IDs.
            $runs_table_escaped = esc_sql( $runs_table );
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table from esc_sql; cannot be placeholder.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $scheduled_run_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$runs_table_escaped} WHERE mode = %s", 'scheduled' ) );
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            if ( !empty( $scheduled_run_ids ) && is_array( $scheduled_run_ids ) ) {
                $scheduled_run_ids = array_values( array_filter( array_map( 'absint', $scheduled_run_ids ) ) );
                if ( !empty( $scheduled_run_ids ) ) {
                    $placeholders = implode( ',', array_fill( 0, count( $scheduled_run_ids ), '%d' ) );
                    $items_table_escaped = esc_sql( $items_table );
                    // Delete scan items for scheduled runs.
                    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Table from esc_sql; $placeholders is %d,%d,.. matching spread.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->query( $wpdb->prepare( "DELETE FROM {$items_table_escaped} WHERE run_id IN ({$placeholders})", ...$scheduled_run_ids ) );
                    // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
                    // Delete scheduled scan runs.
                    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Table from esc_sql; $placeholders is %d,%d,.. matching spread.
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
                    $wpdb->query( $wpdb->prepare( "DELETE FROM {$runs_table_escaped} WHERE id IN ({$placeholders})", ...$scheduled_run_ids ) );
                    // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
                }
            }
        }
    }

}
if ( !function_exists( 'pt_wuclean_reset_premium_settings_on_license_change' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Handle license change events to detect downgrades.
     * Called by Freemius after_license_change hook.
     *
     * @param string $status License change status (e.g., 'changed', 'downgraded').
     * @param object $plan   Plan object from Freemius (unused; required by hook signature).
     * @return void
     */
    function pt_wuclean_reset_premium_settings_on_license_change(  $status, $plan  ) {
        // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $plan required by Freemius hook.
        $status = ( is_string( $status ) ? strtolower( $status ) : '' );
        // Only reset when the license transitions away from a paid plan.
        // Note: do NOT use is_not_paying() here.
        $reset_statuses = array(
            'downgraded',
            'expired',
            'cancelled',
            'deactivated'
        );
        if ( in_array( $status, $reset_statuses, true ) ) {
            pt_wuclean_reset_premium_settings_on_downgrade( null );
        }
    }

}
if ( !function_exists( 'pt_wuclean_render_pagination' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Centralized pagination function (WordPress standard with page input).
     * Used across all modules for consistent UX.
     *
     * @param array $args {
     *     Pagination arguments.
     *     @type int    $current_page    Current page number.
     *     @type int    $total_pages     Total number of pages.
     *     @type string $base_url        Base URL for pagination links.
     *     @type string $page_param      URL parameter name for page number (default: 'paged').
     *     @type array  $preserve_params Additional GET parameters to preserve (optional).
     * }
     * @return void
     */
    function pt_wuclean_render_pagination(  $args  ) {
        $defaults = array(
            'current_page'    => 1,
            'total_pages'     => 1,
            'base_url'        => '',
            'page_param'      => 'paged',
            'preserve_params' => array(),
        );
        $args = wp_parse_args( $args, $defaults );
        $current_page = (int) $args['current_page'];
        $total_pages = (int) $args['total_pages'];
        $base_url = $args['base_url'];
        $page_param = $args['page_param'];
        $preserve_params = $args['preserve_params'];
        // Don't show pagination if only 1 page.
        if ( $total_pages <= 1 ) {
            return;
        }
        // Build base args.
        $base_args = $preserve_params;
        echo '<div style="text-align: center; margin: 20px 0;">';
        echo '<div class="tablenav" style="display: inline-flex; gap: 5px; align-items: center;">';
        // First page button.
        if ( $current_page > 1 ) {
            $first_args = array_merge( $base_args, array(
                $page_param => 1,
            ) );
            $first_url = add_query_arg( $first_args, $base_url );
            echo '<a href="' . esc_url( $first_url ) . '" class="button" title="' . esc_attr__( 'First page', 'wuclean-database-cleaner-for-woocommerce' ) . '">« ' . esc_html__( 'First', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
        } else {
            echo '<span class="button" style="background: #f0f0f0; color: #ccc; cursor: not-allowed;">« ' . esc_html__( 'First', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
        }
        // Previous page button.
        if ( $current_page > 1 ) {
            $prev_args = array_merge( $base_args, array(
                $page_param => $current_page - 1,
            ) );
            $prev_url = add_query_arg( $prev_args, $base_url );
            echo '<a href="' . esc_url( $prev_url ) . '" class="button" title="' . esc_attr__( 'Previous page', 'wuclean-database-cleaner-for-woocommerce' ) . '">‹ ' . esc_html__( 'Prev', 'wuclean-database-cleaner-for-woocommerce' ) . '</a>';
        } else {
            echo '<span class="button" style="background: #f0f0f0; color: #ccc; cursor: not-allowed;">‹ ' . esc_html__( 'Prev', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
        }
        // Page jump uses JavaScript instead of nested form (avoids HTML form nesting issues).
        echo '<span style="white-space: nowrap;">' . esc_html__( 'Page', 'wuclean-database-cleaner-for-woocommerce' ) . '</span>';
        echo '<input type="number" id="pt-wuclean-page-jump-' . esc_attr( $page_param ) . '" value="' . absint( $current_page ) . '" min="1" max="' . absint( $total_pages ) . '" style="width: 60px; padding: 3px 6px; height: 28px; text-align: center; box-sizing: border-box;">';
        /* translators: %d: total number of pages. */
        echo '<span style="white-space: nowrap;">' . esc_html( sprintf( __( 'of %d', 'wuclean-database-cleaner-for-woocommerce' ), $total_pages ) ) . '</span>';
        // Build the URL for page jump.
        $page_jump_url = add_query_arg( $base_args, $base_url );
        echo '<button type="button" class="button pt-wuclean-page-jump-btn" data-base-url="' . esc_attr( $page_jump_url ) . '" data-page-param="' . esc_attr( $page_param ) . '" data-max-pages="' . absint( $total_pages ) . '" data-input-id="pt-wuclean-page-jump-' . esc_attr( $page_param ) . '" style="padding: 0 10px; height: 28px; line-height: 26px;">' . esc_attr__( 'Go', 'wuclean-database-cleaner-for-woocommerce' ) . '</button>';
        // Next page button.
        if ( $current_page < $total_pages ) {
            $next_args = array_merge( $base_args, array(
                $page_param => $current_page + 1,
            ) );
            $next_url = add_query_arg( $next_args, $base_url );
            echo '<a href="' . esc_url( $next_url ) . '" class="button" title="' . esc_attr__( 'Next page', 'wuclean-database-cleaner-for-woocommerce' ) . '">' . esc_html__( 'Next', 'wuclean-database-cleaner-for-woocommerce' ) . ' ›</a>';
        } else {
            echo '<span class="button" style="background: #f0f0f0; color: #ccc; cursor: not-allowed;">' . esc_html__( 'Next', 'wuclean-database-cleaner-for-woocommerce' ) . ' ›</span>';
        }
        // Last page button.
        if ( $current_page < $total_pages ) {
            $last_args = array_merge( $base_args, array(
                $page_param => $total_pages,
            ) );
            $last_url = add_query_arg( $last_args, $base_url );
            echo '<a href="' . esc_url( $last_url ) . '" class="button" title="' . esc_attr__( 'Last page', 'wuclean-database-cleaner-for-woocommerce' ) . '">' . esc_html__( 'Last', 'wuclean-database-cleaner-for-woocommerce' ) . ' »</a>';
        } else {
            echo '<span class="button" style="background: #f0f0f0; color: #ccc; cursor: not-allowed;">' . esc_html__( 'Last', 'wuclean-database-cleaner-for-woocommerce' ) . ' »</span>';
        }
        echo '</div>';
        echo '</div>';
    }

}
if ( !function_exists( 'pt_wuclean_render_pro_automation_callout' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Render the collapsed "Features & benefits included in Pro" callout (Settings tabs only).
     *
     * IMPORTANT:
     * - Free users see this as marketing/progressive disclosure.
     * - Premium users should not see this.
     * - It is not used to disable built-in functionality.
     *
     * @param string[] $benefits List of benefit lines (plain text).
     * @param string   $context  Unique short context identifier (used for element ids).
     * @return void
     */
    function pt_wuclean_render_pro_automation_callout(  array $benefits, string $context = 'module'  ) : void {
        if ( !function_exists( 'pt_wuclean_fs' ) ) {
            return;
        }
        if ( pt_wuclean_fs()->is_not_paying() ) {
            $context = sanitize_key( $context );
            $heading = esc_html__( 'Features & benefits included in Pro', 'wuclean-database-cleaner-for-woocommerce' );
            $cta_text = esc_html__( 'Get Pro', 'wuclean-database-cleaner-for-woocommerce' );
            $cta_url = pt_wuclean_fs()->get_upgrade_url();
            $learn_text = esc_html__( 'Learn more', 'wuclean-database-cleaner-for-woocommerce' );
            $learn_url = 'https://paythem.net/wuclean-the-woocommerce-cleanup-assistant-plugin/';
            echo '<div style="max-width: 1600px; margin: 15px 0 0 0;">';
            echo '<details style="background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; padding: 12px 14px;">';
            echo '<summary style="cursor: pointer; font-weight: 600; list-style: none;">';
            echo '<span style="display: inline-flex; align-items: center; gap: 8px;">';
            echo '<span style="display: inline-flex; align-items: center; gap: 5px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: bold;">👑 PRO</span>';
            echo '<span>' . esc_html( $heading ) . '</span>';
            echo '</span>';
            echo '<span style="float: right; color: #646970;">▾</span>';
            echo '</summary>';
            echo '<div style="margin-top: 12px;">';
            if ( !empty( $benefits ) ) {
                echo '<ul style="margin: 0 0 12px 0; padding-left: 18px;">';
                foreach ( $benefits as $benefit ) {
                    $benefit = ( is_string( $benefit ) ? $benefit : '' );
                    if ( '' === $benefit ) {
                        continue;
                    }
                    // Green tick at the start of each benefit.
                    echo '<li style="margin: 0 0 8px 0; position: relative; padding-left: 22px;">';
                    echo '<span style="position: absolute; left: 0; top: 0; color: #46b450; font-size: 14px; font-weight: bold;">✓</span>';
                    echo '<span>' . esc_html( $benefit ) . '</span>';
                    echo '</li>';
                }
                echo '</ul>';
            }
            echo '<p style="margin: 0;">';
            echo '<a href="' . esc_url( $cta_url ) . '" class="button button-primary" style="text-decoration: none; margin-right: 8px;">' . esc_html( $cta_text ) . '</a>';
            echo '<a href="' . esc_url( $learn_url ) . '" target="_blank" rel="noopener noreferrer" style="font-size: 13px; color: #2271b1; text-decoration: none;">' . esc_html( $learn_text ) . '</a>';
            echo '</p>';
            echo '</div>';
            echo '</details>';
            echo '</div>';
        }
    }

}
if ( !function_exists( 'pt_wuclean_render_dashboard_module_card_open' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Render the common dashboard module card shell.
     *
     * @param string $extra_style Optional extra inline CSS appended to the base card shell.
     * @return void
     */
    function pt_wuclean_render_dashboard_module_card_open(  $extra_style = ''  ) : void {
        $base_style = 'background: #fff; border: 1px solid #ccd0d4; border-radius: 4px; padding: 20px; transition: box-shadow 0.2s; display: flex; flex-direction: column;';
        $style = trim( $base_style . ' ' . (string) $extra_style );
        echo '<div style="' . esc_attr( $style ) . '">';
    }

}
if ( !function_exists( 'pt_wuclean_render_dashboard_pro_automation_callout' ) && !pt_wuclean_legacy_pro_is_active() && !pt_wuclean_legacy_free_is_active() ) {
    /**
     * Render the dashboard-wide Pro features & benefits callout (collapsed by default).
     *
     * Free users only: marketing/progressive disclosure.
     *
     * @return void
     */
    function pt_wuclean_render_dashboard_pro_automation_callout() : void {
        if ( !function_exists( 'pt_wuclean_fs' ) ) {
            return;
        }
        if ( pt_wuclean_fs()->is_not_paying() ) {
            $heading = esc_html__( 'Features & benefits included in Pro', 'wuclean-database-cleaner-for-woocommerce' );
            $cta_text = esc_html__( 'Get Pro', 'wuclean-database-cleaner-for-woocommerce' );
            $cta_url = pt_wuclean_fs()->get_upgrade_url();
            $learn_text = esc_html__( 'Learn more', 'wuclean-database-cleaner-for-woocommerce' );
            $learn_url = 'https://paythem.net/wuclean-the-woocommerce-cleanup-assistant-plugin/';
            $modules = array(
                'products'          => array(
                    'title'    => __( 'Products', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Auto-detect and report issues and orphan data', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-delete orphaned data and unused attributes', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-delete trashed products after 24 hours', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '📦',
                    'icon_css' => 'color: #9c27b0;',
                ),
                'orders'            => array(
                    'title'    => __( 'Orders (HPOS)', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Automate order flagging and cleanup cycles', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-detect and delete orphaned order meta', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-delete expired coupons and order notes', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '🛒',
                    'icon_css' => 'color: #ff9800;',
                ),
                'customers'         => array(
                    'title'    => __( 'Customers', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Auto-detect fake and inactive customer accounts', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Automate reactivation emails to customers', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-delete orphan data and flagged customers', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '👥',
                    'icon_css' => 'color: #673ab7;',
                ),
                'images'            => array(
                    'title'    => __( 'Images', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Schedule periodic Media Library scans to find unused images', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Advanced Deep scan and Uploads folder cleanup', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Auto-clean leftover deleted product images', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '🖼️',
                    'icon_css' => 'color: #2196f3;',
                ),
                'scheduled_actions' => array(
                    'title'    => __( 'Scheduled Actions', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Automate daily cleanup of scheduled actions', 'wuclean-database-cleaner-for-woocommerce' ), __( 'See scheduled action summary and WC Log data', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Email reports with counts and database impact', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '⏱️',
                    'icon_css' => 'color: #4caf50;',
                ),
                'other'             => array(
                    'title'    => __( 'Other Cleanup', 'wuclean-database-cleaner-for-woocommerce' ),
                    'bullets'  => array(__( 'Automate daily cleanup of WordPress clutter', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Detect and delete orphaned database records', 'wuclean-database-cleaner-for-woocommerce' ), __( 'Email notifications for cleanup actions and counts', 'wuclean-database-cleaner-for-woocommerce' )),
                    'icon'     => '🔧',
                    'icon_css' => 'color: #00bcd4;',
                ),
            );
            echo '<div style="max-width: 1600px; margin: 20px 0 0 0;">';
            echo '<details style="background: #fff; border: 1px solid #ccd0d4; border-radius: 6px; padding: 14px 16px;">';
            echo '<summary style="cursor: pointer; font-weight: 600; list-style: none;">';
            echo '<span style="display: inline-flex; align-items: center; gap: 8px;">';
            echo '<span style="display: inline-flex; align-items: center; gap: 5px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: bold;">👑 PRO</span>';
            echo '<span>' . esc_html( $heading ) . '</span>';
            echo '</span>';
            echo '<span style="float: right; color: #646970;">▾</span>';
            echo '</summary>';
            echo '<div style="margin-top: 14px;">';
            echo '<div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px;">';
            foreach ( $modules as $module ) {
                $title = ( isset( $module['title'] ) ? $module['title'] : '' );
                $bullets = ( isset( $module['bullets'] ) && is_array( $module['bullets'] ) ? $module['bullets'] : array() );
                $icon = ( isset( $module['icon'] ) ? $module['icon'] : '' );
                $icon_css = ( isset( $module['icon_css'] ) ? $module['icon_css'] : '' );
                echo '<div style="border: 1px solid #dcdcde; border-radius: 6px; padding: 12px 12px;">';
                echo '<div style="font-weight: 600; margin-bottom: 8px;">';
                echo '<span style="margin-right: 6px;' . esc_attr( $icon_css ) . '">' . esc_html( $icon ) . '</span>';
                echo esc_html( $title );
                echo '</div>';
                if ( !empty( $bullets ) ) {
                    echo '<ul style="margin: 0; padding-left: 0; list-style: none;">';
                    foreach ( array_slice( $bullets, 0, 3 ) as $bullet ) {
                        $bullet = ( is_string( $bullet ) ? $bullet : '' );
                        if ( '' === $bullet ) {
                            continue;
                        }
                        echo '<li style="margin: 0 0 8px 0; position: relative; padding-left: 22px;">';
                        echo '<span style="position: absolute; left: 0; top: 0; color: #46b450; font-size: 14px; font-weight: bold;">✓</span>';
                        echo '<span style="color: #1d2327;">' . esc_html( $bullet ) . '</span>';
                        echo '</li>';
                    }
                    echo '</ul>';
                }
                echo '</div>';
            }
            echo '</div>';
            echo '<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 14px;">';
            echo '<a href="' . esc_url( $cta_url ) . '" class="button button-primary" style="text-decoration: none;">' . esc_html( $cta_text ) . '</a>';
            echo '<a href="' . esc_url( $learn_url ) . '" target="_blank" rel="noopener noreferrer" style="font-size: 13px; color: #2271b1; text-decoration: none;">' . esc_html( $learn_text ) . '</a>';
            echo '</div>';
            echo '</div>';
            echo '</details>';
            echo '</div>';
        }
    }

}
// Instantiate the plugin and register activation/deactivation hooks.
if ( class_exists( 'pt_wuclean_Plugin' ) ) {
    $pt_wuclean_instance = new pt_wuclean_Plugin();
    register_activation_hook( __FILE__, array($pt_wuclean_instance, 'activate_plugin') );
    register_deactivation_hook( __FILE__, array($pt_wuclean_instance, 'deactivate_plugin') );
}