merge, preserve(_all) => emit_alongside, * (legacy_)broad => suppress_third_party. Unknown values fall back to merge. * * @param mixed $value Stored or submitted value. */ function smfree_normalize_coexistence_mode( $value ): string { $map = array( 'merge' => 'merge', 'suppress_third_party' => 'suppress_third_party', 'emit_alongside' => 'emit_alongside', 'safe' => 'merge', 'preserve' => 'emit_alongside', 'preserve_all' => 'emit_alongside', 'broad' => 'suppress_third_party', 'legacy_broad' => 'suppress_third_party', ); return is_string( $value ) && isset( $map[ $value ] ) ? $map[ $value ] : 'merge'; } /** * Effective coexistence mode. Normalizes at read time so sites whose stored * value predates 1.1.0 behave correctly even if the upgrade routine has not * run yet. */ function smfree_get_coexistence_mode(): string { return smfree_normalize_coexistence_mode( get_option( SMFREE_OPTION_PREFIX . 'coexistence_mode', 'merge' ) ); } /** * Main plugin bootstrap. * * Loads dependencies, initializes components on the `init` hook, and * registers activation/deactivation callbacks. */ final class SMFREE_Plugin { /** * Singleton instance. * * @var SMFREE_Plugin|null */ private static $instance = null; /** * Get singleton instance. * * @return SMFREE_Plugin */ public static function get_instance(): SMFREE_Plugin { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Constructor. */ private function __construct() { register_activation_hook( __FILE__, array( $this, 'activate' ) ); register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); add_action( 'init', array( $this, 'init' ), 5 ); add_action( 'plugins_loaded', array( $this, 'maybe_migrate_legacy_options' ) ); add_action( 'plugins_loaded', array( $this, 'maybe_upgrade' ), 11 ); } /** * Run upgrade routines when the stored version differs from the code. * WordPress does not fire the activation hook on plugin updates, so this * is what migrates sites that update in place. */ public function maybe_upgrade(): void { $stored = get_option( SMFREE_OPTION_PREFIX . 'version', false ); if ( false === $stored || SMFREE_VERSION === $stored ) { return; } $this->migrate_coexistence_mode(); $this->add_default_options(); update_option( SMFREE_OPTION_PREFIX . 'version', SMFREE_VERSION, false ); } /** * One-time rewrite of pre-1.1.0 coexistence mode names to the new ones. * Read-time normalization in smfree_get_coexistence_mode() covers the * same mapping in case this never runs. */ private function migrate_coexistence_mode(): void { $stored = get_option( SMFREE_OPTION_PREFIX . 'coexistence_mode', false ); if ( false === $stored ) { return; } $normalized = smfree_normalize_coexistence_mode( $stored ); if ( $normalized !== $stored ) { update_option( SMFREE_OPTION_PREFIX . 'coexistence_mode', $normalized, false ); } } /** * Add any missing options with their defaults. Never overwrites. */ private function add_default_options(): void { $defaults = array( 'coexistence_mode' => array( 'merge', 'no' ), 'enabled' => array( 1, 'yes' ), 'faq_autodetect' => array( 1, 'yes' ), 'debug_faq_detection' => array( 0, 'yes' ), ); foreach ( $defaults as $key => $default ) { if ( false === get_option( SMFREE_OPTION_PREFIX . $key, false ) ) { add_option( SMFREE_OPTION_PREFIX . $key, $default[0], '', $default[1] ); } } } /** * One-time migration of pre-1.0.15 `smf_*` options to `smfree_*`. * * Runs only when the new version option is absent and the legacy one is * present, so it is a no-op on fresh installs and after it has run once. */ public function maybe_migrate_legacy_options(): void { if ( false !== get_option( SMFREE_OPTION_PREFIX . 'version', false ) ) { return; } if ( false === get_option( 'smf_version', false ) ) { return; } $keys = array( 'enabled', 'coexistence_mode', 'version', 'activated_at' ); foreach ( $keys as $key ) { $value = get_option( 'smf_' . $key, null ); if ( null !== $value && false === get_option( SMFREE_OPTION_PREFIX . $key, false ) ) { add_option( SMFREE_OPTION_PREFIX . $key, $value, '', 'enabled' === $key ? 'yes' : 'no' ); } delete_option( 'smf_' . $key ); } update_option( SMFREE_OPTION_PREFIX . 'version', SMFREE_VERSION, false ); delete_transient( 'smf_coverage_cache' ); } /** * Initialize plugin components. */ public function init(): void { $this->load_dependencies(); \SchemaMonsterFree\Frontend::init(); if ( is_admin() ) { \SchemaMonsterFree\Admin::init(); \SchemaMonsterFree\SMFree_Meta_Box::init(); } } /** * Load all PHP files. Order matters: helpers → detection → mapper → * templates → engine → frontend/admin. */ private function load_dependencies(): void { $base = SMFREE_PLUGIN_DIR . 'includes/'; require_once $base . 'class-detection.php'; require_once $base . 'class-harvester.php'; require_once $base . 'class-mapper.php'; require_once $base . 'class-eligibility.php'; require_once $base . 'class-coexistence.php'; // Templates. $tpl = $base . 'templates/'; require_once $tpl . 'class-website.php'; require_once $tpl . 'class-organization.php'; require_once $tpl . 'class-webpage.php'; require_once $tpl . 'class-blogposting.php'; require_once $tpl . 'class-breadcrumblist.php'; require_once $tpl . 'class-faqpage.php'; require_once $tpl . 'class-howto.php'; require_once $tpl . 'class-videoobject.php'; require_once $tpl . 'class-sitenavigationelement.php'; require_once $tpl . 'class-product.php'; // Per-page overrides, then FAQ detection (uses the overrides' @type helpers). require_once $base . 'class-smfree-overrides.php'; require_once $base . 'class-smfree-faq-detector.php'; require_once $base . 'class-engine.php'; require_once $base . 'class-frontend.php'; if ( is_admin() ) { require_once $base . 'class-upgrade-card.php'; require_once $base . 'class-admin.php'; require_once $base . 'class-smfree-meta-box.php'; } } /** * Plugin activation. * * Sets a version option so future upgrade routines have an anchor. * Deliberately does NOT touch any option keys outside the smfree_ namespace. */ public function activate(): void { // Carry legacy settings over before defaults are written below. $this->maybe_migrate_legacy_options(); $existing = get_option( SMFREE_OPTION_PREFIX . 'version', '' ); if ( '' === $existing ) { add_option( SMFREE_OPTION_PREFIX . 'version', SMFREE_VERSION, '', 'no' ); add_option( SMFREE_OPTION_PREFIX . 'activated_at', time(), '', 'no' ); } else { update_option( SMFREE_OPTION_PREFIX . 'version', SMFREE_VERSION, false ); } $this->migrate_coexistence_mode(); $this->add_default_options(); } /** * Plugin deactivation. * * Leaves stored options in place (uninstall removes them). Only clears * runtime transients this plugin owns. */ public function deactivate(): void { delete_transient( SMFREE_OPTION_PREFIX . 'coverage_cache' ); } } // Boot. SMFREE_Plugin::get_instance();