%s', esc_html__( 'Loading…', 'pushly' ) ); } public function enqueue_scripts( string $admin_page ): void { if ( 'toplevel_page_pushly' !== $admin_page ) { return; } $asset_file = PUSHLY__DIR_BUILD . '/settings.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_enqueue_script( 'pushly-script', plugins_url( 'build/settings.js', PUSHLY__DIR_BUILD ), $asset['dependencies'], $asset['version'], [ 'in_footer' => true ] ); $options = get_option( 'pushly', [] ); wp_add_inline_script( 'pushly-script', 'const pushly_env = ' . wp_json_encode( [ 'CDN_DOMAIN' => PUSHLY__CDN_DOMAIN, 'API_NONCE' => wp_create_nonce( 'wp_rest' ), 'debug_logging_enabled' => ! empty( $options['debug_logging_enabled'] ), ] ), 'before' ); wp_enqueue_style( 'pushly-style', plugins_url( 'build/settings.css', PUSHLY__DIR_BUILD ), array_filter( $asset['dependencies'], fn( $style ) => wp_style_is( $style, 'registered' ) ), $asset['version'] ); } /** * @param string[] $actions * @return string[] */ public function add_links_to_plugin( array $actions ): array { return array_merge( $actions, [ 'settings' => '' . __( 'Settings', 'pushly' ) . '', ] ); } /** * One-time option migrations. Hooked to admin_init only so DB writes * never happen on REST requests. */ public function maybe_migrate_options(): void { $new_options = []; $legacy_options = get_option( 'pushly_options' ); if ( ! empty( $legacy_options ) ) { delete_option( 'pushly_options' ); if ( ! empty( $legacy_options['pushly_domain_key'] ) ) { $new_options['sdk_key'] = $legacy_options['pushly_domain_key']; } } $current_options = get_option( 'pushly', [] ); if ( empty( $current_options['enabled_post_types'] ) ) { $new_options['enabled_post_types'] = [ 'post' ]; } if ( ! empty( $new_options ) ) { update_option( 'pushly', array_merge( $new_options, $current_options ) ); } } public function register_settings(): void { $schema = [ 'type' => 'object', 'properties' => [ 'domain_id' => [ 'type' => 'integer' ], 'sdk_key' => [ 'type' => 'string' ], 'api_key' => [ 'type' => [ 'string', 'null' ] ], 'sending_enabled' => [ 'type' => 'boolean' ], 'auto_send_enabled' => [ 'type' => 'boolean' ], 'enabled_post_types' => [ 'type' => 'array' ], 'initialization_disabled' => [ 'type' => 'boolean' ], 'debug_logging_enabled' => [ 'type' => 'boolean' ], ], ]; register_setting( 'options', 'pushly', [ 'type' => 'object', 'show_in_rest' => [ 'schema' => $schema ], 'sanitize_callback' => [ $this, 'sanitize_settings' ], ] ); } /** * @param array $input * @return array|\WP_Error */ public function sanitize_settings( array $input ) { $current_options = get_option( 'pushly', [] ); $input['domain_id'] = (int) filter_var( $input['domain_id'], FILTER_SANITIZE_NUMBER_INT ); $input['sdk_key'] = sanitize_text_field( $input['sdk_key'] ); // Detect debug_logging_enabled transition from false → true and write environment snapshot. $was_enabled = ! empty( $current_options['debug_logging_enabled'] ); $now_enabled = ! empty( $input['debug_logging_enabled'] ); if ( $now_enabled && ! $was_enabled ) { try { $sdk_key = $input['sdk_key'] ?? ''; if ( strlen( $sdk_key ) >= 4 ) { $masked = str_repeat( '*', strlen( $sdk_key ) - 4 ) . substr( $sdk_key, -4 ); } else { $masked = str_repeat( '*', strlen( $sdk_key ) ); } $snapshot_context = [ 'plugin_version' => PUSHLY__PLUGIN_VERSION, 'wordpress_version' => get_bloginfo( 'version' ), 'php_version' => phpversion(), 'sdk_key' => $masked, 'api_key_configured' => ! empty( $input['api_key'] ), 'sending_enabled' => ! empty( $input['sending_enabled'] ), 'auto_send_enabled' => ! empty( $input['auto_send_enabled'] ), 'enabled_post_types' => $input['enabled_post_types'] ?? [], 'debug_logging_enabled_at' => gmdate( 'c' ), 'active_theme' => wp_get_theme()->get( 'Name' ), 'multisite' => is_multisite(), ]; $log_store = new LogStore(); $log_writer = new LogWriter( $log_store, true ); $log_writer->log_immediate( 'info', 'environment_snapshot', 'Environment snapshot: Captured — Debug logging enabled at ' . gmdate( 'c' ), null, $snapshot_context ); } catch ( \Exception $e ) { // Silently suppress — settings save must proceed even if snapshot fails. } } if ( ! empty( $input['sending_enabled'] ) ) { if ( empty( $input['api_key'] ) ) { return new \WP_Error( 'missing_required_property', 'API key must be provided when sending is enabled.', [ 'status' => 400 ] ); } // Encrypt the API key if it's new or has changed from the stored value. // On first save, current_options['api_key'] is empty so we always encrypt. // On subsequent saves, we only re-encrypt if the user entered a different key. $stored_key = $current_options['api_key'] ?? ''; if ( empty( $stored_key ) || $stored_key !== $input['api_key'] ) { $input['api_key'] = Util::encrypt_api_key( $input['sdk_key'], $input['sdk_key'], $input['api_key'] ); } } return $input; } public function register_settings_errors(): void { settings_errors( 'pushly' ); } }