migrate( $stored, $stored_version ); $this->write( $user_id, $stored ); } return $stored; } /** * Persist an envelope for a user. * * No-ops when the stored value already matches the supplied envelope. * Throws `WC_Data_Exception` when the underlying user meta write fails * for a reason other than the value being unchanged. * * @param int $user_id The user ID. * @param array $envelope The envelope to persist (must have `schema_version` and `preferences` keys). * * @return void * * @throws WC_Data_Exception When the user meta write fails for a non-no-op reason. * * @since 10.8.0 */ public function write( int $user_id, array $envelope ): void { // Skip the write when the stored envelope already matches. This avoids // the ambiguous `false` return from update_user_meta() that means // either "value unchanged" or "DB write failed" — by short-circuiting // the no-op case, a `false` from the call below unambiguously means // the write itself failed and we can surface it. $stored = Users::get_site_user_meta( $user_id, self::META_KEY ); if ( $stored === $envelope ) { return; } $result = Users::update_site_user_meta( $user_id, self::META_KEY, $envelope ); if ( false === $result ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new WC_Data_Exception( 'woocommerce_push_notification_preferences_save_failed', 'Failed to save push notification preferences.', WP_Http::INTERNAL_SERVER_ERROR ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } } /** * Delete a user's stored preferences for this site. * * Multisite keeps a separate envelope per site, so this removes only the * one belonging to the current site. * * @param int $user_id The user ID. * * @return bool True when an envelope was removed, false when there was nothing stored. * * @since 11.2.0 */ public function delete( int $user_id ): bool { return Users::delete_site_user_meta( $user_id, self::META_KEY ); } /** * Upgrade an envelope to the current schema version. * * Pure transformation — does not persist. Missing or malformed * `preferences` entries are replaced with an empty array; the service * layer is responsible for filling in defaults from the empty case. * * @param array $data The stored envelope (expected keys: `schema_version`, `preferences`). * @param int $from_version The schema version currently on disk. * * @return array Envelope upgraded to `self::CURRENT_SCHEMA_VERSION`. * * @since 10.8.0 */ public function migrate( array $data, int $from_version ): array { // Parameter reserved for future schema migrations. unset( $from_version ); $preferences = isset( $data['preferences'] ) && is_array( $data['preferences'] ) ? $data['preferences'] : array(); // For v1 the envelope shape is stable; we only normalize the version tag. return array( 'schema_version' => self::CURRENT_SCHEMA_VERSION, 'preferences' => $preferences, ); } }