<?php

/**
 * Plugin Name:       Rankd Tax Exemption Certificates for WooCommerce
 * Plugin URI:        https://rankd.site/plugins-from-rankd-site/tax-exempt-certificate-manager-for-woocommerce/
 * Description:       Complete tax exemption management for WooCommerce. Role-based exemption, per-user overrides, customer certificate submission, approval workflow, enforced expiry, and audit trail. Free and Pro tiers via Freemius.
 * Version:           1.1.5
 * Author:            Rankd.site
 * Author URI:        https://rankd.site
 * License:           GPL-2.0+
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       rankd-tax-exemption-certificates-for-woocommerce
 * Domain Path:       /languages
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 * WC requires at least: 7.0
 * WC tested up to:   11.0
 *
 */
defined( 'ABSPATH' ) || exit;
// ─────────────────────────────────────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────────────────────────────────────
// Guarded with defined() checks: WordPress does a sandboxed test-load of a
// plugin's main file at the moment it's activated (to catch fatal errors
// before switching it on), then loads it again normally in the same
// request — two inclusions of this file, one process. Without these
// guards that produces harmless but noisy "Constant already defined"
// warnings every time the plugin is activated or reactivated.
if ( !defined( 'RANKD_TX_VERSION' ) ) {
    define( 'RANKD_TX_VERSION', '1.1.5' );
}
if ( !defined( 'RANKD_TX_PLUGIN_FILE' ) ) {
    define( 'RANKD_TX_PLUGIN_FILE', __FILE__ );
}
if ( !defined( 'RANKD_TX_PLUGIN_DIR' ) ) {
    define( 'RANKD_TX_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
}
if ( !defined( 'RANKD_TX_PLUGIN_URL' ) ) {
    define( 'RANKD_TX_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}
if ( !defined( 'RANKD_TX_PLUGIN_SLUG' ) ) {
    define( 'RANKD_TX_PLUGIN_SLUG', 'rankd-tax-exemption-certificates-for-woocommerce' );
}
// User meta keys
if ( !defined( 'RANKD_TX_META_EXEMPT' ) ) {
    define( 'RANKD_TX_META_EXEMPT', '_rankd_tx_is_exempt' );
    // string — override: always|never|'' (legacy '1' = always)
}
if ( !defined( 'RANKD_TX_META_NOTE' ) ) {
    define( 'RANKD_TX_META_NOTE', '_rankd_tx_exempt_note' );
    // string — admin note
}
// Order meta key
if ( !defined( 'RANKD_TX_ORDER_META' ) ) {
    define( 'RANKD_TX_ORDER_META', '_rankd_tx_was_exempt' );
    // bool + source at purchase
}
// Upload subdirectory (inside wp-content/uploads/)
if ( !defined( 'RANKD_TX_UPLOAD_DIR' ) ) {
    define( 'RANKD_TX_UPLOAD_DIR', 'rankd-tax-exempt-certs' );
}
// ─────────────────────────────────────────────────────────────────────────────
// Freemius SDK bootstrap
// ─────────────────────────────────────────────────────────────────────────────
if ( function_exists( 'rankd_tx_fs' ) ) {
    rankd_tx_fs()->set_basename( false, __FILE__ );
    // Freemius sets this to true in the Pro build.
} else {
    // Pro tier has a real review-queue page to anchor Freemius on; Free
    // tier doesn't, so it needs the dedicated settings-redirect page
    // instead. Checked via file_exists (not class_exists) because the
    // autoloader below hasn't been registered yet at this point in the
    // bootstrap — class_exists would incorrectly return false even in
    // the Pro build.
    $rankd_tx_fs_menu_slug = ( file_exists( dirname( __FILE__ ) . '/includes/pro/class-rankd-tx-review-queue.php' ) ? 'rankd-tx-review-queue' : 'rankd-tx-settings' );
    if ( !function_exists( 'rankd_tx_fs' ) ) {
        function rankd_tx_fs() {
            global $rankd_tx_fs, $rankd_tx_fs_menu_slug;
            if ( !isset( $rankd_tx_fs ) ) {
                // Include Freemius SDK.
                require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
                $rankd_tx_fs = fs_dynamic_init( array(
                    'id'               => '29231',
                    'slug'             => 'rankd-tax-exemption-certificates-for-woocommerce',
                    'premium_slug'     => 'rankd-tax-exemption-certificates-for-woocommerce-pro',
                    'type'             => 'plugin',
                    'public_key'       => 'pk_d6654402c678c3399160c100f9df4',
                    'is_premium'       => false,
                    'premium_suffix'   => 'Pro',
                    'has_addons'       => false,
                    'has_paid_plans'   => true,
                    'is_org_compliant' => true,
                    'menu'             => array(
                        'slug'    => $rankd_tx_fs_menu_slug,
                        'parent'  => array(
                            'slug' => 'woocommerce',
                        ),
                        'support' => false,
                    ),
                    'is_live'          => true,
                ) );
            }
            return $rankd_tx_fs;
        }

        // Init Freemius.
        rankd_tx_fs();
        // Signal that SDK was initiated.
        do_action( 'rankd_tx_fs_loaded' );
        function rankd_tx_fs_settings_url() {
            return admin_url( 'admin.php?page=wc-settings&tab=rankd-tx-settings' );
        }

        rankd_tx_fs()->add_filter( 'after_skip_url', 'rankd_tx_fs_settings_url' );
        rankd_tx_fs()->add_filter( 'after_connect_url', 'rankd_tx_fs_settings_url' );
        rankd_tx_fs()->add_filter( 'after_pending_connect_url', 'rankd_tx_fs_settings_url' );
    }
    // ─────────────────────────────────────────────────────────────────────────────
    // Autoloader
    // ─────────────────────────────────────────────────────────────────────────────
    spl_autoload_register( function ( $class ) {
        // Only handle Rankd_Tx_ prefixed classes.
        if ( strpos( $class, 'Rankd_Tx_' ) !== 0 ) {
            return;
        }
        // Skip the stub — already defined above.
        if ( 'Rankd_Tx_Freemius_Stub' === $class ) {
            return;
        }
        // Map class name to file: Rankd_Tx_Some_Class → class-rankd-tx-some-class.php
        $filename = 'class-' . strtolower( str_replace( '_', '-', $class ) ) . '.php';
        $base_dirs = array(
            RANKD_TX_PLUGIN_DIR . 'includes/core/',
            RANKD_TX_PLUGIN_DIR . 'includes/admin/',
            RANKD_TX_PLUGIN_DIR . 'includes/customer/',
            RANKD_TX_PLUGIN_DIR . 'includes/pro/',
            // Stripped from the free build.
            RANKD_TX_PLUGIN_DIR . 'includes/pro/emails/',
        );
        foreach ( $base_dirs as $dir ) {
            $path = $dir . $filename;
            if ( file_exists( $path ) ) {
                require_once $path;
                return;
            }
        }
    } );
    // ─────────────────────────────────────────────────────────────────────────────
    // Bootstrap
    // ─────────────────────────────────────────────────────────────────────────────
    add_action( 'plugins_loaded', function () {
        // WooCommerce must be active.
        if ( !class_exists( 'WooCommerce' ) ) {
            add_action( 'admin_notices', function () {
                echo '<div class="notice notice-error"><p>' . esc_html__( 'Tax Exempt Manager requires WooCommerce to be installed and active.', 'rankd-tax-exemption-certificates-for-woocommerce' ) . '</p></div>';
            } );
            return;
        }
        // WooCommerce minimum version check.
        if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '7.0', '<' ) ) {
            add_action( 'admin_notices', function () {
                echo '<div class="notice notice-error"><p>' . sprintf( 
                    /* translators: %s = minimum WooCommerce version */
                    esc_html__( 'Tax Exempt Manager requires WooCommerce %s or higher. Please update WooCommerce.', 'rankd-tax-exemption-certificates-for-woocommerce' ),
                    '7.0'
                 ) . '</p></div>';
            } );
            return;
        }
        // Boot all modules via the loader.
        Rankd_Tx_Loader::init();
    } );
    // ─────────────────────────────────────────────────────────────────────────────
    // Activation / deactivation hooks
    // ─────────────────────────────────────────────────────────────────────────────
    register_activation_hook( __FILE__, array('Rankd_Tx_Install', 'activate') );
    register_deactivation_hook( __FILE__, function () {
        // Pro only — class-rankd-tx-cron.php is stripped from the free build,
        // and a direct array callback here would fatal through the autoloader.
        if ( class_exists( 'Rankd_Tx_Cron' ) ) {
            Rankd_Tx_Cron::deactivate();
        }
    } );
    // ─────────────────────────────────────────────────────────────────────────────
    // Declare WooCommerce feature compatibility
    // ─────────────────────────────────────────────────────────────────────────────
    add_action( 'before_woocommerce_init', function () {
        if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
            \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
            // Verified compatible after the 1.1.3 fix added the Store API
            // order-created hook alongside the classic one — exemption
            // calculation, order stamping, and the admin badge all confirmed
            // working correctly on a real block-checkout order.
            \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true );
        }
    } );
    // ─────────────────────────────────────────────────────────────────────────────
    // Uninstall handler (registered via Freemius after_uninstall hook)
    // Replaces the standalone uninstall.php file as required by Freemius.
    //
    // NOTE: Does NOT delete uploaded certificate files — these may be needed
    // for compliance and audit records. Delete manually if required:
    //   wp-content/uploads/rankd-tax-exempt-certs/
    // ─────────────────────────────────────────────────────────────────────────────
    function rankd_tx_uninstall() {
        global $wpdb;
        // Off by default — deactivating never removes data, and neither does
        // uninstalling unless the site owner explicitly opted in via
        // WooCommerce → Settings → Tax Exempt Certs → "Remove Data on Uninstall".
        if ( 'yes' !== get_option( 'rankd_tx_delete_data_on_uninstall' ) ) {
            // Still clear scheduled tasks — inert without the plugin's code
            // present to run them, and re-activation reschedules them anyway.
            wp_clear_scheduled_hook( 'rankd_tx_daily_expiry_check' );
            wp_clear_scheduled_hook( 'rankd_tx_daily_warning_check' );
            if ( function_exists( 'as_unschedule_all_actions' ) ) {
                as_unschedule_all_actions( 'rankd_tx_daily_expiry_check', array(), 'rankd-tx' );
                as_unschedule_all_actions( 'rankd_tx_daily_warning_check', array(), 'rankd-tx' );
            }
            return;
        }
        // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- uninstall cleanup, no caching needed.
        // Drop custom tables.
        $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}rankd_tx_certificates" );
        $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}rankd_tx_audit_log" );
        // Remove user meta.
        $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key IN ('_rankd_tx_is_exempt', '_rankd_tx_exempt_note', '_rankd_tx_role_override', '_rankd_tx_role_emailed', 'rankd_tx_upgrade_notice_dismissed', 'rankd_tx_nginx_notice_dismissed')" );
        // Remove order meta (classic tables).
        $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key IN ('_rankd_tx_was_exempt', '_rankd_tx_sources', '_rankd_tx_cert_id')" );
        // Remove order meta (HPOS orders meta table, if present).
        // Table name derives from $wpdb->prefix (not user input); esc_sql() +
        // targeted ignores satisfy Plugin Check's interpolation rules.
        $hpos_meta_table = esc_sql( $wpdb->prefix . 'wc_orders_meta' );
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- table name escaped via esc_sql() above; identifiers cannot use prepare() placeholders.
        if ( $wpdb->get_var( "SHOW TABLES LIKE '{$hpos_meta_table}'" ) === $wpdb->prefix . 'wc_orders_meta' ) {
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- table name escaped via esc_sql() above; meta keys are hardcoded literals.
            $wpdb->query( "DELETE FROM {$hpos_meta_table} WHERE meta_key IN ('_rankd_tx_was_exempt', '_rankd_tx_sources', '_rankd_tx_cert_id')" );
        }
        // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange
        // Remove plugin options.
        $options = array(
            'rankd_tx_version',
            'rankd_tx_needs_rewrite_flush',
            'rankd_tx_endpoint_in_rules',
            'rankd_tx_last_check_run',
            'rankd_tx_file_retention_years',
            'rankd_tx_excluded_roles',
            'rankd_tx_exempt_roles',
            'rankd_tx_editor_roles',
            'rankd_tx_delete_data_on_uninstall',
            'rankd_tx_expiry_warning_days',
            'rankd_tx_admin_notify_email',
            'rankd_tx_contact_email',
            'rankd_tx_renewal_grace_days'
        );
        foreach ( $options as $option ) {
            delete_option( $option );
        }
        // Clear scheduled cron jobs.
        // Remove the custom management capability from every role. Uses
        // wp_roles() rather than the raw $GLOBALS['wp_roles'] global — that
        // global is only reliably populated once something else in the
        // request has already called wp_roles(), and uninstall runs via
        // Freemius's after_uninstall action on a lean bootstrap that may
        // never have warmed it up, which silently skipped this cleanup
        // (same class of bug fixed in Rankd_Tx_Capabilities in 1.8.8).
        $rankd_tx_wp_roles = wp_roles();
        foreach ( array_keys( $rankd_tx_wp_roles->roles ) as $rankd_tx_role_slug ) {
            $rankd_tx_role = get_role( $rankd_tx_role_slug );
            if ( $rankd_tx_role && $rankd_tx_role->has_cap( 'rankd_tx_manage' ) ) {
                $rankd_tx_role->remove_cap( 'rankd_tx_manage' );
            }
        }
        wp_clear_scheduled_hook( 'rankd_tx_daily_expiry_check' );
        wp_clear_scheduled_hook( 'rankd_tx_daily_warning_check' );
        // Remove Action Scheduler actions (primary scheduler since 1.3.0).
        if ( function_exists( 'as_unschedule_all_actions' ) ) {
            as_unschedule_all_actions( 'rankd_tx_daily_expiry_check', array(), 'rankd-tx' );
            as_unschedule_all_actions( 'rankd_tx_daily_warning_check', array(), 'rankd-tx' );
        }
    }

    // Register uninstall logic via Freemius hook (replaces uninstall.php).
    rankd_tx_fs()->add_action( 'after_uninstall', 'rankd_tx_uninstall' );
}
// end else ( Freemius bootstrap )