<?php

/**
 * Plugin Name: TrustLens – Fraud Prevention & Chargeback Defense for WooCommerce
 * Plugin URI: https://webstepper.io/wordpress/plugins/trustlens
 * Description: Prevent WooCommerce fraud with behavioral trust scoring. Catch chargebacks, return abuse, coupon fraud, and card-testing attacks.
 * Version: 1.3.9
 * Author: Webstepper
 * Author URI: https://webstepper.io
 * License: GPLv3 or later
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: trustlens
 * Domain Path: /languages
 * Requires at least: 6.4
 * Tested up to: 7.0
 * Requires PHP: 7.4
 * WC requires at least: 8.0
 * WC tested up to: 9.5
 * Requires Plugins: woocommerce
 *
 *
 * @package TrustLens
 */
defined( 'ABSPATH' ) || exit;
if ( function_exists( 'wstl_fs' ) ) {
    // Another TrustLens build (free or premium) is already loaded this request.
    // Register THIS file's basename so the Freemius SDK can auto-deactivate the
    // duplicate version, then bail — the already-loaded copy owns the request.
    // Bailing here prevents double initialization and "constant already defined"
    // notices when both the free and premium versions are active at once.
    wstl_fs()->set_basename( false, __FILE__ );
    return;
}
if ( !function_exists( 'wstl_fs' ) ) {
    // Create a helper function for easy Freemius SDK access. Prefix wstl_ (4+ chars) per WordPress.org naming guidelines.
    function wstl_fs() {
        global $wstl_fs;
        if ( !isset( $wstl_fs ) ) {
            // Include Freemius SDK.
            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
            // IMPORTANT: keep this config an INLINE array literal passed straight to
            // fs_dynamic_init() — never assign it to a $variable first. Freemius's
            // deploy-time free-build transform (which flips the premium flag and
            // strips the security token below) is text/pattern based and only fires
            // on the inline form; a variable silently defeats it and ships a broken
            // free build (1.3.2). Also keep that token's literal key name OUT of
            // comments — a second occurrence defeats the text strip too (1.3.3).
            $wstl_fs = fs_dynamic_init( array(
                'id'               => '22495',
                'slug'             => 'trustlens',
                'type'             => 'plugin',
                'public_key'       => 'pk_1758562d770e826016238888879ea',
                'is_premium'       => false,
                'is_premium_only'  => false,
                'has_addons'       => false,
                'has_paid_plans'   => true,
                'is_org_compliant' => true,
                'has_affiliation'  => 'customers',
                'menu'             => array(
                    'slug'    => 'trustlens',
                    'support' => false,
                ),
                'is_live'          => true,
            ) );
        }
        return $wstl_fs;
    }

    // Init Freemius.
    wstl_fs();
    // Signal that SDK was initiated.
    do_action( 'wstl_fs_loaded' );
    // Hook uninstall cleanup to Freemius (replaces uninstall.php).
    wstl_fs()->add_action( 'after_uninstall', 'trustlens_uninstall' );
    // Benefit-focused, privacy-accurate opt-in copy. Only the MESSAGE is
    // customized — the screen layout stays Freemius's WordPress.org-approved,
    // conversion-optimized template (never edit vendor/freemius/templates/connect.php).
    // `connect_message` targets new installs; `connect_message_on_update` re-engages
    // existing users (who installed before the opt-in existed) when they update;
    // `connect-message_on-premium` is the license-activation screen shown by the
    // premium build (it uses a separate tag and only 3 args).
    wstl_fs()->add_filter(
        'connect_message',
        'wstl_fs_connect_message',
        10,
        6
    );
    wstl_fs()->add_filter(
        'connect_message_on_update',
        'wstl_fs_connect_message_on_update',
        10,
        6
    );
    wstl_fs()->add_filter(
        'connect-message_on-premium',
        'wstl_fs_connect_message_on_premium',
        10,
        3
    );
    // Clarify Freemius's "pending activation" copy. After opt-in/activation the SDK
    // shows a confirmation-email message in two places (the connect screen and a
    // sticky admin notice); without a clear "this finishes automatically" cue it
    // reads as a failed or looping activation. This uses the SDK's documented
    // i18n override (override_i18n -> fs_override_i18n) — no vendor template edits.
    // Both call sites share the 'pending-activation-message' key, so this single
    // override covers both. It MUST keep exactly three positional %s placeholders,
    // in order: 1) product name, 2) user email, 3) the action phrase
    // ("complete the opt-in"). Numbered (%1$s) placeholders would misalign on the
    // connect screen, where the first sprintf arg is the user's first name.
    //
    // Registered on `init` (not called here at plugin-load time) because the __()
    // inside the override would otherwise run before the 'trustlens' textdomain is
    // loadable, tripping WordPress 6.7+'s "translation loading triggered too early"
    // (_load_textdomain_just_in_time) notice. Freemius renders this string only on
    // later admin hooks, so init is comfortably early enough.
    add_action( 'init', 'wstl_fs_override_pending_activation_i18n' );
    // Brand the Freemius connect + Account screens with the real TrustLens icon.
    // Freemius's default lookup is assets/img/{slug}.* (which we don't ship), so
    // without this it would fetch a remote icon. Pointing it at our local SVG keeps
    // it on-brand and offline.
    wstl_fs()->add_filter( 'plugin_icon', 'wstl_fs_plugin_icon' );
    // Always land the admin on the TrustLens dashboard after the Freemius opt-in /
    // activation flow, whichever way they leave it (Allow & Continue, Skip, pending
    // email, or delegate). These SDK filters pin the destination Freemius computes in
    // get_after_activation_url(), so "opt-in first, dashboard after" is deterministic
    // across every exit path and both builds — no transient-TTL race.
    wstl_fs()->add_filter( 'after_connect_url', 'wstl_fs_after_activation_url' );
    wstl_fs()->add_filter( 'after_skip_url', 'wstl_fs_after_activation_url' );
    wstl_fs()->add_filter( 'after_pending_connect_url', 'wstl_fs_after_activation_url' );
    wstl_fs()->add_filter( 'after_delegation_url', 'wstl_fs_after_activation_url' );
    // Append a small on-brand resources block to the Freemius Account page.
    wstl_fs()->add_action( 'after_account_details', 'wstl_fs_render_account_extras' );
}
/**
 * Register the Freemius "pending activation" copy override.
 *
 * Hooked to `init` rather than run at plugin-load time so the __() below executes
 * after the 'trustlens' textdomain is loadable. Calling it earlier trips WordPress
 * 6.7+'s _load_textdomain_just_in_time "translation triggered too early" notice.
 * See the registration site above for the placeholder contract.
 *
 * @since 1.3.5
 * @return void
 */
