translateAnswer( $form_data ); } /** * Translates the message after sending and the emails to the visitor of a form. * * @param array $form_data The decoded form. */ private function translateAnswer( array $form_data ): array { $places = array(); $confirmations = self::at( $form_data, array( 'settings', 'confirmations' ) ); foreach ( is_array( $confirmations ) ? $confirmations : array() as $id => $confirmation ) { if ( is_array( $confirmation ) && ( ! isset( $confirmation['type'] ) || 'message' === $confirmation['type'] ) ) { self::addPlace( $places, $form_data, array( 'settings', 'confirmations', $id, 'message' ), 'confirmations/c' . $id . '/message' ); } } $notifications = self::at( $form_data, array( 'settings', 'notifications' ) ); foreach ( is_array( $notifications ) ? $notifications : array() as $id => $notification ) { if ( is_array( $notification ) && self::mailsTheVisitor( $notification ) ) { foreach ( self::MAIL_TEXTS as $name ) { self::addPlace( $places, $form_data, array( 'settings', 'notifications', $id, $name ), 'notifications/n' . $id . '/' . $name ); } } } return $this->translate( (int) $form_data['id'], $form_data, $places ); } /** * Before the plugin builds an email of a form sent: an email to the site's owner is built and * sent in the original, until the next email or the end of the sending. Whether the email is * sent stays as the plugin and other code decided. * * @param mixed $process Whether the email is sent. * @param mixed $fields The fields sent. * @param mixed $form_data The form. * @param int|string $notification_id ID of the email in the form's settings. * @return mixed */ public function startNotification( $process, $fields = array(), $form_data = array(), $notification_id = 0 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed -- The plugin passes the fields before the form. $this->endOwnerMails(); $notification = is_array( $form_data ) ? self::at( $form_data, array( 'settings', 'notifications', $notification_id ) ) : null; if ( $process && is_array( $notification ) && ! self::mailsTheVisitor( $notification ) ) { $this->startOwnerMail(); } return $process; } /** * Ends the sending of a form: an email to the site's owner sent last is done. */ public function endSending(): void { $this->endOwnerMails(); } /** * Whether an email of a form writes to the visitor: every address it sends to is a field's tag, * as {field_id="4"}, or the signed-in visitor's {user_email}. The site's address, {admin_email}, * and a fixed address are the site owner's. * * @param array $notification Settings of the email. */ private static function mailsTheVisitor( array $notification ): bool { return self::onlyVisitors( isset( $notification['email'] ) && is_string( $notification['email'] ) ? $notification['email'] : '', static function ( string $recipient ): bool { return 1 === preg_match( '/^\{(field_id="\d+"|user_email)\}$/', $recipient ); } ); } /** * Translates the texts of a form, see TableProvider::translate(), and keeps what its choices * send in the original. * * @param mixed $form_data The decoded form. * @return mixed The form, with its texts changed, or as it was. */ public function filterFormData( $form_data ) { if ( ! is_array( $form_data ) || empty( $form_data['id'] ) ) { return $form_data; } if ( 'translate' === swiftling_mode() ) { $form_data = $this->keepSentValues( $form_data ); } return $this->translate( (int) $form_data['id'], $form_data, $this->places( $form_data ) ); } /** * Makes the choices without a value of their own send their original label: show_values on, and * the label as the value, before the label is translated. * * @param array $form_data Form. * @return array */ private function keepSentValues( array $form_data ): array { $fields = isset( $form_data['fields'] ) && is_array( $form_data['fields'] ) ? $form_data['fields'] : array(); foreach ( $fields as $key => $field ) { if ( ! is_array( $field ) || empty( $field['choices'] ) || ! is_array( $field['choices'] ) || ! empty( $field['show_values'] ) ) { continue; } foreach ( $field['choices'] as $row => $choice ) { if ( is_array( $choice ) && ( ! isset( $choice['value'] ) || '' === (string) $choice['value'] ) && isset( $choice['label'] ) ) { $form_data['fields'][ $key ]['choices'][ $row ]['value'] = (string) $choice['label']; } } $form_data['fields'][ $key ]['show_values'] = '1'; } return $form_data; } /** * Gives the box of a consent the original consent as its value. The plugin draws that box from * the label alone, whatever show_values says, so on a page in another language it would send the * translated consent, which the plugin refuses as an option it does not know: a form with a * required consent could not be sent. keepSentValues() has kept the original as the value of the * choice; the label shows the translation. * * @param mixed $properties Properties of the field, as the plugin's class made them. * @param mixed $field The field. * @return mixed */ public function keepConsentValue( $properties, $field ) { if ( 'translate' !== swiftling_mode() || ! is_array( $properties ) || empty( $properties['inputs'] ) || ! is_array( $field ) || empty( $field['choices'] ) ) { return $properties; } foreach ( $properties['inputs'] as $key => $input ) { if ( isset( $input['attr'], $field['choices'][ $key ]['value'] ) && '' !== (string) $field['choices'][ $key ]['value'] ) { $properties['inputs'][ $key ]['attr']['value'] = (string) $field['choices'][ $key ]['value']; } } return $properties; } /** * Returns where each text of a form is: its name in the texts, "fields/field_3/label", mapped to * the keys that lead to it in the form. * * @param array $form_data Form. * @return array> */ private function places( array $form_data ): array { $places = array(); foreach ( self::FORM_TEXTS as $name ) { self::addPlace( $places, $form_data, array( 'settings', $name ), $name ); } $fields = isset( $form_data['fields'] ) && is_array( $form_data['fields'] ) ? $form_data['fields'] : array(); foreach ( $fields as $key => $field ) { if ( ! is_array( $field ) || in_array( isset( $field['type'] ) ? $field['type'] : '', self::BUILDER_ONLY, true ) ) { continue; } $id = 'fields/field_' . ( isset( $field['id'] ) ? (int) $field['id'] : (int) $key ); foreach ( self::FIELD_TEXTS as $name ) { self::addPlace( $places, $form_data, array( 'fields', $key, $name ), $id . '/' . $name ); } $choices = isset( $field['choices'] ) && is_array( $field['choices'] ) ? $field['choices'] : array(); foreach ( array_keys( $choices ) as $row ) { self::addPlace( $places, $form_data, array( 'fields', $key, 'choices', $row, 'label' ), $id . '/choices/c' . $row ); } } return $places; } /** * Returns the forms, drafts too. * * @return array */ protected function allForms(): array { $forms = array(); foreach ( get_posts( array( 'post_type' => 'wpforms', 'post_status' => array( 'publish', 'draft' ), // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- The forms of the Forms screen; the cap keeps a very large site from a list without end. 'posts_per_page' => 200, 'orderby' => 'title', 'order' => 'ASC', ) ) as $post ) { $forms[] = array( 'id' => (int) $post->ID, 'title' => trim( (string) $post->post_title ), ); } return $forms; } /** * Returns a form, decoded, as the plugin decodes it. * * @param int $form_id Form ID. * @return array|null Null when there is no such form. */ protected function formData( int $form_id ): ?array { $post = get_post( $form_id ); if ( ! $post || 'wpforms' !== $post->post_type ) { return null; } $data = function_exists( 'wpforms_decode' ) ? wpforms_decode( $post->post_content ) : json_decode( $post->post_content, true ); return is_array( $data ) ? $data : null; } /** * Reads the texts of a form through the filters, as a page that shows it and the sending of it * do: the form, the message after sending and the emails to the visitor. * * @param int $form_id Form ID. */ protected function readTexts( int $form_id ): void { $data = $this->formData( $form_id ); if ( null !== $data ) { $this->filterFormData( $data ); $this->translateAnswer( $data ); } } /** * Returns the ways a post shows a form: the shortcode, or the block with the ID of the form. * * @param int $form_id Form ID. * @return array */ protected function pageMarks( int $form_id ): array { return array( array( '[wpforms id="' . $form_id . '"' ), array( "[wpforms id='" . $form_id . "'" ), array( '[wpforms id=' . $form_id . ']' ), array( '