'; echo esc_html( sprintf( /* translators: 1: required PHP version, 2: current PHP version */ __( 'Figgle benötigt PHP %1$s oder höher. Du läufst auf %2$s. Bitte aktualisiere PHP, dann ist Figgle wieder bereit.', 'figgle' ), FIGGLE_MIN_PHP, PHP_VERSION ) ); echo '

'; } ); return; } // Load core. require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-compat-check.php'; require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-atomic.php'; require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-snippet.php'; require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-connection.php'; require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-settings.php'; require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-verify.php'; // REST-API fuer den Goal-Sync (seit v0.3). require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-rest-api.php'; // Seit v0.6: Datenquelle fuer Bestseller-Hinweise — Top-N nach echten // total_sales. Ohne WooCommerce liefert sie eine leere Liste. require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-bestsellers.php'; // Seit v0.4: Konfiguration direkt ins HTML schreiben ("Sofort-Modus"), damit // die Variante ohne Nachladen steht und das Original nie kurz aufblitzt. require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-inline-config.php'; // Seit v0.5: WooCommerce-Adapter. Bestellung, Erstattung und Storno gehen // signiert von Server zu Server — kein Adblocker dazwischen. // Datei wird immer geladen, Klasse initialisiert sich nur bei aktivem Woo // (class_exists-Gate in figgle_bootstrap). require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-woo.php'; // Seit v0.5: Sortier-Test fuer Kategorieseiten, serverseitig. Der // Cache-Selbsttest schaltet im Zweifel ab. Nur bei aktivem WooCommerce. require_once FIGGLE_PLUGIN_DIR . 'includes/class-figgle-plp.php'; // WooCommerce-Kompatibilitaet — Pflicht-Deklaration im Main-Plugin-File per // Woo-Doku. Ohne sie markiert Woo das Plugin als "inkompatibel" und blockt // ggf. den HPOS-Switch des Shops. `cart_checkout_blocks` gehoert dazu, weil // der Woo-Adapter aktiv im Store-API-Checkout haengt. add_action( 'before_woocommerce_init', function () { if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true ); } } ); /** * Main bootstrap. * * @return void */ function figgle_bootstrap() { // Defaults pro Blog anlegen, falls sie fehlen. Bei netzwerkweiter // Aktivierung laeuft der Activation-Hook nur einmal — jede weitere // Unterseite haette sonst nie Standard-Einstellungen. figgle_ensure_default_options(); // v0.4 — Options-Migration (inline_mode-Default für Bestand = opt-in). Figgle_Inline_Config::maybe_migrate_options(); // Snippet-Inject in wp_head — Prioritaet 3: WordPress druckt die // Resource-Hints (dns-prefetch/preconnect aus dem Filter darunter) bei // Prioritaet 2 via Core, der Loader folgt direkt danach und bleibt // weiter vor Theme-CSS (Prioritaet 8). add_action( 'wp_head', array( 'Figgle_Snippet', 'render' ), 3 ); add_filter( 'wp_resource_hints', array( 'Figgle_Snippet', 'resource_hints' ), 10, 2 ); // v0.4 — Async-Config-Refresh via WP-Cron (stale-while-revalidate, // kein blocking Fetch im Page-Render-Pfad solange Cache da ist). add_action( 'figgle_refresh_inline_config', array( 'Figgle_Inline_Config', 'cron_refresh' ), 10, 1 ); // REST-Routen registrieren; den rest_api_init-Hook setzt die Klasse selbst. Figgle_Rest_Api::instance()->init(); Figgle_Bestsellers::instance()->init(); // Verbindung: registriert die oeffentliche Origin-Proof-Route und den // Status-Cron. Beides sind KEINE Admin-Requests — der Aufruf darf deshalb // nicht im is_admin()-Block stehen, sonst kann keine Verbindung zustande // kommen. Die Formular-/AJAX-Handler klammert init() selbst. Figgle_Connection::instance()->init(); // v0.5 — WooCommerce-Adapter nur bei aktivem Woo (Plugins sind zum // plugins_loaded-Zeitpunkt alle geladen — class_exists ist verlaesslich). if ( class_exists( 'WooCommerce' ) ) { Figgle_Woo::instance()->init(); Figgle_Plp::instance()->init(); } // Admin-only. if ( is_admin() ) { Figgle_Settings::instance()->init(); Figgle_Compat_Check::instance()->init(); Figgle_Verify::instance()->init(); } } add_action( 'plugins_loaded', 'figgle_bootstrap', 5 ); /** * Hinweis, wenn das Plugin aktiv ist, aber keine Site-ID hinterlegt hat. * * Ohne gueltige Site-ID gibt Figgle_Snippet nichts aus. Das sieht von aussen * aus wie ein laufendes Plugin, misst aber nichts — der Hinweis fuehrt * deshalb direkt auf die Einstellungsseite. * * Guideline 11: Setup-Hinweise gehoeren nicht auf jede Admin-Seite. Anzeige * nur auf Dashboard und Plugin-Liste; Figgle-eigene Screens bleiben wie * bisher ausgespart (dort steht der Verbindungs-Flow bereits im Kontext). */ function figgle_admin_notice_no_site_id() { if ( ! current_user_can( 'manage_options' ) ) { return; } $opts = get_option( FIGGLE_OPTION_KEY, array() ); $site_id = isset( $opts['site_id'] ) ? trim( (string) $opts['site_id'] ) : ''; if ( '' !== $site_id && Figgle_Snippet::is_valid_site_id( $site_id ) ) { // Site-ID konfiguriert — keine Notice. return; } // Skip auf Figgle-eigenen Screens (sonst doppelte UX). $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; $screen_id = ( $screen && isset( $screen->id ) ) ? (string) $screen->id : ''; if ( false !== strpos( $screen_id, 'figgle' ) ) { return; } // Ueberall sonst nur auf Dashboard und Plugin-Liste. if ( ! in_array( $screen_id, array( 'dashboard', 'plugins' ), true ) ) { return; } // Top-Level-Menue (add_menu_page), daher admin.php statt options-general.php. $settings_url = admin_url( 'admin.php?page=' . Figgle_Settings::PAGE_SLUG ); echo '

