<?php
/**
 * Plugin Name:       CapiFlow – Conversions API (CAPI) Tracking for WooCommerce
 * Plugin URI:        https://capiflowpro.com/capiflow/
 * Description:       Server-side Meta Conversions API (CAPI) tracking for WooCommerce. Maximize ad attribution with browser pixel deduplication, 10/10 Event Match Quality, fraud alerts, and an analytics dashboard.
 * Version:           1.1.1
 * Requires at least: 6.0
 * Requires PHP:      7.4
 * Author:            CapiFlow
 * Author URI:        https://capiflowpro.com/about/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       capiflow
 * Domain Path:       /languages
 * Requires Plugins:  woocommerce
 * WC requires at least: 7.0
 * WC tested up to:   9.0
 *
 * @package CapiFlow
 */

defined('ABSPATH') || exit;

/*
 * Guard against duplicate plugin loading (e.g. plugin + plugin-1 both active).
 * Only the FIRST loaded copy boots. All subsequent copies are silently skipped.
 * This prevents "Cannot redeclare" fatal errors.
 */
if (defined('CAPIFLOW_VERSION')) {
	return;
}

/**
 * ─── Plugin Constants ──────────────────────────────────────────────────────────
 */
define('CAPIFLOW_VERSION', '1.1.1');
define('CAPIFLOW_PLUGIN_FILE', __FILE__);
define('CAPIFLOW_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('CAPIFLOW_PLUGIN_URL', plugin_dir_url(__FILE__));
define('CAPIFLOW_PLUGIN_BASENAME', plugin_basename(__FILE__));
define('CAPIFLOW_MIN_WC_VERSION', '7.0');
define('CAPIFLOW_MIN_PHP_VERSION', '7.4');

/**
 * ─── WP 6.7+ / PHP 8.1+ Deprecation Fix (3-Layer) ─────────────────────────────
 *
 * WordPress 6.7's _load_textdomain_just_in_time() calls strpos() on the return
 * value of $wp_textdomain_registry->get(), which is NULL when no .mo file is
 * registered in wp-content/languages/plugins/. PHP 8.1 emits E_DEPRECATED for
 * strpos(null, ...) → that output breaks headers on every redirect.
 *
 * This affects ALL textdomains (ours AND woocommerce) when called before 'init'.
 *
 * Layer 1: NOOP_Translations in $l10n — makes get_translations_for_domain()
 *          return immediately without calling the JIT loader for OUR domain.
 * Layer 2: Targeted PHP error handler — suppresses E_DEPRECATED for strpos(null)
 *          and str_replace(null) from wp-includes/functions.php ONLY, covering
 *          ALL domains (including 'woocommerce' triggered by our WC interactions).
 * Layer 3: Suppress _doing_it_wrong() for _load_textdomain_just_in_time — the
 *          notice itself cascades into str_replace(null) on WP 6.7+.
 *
 * load_plugin_textdomain() on 'init' overwrites Layer 1 with real translations.
 */
// Layer 1: $l10n NOOP (backward-compatible, works on WP 5.0+).
global $l10n;
if (!isset($l10n['capiflow'])) {
	if (!class_exists('NOOP_Translations', false)) {
		require_once ABSPATH . WPINC . '/pomo/translations.php';
	}
	// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
	$l10n['capiflow'] = new \NOOP_Translations();
}

// Layer 2: Suppress "Passing null" deprecations from WordPress core files (PHP 8.1+).
//
// WordPress core has multiple strpos(null)/str_replace(null)/strip_tags(null) bugs
// in wp-includes/functions.php and wp-admin/admin-header.php.
// Tracked in WP Trac: https://core.trac.wordpress.org/ticket/57579
//                      https://core.trac.wordpress.org/ticket/58699
//
// On servers where ini_set('display_errors', 0) is blocked, these produce output
// that breaks headers. The handler is triple-scoped for safety:
//   1. Only installs on PHP 8.1+ (where E_DEPRECATED fires for null params)
//   2. Only catches E_DEPRECATED severity
//   3. Only suppresses "Passing null" messages from wp-includes/ and wp-admin/
// On WP 6.8+ (where these bugs are fixed), no deprecations match → handler is a no-op.
if (PHP_VERSION_ID >= 80100) {
	set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
		function ($severity, $message, $file) {
			if (
				E_DEPRECATED === $severity
				&& false !== strpos($message, 'Passing null')
				&& (false !== strpos($file, ABSPATH . 'wp-includes' . DIRECTORY_SEPARATOR)
					|| false !== strpos($file, ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR))
			) {
				return true; // Suppress — WP core null-safety bugs, resolved in WP 6.8+.
			}
			return false; // Let default/previous handler process.
		},
		E_DEPRECATED
	);
}

