> The option definitions. */ abstract public function options(): array; /** * Builds the body sent to the service. * * @param IntegrationContext $context Submission being delivered. * @param array $config Stored values for the form. * * @return array The payload, encoded as JSON by the sender. */ abstract public function payload( IntegrationContext $context, array $config ): array; /** * Returns the sentence explaining what to fill in. * * @return string The translated description, which may carry inline markup. */ public function description(): string { return ''; } /** * Determines whether the stored configuration is usable. * * Every option marked required must carry something. An integration with a * rule the definitions cannot express overrides this. * * @param array $config Stored values for one form. * * @return bool True when a submission can be sent. */ public function is_configured( array $config ): bool { foreach ( $this->options() as $option ) { if ( empty( $option['required'] ) ) { continue; } if ( '' === trim( (string) ( $config[ (string) $option['key'] ] ?? '' ) ) ) { return false; } } return true; } /** * Determines whether a particular submission is worth sending. * * Lets an integration skip a submission it cannot use — a mailing list with * no address to add — without that counting as a delivery that failed. * * @param IntegrationContext $context Submission being delivered. * @param array $config Stored values for the form. * * @return bool True when the delivery should go ahead. */ public function should_send( IntegrationContext $context, array $config ): bool { return true; } /** * Returns where a submission should be delivered. * * The default is the shape most services take: one `webhook_url` option * holding the whole address. A service that assembles its endpoint from an * account id or a key overrides this. * * Returning null switches the integration off for that form, which is what a * missing key or an unusable endpoint should do rather than a delivery that * fails every time and fills the log with the same error. * * @param Form $form Form being submitted. * @param array $config Stored values for that form. * * @return Webhook|null The endpoint, or null when there is nothing to send to. */ public function endpoint( Form $form, array $config ): ?Webhook { return $this->target( $this->option( $config, 'webhook_url' ) ); } /** * Describes the single URL option the default endpoint reads. * * @param string $label Field label shown in the editor. * @param string $placeholder Example address. * * @return array The option definition. */ protected function url_option( string $label, string $placeholder ): array { return array( 'key' => 'webhook_url', 'type' => 'url', 'label' => $label, 'placeholder' => $placeholder, 'required' => true, ); } /** * Reads one stored option. * * @param array $config Stored values. * @param string $key Option name. */ protected function option( array $config, string $key ): string { $value = $config[ $key ] ?? ''; $value = is_scalar( $value ) ? trim( (string) $value ) : ''; /* * A credential the site set once, used by every form that did not enter * one of its own. Only options the integration declared shared fall back: * without that check a per form choice left empty would silently pick up * an unrelated site wide value. */ if ( '' === $value && in_array( $key, $this->shared_keys(), true ) ) { return Settings::text( IntegrationRegistry::shared_key( $this->id(), $key ) ); } return $value; } /** * Returns the options that fall back to a site wide value. * * @return array Option names. */ protected function shared_keys(): array { $keys = array(); foreach ( $this->options() as $option ) { if ( ! empty( $option['shared'] ) ) { $keys[] = (string) $option['key']; } } return $keys; } /** * Builds the endpoint this integration posts to. * * The key is prefixed so the delivery log and the retry run can tell an * integration apart from a webhook somebody entered by hand. * * @param string $url Absolute endpoint. * @param array $headers Optional. Extra headers, such as authorisation. * * @return Webhook|null The endpoint, or null when the URL is unusable. */ protected function target( string $url, array $headers = array() ): ?Webhook { // Screened the same way a hand-entered webhook is; see OutboundUrl. if ( '' === $url || ! OutboundUrl::is_safe( $url ) ) { return null; } return new Webhook( key: IntegrationRegistry::PREFIX . $this->id(), name: $this->label(), url: $url, // Integrations authenticate with the service's own credentials; a // signature of ours would be one more header nobody reads. secret: '', headers: $headers, enabled: true ); } }