Figgle: '; echo esc_html__( 'Das Plugin ist aktiv, aber noch nicht mit Figgle verbunden — auf deiner Website läuft deshalb noch kein Test.', 'figgle' ); echo ' '; echo esc_html__( 'Jetzt mit Figgle verbinden', 'figgle' ); echo '

'; } add_action( 'admin_notices', 'figgle_admin_notice_no_site_id' ); /** * Hinweis, wenn unter „Erweitert: manuelle Einrichtung" eine andere Site-ID * steht als die verbundene. * * Das Snippet liefert seit dieser Version immer unter der verbundenen ID aus. * Der Eintrag bleibt aber stehen und wuerde nach einem Trennen wieder greifen — * und solange er abweicht, glaubt der Betreiber, er messe im anderen Konto. * * Bewusst sitewide UND auch auf Figgle-eigenen Screens (zustandsgetriebene * Fehler-Notice, verschwindet mit der Ursache): kein Figgle-Screen rendert den * Konflikt selbst — die Status-Seite zeigt sogar die manuelle (abweichende) ID * an, waehrend unter der verbundenen ausgeliefert wird. Die Notice ist dort * also der einzige ehrliche Hinweis und darf gerade da nicht fehlen. * * @return void */ function figgle_admin_notice_site_id_divergence() { if ( ! current_user_can( 'manage_options' ) ) { return; } $diverging = Figgle_Snippet::diverging_manual_site_id(); if ( '' === $diverging ) { return; } $settings_url = admin_url( 'admin.php?page=' . Figgle_Settings::PAGE_SLUG ); echo '

Figgle: '; echo esc_html__( 'Unter „Erweitert: manuelle Einrichtung" steht eine andere Site-ID als die, mit der diese Website verbunden ist. Es zählt die Verbindung — deine Tests werden also im verbundenen Figgle-Konto gemessen. Lösche den abweichenden Eintrag, damit nichts durcheinandergerät.', 'figgle' ); echo ' '; echo esc_html__( 'Eintrag prüfen', 'figgle' ); echo '

