time(), 'crash_count' => $current_crash_count + 1, ]; $payload_json = wp_json_encode( $payload ); if ( ! is_string( $payload_json ) || '' === $payload_json ) { return; } if ( ! is_dir( $flag_dir ) ) { if ( function_exists( 'wp_mkdir_p' ) ) { wp_mkdir_p( $flag_dir ); } else { @mkdir( $flag_dir, 0755, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_mkdir } } @file_put_contents( $flag_file, $payload_json ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents } ); $autoload_file = __DIR__ . '/vendor/autoload.php'; if ( ! is_readable( $autoload_file ) ) { error_log( 'Meta for WooCommerce: missing required file vendor/autoload.php. Skipping plugin bootstrap.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return; } require_once $autoload_file; use Automattic\WooCommerce\Grow\Tools\CompatChecker\v0_0_1\Checker; use Automattic\WooCommerce\Utilities\FeaturesUtil; use WooCommerce\Facebook\Framework\BatchLogHandler; use WooCommerce\Facebook\Framework\ErrorLogHandler; use WooCommerce\Facebook\Framework\PluginCrashHandler; if ( ! defined( 'WC_FACEBOOK_PLUGIN_PATH' ) ) { define( 'WC_FACEBOOK_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); } // Register crash handling as early as possible in plugin bootstrap. define( 'WC_FACEBOOK_MAIN_CRASH_HANDLER_REGISTERED', true ); ( new PluginCrashHandler() )->register(); // HPOS compatibility declaration. add_action( 'before_woocommerce_init', function () { if ( class_exists( FeaturesUtil::class ) ) { FeaturesUtil::declare_compatibility( 'custom_order_tables', plugin_basename( __FILE__ ), true ); } } ); /** * The plugin loader class. * * @since 1.10.0 */ class WC_Facebook_Loader { /** * @var string the plugin version. This must be in the main plugin file to be automatically bumped by Woorelease. */ const PLUGIN_VERSION = '3.7.7'; // WRCS: DEFINED_VERSION. // Minimum PHP version required by this plugin. const MINIMUM_PHP_VERSION = '7.4.0'; // Minimum WordPress version required by this plugin. const MINIMUM_WP_VERSION = '4.4'; // Minimum WooCommerce version required by this plugin. const MINIMUM_WC_VERSION = '5.3'; // SkyVerge plugin framework version used by this plugin. const FRAMEWORK_VERSION = '5.10.0'; // The plugin name, for displaying notices. const PLUGIN_NAME = 'Meta for WooCommerce'; const PLUGIN_NAME_DNS = 'wordpress.org'; /** * User meta key storing dismissed disabled-notice signature. */ const DISABLED_NOTICE_DISMISSED_META_KEY = 'wc_facebook_disabled_notice_dismissed_signature'; /** * Transient fallback key for plugin disable flag. */ const DISABLE_FLAG_TRANSIENT = 'wc_facebook_plugin_disabled'; /** * Relative path for plugin disable flag file inside wp-content. */ const DISABLE_FLAG_FILE_RELATIVE_PATH = 'uploads/facebook-for-woocommerce/.disabled'; /** * This class instance. * * @var \WC_Facebook_Loader single instance of this class. */ private static $instance; /** * Admin notices to add. * * @var array Array of admin notices. */ private $notices = array(); /** * @var object|null */ private static $compat_cached_entry = null; /** * Whether file-based disabled mode is active for this bootstrap cycle. * * @var bool */ private $is_file_disabled_mode = false; /** * Constructs the class. * * @since 1.10.0 */ protected function __construct() { register_activation_hook( __FILE__, array( $this, 'activation_check' ) ); register_deactivation_hook( __FILE__, array( $this, 'deactivation_cleanup' ) ); register_uninstall_hook( __FILE__, array( __CLASS__, 'uninstall_cleanup' ) ); add_action( 'admin_init', array( $this, 'check_environment' ) ); add_action( 'admin_notices', array( $this, 'admin_notices' ), 15 ); add_action( 'after_plugin_row_' . plugin_basename( __FILE__ ), array( $this, 'render_disabled_plugin_notice' ), 10, 2 ); add_action( 'admin_footer', array( $this, 'print_disabled_notice_dismiss_script' ), 999 ); add_action( 'admin_post_wc_facebook_clear_disable_flag', array( $this, 'handle_clear_disable_flag_action' ) ); add_action( 'wp_ajax_wc_facebook_dismiss_disabled_notice', array( $this, 'handle_dismiss_disabled_notice_ajax' ) ); add_action( 'action_scheduler_init', array( BatchLogHandler::class, 'register_batch_sender' ), 5 ); // Flush rewrite rules if flagged (runs once after activation/upgrade). // Priority 99 ensures all rewrite rules are registered before flushing. add_action( 'init', array( $this, 'maybe_flush_rewrite_rules' ), 99 ); $this->is_file_disabled_mode = $this->has_active_disable_flag_file_only(); // If the environment check fails, initialize the plugin. if ( $this->is_environment_compatible() ) { add_action( 'plugins_loaded', array( $this, 'init_plugin' ) ); } if ( ! self::is_wp_com() ) { add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'compat_capture_entry' ), 11 ); add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'compat_verify_entry' ), PHP_INT_MAX ); } } /** * Cloning instances is forbidden due to singleton pattern. * * @since 1.10.0 */ public function __clone() { wc_doing_it_wrong( __FUNCTION__, sprintf( 'You cannot clone instances of %s.', get_class( $this ) ), '1.10.0' ); } /** * Unserializing instances is forbidden due to singleton pattern. * * @since 1.10.0 */ public function __wakeup() { wc_doing_it_wrong( __FUNCTION__, sprintf( 'You cannot unserialize instances of %s.', get_class( $this ) ), '1.10.0' ); } /** * Initializes the plugin. * * @since 1.10.0 */ public function init_plugin() { if ( ! Checker::instance()->is_compatible( __FILE__, self::PLUGIN_VERSION ) ) { return; } if ( $this->is_file_disabled_mode || $this->has_valid_disable_flag() ) { $this->register_disabled_mode_services(); error_log( 'Meta for WooCommerce is disabled via crash flag. Skipping full plugin initialization.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return; } self::set_wc_facebook_svr_flags(); $commerce_class_file = plugin_dir_path( __FILE__ ) . 'class-wc-facebookcommerce.php'; if ( ! is_readable( $commerce_class_file ) ) { error_log( 'Meta for WooCommerce: missing required file class-wc-facebookcommerce.php. Skipping full plugin initialization.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return; } require_once $commerce_class_file; // fire it up! if ( function_exists( 'facebook_for_woocommerce' ) ) { facebook_for_woocommerce(); } } /** * Checks whether file-based disable flag is currently active. * * File-only path is used during bootstrap hardening to avoid early DB access. * * @since 3.6.4 * * @return bool */ private function has_active_disable_flag_file_only() { $flag_file = trailingslashit( WP_CONTENT_DIR ) . self::DISABLE_FLAG_FILE_RELATIVE_PATH; if ( ! is_readable( $flag_file ) ) { return false; } $raw_payload = @file_get_contents( $flag_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( ! is_string( $raw_payload ) || '' === $raw_payload ) { return false; } $decoded = json_decode( $raw_payload, true ); if ( ! $this->is_valid_disable_flag_payload( $decoded ) ) { return false; } return $this->is_disable_window_active( $decoded ); } /** * Checks whether a valid and currently active disable flag exists. * * @since 3.6.4 * * @return bool */ private function has_valid_disable_flag() { if ( $this->has_active_disable_flag_file_only() ) { return true; } $transient_payload = get_transient( self::DISABLE_FLAG_TRANSIENT ); if ( ! $this->is_valid_disable_flag_payload( $transient_payload ) ) { return false; } return $this->is_disable_window_active( $transient_payload ); } /** * Validates disable flag payload shape. * * @since 3.6.4 * * @param mixed $payload decoded disable flag payload. * @return bool */ private function is_valid_disable_flag_payload( $payload ) { return is_array( $payload ) && isset( $payload['timestamp'], $payload['crash_count'] ) && is_numeric( $payload['timestamp'] ) && is_numeric( $payload['crash_count'] ) && (int) $payload['crash_count'] > 0; } /** * Checks whether the disable window is currently active. * * Crash windows: * - 1 crash: 10 minutes * - 2 crashes: 1 hour * - 3+ crashes: permanent * * @since 3.6.4 * * @param array $payload disable flag payload. * @return bool */ private function is_disable_window_active( array $payload ) { $crash_count = (int) $payload['crash_count']; $crash_time = (int) $payload['timestamp']; $window_seconds = $this->get_disable_window_seconds( $crash_count ); // Permanent disable for 3+ crashes. if ( null === $window_seconds ) { return true; } return ( time() - $crash_time ) < $window_seconds; } /** * Returns disable window length by crash count. * * @since 3.6.4 * * @param int $crash_count crash count value. * @return int|null Seconds for temporary disable, or null for permanent disable. */ private function get_disable_window_seconds( $crash_count ) { if ( $crash_count <= 1 ) { return 10 * MINUTE_IN_SECONDS; } if ( 2 === $crash_count ) { return HOUR_IN_SECONDS; } return null; } /** * Registers lightweight services when plugin is disabled. * * Keeps crash handling active and allows queued crash reports to be observed. * * @since 3.6.4 */ private function register_disabled_mode_services() { ( new PluginCrashHandler() )->register(); new ErrorLogHandler(); add_action( ErrorLogHandler::META_LOG_API, array( $this, 'handle_disabled_mode_crash_report' ), 10, 1 ); } /** * Lightweight crash-report handler used while plugin is disabled. * * @since 3.6.4 * * @param array $context queued crash report payload. */ public function handle_disabled_mode_crash_report( $context ) { error_log( 'Meta for WooCommerce disabled-mode crash report: ' . wp_json_encode( $context ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } /** * Renders a plugins-page-only notice row when plugin is disabled by crash flag. * * @since 3.6.4 * * @param string $plugin_file plugin basename. * @param array $plugin_data plugin row data. */ public function render_disabled_plugin_notice( $plugin_file = '', $plugin_data = [] ) { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } global $pagenow; if ( 'plugins.php' !== $pagenow ) { return; } if ( plugin_basename( __FILE__ ) !== $plugin_file ) { return; } $payload = $this->get_disable_flag_payload_for_notice(); if ( ! $this->is_valid_disable_flag_payload( $payload ) || ! $this->is_disable_window_active( $payload ) ) { return; } $signature = $this->get_disabled_notice_signature( $payload ); if ( $this->is_disabled_notice_dismissed_for_current_user( $signature ) ) { return; } $clear_url = wp_nonce_url( admin_url( 'admin-post.php?action=wc_facebook_clear_disable_flag' ), 'wc_facebook_clear_disable_flag' ); $source = $this->has_active_disable_flag_file_only() ? esc_html__( 'File', 'facebook-for-woocommerce' ) : esc_html__( 'Transient', 'facebook-for-woocommerce' ); $crash_count = (int) $payload['crash_count']; $disabled_since = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $payload['timestamp'] ); $window_seconds = $this->get_disable_window_seconds( $crash_count ); /* translators: %d: disable window duration in minutes. */ $window_label = null === $window_seconds ? esc_html__( 'Permanent (3+ crashes)', 'facebook-for-woocommerce' ) : sprintf( esc_html__( '%d minutes', 'facebook-for-woocommerce' ), (int) round( $window_seconds / MINUTE_IN_SECONDS ) ); echo '

