` keyed by connector id. * * Swept on uninstall by {@see Credentials::PREFIX}'s pattern — see this class's docblock. */ public const OPTION = 'plugixa_crm_integration_enabled'; /** * Connector ids that would collide with a framework option and are therefore refused. * * `enabled` is this class's own option; `opts_*` is {@see Options::PREFIX}'s tail. Both live * inside {@see Credentials::PREFIX} so that the uninstall sweep catches them, and the price of * that is this short reserved list. {@see IntegrationRegistry::reserved()} enforces it. */ public const RESERVED = array( 'enabled', 'options' ); /** * Whether an id would collide with a framework option. * * @param string $integration Connector id. */ public static function reserved( string $integration ): bool { $id = sanitize_key( $integration ); return in_array( $id, self::RESERVED, true ) || str_starts_with( $id, 'opts_' ); } /** * Whether one connector is switched on. * * DEFAULTS TO FALSE, AND A NEWLY INSTALLED CONNECTOR IS THEREFORE OFF. A connector that starts * working the moment its code arrives is a connector that starts writing records on somebody's * site because they updated a plugin. Switching it on is a decision, and it is the decision the * card exists to ask for. * * @param string $integration Connector id. */ public function is_enabled( string $integration ): bool { $all = $this->all(); $id = sanitize_key( $integration ); return ! empty( $all[ $id ] ); } /** * Switch one connector on or off. * * @param string $integration Connector id. * @param bool $on The new state. */ public function set( string $integration, bool $on ): void { $id = sanitize_key( $integration ); $all = $this->all(); if ( ( $all[ $id ] ?? false ) === $on ) { // Nothing changed, so nothing is announced. Without this a screen that saves on every // render would tell a connector it had just been switched on, repeatedly, and anything // listening for that would do its first-run work over and over. return; } $all[ $id ] = $on; $this->store( $all ); /** * A connector has just been switched on or off. * * THE FRAMEWORK OWNS THE SWITCH, SO THIS IS HOW A CONNECTOR HEARS ABOUT IT. Deliberately an * action carrying two scalars rather than a method on {@see IntegrationInterface}: a * connector that has nothing to do when it is switched on — which is most of them — should * not have to implement an empty method to say so, and a sibling plugin listening for this * names no class of ours. * * Fired only on a real change, never on a re-save of the same value. * * @param string $integration The connector id. * @param bool $on Its new state. */ do_action( 'plugixa_crm_integration_switched', $id, $on ); } /** * Forget one connector's switch entirely, so it reads as off again. * * Distinct from `set( $id, false )` only in what it leaves behind: this removes the key rather * than storing a false against it, which keeps the option from growing a row for every connector * a site has ever had installed. * * @param string $integration Connector id. */ public function forget( string $integration ): void { $all = $this->all(); unset( $all[ sanitize_key( $integration ) ] ); $this->store( $all ); } /** * Every switch this site has set. * * @return array */ public function all(): array { $stored = get_option( self::OPTION, array() ); if ( ! is_array( $stored ) ) { return array(); } $clean = array(); foreach ( $stored as $id => $on ) { $clean[ sanitize_key( (string) $id ) ] = (bool) $on; } return $clean; } /** * @param array $all Every switch. */ private function store( array $all ): void { // autoload 'yes': see the class docblock — this is asked on the order and submission hot // paths, not only when an admin screen is open. update_option( self::OPTION, array_map( 'boolval', $all ), true ); } }