, text:string, recipients:string} */ public static function for( EventDefinition $event ): array { $stored = self::allStored()[ $event->settingsKey() ] ?? []; return [ // Off by default, always. Switching the module on must not start // texting a store's entire customer base with wording nobody has read // — and on a paid SMS line, spending their credit. 'enabled' => (bool) ( $stored['enabled'] ?? false ), 'channel' => (string) ( $stored['channel'] ?? 'sms' ), 'pattern' => (string) ( $stored['pattern'] ?? '' ), // Pattern variable => template. Empty means "send the plugin's own // names", which is what every event saved before 0.14.0 has. 'pattern_map' => is_array( $stored['pattern_map'] ?? null ) ? self::cleanMap( $stored['pattern_map'] ) : [], 'text' => isset( $stored['text'] ) && '' !== $stored['text'] ? (string) $stored['text'] : $event->defaultText, // Admin notifications need somewhere to go; a comma-separated list is // what a shop owner can actually maintain. 'recipients' => (string) ( $stored['recipients'] ?? '' ), ]; } /** * Is this notification switched on? * * @param EventDefinition $event The notification. */ public static function isEnabled( EventDefinition $event ): bool { return self::for( $event )['enabled']; } /** * Save one notification's configuration. * * @param EventDefinition $event The notification. * @param array{enabled?:bool, channel?:string, pattern?:string, pattern_map?:array, text?:string, recipients?:string} $values New values. */ public static function save( EventDefinition $event, array $values ): void { $all = self::allStored(); $channel = (string) ( $values['channel'] ?? 'sms' ); $all[ $event->settingsKey() ] = [ 'enabled' => ! empty( $values['enabled'] ), 'channel' => in_array( $channel, [ 'sms', 'email', 'both' ], true ) ? $channel : 'sms', 'pattern' => sanitize_text_field( (string) ( $values['pattern'] ?? '' ) ), // A mapping without a pattern means nothing; dropped so a stale one // cannot resurface when a different pattern is entered later. 'pattern_map' => '' !== trim( (string) ( $values['pattern'] ?? '' ) ) && is_array( $values['pattern_map'] ?? null ) ? self::cleanMap( $values['pattern_map'] ) : [], 'text' => sanitize_textarea_field( (string) ( $values['text'] ?? '' ) ), 'recipients' => sanitize_text_field( (string) ( $values['recipients'] ?? '' ) ), ]; $module = Settings::all( NotificationsModule::SLUG ); $module[ self::KEY ] = $all; Settings::save( NotificationsModule::SLUG, $module ); } /** * Flip one notification on or off. * * Its own method because toggling is by far the most common change, and the * admin list offers it inline rather than making someone open a form to * tick one box. * * @param EventDefinition $event The notification. * @param bool $enabled Desired state. */ public static function setEnabled( EventDefinition $event, bool $enabled ): void { $current = self::for( $event ); $current['enabled'] = $enabled; self::save( $event, $current ); } /** * A mapping with safe keys and values, at most twenty entries. * * @param array $map Raw mapping. * * @return array */ public static function cleanMap( array $map ): array { $clean = []; foreach ( $map as $name => $template ) { $name = (string) preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $name ); if ( '' === $name || ! is_scalar( $template ) || count( $clean ) >= 20 ) { continue; } $clean[ $name ] = sanitize_text_field( (string) $template ); } return $clean; } /** * Every stored configuration, keyed by settings key. * * @return array> */ private static function allStored(): array { $stored = Settings::get( NotificationsModule::SLUG . '.' . self::KEY, [] ); return is_array( $stored ) ? $stored : []; } }