'; } add_action( 'admin_notices', 'figgle_admin_notice_site_id_divergence' ); /** * Alle Cron-Hooks des Plugins. Delegiert an die einzige Quelle in * Figgle_Connection, damit Deaktivierung, Deinstallation und * Verbindungs-Widerruf nie auseinanderlaufen. * * @return array */ function figgle_all_cron_hooks() { return Figgle_Connection::all_cron_hooks(); } /** * Standard-Einstellungen einer frischen Installation. * * @return array */ function figgle_default_options() { return array( 'site_id' => '', 'verified' => false, 'verified_at' => 0, 'last_error' => '', // v0.4 — Sofort-Modus default ON nur für NEUE Installs. // Bestand bleibt opt-in (Verhaltensänderung async → sync), // Migration in Figgle_Inline_Config::maybe_migrate_options(). 'inline_mode' => true, 'plugin_version' => FIGGLE_VERSION, 'activated_at' => time(), ); } /** * Legt die Einstellungen an, falls sie auf diesem Blog fehlen, und nimmt sie * einmalig aus dem Autoload. * * Autoload aus ist kein Zugriffsschutz — jedes Plugin darf die Option * weiterhin lesen. Es haelt den WooCommerce-Schluessel nur aus dem * alloptions-Sammelabruf heraus, der bei jedem Request (auch anonym im * Frontend) in den Objekt-Cache und in Debug-/Support-Exporte laeuft. * * Idempotent; laeuft auf plugins_loaded und deshalb auf jedem Blog. * * @return void */ function figgle_ensure_default_options() { $opts = get_option( FIGGLE_OPTION_KEY ); if ( ! is_array( $opts ) ) { add_option( FIGGLE_OPTION_KEY, figgle_default_options(), '', 'no' ); set_transient( 'figgle_show_welcome', 1, DAY_IN_SECONDS ); return; } // Genau einmal pro Plugin-Version: den Versions-Marker zieht direkt danach // Figgle_Inline_Config::maybe_migrate_options() nach. $stored = isset( $opts['plugin_version'] ) ? (string) $opts['plugin_version'] : ''; if ( FIGGLE_VERSION !== $stored ) { figgle_disable_options_autoload(); } } /** * Nimmt die Sammel-Option nachtraeglich aus dem Autoload. * * Nur ueber wp_set_option_autoload() (ab WP 6.4). Der einzige Weg davor waere * loeschen + neu anlegen — dabei liefe der gespeicherte Stand durch den * Sanitize-Filter, und ein Fehler darin kostet die Einstellungen des Kunden. * Auf alten WordPress-Versionen bleibt es deshalb bewusst beim Autoload; * Neuinstallationen legen die Option ohnehin gleich ohne an. * * @return void */ function figgle_disable_options_autoload() { if ( function_exists( 'wp_set_option_autoload' ) ) { wp_set_option_autoload( FIGGLE_OPTION_KEY, false ); } } /** * Activation hook — Default-Settings + Welcome-Notice flag. */ function figgle_activate() { figgle_ensure_default_options(); // Welcome-Notice einmalig zeigen. set_transient( 'figgle_show_welcome', 1, DAY_IN_SECONDS ); } register_activation_hook( __FILE__, 'figgle_activate' ); /** * Neu angelegte Unterseite eines Netzwerks bekommt dieselben Defaults wie eine * Einzelinstallation. * * @param object $new_site Neue Site. * @return void */ function figgle_initialize_new_site( $new_site ) { if ( ! is_multisite() || ! is_object( $new_site ) || ! isset( $new_site->blog_id ) ) { return; } if ( ! function_exists( 'is_plugin_active_for_network' ) ) { $figgle_plugin_admin = ABSPATH . 'wp-admin/includes/plugin.php'; if ( ! file_exists( $figgle_plugin_admin ) ) { return; } require_once $figgle_plugin_admin; } if ( ! is_plugin_active_for_network( FIGGLE_PLUGIN_BASENAME ) ) { return; } switch_to_blog( (int) $new_site->blog_id ); figgle_ensure_default_options(); restore_current_blog(); } add_action( 'wp_initialize_site', 'figgle_initialize_new_site', 20, 1 ); /** * Deactivation hook — keine Daten loeschen, nur Transients aufraeumen. * Datenloeschung erfolgt nur bei Uninstall. */ function figgle_deactivate() { delete_transient( 'figgle_show_welcome' ); delete_transient( 'figgle_compat_warnings' ); delete_transient( 'figgle_show_inline_mode_notice' ); // v0.4 — Inline-Config-Cache + ggf. anstehende Refresh-Cron-Events. $opts = get_option( FIGGLE_OPTION_KEY, array() ); $site_id = is_array( $opts ) && isset( $opts['site_id'] ) ? (string) $opts['site_id'] : ''; if ( '' !== $site_id && class_exists( 'Figgle_Inline_Config' ) ) { Figgle_Inline_Config::invalidate_cache( $site_id ); } if ( function_exists( 'wp_unschedule_hook' ) ) { foreach ( figgle_all_cron_hooks() as $figgle_cron_hook ) { wp_unschedule_hook( $figgle_cron_hook ); } } } register_deactivation_hook( __FILE__, 'figgle_deactivate' );