form|mail|settings, 'level' => error|warning, 'message' => string ]. * * @return array */ public function issues(): array { $issues = array(); $add = static function ( string $tab, string $level, string $message ) use ( &$issues ): void { $issues[] = array( 'tab' => $tab, 'level' => $level, 'message' => $message, ); }; // Template. $fields = $this->form->fields(); foreach ( $this->form->problems() as $error ) { $add( 'form', 'error', $error ); } $inputs = $this->form->input_fields(); if ( array() === $inputs ) { $add( 'form', 'error', __( 'The form has no input fields.', 'flexsolutions-form-builder' ) ); } if ( ! array_filter( $fields, static fn( Field $f ) => 'submit' === $f->type ) ) { $add( 'form', 'error', __( 'The form has no [submit] button.', 'flexsolutions-form-builder' ) ); } foreach ( $this->form->conditions() as $field_name => $conditions ) { foreach ( $conditions as $condition ) { if ( ! isset( $inputs[ $condition->field ] ) ) { $add( 'form', 'error', sprintf( /* translators: 1: field name in the condition, 2: field inside the group. */ __( 'The [if] group around [%2$s] refers to an unknown field "%1$s", so the group is always hidden.', 'flexsolutions-form-builder' ), $condition->field, $field_name ) ); } } } $has_files = array() !== array_filter( $inputs, static fn( Field $f ): bool => 'file' === $f->type ); if ( $has_files && empty( $this->form->mail['attach_files'] ) && empty( $this->form->settings['store_entries'] ) ) { $add( 'mail', 'warning', __( 'This form accepts files, but they are neither attached to the email nor stored as entries, so uploaded files are discarded.', 'flexsolutions-form-builder' ) ); } // Mail. $mail = $this->form->mail; if ( '' === $mail['recipient'] ) { $add( 'mail', 'warning', sprintf( /* translators: %s: email address. */ __( 'No recipient set. Messages will go to the site admin email (%s).', 'flexsolutions-form-builder' ), (string) get_option( 'admin_email' ) ) ); } $known = array_merge( array_keys( $inputs ), array_map( static fn( string $n ): string => '_raw_' . $n, array_keys( $inputs ) ), Mailer::SPECIAL_TAGS ); // Address lists may only use placeholders of piped choice fields (routing). foreach ( array( 'recipient' => __( 'To', 'flexsolutions-form-builder' ), 'cc' => __( 'CC', 'flexsolutions-form-builder' ), 'bcc' => __( 'BCC', 'flexsolutions-form-builder' ), ) as $key => $label ) { foreach ( self::placeholders( (string) ( $mail[ $key ] ?? '' ) ) as $name ) { if ( ! isset( $inputs[ $name ] ) || ! $inputs[ $name ]->has_pipes() ) { $add( 'mail', 'error', sprintf( /* translators: 1: placeholder, 2: setting label. */ __( '[%1$s] in %2$s must be a drop-down, radio or checkbox field whose choices use pipes, e.g. "Sales|sales@example.com". It will be ignored.', 'flexsolutions-form-builder' ), $name, $label ) ); } } } $check = array( 'subject' => __( 'Subject', 'flexsolutions-form-builder' ), 'body' => __( 'Message body', 'flexsolutions-form-builder' ), 'reply_to' => __( 'Reply-To', 'flexsolutions-form-builder' ), 'from_name' => __( 'From name', 'flexsolutions-form-builder' ), 'autoreply_subject' => __( 'Auto-reply subject', 'flexsolutions-form-builder' ), 'autoreply_body' => __( 'Auto-reply body', 'flexsolutions-form-builder' ), ); foreach ( $check as $key => $label ) { foreach ( self::placeholders( (string) $mail[ $key ] ) as $name ) { if ( ! in_array( $name, $known, true ) ) { $add( 'mail', 'warning', sprintf( /* translators: 1: placeholder, 2: setting label. */ __( 'Unknown placeholder [%1$s] in %2$s. It will be sent as plain text.', 'flexsolutions-form-builder' ), $name, $label ) ); } } } if ( Form::is_placeholder( (string) $mail['reply_to'] ) && ! $this->is_email_field( trim( (string) $mail['reply_to'], '[]' ) ) ) { $add( 'mail', 'error', __( 'Reply-To must be an email field placeholder, such as [your-email].', 'flexsolutions-form-builder' ) ); } if ( ! empty( $mail['autoreply_enabled'] ) ) { if ( ! $this->is_email_field( trim( (string) $mail['autoreply_to'], '[]' ) ) ) { $add( 'mail', 'error', __( 'Auto-reply is on, but "Auto-reply to" is not an email field placeholder, so no auto-reply will be sent.', 'flexsolutions-form-builder' ) ); } // Content the visitor controls, mailed to an address the visitor controls, is a spam relay. $used = array_map( static fn( string $n ): string => str_starts_with( $n, '_raw_' ) ? substr( $n, 5 ) : $n, array_merge( self::placeholders( (string) $mail['autoreply_subject'] ), self::placeholders( (string) $mail['autoreply_body'] ) ) ); $echoed = array_values( array_intersect( array_keys( $inputs ), $used ) ); if ( array() !== $echoed ) { $add( 'mail', 'warning', sprintf( /* translators: %s: list of placeholders. */ __( 'The auto-reply repeats visitor input (%s). Spammers can abuse this to send their text through your site. Consider removing it.', 'flexsolutions-form-builder' ), '[' . implode( '], [', $echoed ) . ']' ) ); } } $from_domain = $this->from_domain(); $site_domain = preg_replace( '/^www\./i', '', (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); if ( '' !== $from_domain && '' !== $site_domain && strtolower( $from_domain ) !== strtolower( $site_domain ) && ! str_ends_with( strtolower( $from_domain ), '.' . strtolower( $site_domain ) ) ) { $add( 'mail', 'warning', sprintf( /* translators: 1: sender domain, 2: site domain. */ __( 'Mail is sent from @%1$s, which differs from this site (%2$s). Make sure that domain authorises your server (SPF/DKIM), or messages may land in spam.', 'flexsolutions-form-builder' ), $from_domain, $site_domain ) ); } // Settings. if ( Captcha::requested_by( $this->form ) && null === Captcha::active() ) { $add( 'settings', 'error', __( 'CAPTCHA is switched on, but no provider is configured in Flexsolutions Form Builder → Settings, so the form is not protected by CAPTCHA.', 'flexsolutions-form-builder' ) ); } if ( ! empty( $this->form->settings['akismet'] ) && ! Content_Filter::akismet_available() ) { $add( 'settings', 'warning', __( 'Akismet checking is on, but Akismet is not active with an API key.', 'flexsolutions-form-builder' ) ); } if ( ! empty( $this->form->settings['demo_mode'] ) ) { $add( 'settings', 'warning', __( 'Demo mode is on: submissions are validated but no email is sent.', 'flexsolutions-form-builder' ) ); } /** * Filters configuration issues for a form. * * @param array $issues Issues found. * @param Form $form Form. */ return (array) apply_filters( 'tflexforms_config_issues', $issues, $this->form ); } /** * Placeholder names used in a text, e.g. "[a] [_date]" => [ 'a', '_date' ]. * * @param string $text Text. * @return string[] */ public static function placeholders( string $text ): array { preg_match_all( '/\[(_?[A-Za-z][A-Za-z0-9_-]*)\]/', $text, $m ); return array_values( array_unique( $m[1] ) ); } /** * Whether a field name refers to an email field in this form. * * @param string $name Field name. */ private function is_email_field( string $name ): bool { $fields = $this->form->input_fields(); return isset( $fields[ $name ] ) && 'email' === $fields[ $name ]->type; } /** * Domain of the address WordPress will send from (after other plugins' filters). */ private function from_domain(): string { $host = (string) wp_parse_url( network_home_url(), PHP_URL_HOST ); $host = preg_replace( '/^www\./i', '', $host ); $from = (string) ( $this->form->mail['from_email'] ?? '' ); if ( '' === $from ) { $from = (string) apply_filters( 'wp_mail_from', 'wordpress@' . $host ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reading core's filter. } $at = strrpos( $from, '@' ); return false === $at ? '' : substr( $from, $at + 1 ); } }