'none', 'error' => '', ); } $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); $subject = ! empty( $config['subject'] ) ? self::replace( $config['subject'], $config, $data, false ) /* translators: 1: form name, 2: site name */ : sprintf( __( 'New submission via "%1$s" – %2$s', 'alpeg-forms' ), $config['title'], $site ); $rows = self::table( $data ); $meta = ''; if ( $page_url ) { $meta .= '

' . esc_html__( 'Sent from:', 'alpeg-forms' ) . ' ' . esc_html( $page_url ) . '

'; } if ( $entry_id ) { $link = admin_url( 'admin.php?page=alpeg-forms&view=' . (int) $entry_id ); $meta .= '

' . esc_html__( 'View in dashboard', 'alpeg-forms' ) . '

'; } $body = self::wrap( /* translators: %s: form name */ sprintf( __( 'New submission: %s', 'alpeg-forms' ), $config['title'] ), $rows . $meta ); $headers = array( 'Content-Type: text/html; charset=UTF-8' ); $reply = self::first_email( $data ); if ( $reply ) { $headers[] = 'Reply-To: ' . $reply; } $attachments = array(); $tmp_dir = ''; if ( ! empty( $config['attach'] ) ) { list( $attachments, $tmp_dir ) = self::prepare_attachments( $data ); } $mail = apply_filters( 'alpeg_forms_notify_mail', array( 'to' => $to, 'subject' => self::clean_subject( $subject ), 'body' => $body, 'headers' => $headers, 'attachments' => $attachments, ), $config, $data ); $ok = self::send( $mail ); self::cleanup_attachments( $tmp_dir ); return array( 'status' => $ok ? 'sent' : 'failed', 'error' => $ok ? '' : self::$last_error, ); } /** * Confirmation to the sender. * * @param array $config Configuration. * @param array $data Values. * @param string $email Recipient. * @return bool */ public static function confirm( array $config, array $data, $email ) { if ( ! is_email( $email ) ) { return false; } $d = Renderer::defaults(); $subject = self::replace( ! empty( $config['confirm']['subject'] ) ? $config['confirm']['subject'] : $d['confirmationSubject'], $config, $data, false ); $message = ! empty( $config['confirm']['message'] ) ? $config['confirm']['message'] : $d['confirmationMessage']; $body = self::wrap( $subject, self::replace( $message, $config, $data, true ) ); $mail = apply_filters( 'alpeg_forms_confirm_mail', array( 'to' => $email, 'subject' => self::clean_subject( $subject ), 'body' => $body, 'headers' => array( 'Content-Type: text/html; charset=UTF-8' ), 'attachments' => array(), ), $config, $data ); return self::send( $mail ); } /** * Send a test mail (mail diagnostics). * * @param string $to Recipient. * @return array{ok:bool,error:string} */ public static function test( $to ) { $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); $body = self::wrap( __( 'Test mail from ALPEG Forms', 'alpeg-forms' ), '

' . esc_html__( 'If you are reading this, your website can send emails. Please also check your spam folder: if this mail ended up there, set up an SMTP plugin.', 'alpeg-forms' ) . '

' . '

' . esc_html( $site ) . ' · ' . esc_html( home_url() ) . '

' ); $ok = self::send( array( 'to' => $to, /* translators: %s: site name */ 'subject' => sprintf( __( 'Test mail from ALPEG Forms – %s', 'alpeg-forms' ), $site ), 'body' => $body, 'headers' => array( 'Content-Type: text/html; charset=UTF-8' ), 'attachments' => array(), ) ); return array( 'ok' => $ok, 'error' => $ok ? '' : self::$last_error, ); } /** * Send via wp_mail and catch errors. * * @param array $mail to, subject, body, headers, attachments. * @return bool */ private static function send( array $mail ) { self::$last_error = ''; $catch = static function ( $error ) { if ( is_wp_error( $error ) ) { self::$last_error = wp_strip_all_tags( $error->get_error_message() ); } }; add_action( 'wp_mail_failed', $catch ); $ok = wp_mail( $mail['to'], $mail['subject'], $mail['body'], $mail['headers'], isset( $mail['attachments'] ) ? $mail['attachments'] : array() ); remove_action( 'wp_mail_failed', $catch ); if ( ! $ok ) { if ( '' === self::$last_error ) { self::$last_error = __( 'The mail system of the server reported an unknown error.', 'alpeg-forms' ); } update_option( 'alpeg_forms_last_mail_error', array( 'time' => time(), 'message' => self::$last_error, ), false ); } return (bool) $ok; } /** * Copy uploaded files under their original names into a temporary * directory so that the attachments carry readable names. * * @param array $data Values. * @return array [string[] $paths, string $tmp_dir] */ private static function prepare_attachments( array $data ) { $paths = array(); $total = 0; $limit = (int) apply_filters( 'alpeg_forms_attachment_limit', 15 * MB_IN_BYTES ); $dir = trailingslashit( get_temp_dir() ) . 'alpeg-forms-' . strtolower( wp_generate_password( 12, false ) ); foreach ( $data as $row ) { if ( empty( $row['files'] ) ) { continue; } foreach ( $row['files'] as $file ) { $src = Files::absolute_path( $file['path'] ); if ( ! $src || $total + filesize( $src ) > $limit ) { continue; } if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) { return array( array(), '' ); } $name = sanitize_file_name( $file['name'] ); $dest = $dir . '/' . ( count( $paths ) + 1 ) . '-' . $name; if ( copy( $src, $dest ) ) { $paths[] = $dest; $total += filesize( $src ); } } } return array( $paths, $paths ? $dir : '' ); } /** * Remove the temporary attachment directory. * * @param string $dir Directory. */ private static function cleanup_attachments( $dir ) { if ( ! $dir || ! is_dir( $dir ) ) { return; } foreach ( (array) glob( $dir . '/*' ) as $f ) { wp_delete_file( $f ); } @rmdir( $dir ); // phpcs:ignore } /** * Replace placeholders: {fields}, {form_title}, {site_name} and {field-id}. * * @param string $text Text. * @param array $config Configuration. * @param array $data Values. * @param bool $html Output as HTML (escaped, line breaks). * @return string */ private static function replace( $text, array $config, array $data, $html ) { $map = array( '{form_title}' => $config['title'], '{site_name}' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), ); foreach ( $data as $row ) { $map[ '{' . $row['id'] . '}' ] = $row['value']; } if ( ! $html ) { return strtr( str_replace( '{fields}', '', $text ), $map ); } // Escape first, then replace – so no input can inject HTML. $escaped = array(); foreach ( $map as $k => $v ) { $escaped[ esc_html( $k ) ] = esc_html( $v ); } $out = array(); foreach ( explode( '{fields}', $text ) as $part ) { $out[] = '

' . nl2br( strtr( esc_html( $part ), $escaped ) ) . '

'; } return implode( self::table( $data ), $out ); } /** * Values as a table. * * @param array $data Values. * @return string */ private static function table( array $data ) { $html = ''; foreach ( $data as $row ) { $html .= '' . ''; } return $html . '
' . esc_html( $row['label'] ) . '' . nl2br( esc_html( '' !== $row['value'] ? $row['value'] : '–' ) ) . '
'; } /** * Mail frame. * * @param string $title Heading. * @param string $inner Content (already escaped). * @return string */ private static function wrap( $title, $inner ) { return '' . '
' . '

' . esc_html( $title ) . '

' . $inner . '
'; } /** * Recipients: block setting → plugin setting → admin email. * * @param string $list Comma-separated list. * @return string[] */ public static function recipients( $list ) { foreach ( array( $list, alpeg_forms_setting( 'notify_email', '' ), get_option( 'admin_email' ) ) as $candidate ) { $valid = array_values( array_filter( array_map( 'trim', explode( ',', (string) $candidate ) ), 'is_email' ) ); if ( $valid ) { return $valid; } } return array(); } /** * First email address among the values. * * @param array $data Values. * @return string */ private static function first_email( array $data ) { foreach ( $data as $row ) { if ( 'email' === $row['type'] && is_email( $row['value'] ) ) { return $row['value']; } } return ''; } /** * Subject without line breaks (protection against header injection). * * @param string $s Subject. * @return string */ private static function clean_subject( $s ) { return trim( preg_replace( '/[\r\n\t]+/', ' ', wp_strip_all_tags( $s ) ) ); } }