<?php
/**
 * Plugin Name:       Quick Variable Products – Generate Variations for WooCommerce
 * Plugin URI:        https://erenlabs.net/
 * Description:       Create variable products fast: pick attributes, generate every variation in one click, then set price, SKU and stock per row. No CSV needed. Pro adds a catalog bulk editor, add-to-existing, recipes and conflict-safe undo.
 * Version:           1.1.3
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 * Author:            Eren Labs
 * Author URI:        https://erenlabs.net
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       quick-variable-products
 * Domain Path:       /languages
 * WC requires at least: 6.0
 * WC tested up to:   10.9
 *
 *
 * @package ErenLabs\QuickVariableProducts
 */

defined( 'ABSPATH' ) || exit;

// defined()/function_exists() guards throughout this file: the free and Pro builds
// are separate installable artifacts of the SAME plugin. While Freemius switches a
// site from free to Pro, both can briefly be installed together; the guards keep
// that window fatal-free so the plugins_loaded yield (Core::should_boot) can run.
if ( ! defined( 'ERENQVP_VER' ) ) {
	define( 'ERENQVP_VER', '1.1.3' );
	define( 'ERENQVP_FILE', __FILE__ );
	define( 'ERENQVP_DIR', plugin_dir_path( __FILE__ ) );
	define( 'ERENQVP_URL', plugin_dir_url( __FILE__ ) );
	define( 'ERENQVP_BASENAME', plugin_basename( __FILE__ ) );
	define( 'ERENQVP_CAP', 'manage_woocommerce' ); // Who can use the Quick Add screen.
}


/** Multibyte-safe helpers that also work on minimal PHP builds without mbstring. */
if ( ! function_exists( 'erenqvp_substr' ) ) :
function erenqvp_substr( $value, $start, $length = null ) {
	$value = (string) $value;
	if ( function_exists( 'mb_substr' ) ) {
		return null === $length ? mb_substr( $value, $start, mb_strlen( $value, 'UTF-8' ), 'UTF-8' ) : mb_substr( $value, $start, $length, 'UTF-8' );
	}
	if ( function_exists( 'iconv_substr' ) ) {
		$result = null === $length ? iconv_substr( $value, $start, 2147483647, 'UTF-8' ) : iconv_substr( $value, $start, $length, 'UTF-8' );
		if ( false !== $result ) {
			return $result;
		}
	}
	return null === $length ? substr( $value, $start ) : substr( $value, $start, $length );
}
endif;

if ( ! function_exists( 'erenqvp_strlen' ) ) :
function erenqvp_strlen( $value ) {
	$value = (string) $value;
	if ( function_exists( 'mb_strlen' ) ) {
		return mb_strlen( $value, 'UTF-8' );
	}
	if ( function_exists( 'iconv_strlen' ) ) {
		$length = iconv_strlen( $value, 'UTF-8' );
		if ( false !== $length ) {
			return $length;
		}
	}
	return strlen( $value );
}
endif;

if ( ! function_exists( 'erenqvp_strtolower' ) ) :
function erenqvp_strtolower( $value ) {
	return function_exists( 'mb_strtolower' ) ? mb_strtolower( (string) $value, 'UTF-8' ) : strtolower( (string) $value );
}
endif;

/**
 * Asset version string: plugin version + the file's mtime, so a changed asset
 * always busts browser/proxy caches even between releases that share a version
 * (a within-version hotfix, or a same-version rebuild). Falls back to the plugin
 * version if the file is missing.
 *
 * @param string $rel Path relative to the plugin root, e.g. 'assets/qvp-admin.js'.
 * @return string
 */
if ( ! function_exists( 'erenqvp_asset_ver' ) ) :
function erenqvp_asset_ver( $rel ) {
	$path = ERENQVP_DIR . ltrim( $rel, '/' );
	return is_readable( $path ) ? ERENQVP_VER . '.' . (string) filemtime( $path ) : ERENQVP_VER;
}
endif;

