> The option definitions. */ public function options(): array { return array( $this->url_option( __( 'Webhook URL', 'devplugins-custom-form-builder' ), 'https://discord.com/api/webhooks/…' ), array( 'key' => 'username', 'type' => 'text', 'label' => __( 'Post as', 'devplugins-custom-form-builder' ), 'description' => __( 'Overrides the name the webhook was created with.', 'devplugins-custom-form-builder' ), ), ); } /** * Builds the body sent to Discord. * * @param IntegrationContext $context Submission being delivered. * @param array $config Stored values for the form. * * @return array The payload. */ public function payload( IntegrationContext $context, array $config ): array { $fields = array(); foreach ( $context->labelled() as $label => $value ) { if ( '' === trim( $value ) ) { continue; } if ( count( $fields ) >= self::MAX_FIELDS ) { break; } $fields[] = array( // Discord refuses an embed field with an empty name or value and // caps them at 256 and 1024 characters. 'name' => $this->truncate( (string) $label, 256 ), 'value' => $this->truncate( $this->literal( $value ), 1024 ), 'inline' => false, ); } $embed = array( 'title' => $this->truncate( $context->form()->title(), 256 ), 'description' => array() === $fields ? __( 'No answers were recorded.', 'devplugins-custom-form-builder' ) : '', 'timestamp' => gmdate( 'c' ), 'fields' => $fields, ); $url = $context->entry_url(); if ( '' !== $url ) { $embed['url'] = $url; } $payload = array( 'embeds' => array( $embed ), /* * Nothing a visitor typed may ping anyone. Discord parses @everyone, * @here and role or user mentions out of message content by default, * so an answer of "@everyone" notified the whole server. */ 'allowed_mentions' => array( 'parse' => array() ), ); $username = $this->option( $config, 'username' ); if ( '' !== $username ) { $payload['username'] = $username; } return $payload; } /** * Shortens text to a number of characters. * * @param string $text Raw text. * @param int $limit Largest length. */ private function truncate( string $text, int $limit ): string { $text = '' === trim( $text ) ? '—' : $text; return mb_strlen( $text ) > $limit ? mb_substr( $text, 0, $limit - 1 ) . '…' : $text; } /** * Escapes Discord markdown in a visitor's answer. * * Embed field values are rendered as markdown, so `[Verify your account](https://…)` * typed into a form became a clickable masked link in the team's channel, and * `@everyone` or `<@&role>` looked like a real mention. Each markdown character is * escaped with a backslash, which Discord shows as the character itself. * * @param string $text Raw answer. */ private function literal( string $text ): string { return (string) preg_replace_callback( '/[\\\\*_~`|>\[\]()<@#:-]/', static fn( array $found ): string => '\\' . $found[0], $text ); } }