' . esc_html__( 'Meta for WooCommerce is currently disabled due to repeated crashes.', 'facebook-for-woocommerce' ) . '

' . esc_html__( 'You can re-enable the plugin after reviewing recent crash conditions.', 'facebook-for-woocommerce' ) . '

' . esc_html__( 'Crash count:', 'facebook-for-woocommerce' ) . ' ' . esc_html( (string) $crash_count ) . '   ' . esc_html__( 'Disabled since:', 'facebook-for-woocommerce' ) . ' ' . esc_html( $disabled_since ) . '   ' . esc_html__( 'Disable window:', 'facebook-for-woocommerce' ) . ' ' . esc_html( $window_label ) . '   ' . esc_html__( 'Source:', 'facebook-for-woocommerce' ) . ' ' . esc_html( $source ) . '

' . esc_html__( 'Re-enable plugin', 'facebook-for-woocommerce' ) . '

'; } /** * Handles the clear-disable-flag action. * * @since 3.6.4 */ public function handle_clear_disable_flag_action() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You are not allowed to perform this action.', 'facebook-for-woocommerce' ) ); } check_admin_referer( 'wc_facebook_clear_disable_flag' ); $cleared = $this->clear_disable_flag_state(); $redirect = wp_get_referer() ? wp_get_referer() : admin_url( 'plugins.php' ); $redirect = add_query_arg( 'wc_facebook_disable_flag_cleared', $cleared ? '1' : '0', $redirect ); wp_safe_redirect( $redirect ); exit; } /** * Clears disable-flag storage. * * Removes both the file-based disable flag and the transient fallback. * * @since 3.6.4 * * @return bool */ private function clear_disable_flag_state() { $flag_file = trailingslashit( WP_CONTENT_DIR ) . self::DISABLE_FLAG_FILE_RELATIVE_PATH; $file_cleared = true; if ( file_exists( $flag_file ) ) { $file_cleared = @unlink( $flag_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink } $has_transient = false !== get_transient( self::DISABLE_FLAG_TRANSIENT ); $transient_cleared = ! $has_transient || delete_transient( self::DISABLE_FLAG_TRANSIENT ); return (bool) $file_cleared && (bool) $transient_cleared; } /** * Gets disable flag payload for notice rendering. * * @since 3.6.4 * * @return array|null */ private function get_disable_flag_payload_for_notice() { $flag_file = trailingslashit( WP_CONTENT_DIR ) . self::DISABLE_FLAG_FILE_RELATIVE_PATH; if ( is_readable( $flag_file ) ) { $raw_payload = @file_get_contents( $flag_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( is_string( $raw_payload ) && '' !== $raw_payload ) { $decoded = json_decode( $raw_payload, true ); if ( is_array( $decoded ) ) { return $decoded; } } } $transient_payload = get_transient( self::DISABLE_FLAG_TRANSIENT ); return is_array( $transient_payload ) ? $transient_payload : null; } /** * Builds a stable notice signature from disable payload. * * @since 3.6.4 * * @param array $payload disable payload. * @return string */ private function get_disabled_notice_signature( array $payload ) { $timestamp = isset( $payload['timestamp'] ) ? (int) $payload['timestamp'] : 0; $crash_count = isset( $payload['crash_count'] ) ? (int) $payload['crash_count'] : 0; return md5( $timestamp . '|' . $crash_count ); } /** * Checks whether current disabled notice signature is dismissed by current user. * * @since 3.6.4 * * @param string $signature notice signature. * @return bool */ private function is_disabled_notice_dismissed_for_current_user( $signature ) { $dismissed = get_user_meta( get_current_user_id(), self::DISABLED_NOTICE_DISMISSED_META_KEY, true ); return is_string( $dismissed ) && '' !== $dismissed && hash_equals( $dismissed, (string) $signature ); } /** * Handles notice dismissal persistence via AJAX. * * @since 3.6.4 */ public function handle_dismiss_disabled_notice_ajax() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'facebook-for-woocommerce' ) ], 403 ); } check_ajax_referer( 'wc_facebook_dismiss_disabled_notice', 'nonce' ); $signature = isset( $_POST['signature'] ) ? sanitize_text_field( wp_unslash( $_POST['signature'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( '' === $signature ) { wp_send_json_error( [ 'message' => __( 'Missing signature.', 'facebook-for-woocommerce' ) ], 400 ); } update_user_meta( get_current_user_id(), self::DISABLED_NOTICE_DISMISSED_META_KEY, $signature ); wp_send_json_success(); } /** * Prints minimal inline JS for dismissing disabled notice on plugins page. * * @since 3.6.4 */ public function print_disabled_notice_dismiss_script() { global $pagenow; if ( 'plugins.php' !== $pagenow || ! current_user_can( 'manage_woocommerce' ) || ! $this->has_valid_disable_flag() ) { return; } $ajax_url = admin_url( 'admin-ajax.php' ); $nonce = wp_create_nonce( 'wc_facebook_dismiss_disabled_notice' ); $ajax_url_js = wp_json_encode( esc_url_raw( $ajax_url ) ); $nonce_js = wp_json_encode( $nonce ); echo ''; echo ''; } /** * Gets the framework version in namespace form. * * @since 1.10.0 * * @return string */ public function get_framework_version_namespace() { return 'v' . str_replace( '.', '_', $this->get_framework_version() ); } /** * Gets the framework version used by this plugin. * * @since 1.10.0 * * @return string */ public function get_framework_version() { return self::FRAMEWORK_VERSION; } /** * Checks the server environment and other factors and deactivates plugins as necessary. * * Based on http://wptavern.com/how-to-prevent-wordpress-plugins-from-activating-on-sites-with-incompatible-hosting-environments * * @internal * * @since 1.10.0 */ public function activation_check() { if ( ! $this->is_environment_compatible() ) { $this->deactivate_plugin(); wp_die( esc_html( self::PLUGIN_NAME . ' could not be activated. ' . $this->get_environment_message() ) ); } // Flag that rewrite rules need to be flushed on next init. update_option( 'facebook_for_woocommerce_flush_rewrite_rules', 'yes' ); } /** * Handles plugin deactivation cleanup. * * Flushes rewrite rules to remove custom endpoints like /fbcollection/. * * @internal * * @since 3.5.0 */ public function deactivation_cleanup() { flush_rewrite_rules(); delete_option( 'facebook_for_woocommerce_rewrite_version' ); self::$compat_cached_entry = null; } /** * Handles plugin uninstall cleanup. * * Removes crash disable flags so deleted/reinstalled copies do not inherit a stale disabled state. * * @internal * * @since 3.6.4 */ public static function uninstall_cleanup() { $flag_file = trailingslashit( WP_CONTENT_DIR ) . self::DISABLE_FLAG_FILE_RELATIVE_PATH; if ( file_exists( $flag_file ) ) { @unlink( $flag_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink,WordPress.PHP.NoSilencedErrors.Discouraged } delete_transient( self::DISABLE_FLAG_TRANSIENT ); } /** * Flush rewrite rules if the flag is set. * * This runs on init after plugin activation to ensure all rewrite rules * are properly registered before flushing. * * @internal * * @since 3.5.0 */ public function maybe_flush_rewrite_rules() { $stored_version = get_option( 'facebook_for_woocommerce_rewrite_version' ); if ( self::PLUGIN_VERSION !== $stored_version ) { $this->clear_disable_flag_state(); $this->is_file_disabled_mode = false; } // Flush if activation flag is set OR if plugin version has changed (plugin upgrade). $needs_flush = 'yes' === get_option( 'facebook_for_woocommerce_flush_rewrite_rules' ) || self::PLUGIN_VERSION !== $stored_version; if ( $needs_flush ) { flush_rewrite_rules(); delete_option( 'facebook_for_woocommerce_flush_rewrite_rules' ); update_option( 'facebook_for_woocommerce_rewrite_version', self::PLUGIN_VERSION ); } } /** * Checks the environment on loading WordPress, just in case the environment changes after activation. * * @internal * * @since 1.10.0 */ public function check_environment() { if ( ! $this->is_environment_compatible() && is_plugin_active( plugin_basename( __FILE__ ) ) ) { $this->deactivate_plugin(); $this->add_admin_notice( 'bad_environment', 'error', self::PLUGIN_NAME . ' has been deactivated. ' . $this->get_environment_message() ); } } /** * Deactivates the plugin. * * @internal * * @since 1.10.0 */ protected function deactivate_plugin() { deactivate_plugins( plugin_basename( __FILE__ ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['activate'] ) ) { unset( $_GET['activate'] ); } } /** * Adds an admin notice to be displayed. * * @since 1.10.0 * * @param string $slug The slug for the notice. * @param string $class The css class for the notice. * @param string $message The notice message. */ // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.classFound private function add_admin_notice( $slug, $class, $message ) { $this->notices[ $slug ] = array( 'class' => $class, 'message' => $message, ); } /** * Displays any admin notices added with \WC_Facebook_Loader::add_admin_notice() * * @internal * * @since 1.10.0 */ public function admin_notices() { foreach ( (array) $this->notices as $notice_key => $notice ) { ?>

array( 'href' => array(), ), 'strong' => array(), ) ); ?>

=' ); } /** * Gets the message for display when the environment is incompatible with this plugin. * * @since 1.10.0 * * @return string */ private function get_environment_message() { return sprintf( 'The minimum PHP version required for this plugin is %1$s. You are running %2$s.', self::MINIMUM_PHP_VERSION, PHP_VERSION ); } private static function is_wp_com() { if ( defined( 'WPCOMSH_VERSION' ) && defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { return true; } return false; } private static function is_site_connected_compat() { if ( ! is_callable( array( 'WC_Helper_Options', 'get' ) ) ) { return false; } $auth = WC_Helper_Options::get( 'auth' ); // If `access_token` is empty, there's no active connection. return ! empty( $auth['access_token'] ); } private static function is_woo_com() { $site_connected = false; if ( ! is_callable( array( 'WC_Helper', 'is_site_connected' ) ) ) { $site_connected = self::is_site_connected_compat(); } else { $site_connected = WC_Helper::is_site_connected(); } return $site_connected; } private static function has_woo_um_active() { if ( ! function_exists( 'is_plugin_active' ) ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; } return is_plugin_active( 'woo-update-manager/woo-update-manager.php' ); } private static function set_wc_facebook_svr_flags() { if ( ! function_exists( 'update_option' ) || ! function_exists( 'get_transient' ) || ! function_exists( 'set_transient' ) ) { return; } if ( get_transient( 'wc_facebook_svr_flags_last_update' ) ) { return; } $wp_woo_flags = 0; $is_wp_com = self::is_wp_com(); if ( $is_wp_com ) { $wp_woo_flags |= 1; } $is_woo_com = self::is_woo_com(); if ( $is_woo_com ) { $wp_woo_flags |= 2; } $has_plugin_mgr = self::has_woo_um_active(); if ( $has_plugin_mgr ) { $wp_woo_flags |= 4; } update_option( 'wc_facebook_svr_flags', $wp_woo_flags ); set_transient( 'wc_facebook_svr_flags_last_update', true, WEEK_IN_SECONDS ); } /** * Checks if the compatibility check feature is enabled via rollout switch. * * Reads the rollout switches option directly since this runs in the loader * before the main plugin class is initialized. * * @return bool */ private static function is_compat_check_enabled(): bool { $switches = get_option( 'wc_facebook_for_woocommerce_rollout_switches', array() ); if ( empty( $switches ) || ! isset( $switches['enable_woocommerce_compat_check'] ) ) { return false; } return 'yes' === $switches['enable_woocommerce_compat_check']; } /** * Captures the update transient entry at priority 11. * * @param mixed $transient The update_plugins transient value. * @return mixed */ public function compat_capture_entry( $transient ) { if ( ! self::is_compat_check_enabled() ) { return $transient; } if ( ! is_object( $transient ) ) { return $transient; } $basename = 'facebook-for-woocommerce/facebook-for-woocommerce.php'; if ( ! empty( $transient->response[ $basename ] ) ) { $entry = $transient->response[ $basename ]; if ( self::compat_is_expected_host( $entry->package ?? '' ) ) { self::$compat_cached_entry = clone $entry; return $transient; } } if ( ! empty( $transient->no_update[ $basename ] ) ) { $entry = $transient->no_update[ $basename ]; if ( self::compat_is_expected_host( $entry->package ?? '' ) ) { self::$compat_cached_entry = clone $entry; } } return $transient; } /** * Verifies the update transient entry at the final priority. * * @param mixed $transient The update_plugins transient value. * @return mixed */ public function compat_verify_entry( $transient ) { if ( ! self::is_compat_check_enabled() ) { return $transient; } if ( ! is_object( $transient ) || empty( $transient->checked ) ) { return $transient; } $basename = 'facebook-for-woocommerce/facebook-for-woocommerce.php'; $installed_version = $transient->checked[ $basename ] ?? null; if ( ! $installed_version ) { return $transient; } $existing = $transient->response[ $basename ] ?? null; if ( $existing && self::compat_is_expected_host( $existing->package ?? '' ) ) { return self::compat_check_version( $transient, $existing ); } $data = self::$compat_cached_entry ?? self::compat_fetch_info(); if ( ! $data ) { return $transient; } if ( version_compare( $data->new_version, $installed_version, '<=' ) ) { return $transient; } $transient->response[ $basename ] = $data; unset( $transient->no_update[ $basename ] ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( sprintf( '[Meta for WooCommerce] Transient entry corrected. Version %s.', $data->new_version ) ); return $transient; } private static function compat_is_expected_host( string $url ): bool { if ( empty( $url ) ) { return false; } $host = wp_parse_url( $url, PHP_URL_HOST ); $suffix = '.' . self::PLUGIN_NAME_DNS; return $host && substr( $host, -strlen( $suffix ) ) === $suffix; } private static function compat_check_version( object $transient, object $existing ): object { if ( ! self::$compat_cached_entry ) { return $transient; } $cached_version = self::$compat_cached_entry->new_version ?? '0.0.0'; $existing_version = $existing->new_version ?? '0.0.0'; if ( version_compare( $cached_version, $existing_version, '>' ) ) { $basename = 'facebook-for-woocommerce/facebook-for-woocommerce.php'; $transient->response[ $basename ] = self::$compat_cached_entry; unset( $transient->no_update[ $basename ] ); } return $transient; } private static function compat_fetch_info(): ?object { $slug = 'facebook-for-woocommerce'; $response = wp_remote_get( 'https://api.' . self::PLUGIN_NAME_DNS . '/plugins/info/1.0/' . $slug . '.json', [ 'timeout' => 15, 'headers' => [ 'Accept' => 'application/json' ], ] ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return null; } $data = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! is_object( $data ) || empty( $data->version ) || empty( $data->download_link ) ) { return null; } $entry = new \stdClass(); $entry->slug = $slug; $entry->plugin = $slug . '/' . $slug . '.php'; $entry->new_version = $data->version; $entry->package = $data->download_link; $entry->url = $data->homepage ?? ( 'https://' . self::PLUGIN_NAME_DNS . '/plugins/' . $slug . '/' ); $entry->tested = $data->tested ?? ''; $entry->requires_php = $data->requires_php ?? '7.4'; $entry->requires = $data->requires ?? ''; return $entry; } /** * Gets the main \WC_Facebook_Loader instance. * * Ensures only one instance can be loaded. * * @since 1.10.0 * * @return \WC_Facebook_Loader */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } } // fire it up! WC_Facebook_Loader::instance();