// Layer 3: Suppress _doing_it_wrong() for JIT textdomain loading.
// The notice itself triggers str_replace(null) in _doing_it_wrong() on WP 6.7+/PHP 8.1.
add_filter('doing_it_wrong_trigger_error', function ($trigger, $function_name) {
	if ('_load_textdomain_just_in_time' === $function_name) {
		return false;
	}
	return $trigger;
}, 10, 2);

/**
 * ─── HPOS Compatibility Declaration (WooCommerce 8.2+ mandatory) ───────────────
 */
add_action('before_woocommerce_init', function () {
	if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
		\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
			'custom_order_tables',
			__FILE__,
			true
		);
	}
});

/**
 * ─── Environment Checks ────────────────────────────────────────────────────────
 *
 * IMPORTANT: Do NOT use __() or any translation functions here.
 * This function is called from capiflow_init() which fires on plugins_loaded
 * (priority 20) — BEFORE the 'init' hook where textdomains are loaded.
 * Using __() here triggers WP 6.7+ JIT textdomain loading → _doing_it_wrong()
 * → wp_trigger_error(null) → strpos(null) → PHP 8.1+ deprecation notice
 * → output → headers already sent → redirect failure.
 *
 * Plain English is fine — these only show on environmental failures.
 *
 * @return string[] Array of error messages. Empty = all good.
 */
function capiflow_check_requirements()
{
	$errors = array();

	if (version_compare(PHP_VERSION, CAPIFLOW_MIN_PHP_VERSION, '<')) {
		$errors[] = 'CapiFlow requires PHP ' . CAPIFLOW_MIN_PHP_VERSION . ' or higher. You are running PHP ' . PHP_VERSION . '.';
	}

	if (!class_exists('WooCommerce')) {
		$errors[] = 'CapiFlow requires WooCommerce to be installed and activated.';
	} elseif (defined('WC_VERSION') && version_compare(WC_VERSION, CAPIFLOW_MIN_WC_VERSION, '<')) {
		$errors[] = 'CapiFlow requires WooCommerce ' . CAPIFLOW_MIN_WC_VERSION . ' or higher. You are running ' . WC_VERSION . '.';
	}

	if (!extension_loaded('openssl')) {
		$errors[] = 'CapiFlow requires the OpenSSL PHP extension.';
	}

	return $errors;
}

/**
 * ─── Admin Error Notices ────────────────────────────────────────────────────────
 */
function capiflow_admin_notices()
{
	$errors = capiflow_check_requirements();
	if (empty($errors)) {
		return;
	}
	foreach ($errors as $error) {
		echo '<div class="notice notice-error"><p><strong>CapiFlow:</strong> '
			. esc_html($error) . '</p></div>';
	}
}
add_action('admin_notices', 'capiflow_admin_notices');

/**
 * ─── Plugin Activation Hook ────────────────────────────────────────────────────
 *
 * IMPORTANT: Do NOT use __() or any translation functions here.
 * Activation fires before 'init', so textdomains are not loaded.
 * WP 6.7+ will trigger _doing_it_wrong() → wp_trigger_error(null) → strpos(null)
 * which causes a PHP 8.1+ deprecation notice that breaks the redirect headers.
 *
 * Use plain English strings only. Translated error messages are shown
 * via capiflow_admin_notices() which fires on admin_notices (after init).
 */