function wstl_fs_override_pending_activation_i18n() {
    wstl_fs()->override_i18n( array(
        'pending-activation-message' => __( 'You should receive a confirmation email for %s at %s within a few minutes. Open it and click the button to %s — activation then completes automatically, so you can safely leave this page. Don\'t see it? Check your spam folder.', 'trustlens' ),
    ) );
}

/**
 * Custom Freemius opt-in message for new installs.
 *
 * The text args ($first_name, $product_title) arrive raw and are escaped here;
 * $site_link and $freemius_link arrive as pre-built, safe HTML from the SDK.
 *
 * @since 1.3.1
 * @param string $message       Default opt-in message (ignored — replaced).
 * @param string $first_name    Current user's first name (raw).
 * @param string $product_title Plugin name (raw).
 * @param string $user_login    Current user's login (raw, unused).
 * @param string $site_link     HTML link to the current site.
 * @param string $freemius_link HTML link to Freemius permissions/terms.
 * @return string Custom message HTML.
 */
function wstl_fs_connect_message(
    $message,
    $first_name,
    $product_title,
    $user_login,
    $site_link,
    $freemius_link
) {
    return sprintf(
        /* translators: 1: user's first name (bold), 2: product name (bold), 3: this site's link, 4: Freemius permissions/terms link. */
        __( 'Hey %1$s, welcome to %2$s! Opt in to share your site\'s basic WordPress environment (like PHP and plugin versions) from %3$s so we can keep TrustLens compatible with your store, fix issues faster, and send you security and feature updates. We never collect your customers\' data, orders, or payments. %4$s', 'trustlens' ),
        '<b>' . esc_html( $first_name ) . '</b>',
        '<b>' . esc_html( $product_title ) . '</b>',
        $site_link,
        $freemius_link
    );
}

