created_default = $this->initialize_consent(); $loader = BLOCKS_PLUGIN_DIR . '/includes/signls/loader.php'; if ( ! \is_file( $loader ) ) { return; } require_once $loader; \Blocks_Lifecycle::register_for( self::CONSENT_OPTION, $this->consent_changed( ... ) ); \add_action( 'admin_init', $this->add_policy_content( ... ) ); \add_action( 'activated_plugin', $this->activation_complete( ... ), 1, 2 ); if ( 0 < \did_action( 'plugins_loaded' ) ) { $this->after_boot(); return; } \add_action( 'plugins_loaded', $this->after_boot( ... ), PHP_INT_MAX ); } public function after_boot(): void { if ( $this->adapter_booted || ! \class_exists( 'Signls_Sdk_Bridge_1_1_7', false ) ) { return; } if ( ! \interface_exists( 'Signls\\Sdk\\V1\\ProductAdapterInterface' ) && \is_callable( array( 'Signls_Sdk_Loader', 'boot' ) ) ) { \Signls_Sdk_Loader::boot(); } if ( ! \interface_exists( 'Signls\\Sdk\\V1\\ProductAdapterInterface' ) ) { return; } $adapter = new Adapter( $this->settings ); if ( ! \Signls_Sdk_Bridge_1_1_7::register( array( 'product_slug' => self::PRODUCT, 'main_file' => BLOCKS_PLUGIN, 'sdk_path' => BLOCKS_PLUGIN_DIR . '/includes/signls', 'sdk_version' => self::SDK_VERSION, 'adapter' => $adapter, ) ) ) { return; } $this->adapter_booted = true; foreach ( $adapter->observed_options() as $option_name ) { \Blocks_Lifecycle::register_for( $option_name, $this->relevant_option_changed( ... ) ); } $this->synchronize( $this->created_default ? 'default_enabled' : 'product_state_sync' ); if ( self::record_upgrade() ) { \Signls_Sdk_Bridge_1_1_7::relevant_change( self::PRODUCT ); } } public function activation_complete( string $plugin, bool $network_wide ): void { unset( $network_wide ); if ( BLOCKS_PLUGIN_BASENAME !== $plugin ) { return; } $this->after_boot(); if ( ! $this->adapter_booted || ! self::enabled() ) { return; } \Signls_Sdk_Bridge_1_1_7::relevant_change( self::PRODUCT ); \Signls_Sdk_Bridge_1_1_7::flush_immediate( self::PRODUCT ); } public static function enabled(): bool { $value = \get_option( self::CONSENT_OPTION, 1 ); return \is_scalar( $value ) && 0 < (int) $value; } public static function install_id(): string { $stored = \get_option( self::INSTALL_ID_OPTION, '' ); $stored = \is_string( $stored ) ? \strtolower( $stored ) : ''; if ( 1 === \preg_match( '/^[a-f0-9]{32}$/D', $stored ) ) { return $stored; } $generated = \bin2hex( \random_bytes( 16 ) ); if ( false === \get_option( self::INSTALL_ID_OPTION, false ) ) { \add_option( self::INSTALL_ID_OPTION, $generated, '', false ); } else { \update_option( self::INSTALL_ID_OPTION, $generated, false ); } $saved = \get_option( self::INSTALL_ID_OPTION, '' ); if ( ! \is_string( $saved ) || $generated !== $saved ) { throw new \RuntimeException( 'Unable to persist the Blocks installation identity.' ); } return $generated; } public static function clear_schedules(): void { $hash = \substr( \hash( 'sha256', self::PRODUCT ), 0, 12 ); \wp_clear_scheduled_hook( 'signls_sdk_v1_' . $hash . '_routine' ); \wp_clear_scheduled_hook( 'signls_sdk_v1_' . $hash . '_refresh' ); } public static function record_activation(): void { self::record_lifecycle_event( 'activations', true ); } public static function record_deactivation(): void { self::record_lifecycle_event( 'deactivations', false ); } public static function deactivate(): void { if ( \class_exists( 'Signls_Sdk_Bridge_1_1_7', false ) ) { \Signls_Sdk_Bridge_1_1_7::deactivate( self::PRODUCT ); return; } self::clear_schedules(); } public static function uninstall(): void { self::clear_schedules(); \delete_option( self::CONSENT_OPTION ); \delete_option( self::INSTALL_ID_OPTION ); \delete_option( self::LIFECYCLE_OPTION ); \delete_option( 'signls_sdk_v1_' . \substr( \hash( 'sha256', self::PRODUCT ), 0, 12 ) ); $sdk = BLOCKS_PLUGIN_DIR . '/includes/signls/sdk.php'; if ( \is_file( $sdk ) ) { require_once $sdk; } if ( \class_exists( 'Signls\\Sdk\\V1\\CounterStore' ) ) { global $wpdb; if ( ! $wpdb instanceof \wpdb ) { return; } $table = \Signls\Sdk\V1\CounterStore::table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only existence guard before SDK-owned uninstall cleanup. $exists = $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ); if ( ! $exists ) { return; } try { \Signls\Sdk\V1\CounterStore::delete_product( self::PRODUCT ); } catch ( \Throwable $error ) { unset( $error ); } } } public function add_policy_content(): void { if ( ! \function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = '
' . \esc_html__( 'Blocks shares a bounded product snapshot with Signls to guide product improvement, compatibility, support, and reliability work. Sharing is enabled by default on a new installation and an administrator can turn it off at any time under Blocks Settings.', 'blocks' ) . '
'; $content .= '' . \esc_html__( 'The snapshot contains Blocks, WordPress, and PHP versions; multisite status; and fixed aggregate 0 or 1 states for supported content, builder, commerce, analytics, anti-spam, live chat, performance, security, SVG, compatibility, lifecycle, and reliability capabilities. It uses random pseudonymous installation and device identifiers.', 'blocks' ) . '
'; $content .= '' . \esc_html__( 'It excludes content, field values, email addresses, physical addresses, URLs, WordPress or commerce IDs, secrets, tokens, raw errors, customer and order data, messages, IP addresses, page journeys, and arbitrary settings or strings. Delivery receipts are retained for 90 days, current snapshots are replaced by newer observations, and daily installation aggregates are retained for long-term product analysis.', 'blocks' ) . '
'; \wp_add_privacy_policy_content( 'Blocks', \wp_kses_post( $content ) ); } private function initialize_consent(): bool { if ( null !== \get_option( self::CONSENT_OPTION, null ) ) { return false; } return \add_option( self::CONSENT_OPTION, 1, '', false ); } private function consent_changed( mixed $old_value, mixed $new_value ): void { $old_enabled = \is_scalar( $old_value ) && 0 < (int) $old_value; $new_enabled = \is_scalar( $new_value ) && 0 < (int) $new_value; if ( $old_enabled === $new_enabled ) { return; } $this->synchronize( 'admin_settings' ); } private function relevant_option_changed( mixed $old_value, mixed $new_value ): void { if ( $old_value === $new_value || ! self::enabled() || ! \class_exists( 'Signls_Sdk_Bridge_1_1_7', false ) ) { return; } try { \Signls_Sdk_Bridge_1_1_7::relevant_change( self::PRODUCT ); } catch ( \Throwable $error ) { unset( $error ); } } private function synchronize( string $source ): bool { if ( ! \class_exists( 'Signls_Sdk_Bridge_1_1_7', false ) ) { if ( ! self::enabled() ) { self::clear_schedules(); } return false; } try { $state = \Signls_Sdk_Bridge_1_1_7::state( self::PRODUCT ); $sdk_status = isset( $state['consent_status'] ) && \is_string( $state['consent_status'] ) ? $state['consent_status'] : 'unset'; if ( self::enabled() && 'enabled' !== $sdk_status ) { return \Signls_Sdk_Bridge_1_1_7::enable( self::PRODUCT, $source, self::NOTICE_VERSION ); } if ( ! self::enabled() && 'disabled' !== $sdk_status ) { return \Signls_Sdk_Bridge_1_1_7::disable( self::PRODUCT, $source, self::NOTICE_VERSION ); } return true; } catch ( \Throwable $error ) { unset( $error ); return false; } } private static function record_lifecycle_event( string $event, bool $active ): void { $lifecycle = \get_option( self::LIFECYCLE_OPTION, array() ); $lifecycle = \is_array( $lifecycle ) ? $lifecycle : array(); $items = isset( $lifecycle[ $event ] ) && \is_array( $lifecycle[ $event ] ) ? $lifecycle[ $event ] : array(); if ( 'activations' === $event ) { $lifecycle['is_reactivation'] = ! empty( $items ); } $items[] = \time(); $lifecycle[ $event ] = \array_slice( $items, -256 ); $lifecycle['is_active'] = $active; \update_option( self::LIFECYCLE_OPTION, $lifecycle, false ); } private static function record_upgrade(): bool { $lifecycle = \get_option( self::LIFECYCLE_OPTION, array() ); $lifecycle = \is_array( $lifecycle ) ? $lifecycle : array(); $previous = isset( $lifecycle['version'] ) && \is_scalar( $lifecycle['version'] ) ? (string) $lifecycle['version'] : ''; $current = \defined( 'BLOCKS_VERSION' ) ? (string) BLOCKS_VERSION : ''; if ( '' === $current || '' === $previous || $current === $previous ) { if ( '' !== $current && '' === $previous ) { $lifecycle['version'] = $current; \update_option( self::LIFECYCLE_OPTION, $lifecycle, false ); } return false; } $upgrades = isset( $lifecycle['upgrades'] ) && \is_array( $lifecycle['upgrades'] ) ? $lifecycle['upgrades'] : array(); $upgrades[] = array( 'from' => $previous, 'to' => $current, 'timestamp' => \time(), ); $lifecycle['upgrades'] = \array_slice( $upgrades, -256 ); $lifecycle['version'] = $current; \update_option( self::LIFECYCLE_OPTION, $lifecycle, false ); return true; } }