// Freemius SDK — ships in BOTH builds (GPL, wp.org-compliant). It powers opt-in,
// licence checks, automatic updates and the checkout/upgrade UI. is_premium is
// true only in the pro build (which defines PRO_ACTIVE); the pro licence gate is
// bound to can_use_premium_code, so a licence never flips features in the free
// build (it carries no premium code). The id + public_key below are the real
// Freemius product credentials (both public values); the secret key is never
// committed. The block stays a no-op until the SDK is bundled in freemius/ —
// see docs/FREEMIUS-TODO.md.
if ( ! function_exists( 'erenqvp_fs' ) ) {
	/**
	 * Freemius instance singleton.
	 *
	 * @return Freemius|null
	 */
	function erenqvp_fs() {
		global $erenqvp_fs;
		if ( ! isset( $erenqvp_fs ) ) {
			$erenqvp_fs = null;
			if ( is_readable( __DIR__ . '/freemius/start.php' ) ) {
				require_once __DIR__ . '/freemius/start.php';
				$erenqvp_fs = fs_dynamic_init(
					array(
						'id'                  => '34490',
						'slug'                => 'quick-variable-products',
						'type'                => 'plugin',
						'public_key'          => 'pk_db583c5d7cd12b21ace90003c97b1',
						'is_premium'          => false,
						'premium_suffix'      => 'Pro',
						'has_premium_version' => true,
						'has_addons'          => false,
						'has_paid_plans'      => true,
						'is_org_compliant'    => true,
						'menu'                => array(
							'slug'       => 'erenqvp-add',
							'first-path' => 'admin.php?page=erenqvp-add',
							'account'    => true,
							'contact'    => true,
							'support'    => false,
						),
					)
				);
			}
		}
		return $erenqvp_fs;
	}

	// Init Freemius (only when the bundled SDK is present).
	if ( erenqvp_fs() ) {
		erenqvp_fs()->add_filter(
			'plugin_icon',
			static function () {
				return __DIR__ . '/assets/img/icon-256.png';
			}
		);
		do_action( 'erenqvp_fs_loaded' );

		// The SDK's English-only pricing submenu is replaced by our own localized
		// Upgrade screen; the direct-support Contact page is part of the paid offering.
		erenqvp_fs()->add_filter(
			'is_submenu_visible',
			static function ( $visible, $id ) {
				if ( 'pricing' === $id ) {
					return false;
				}
				if ( 'contact' === $id ) {
					return function_exists( 'erenqvp_is_pro' ) ? erenqvp_is_pro() : $visible;
				}
				return $visible;
			},
			10,
			2
		);
	}
}

/**
 * Whether Pro features are unlocked. The free build always returns false (it
 * carries no premium code); the pro build binds this to the Freemius licence.
 *
 * @return bool
 */
if ( ! function_exists( 'erenqvp_is_pro' ) ) :
function erenqvp_is_pro() {
	$pro = false;
	return (bool) apply_filters( 'erenqvp_is_pro', $pro );
}
endif;


/**
 * Per-licence-tier Freemius CHECKOUT URL. The hosted checkout is the only
 * purchase destination (never a marketing page): product 34490, plan "pro"
 * 56756 — the Single/5/20-site annual prices live in the Freemius dashboard,
 * so a price change there never requires a plugin release.
 *
 * @param int $licenses Licence count of the tier (1, 5 or 20).
 * @return string
 */
if ( ! function_exists( 'erenqvp_checkout_url' ) ) :
function erenqvp_checkout_url( $licenses = 1 ) {
	$url = 'https://checkout.freemius.com/plugin/34490/plan/56756/?licenses=' . max( 1, absint( $licenses ) );
	return (string) apply_filters( 'erenqvp_checkout_url', $url, $licenses );
}
endif;

/**
 * Where upgrade buttons point: the Freemius checkout (single-site tier). Once
 * the SDK ships in freemius/, this can defer to the SDK's checkout URL — never
 * to get_upgrade_url(), whose in-dashboard pricing submenu we deliberately hide
 * in favour of the localized Upgrade landing page.
 *
 * @return string
 */
if ( ! function_exists( 'erenqvp_upgrade_url' ) ) :
function erenqvp_upgrade_url() {
	return (string) apply_filters( 'erenqvp_upgrade_url', erenqvp_checkout_url( 1 ) );
}
endif;

/**
 * Shipped translation languages (native names), in display order.
 *
 * @return array locale => native label
 */
if ( ! function_exists( 'erenqvp_locales' ) ) :
function erenqvp_locales() {
	return (array) apply_filters(
		'erenqvp_locales',
		array(
			'en_US' => 'English',
			'tr_TR' => 'Türkçe',
			'es_ES' => 'Español',
			'de_DE' => 'Deutsch',
			'fr_FR' => 'Français',
			'it_IT' => 'Italiano',
			'pt_BR' => 'Português (Brasil)',
			'ar'    => 'العربية',
		)
	);
}
endif;

/**
 * Valid values for the switcher: 'auto' (follow the site) plus every shipped locale.
 *
 * @return array
 */