/**
 * Custom Freemius opt-in message shown to existing users when the opt-in is
 * introduced on a plugin update — re-engages people who never saw it, without
 * nagging anyone who has already opted in or skipped.
 *
 * @since 1.3.1
 * @param string $message       Default message (ignored — replaced).
 * @param string $first_name    Current user's first name (raw).
 * @param string $product_title Plugin name (raw).
 * @param string $user_login    Current user's login (raw, unused).
 * @param string $site_link     HTML link to the current site.
 * @param string $freemius_link HTML link to Freemius permissions/terms.
 * @return string Custom message HTML.
 */
function wstl_fs_connect_message_on_update(
    $message,
    $first_name,
    $product_title,
    $user_login,
    $site_link,
    $freemius_link
) {
    return sprintf(
        /* translators: 1: user's first name (bold), 2: product name (bold), 3: this site's link, 4: Freemius permissions/terms link. */
        __( 'Hey %1$s, thanks for using %2$s! We\'ve added an optional opt-in: share your site\'s basic WordPress environment from %3$s so we can catch compatibility issues early and keep improving TrustLens. We never collect your customers\' data, orders, or payments, and skipping is fine — TrustLens keeps working. %4$s', 'trustlens' ),
        '<b>' . esc_html( $first_name ) . '</b>',
        '<b>' . esc_html( $product_title ) . '</b>',
        $site_link,
        $freemius_link
    );
}

/**
 * Custom message for the premium license-activation screen (shown by the Pro
 * build when a license key is required). This is a different Freemius filter
 * (`connect-message_on-premium`) than the opt-in messages and receives only
 * three arguments.
 *
 * @since 1.3.1
 * @param string $message       Default message (ignored — replaced).
 * @param string $first_name    Current user's first name (raw).
 * @param string $product_title Plugin name (raw).
 * @return string Custom message HTML.
 */
function wstl_fs_connect_message_on_premium(  $message, $first_name, $product_title  ) {
    return sprintf( 
        /* translators: 1: user's first name (bold), 2: product name (bold). */
        __( 'Welcome to %2$s Pro, %1$s! Enter your license key below to activate Pro — automation rules, the chargeback monitor and dispute evidence reports, scheduled reports, and payment-risk controls. Your key is in your purchase confirmation email and on your Freemius account dashboard.', 'trustlens' ),
        '<b>' . esc_html( $first_name ) . '</b>',
        '<b>' . esc_html( $product_title ) . '</b>'
     );
}

/**
 * Path to the local plugin icon used to brand the Freemius connect/account
 * screens (filter callback for Freemius `plugin_icon`).
 *
 * @since 1.3.1
 * @return string Absolute filesystem path to the icon.
 */
function wstl_fs_plugin_icon() {
    return TRUSTLENS_PLUGIN_DIR . 'assets/images/icon.svg';
}

/**
 * Force the TrustLens dashboard as the landing page after the Freemius opt-in /
 * activation flow.
 *
 * Freemius routes every opt-in exit (Allow & Continue, Skip, pending-email activation,
 * or delegate-to-admin) through Freemius::get_after_activation_url(), which applies the
 * after_connect_url / after_skip_url / after_pending_connect_url / after_delegation_url
 * filters to the URL it computed. Returning the dashboard here pins that destination so
 * a fresh install reliably lands on the dashboard after the opt-in screen instead of
 * depending on Freemius's implicit default. Network admin is left untouched — we never
 * force-redirect there (matches wstl_redirect_after_activation()).
 *
 * @since 1.3.9
 * @param string $url Freemius-computed destination URL.
 * @return string Dashboard URL for site-level admin; the original URL in network admin.
 */
function wstl_fs_after_activation_url(  $url  ) {
    if ( is_network_admin() ) {
        return $url;
    }
    return admin_url( 'admin.php?page=trustlens' );
}

/**
 * Append an on-brand "resources" block beneath the Freemius Account details.
 *
 * Runs on Freemius's `after_account_details` action. Uses minimal inline styling
 * because the plugin's admin CSS is not enqueued on the Freemius-rendered Account
 * page. All links are verified-live (docs hub) or SDK-generated (support/contact).
 *
 * @since 1.3.1
 */
