*/ public function get( string $integration ): array { $stored = get_option( self::option( $integration ), array() ); if ( ! is_array( $stored ) ) { return array(); } $clean = array(); foreach ( $stored as $key => $value ) { if ( is_scalar( $value ) ) { $clean[ (string) $key ] = (string) $value; } } return $clean; } /** * One stored value, or the fallback when this site has never set it. * * EVERY VALUE IS A STRING, GOING BOTH WAYS. The client normalises on the way out — see * `shared/SchemaField.tsx`, whose docblock records why a number arriving as a number in one * browser and a string in another is the one thing that must never happen — and this keeps the * server's half of that bargain. Callers cast at the point of use, where they know whether they * wanted an int, a float or a list. * * @param string $integration Connector id. * @param string $key Option key. * @param string $fallback What the connector's schema says the default is. */ public function value( string $integration, string $key, string $fallback = '' ): string { $all = $this->get( $integration ); return array_key_exists( $key, $all ) ? $all[ $key ] : $fallback; } /** * Replace everything stored for one connector. * * @param string $integration Connector id. * @param array $values Option key => value. */ public function put( string $integration, array $values ): bool { $clean = array(); foreach ( $values as $key => $value ) { if ( is_scalar( $value ) ) { $clean[ (string) $key ] = (string) $value; } } // autoload 'no': read when a connector runs, not when a visitor loads a page. The switch that // IS on the hot path lives in {@see State} and is autoloaded on its own. return update_option( self::option( $integration ), $clean, false ); } /** * Merge values over what is stored. * * @param string $integration Connector id. * @param array $values Option key => value. */ public function merge( string $integration, array $values ): bool { return $this->put( $integration, array_merge( $this->get( $integration ), $values ) ); } /** * Forget one connector's options. * * NOT CALLED WHEN A CONNECTOR IS SWITCHED OFF, and this is the same rule * {@see IntegrationInterface::disable()} states for synced data: somebody switching WooCommerce * off for an afternoon has not asked to lose the threshold and the pipeline they chose. Only * `forget()` on the connector — the explicit, destructive action — reaches this. * * @param string $integration Connector id. */ public function forget( string $integration ): void { delete_option( self::option( $integration ) ); } /** * @param string $integration Connector id. */ private static function option( string $integration ): string { return self::PREFIX . sanitize_key( $integration ); } }