if ( ! function_exists( 'erenqvp_available_locales' ) ) :
function erenqvp_available_locales() {
	return array_merge( array( 'auto' => __( 'Automatic (site language)', 'quick-variable-products' ) ), erenqvp_locales() );
}
endif;

/**
 * The current user's per-plugin language preference ('auto' = follow the site).
 *
 * @return string
 */
if ( ! function_exists( 'erenqvp_user_locale_pref' ) ) :
function erenqvp_user_locale_pref() {
	$pref = get_user_meta( get_current_user_id(), 'erenqvp_locale', true );
	return ( $pref && array_key_exists( $pref, erenqvp_available_locales() ) ) ? $pref : 'auto';
}
endif;

/**
 * The locale actually shown to the current user for this plugin: their explicit
 * choice, or the closest shipped match to the site language.
 *
 * @return string
 */
if ( ! function_exists( 'erenqvp_effective_locale' ) ) :
function erenqvp_effective_locale() {
	$pref = erenqvp_user_locale_pref();
	if ( 'auto' !== $pref ) {
		return $pref;
	}
	$known = array_keys( erenqvp_locales() );
	$site  = get_locale();
	if ( in_array( $site, $known, true ) ) {
		return $site;
	}
	$lang = substr( $site, 0, 2 );
	foreach ( $known as $k ) {
		if ( substr( $k, 0, 2 ) === $lang ) {
			return $k;
		}
	}
	return 'en_US';
}
endif;

/**
 * @param string $locale Locale code.
 * @return bool Whether the locale is right-to-left.
 */
if ( ! function_exists( 'erenqvp_is_rtl_locale' ) ) :
function erenqvp_is_rtl_locale( $locale ) {
	return in_array( substr( (string) $locale, 0, 2 ), array( 'ar', 'he', 'fa', 'ur', 'ps' ), true );
}
endif;

/**
 * Where "I need help" goes: the wp.org support forum for free users, the Freemius
 * contact page once the SDK is present and the licence is active. Filterable.
 *
 * @return string
 */
if ( ! function_exists( 'erenqvp_support_url' ) ) :
function erenqvp_support_url() {
	$url = 'https://wordpress.org/support/plugin/quick-variable-products/';
	if ( function_exists( 'erenqvp_fs' ) && erenqvp_fs() && erenqvp_is_pro() ) {
		$url = erenqvp_fs()->contact_url();
	}
	return (string) apply_filters( 'erenqvp_support_url', $url );
}
endif;

/**
 * Apply the per-user language override to OUR text domain only — every other
 * plugin and the rest of wp-admin keep the site language.
 */
add_filter(
	'plugin_locale',
	static function ( $locale, $domain ) {
		if ( 'quick-variable-products' === $domain ) {
			$pref = erenqvp_user_locale_pref();
			if ( 'auto' !== $pref ) {
				return $pref;
			}
		}
		return $locale;
	},
	10,
	2
);

// Uninstall cleanup. The free build also ships uninstall.php (wp.org runs it
// standalone); the pro build ships none (the Freemius deployment processor
// requires uninstall.php be absent) — it cleans up through the SDK's
// after_uninstall hook instead. The routine is idempotent.
require_once ERENQVP_DIR . 'includes/uninstall-cleanup.php';
if ( function_exists( 'erenqvp_fs' ) && erenqvp_fs() ) {
	erenqvp_fs()->add_action( 'after_uninstall', 'erenqvp_uninstall_cleanup' );
}

// Factory autoloader (Strauss-prefixed shared core). Optional at runtime: a dev
// tree deployed without composer still runs the free engine through the manual
// requires below; only the Core wiring (HPOS declaration, should_boot guard) needs it.
$erenqvp_autoload = __DIR__ . '/vendor/autoload.php';
if ( is_readable( $erenqvp_autoload ) ) {
	require $erenqvp_autoload;
}


/**
 * First-run onboarding: after single activation, land the user on the Add Product screen.
 */