function wstl_fs_render_account_extras() {
    $dashboard_url = admin_url( 'admin.php?page=trustlens' );
    $docs_url = ( function_exists( 'wstl_get_docs_url' ) ? wstl_get_docs_url() : 'https://webstepper.io/docs/trustlens/' );
    $support_url = ( function_exists( 'wstl_fs' ) && method_exists( wstl_fs(), 'contact_url' ) ? wstl_fs()->contact_url() : 'https://webstepper.io/wordpress/plugins/trustlens' );
    // On-brand card. Styling is inline because the plugin's admin CSS is not
    // enqueued on the Freemius-rendered Account page; #3377EE is the TrustLens
    // admin primary (see --wstl-color-primary in admin/css/admin.css).
    $card_style = 'margin:20px 0 0;padding:18px 22px;background:#fff;border:1px solid #e5e9f0;border-left:3px solid #3377EE;border-radius:10px;box-shadow:0 1px 2px rgba(16,24,40,.05);max-width:760px;';
    $link_style = 'color:#3377EE;text-decoration:none;font-weight:500;';
    $sep = '<span style="color:#c7ced9;" aria-hidden="true">&nbsp;&middot;&nbsp;</span>';
    ?>
	<div class="wstl-account-extras" style="<?php 
    echo esc_attr( $card_style );
    ?>">
		<h3 style="margin:0 0 4px;font-size:15px;font-weight:600;color:#1f2733;"><?php 
    esc_html_e( 'TrustLens resources', 'trustlens' );
    ?></h3>
		<p style="margin:0;font-size:13px;color:#5b6675;"><?php 
    esc_html_e( 'Guides, setup help, and support for your TrustLens install.', 'trustlens' );
    ?></p>
		<p style="margin:12px 0 0;font-size:13px;">
			<a style="<?php 
    echo esc_attr( $link_style );
    ?>" href="<?php 
    echo esc_url( $dashboard_url );
    ?>"><?php 
    esc_html_e( 'Dashboard', 'trustlens' );
    ?></a>
			<?php 
    echo $sep;
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static, hardcoded separator markup.
    ?>
			<a style="<?php 
    echo esc_attr( $link_style );
    ?>" href="<?php 
    echo esc_url( $docs_url );
    ?>" target="_blank" rel="noopener noreferrer"><?php 
    esc_html_e( 'Documentation', 'trustlens' );
    ?></a>
			<?php 
    echo $sep;
    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static, hardcoded separator markup.
    ?>
			<a style="<?php 
    echo esc_attr( $link_style );
    ?>" href="<?php 
    echo esc_url( $support_url );
    ?>" target="_blank" rel="noopener noreferrer"><?php 
    esc_html_e( 'Get support', 'trustlens' );
    ?></a>
		</p>
	</div>
	<?php 
}

/**
 * Remove all TrustLens data on uninstall.
 *
 * Hooked to Freemius after_uninstall action. Only runs if the user
 * has enabled data removal in Settings > Data.
 *
 * @since 1.0.0
 */
function trustlens_uninstall() {
    global $wpdb;
    require_once __DIR__ . '/includes/class-trustlens-data-purge.php';
    // Always clear scheduled work, regardless of the data-removal opt-in, so no
    // orphaned WP-Cron or Action Scheduler jobs reference the removed plugin.
    TrustLens_Data_Purge::purge_schedules();
    // Destructive data removal is opt-in.
    if ( !get_option( 'trustlens_remove_data_on_uninstall', false ) ) {
        return;
    }
    // Drift-proof, prefix-driven removal of every TrustLens table, option,
    // transient, and user-meta key.
    TrustLens_Data_Purge::purge_data( $wpdb );
}

/**
 * Plugin constants.
 */
