|null */ private ?array $cache = null; /** * Builds the site wide setting name of a shared credential. * * @param string $id Integration slug. * @param string $key Option name. */ public static function shared_key( string $id, string $key ): string { return 'integration_' . $id . '_' . $key; } /** * Returns the integrations a site has switched on. * * An empty list means the setting has never been saved, which is different * from a site that deliberately unticked everything — the first should show * all of them, so a fresh install is not an empty screen with no explanation. * * @return array Integrations keyed by slug. */ public function all(): array { $allowed = Settings::get( 'integrations_enabled' ); if ( ! is_array( $allowed ) ) { return $this->registered(); } return array_intersect_key( $this->registered(), array_flip( array_map( 'strval', $allowed ) ) ); } /** * Returns every integration that exists, whether switched on or not. * * The settings screen needs this: a service cannot be offered as a choice by * a list that has already removed the ones nobody chose. * * @return array Integrations keyed by slug. */ public function registered(): array { if ( null !== $this->cache ) { return $this->cache; } $services = array( new Slack(), new Discord(), new Zapier(), new Make(), new Mailchimp(), new HubSpot(), new GoogleSheets(), ); $integrations = array(); foreach ( $services as $service ) { $integrations[ $service->id() ] = $service; } /** * Filters the available integrations. * * An add-on registers a service by adding one object implementing * {@see Integration}; the editor, the storage and the delivery follow from * its own description of itself. * * @since 1.0.0 * * @param array $integrations Integrations keyed by slug. */ $integrations = (array) apply_filters( 'formpilot_integrations', $integrations ); $this->cache = array_filter( $integrations, static fn( $integration ): bool => $integration instanceof Integration ); return $this->cache; } /** * Returns one integration. * * @param string $id Integration slug, with or without the delivery prefix. * * @return Integration|null The integration, or null when nothing is registered under that slug. */ public function find( string $id ): ?Integration { if ( str_starts_with( $id, self::PREFIX ) ) { $id = substr( $id, strlen( self::PREFIX ) ); } return $this->all()[ $id ] ?? null; } /** * Returns a form's configuration for one integration. * * @param Form $form Form to read. * @param string $id Integration slug. * * @return array The stored values, empty when never configured. */ public function config( Form $form, string $id ): array { $stored = $form->setting( self::SETTING, array() ); $stored = is_array( $stored ) ? $stored : array(); $config = $stored[ $id ] ?? array(); return is_array( $config ) ? $config : array(); } /** * Determines whether a form has switched an integration on and filled it in. * * @param Form $form Form to read. * @param Integration $integration Integration to check. * * @return bool True when a submission should be delivered to it. */ public function is_enabled( Form $form, Integration $integration ): bool { $config = $this->config( $form, $integration->id() ); return ! empty( $config['enabled'] ) && $integration->is_configured( $config ); } /** * Returns the integrations a form should deliver to. * * @param Form $form Form to read. * * @return array Integrations keyed by slug. */ public function active( Form $form ): array { return array_filter( $this->all(), fn( Integration $integration ): bool => $this->is_enabled( $form, $integration ) ); } /** * Rebuilds the endpoint of a delivery that is being retried. * * The log stores which integration a row belongs to, not where it was sent: * an API key that has been rotated since should be picked up on the retry * rather than replayed from a copy taken hours ago. * * @param Form $form Form the delivery belongs to. * @param string $key Delivery key, including the prefix. * * @return Webhook|null The endpoint, or null when it can no longer be built. */ public function endpoint( Form $form, string $key ): ?Webhook { $integration = $this->find( $key ); if ( ! $integration instanceof Integration || ! $this->is_enabled( $form, $integration ) ) { return null; } return $integration->endpoint( $form, $this->config( $form, $integration->id() ) ); } /** * Returns the shared secret an integration sends in its request body. * * Read live rather than replayed: the delivery log stores the body with * credentials masked, so a retry asks for the value that is configured *now*. * * @param Form $form Form the delivery belongs to. * @param string $key Delivery key, including the prefix. * * @return string The secret, or an empty string when there is none to give. */ public function secret_for( Form $form, string $key ): string { $integration = $this->find( $key ); if ( ! $integration instanceof Integration || ! $this->is_enabled( $form, $integration ) ) { return ''; } $config = $this->config( $form, $integration->id() ); return isset( $config['secret'] ) && is_scalar( $config['secret'] ) ? trim( (string) $config['secret'] ) : ''; } }