function capiflow_activate()
{
	// Raw requirement checks — NO __() calls (textdomain not loaded yet).
	$errors = array();

	if (version_compare(PHP_VERSION, CAPIFLOW_MIN_PHP_VERSION, '<')) {
		$errors[] = 'CapiFlow requires PHP ' . CAPIFLOW_MIN_PHP_VERSION . ' or higher. You are running PHP ' . PHP_VERSION . '.';
	}

	if (!class_exists('WooCommerce')) {
		$errors[] = 'CapiFlow requires WooCommerce to be installed and activated.';
	} elseif (defined('WC_VERSION') && version_compare(WC_VERSION, CAPIFLOW_MIN_WC_VERSION, '<')) {
		$errors[] = 'CapiFlow requires WooCommerce ' . CAPIFLOW_MIN_WC_VERSION . ' or higher. You are running ' . WC_VERSION . '.';
	}

	if (!extension_loaded('openssl')) {
		$errors[] = 'CapiFlow requires the OpenSSL PHP extension.';
	}

	if (!empty($errors)) {
		deactivate_plugins(plugin_basename(__FILE__));
		wp_die(
			wp_kses(implode('<br>', $errors), array('br' => array())),
			'Plugin Activation Error',
			array('back_link' => true)
		);
	}

	// Create database tables.
	require_once CAPIFLOW_PLUGIN_DIR . 'includes/class-db-manager.php';
	\CapiFlow\Core\DB_Manager::create_tables();

	// Set default options (negative events removed — Pro only).
	$defaults = array(
		'capiflow_graph_api_version' => 'v25.0',
		'capiflow_purchase_trigger' => 'processing',
		'capiflow_queue_interval' => 1,
		'capiflow_batch_size' => 50,
		'capiflow_api_timeout' => 15,
		'capiflow_hourly_rate_limit' => 2000,
		'capiflow_global_hourly_limit' => 10000,
		'capiflow_ip_trusted_limit' => 300,
		'capiflow_ip_suspicious_limit' => 30,
		'capiflow_environment_mode' => 'production',
		'capiflow_debug_mode' => false,
		'capiflow_send_pageview' => true,
		'capiflow_send_viewcontent' => true,
		'capiflow_send_search' => false,
		'capiflow_send_addtocart' => true,
		'capiflow_send_initiatecheckout' => true,
		'capiflow_send_addpaymentinfo' => false,
		'capiflow_send_purchase' => true,
		'capiflow_keep_data_on_uninstall' => true,
	);

	foreach ($defaults as $key => $value) {
		if (false === get_option($key)) {
			add_option($key, $value);
		}
	}

	// Store activation timestamp — used to skip pre-existing orders.
	// add_option() won't overwrite if already set (safe for plugin updates/reactivations).
	if (false === get_option('capiflow_installed_at')) {
		add_option('capiflow_installed_at', time());
	}

	// Set transient for post-activation redirect to Setup Wizard.
	set_transient('capiflow_activation_redirect', 1, 30);

	// Fire Pro hook (Pro plugin creates its own tables here).
	do_action('capiflow_activation_tables');

	// Note: flush_rewrite_rules() removed — it triggers early translation
	// loading on WP 6.7+ and causes the same strpos(null) cascade.
	// REST routes registered via register_rest_route() don't need flushing.
}
register_activation_hook(__FILE__, 'capiflow_activate');

/**
 * ─── Plugin Deactivation Hook ──────────────────────────────────────────────────
 *
 * Unschedule ALL Action Scheduler hooks to prevent Fatal Errors after deactivation.
 * Re-activation re-schedules via Plugin_Loader::schedule_tasks().
 */
function capiflow_deactivate()
{
	if (function_exists('as_unschedule_all_actions')) {
		foreach (array(
			'capiflow_process_queue',
			'capiflow_record_purchase_analytics',
			'capiflow_scan_abandoned_carts',
			'capiflow_fetch_dataset_quality',
			'capiflow_daily_maintenance',
		) as $hook) {
			as_unschedule_all_actions($hook);
		}
	}

	// Clear activation redirect transient.
	delete_transient('capiflow_activation_redirect');

	// Note: flush_rewrite_rules() removed — same WP 6.7+/PHP 8.1+ issue.
}
register_deactivation_hook(__FILE__, 'capiflow_deactivate');

/**
 * ─── Global Accessor ───────────────────────────────────────────────────────────
 *
 * @return \CapiFlow\Core\Plugin_Loader
 */
function capiflow()
{
	return \CapiFlow\Core\Plugin_Loader::instance();
}

/**
 * ─── Text Domain ─────────────────────────────────────────────────────────────────
 *
 * WordPress 4.6+ automatically loads translations for plugins hosted on
 * wordpress.org via JIT (Just-In-Time) loading. Manual load_plugin_textdomain()
 * is no longer needed and triggers a WordPress Plugin Check warning.
 * Our NOOP_Translations protection (registered above) handles WP 6.7+
 * compatibility by pre-registering the textdomain.
 */

/**
 * ─── Initialize Plugin ─────────────────────────────────────────────────────────
 */
function capiflow_init()
{
	$errors = capiflow_check_requirements();
	if (!empty($errors)) {
		return;
	}

	// Load core bootstrap class.
	require_once CAPIFLOW_PLUGIN_DIR . 'includes/class-plugin-loader.php';

	// Boot the plugin.
	capiflow()->init();
}
add_action('plugins_loaded', 'capiflow_init', 20);