define( 'TRUSTLENS_VERSION', '1.3.9' );
define( 'TRUSTLENS_DB_VERSION', '1.3.8' );
define( 'TRUSTLENS_PLUGIN_FILE', __FILE__ );
define( 'TRUSTLENS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'TRUSTLENS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'TRUSTLENS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
/*
 * Pro-build marker. Loaded as early as possible (and on every request, before
 * the requirements/WooCommerce gates) so TRUSTLENS_IS_PRO_BUILD is defined
 * before any code calls wstl_has_pro_features() / wstl_can_use_pro() — including
 * activation and the plugins_loaded:5 upgrade path. Stripped from the free build
 * by Freemius (it is listed in the @fs_premium_only header above), so the
 * constant is simply absent there. file_exists keeps the free path a no-op.
 */
if ( file_exists( TRUSTLENS_PLUGIN_DIR . 'includes/pro-marker.php' ) ) {
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/pro-marker.php';
}
/*
 * Translations: When hosted on WordPress.org, WordPress 4.6+ automatically loads
 * translations for the plugin slug (trustlens) from translate.wordpress.org.
 * We do not call load_plugin_textdomain() so that the directory auto-loading is used.
 * @see https://make.wordpress.org/core/2024/10/21/i18n-improvements-6-7/
 */
/**
 * Check plugin requirements before loading.
 *
 * @since 1.0.0
 * @return bool True if requirements met, false otherwise.
 */
function wstl_check_requirements() : bool {
    $errors = array();
    // Check PHP version.
    if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
        $errors[] = sprintf( 
            /* translators: %s: Required PHP version */
            __( 'TrustLens requires PHP %s or higher.', 'trustlens' ),
            '7.4'
         );
    }
    // Check WordPress version.
    if ( version_compare( get_bloginfo( 'version' ), '6.4', '<' ) ) {
        $errors[] = sprintf( 
            /* translators: %s: Required WordPress version */
            __( 'TrustLens requires WordPress %s or higher.', 'trustlens' ),
            '6.4'
         );
    }
    // Display errors if any.
    if ( !empty( $errors ) ) {
        add_action( 'admin_notices', function () use($errors) {
            echo '<div class="notice notice-error"><p>';
            echo esc_html( implode( ' ', $errors ) );
            echo '</p></div>';
        } );
        return false;
    }
    return true;
}

/**
 * Check if WooCommerce is active.
 *
 * @since 1.0.0
 * @return bool True if WooCommerce is active.
 */
function wstl_is_woocommerce_active() : bool {
    return class_exists( 'WooCommerce' );
}

/**
 * Display WooCommerce required notice.
 *
 * Only shows on WordPress < 6.5 where the Requires Plugins header
 * is not supported. On 6.5+, WordPress handles dependency notices natively.
 *
 * @since 1.0.0
 */
function wstl_woocommerce_required_notice() : void {
    // WordPress 6.5+ handles dependency notices via the Requires Plugins header.
    if ( version_compare( get_bloginfo( 'version' ), '6.5', '>=' ) ) {
        return;
    }
    if ( !current_user_can( 'activate_plugins' ) ) {
        return;
    }
    $install_url = admin_url( 'plugin-install.php?s=woocommerce&tab=search&type=term' );
    echo '<div class="notice notice-warning"><p>';
    printf( 
        /* translators: %1$s: opening link tag, %2$s: closing link tag */
        esc_html__( 'TrustLens requires WooCommerce to be installed and active. %1$sInstall WooCommerce%2$s', 'trustlens' ),
        '<a href="' . esc_url( $install_url ) . '">',
        '</a>'
     );
    echo '</p></div>';
}

/**
 * Initialize the plugin.
 *
 * @since 1.0.0
 */
function wstl_init() : void {
    // Check WooCommerce.
    if ( !wstl_is_woocommerce_active() ) {
        add_action( 'admin_notices', 'wstl_woocommerce_required_notice' );
        return;
    }
    // Load plugin files.
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/functions.php';
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/class-trustlens.php';
    // Initialize main class.
    TrustLens::instance();
}

/**
 * Run on plugin activation.
 *
 * @since 1.0.0
 */
function wstl_activate() : void {
    // Activation fires BEFORE the normal loader (wstl_init) that pulls in
    // functions.php, but the install routine depends on its helpers (e.g.
    // wstl_setting_defaults() in create_options()). Load it here first.
    // require_once is idempotent if the normal loader already ran.
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/functions.php';
    // Check requirements first.
    if ( !wstl_check_requirements() ) {
        return;
    }
    // Load install class and run activation.
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/class-install.php';
    TrustLens_Install::activate();
}

/**
 * Redirect to TrustLens dashboard immediately after activation.
 *
 * This provides a reliable redirect path on each activation, including
 * cases where transient-based redirects may be skipped.
 *
 * @since 1.1.1
 * @param string $plugin       Activated plugin basename.
 * @param bool   $network_wide Whether activated network-wide.
 */
