*/ public static function available(): array { $drivers = []; foreach ( DriverRegistry::all( DriverRegistry::CHANNEL_SMS ) as $id => $driver ) { if ( $driver instanceof SmsDriver ) { $drivers[ $id ] = $driver; } } return $drivers; } /** * Enabled gateway ids exactly as chosen, including unconfigured ones. * * For the gateways screen, which shows an enabled but incomplete gateway so * the store owner can see why it is not being used. * * Nothing chosen yet falls back to the single provider picked before this * list existed, and then to every configured gateway — so a store that never * visits the gateways screen keeps sending exactly as it did. * * @return list */ public static function enabledIds(): array { $available = self::available(); $stored = Settings::get( self::OPTION_ORDER, [] ); $ids = []; foreach ( is_array( $stored ) ? $stored : [] as $id ) { if ( is_string( $id ) && isset( $available[ $id ] ) ) { $ids[ $id ] = $id; } } if ( [] !== $ids ) { return array_values( $ids ); } $legacy = (string) Settings::get( 'notifications.sms_driver', '' ); if ( isset( $available[ $legacy ] ) ) { return [ $legacy ]; } foreach ( $available as $id => $driver ) { if ( $driver->isConfigured() ) { $ids[ $id ] = $id; } } return array_values( $ids ); } /** * The gateways to send through, in order. * * An enabled gateway with incomplete credentials is left out: keeping it * would make every send spend a network timeout learning what * `isConfigured()` already knows. * * @return list */ public static function enabled(): array { $available = self::available(); $drivers = []; foreach ( self::enabledIds() as $id ) { $driver = $available[ $id ] ?? null; if ( null !== $driver && $driver->isConfigured() ) { $drivers[] = $driver; } } return $drivers; } /** * The enabled gateways that can read a code out in a phone call, in order. * * @return list */ public static function voiceCapable(): array { return array_values( array_filter( self::enabled(), static fn( SmsDriver $driver ): bool => $driver instanceof VoiceOtpDriver ) ); } /** * The failover policy. */ public static function policy(): string { return self::POLICY_FIRST_ONLY === Settings::get( self::OPTION_POLICY, self::POLICY_FAILOVER ) ? self::POLICY_FIRST_ONLY : self::POLICY_FAILOVER; } /** * Whether a failure should move on to the next gateway. */ public static function failsOver(): bool { return self::POLICY_FAILOVER === self::policy(); } /** * Whether anything can be sent at all. No network access. */ public static function canSend(): bool { return [] !== self::enabled(); } }