register_activation_hook(
	__FILE__,
	static function () {
		// No capabilities to grant (the screens use the core manage_woocommerce cap) and no custom
		// tables to migrate, so activation only primes the first-run onboarding redirect.
		set_transient( 'erenqvp_activation_redirect', 1, 60 );
	}
);
add_action(
	'admin_init',
	static function () {
		if ( ! get_transient( 'erenqvp_activation_redirect' ) ) {
			return;
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only bulk-activation check; nothing is processed.
		if ( wp_doing_ajax() || is_network_admin() || isset( $_GET['activate-multi'] ) ) {
			return;
		}
		delete_transient( 'erenqvp_activation_redirect' );
		if ( ! class_exists( 'WooCommerce' ) || ! current_user_can( ERENQVP_CAP ) ) {
			return;
		}
		wp_safe_redirect( admin_url( 'admin.php?page=erenqvp-add' ) );
		exit;
	}
);

/**
 * Declare HPOS compatibility. When the factory core is present it declares this
 * (plus checkout-blocks) itself in Bootstrap::core(); this manual fallback covers
 * a dev tree deployed without vendor/.
 */
add_action(
	'before_woocommerce_init',
	static function () {
		if ( class_exists( '\\ErenLabs\\QuickVariableProducts\\Bootstrap' ) ) {
			return; // Core::boot() declares compatibility.
		}
		if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
			\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
		}
	}
);

/**
 * Boot once all plugins are loaded so we can check for WooCommerce.
 */
add_action(
	'plugins_loaded',
	static function () {
		// Factory core wiring: boot it (declares HPOS/blocks compat) and honour the
		// free-yields-to-pro guard. Degrades gracefully when deployed without vendor/.
		if ( class_exists( '\\ErenLabs\\QuickVariableProducts\\Bootstrap' ) && ! \ErenLabs\QuickVariableProducts\Bootstrap::should_boot() ) {
			return;
		}
		// Exactly-one-boot guard: in the brief window where the free AND Pro artifacts
		// are both installed and active, whichever copy gets here first boots the engine;
		// the other returns instead of redeclaring the ERENQVP_* classes (a fatal).
		if ( defined( 'ERENQVP_BOOTED' ) ) {
			return;
		}
		define( 'ERENQVP_BOOTED', true );

		add_action(
			'init',
			static function () {
				$domain = 'quick-variable-products';
				$pref   = erenqvp_user_locale_pref();
				if ( 'auto' === $pref ) {
					// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- the pro build is distributed off WordPress.org and bundles translations; the free build's wp.org language packs still auto-load.
					load_plugin_textdomain( $domain, false, dirname( ERENQVP_BASENAME ) . '/languages' );
					return;
				}
				// Explicit per-user override. Since WP 6.5 translations are stored PER LOCALE, so the
				// preferred .mo must be loaded under the CURRENT locale key for lookups to hit it.
				// Unloading first also marks the domain non-reloadable, stopping the just-in-time loader
				// from pulling the site language back in (exactly what the en_US choice wants).
				unload_textdomain( $domain );
				if ( 'en_US' !== $pref ) {
					$current = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
					// Load the preferred locale from the bundled catalog (pro build) OR the
					// WordPress.org language pack (free build, once the translations from
					// translate.wordpress.org are installed). Whichever exists wins.
					foreach ( array(
						ERENQVP_DIR . 'languages/' . $domain . '-' . $pref . '.mo',
						WP_LANG_DIR . '/plugins/' . $domain . '-' . $pref . '.mo',
					) as $mofile ) {
						if ( file_exists( $mofile ) ) {
							load_textdomain( $domain, $mofile, $current );
							break;
						}
					}
				}
			},
			0
		);

		if ( ! class_exists( 'WooCommerce' ) ) {
			add_action(
				'admin_notices',
				static function () {
					echo '<div class="notice notice-error"><p>';
					echo esc_html__( 'Quick Variable Products requires WooCommerce to be installed and active.', 'quick-variable-products' );
					echo '</p></div>';
				}
			);
			return;
		}

		// Free engine.
		require_once ERENQVP_DIR . 'includes/class-qvp-attributes.php';
		require_once ERENQVP_DIR . 'includes/class-qvp-barcode.php';
		require_once ERENQVP_DIR . 'includes/class-qvp-product.php';
		require_once ERENQVP_DIR . 'includes/class-qvp-ajax.php';
		require_once ERENQVP_DIR . 'includes/class-qvp-admin.php';
		require_once ERENQVP_DIR . 'includes/class-qvp-upgrade.php';

		ERENQVP_Ajax::init();
		ERENQVP_Admin::init();
		// The Upgrade page ships in BOTH builds — it is the one Pro pointer wp.org
		// allows in free. It hides itself for users who already have Pro.
		ERENQVP_Upgrade::init();

	},
	20
);

/**
 * Settings link on the Plugins screen.
 */
add_filter(
	'plugin_action_links_' . plugin_basename( __FILE__ ),
	static function ( $links ) {
		$url  = admin_url( 'admin.php?page=erenqvp-add' );
		$open = '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Add Product', 'quick-variable-products' ) . '</a>';
		array_unshift( $links, $open );
		return $links;
	}
);