function wstl_redirect_after_activation(  string $plugin, bool $network_wide  ) : void {
    if ( TRUSTLENS_PLUGIN_BASENAME !== $plugin ) {
        return;
    }
    if ( wp_doing_ajax() || defined( 'WP_CLI' ) && WP_CLI ) {
        return;
    }
    // Do not redirect on multisite network activation or bulk activation.
    // phpcs:disable WordPress.Security.NonceVerification.Recommended
    if ( $network_wide || is_network_admin() || isset( $_GET['activate-multi'] ) ) {
        return;
    }
    // phpcs:enable WordPress.Security.NonceVerification.Recommended
    // If the Freemius opt-in / license-activation screen is still pending (a fresh
    // install that hasn't opted in or skipped yet), don't bounce through the
    // dashboard. Let Freemius redirect straight to its connect screen so the
    // first-run funnel is opt-in first, dashboard after. Users who already opted
    // in or skipped are not in activation mode, so they still land on the dashboard.
    if ( function_exists( 'wstl_fs' ) && wstl_fs()->is_activation_mode() ) {
        return;
    }
    wp_safe_redirect( admin_url( 'admin.php?page=trustlens' ) );
    exit;
}

/**
 * Run on plugin deactivation.
 *
 * @since 1.0.0
 */
function wstl_deactivate() : void {
    require_once TRUSTLENS_PLUGIN_DIR . 'includes/class-install.php';
    if ( file_exists( TRUSTLENS_PLUGIN_DIR . 'includes/class-scheduled-reports.php' ) ) {
        require_once TRUSTLENS_PLUGIN_DIR . 'includes/class-scheduled-reports.php';
    }
    TrustLens_Install::deactivate();
}

// Check requirements.
if ( !wstl_check_requirements() ) {
    return;
}
// Register custom cron schedules.
add_filter( 'cron_schedules', 'wstl_cron_schedules' );
/**
 * Add custom cron intervals.
 *
 * @since 1.0.0
 * @param array $schedules Existing cron schedules.
 * @return array Modified schedules.
 */
function wstl_cron_schedules(  array $schedules  ) : array {
    if ( !isset( $schedules['weekly'] ) ) {
        $schedules['weekly'] = array(
            'interval' => WEEK_IN_SECONDS,
            'display'  => __( 'Once Weekly', 'trustlens' ),
        );
    }
    if ( !isset( $schedules['monthly'] ) ) {
        $schedules['monthly'] = array(
            'interval' => MONTH_IN_SECONDS,
            'display'  => __( 'Once Monthly', 'trustlens' ),
        );
    }
    return $schedules;
}

// Register activation/deactivation hooks.
register_activation_hook( __FILE__, 'wstl_activate' );
register_deactivation_hook( __FILE__, 'wstl_deactivate' );
add_action(
    'activated_plugin',
    'wstl_redirect_after_activation',
    20,
    2
);
// Surface Dashboard + Settings shortcuts on the WordPress Plugins screen.
add_filter( 'plugin_action_links_' . TRUSTLENS_PLUGIN_BASENAME, function ( array $links ) : array {
    $shortcuts = array('<a href="' . esc_url( admin_url( 'admin.php?page=trustlens' ) ) . '">' . esc_html__( 'Dashboard', 'trustlens' ) . '</a>', '<a href="' . esc_url( admin_url( 'admin.php?page=trustlens-settings' ) ) . '">' . esc_html__( 'Settings', 'trustlens' ) . '</a>');
    return array_merge( $shortcuts, $links );
} );
// Load install class at module scope so its `plugins_loaded:5` maybe_upgrade
// hook actually fires on normal requests (and not only during activation).
// Needed so silent WP plugin updates still pick up DB migrations and one-time
// backfill scheduling without requiring a manual deactivate/reactivate.
require_once TRUSTLENS_PLUGIN_DIR . 'includes/class-install.php';
// Initialize plugin after plugins are loaded.
add_action( 'plugins_loaded', 'wstl_init' );
/**
 * Declare HPOS compatibility.
 *
 * @since 1.0.0
 */
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 );
    }
} );