Make WordPress Core

Ticket #52283: 52283.patch

File 52283.patch, 22.9 KB (added by imath, 5 years ago)
  • new file src/wp-includes/class-plancake-email-parser.php

    diff --git src/wp-includes/class-plancake-email-parser.php src/wp-includes/class-plancake-email-parser.php
    new file mode 100755
    index 0000000000..ba8d4bafb2
    - +  
     1<?php
     2
     3/*************************************************************************************
     4* ===================================================================================*
     5* Software by: Danyuki Software Limited                                              *
     6* This file is part of Plancake.                                                     *
     7*                                                                                    *
     8* Copyright 2009-2010-2011 by:     Danyuki Software Limited                          *
     9* Support, News, Updates at:  http://www.plancake.com                                *
     10* Licensed under the LGPL version 3 license.                                         *                                                       *
     11* Danyuki Software Limited is registered in England and Wales (Company No. 07554549) *
     12**************************************************************************************
     13* Plancake is distributed in the hope that it will be useful,                        *
     14* but WITHOUT ANY WARRANTY; without even the implied warranty of                     *
     15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                      *
     16* GNU Lesser General Public License v3.0 for more details.                           *
     17*                                                                                    *
     18* You should have received a copy of the GNU Lesser General Public License           *
     19* along with this program.  If not, see <http://www.gnu.org/licenses/>.              *
     20*                                                                                    *
     21**************************************************************************************
     22*
     23* Valuable contributions by:
     24* - Chris
     25*
     26**************************************************************************************
     27*
     28* Adaptations for WordPress use:
     29* - imath
     30*
     31* **************************************************************************************/
     32
     33/**
     34 * Extracts the headers and the body of an email
     35 * Obviously it can't extract the bcc header because it doesn't appear in the content
     36 * of the email.
     37 *
     38 * N.B.: if you deal with non-English languages, we recommend you install the IMAP PHP extension:
     39 * the Plancake PHP Email Parser will detect it and used it automatically for better results.
     40 *
     41 * For more info, check:
     42 * https://github.com/plancake/official-library-php-email-parser
     43 *
     44 * @author dan
     45 */
     46class Plancake_Email_Parser {
     47        /**
     48         * Plaintext code.
     49         *
     50         * @var int
     51         */
     52        const PLAINTEXT = 1;
     53
     54        /**
     55         * HTML code.
     56         *
     57         * @var int
     58         */
     59        const HTML = 2;
     60
     61        /**
     62         * IMAP extension availability.
     63         *
     64         * @var boolean
     65         */
     66        private $is_imap_extension_available = false;
     67
     68        /**
     69         * The Email raw content.
     70         *
     71         * @var string
     72         */
     73        private $email_raw_content;
     74
     75        /**
     76         * Email header fields.
     77         *
     78         * @var array
     79         */
     80        protected $raw_fields;
     81
     82        /**
     83         * Lines of the Email body.
     84         *
     85         * @var array
     86         */
     87        protected $raw_body_lines;
     88
     89        /**
     90         * Class constructor.
     91         *
     92         * @param string $email_raw_content The Email raw content.
     93         */
     94        public function __construct( $email_raw_content ) {
     95                $this->email_raw_content = $email_raw_content;
     96
     97                $this->extract_headers_and_rawbody();
     98
     99                if ( function_exists( 'imap_open' ) ) {
     100                        $this->is_imap_extension_available = true;
     101                }
     102        }
     103
     104        /**
     105         * Extracts the email header and body.
     106         *
     107         * @access private
     108         */
     109        private function extract_headers_and_rawbody() {
     110                $lines          = preg_split( "/(\r?\n|\r)/", $this->email_raw_content );
     111                $current_header = '';
     112                $i              = 0;
     113
     114                foreach ( $lines as $line ) {
     115                        if ( self::is_new_line( $line ) ) {
     116                                // end of headers.
     117                                $this->raw_body_lines = array_slice( $lines, $i );
     118                                break;
     119                        }
     120
     121                        if ( $this->is_line_starting_with_printable_char( $line ) ) {
     122                                // start of new header.
     123                                preg_match( '/([^:]+): ?(.*)$/', $line, $matches );
     124                                $new_header = strtolower( $matches[1] );
     125                                $value      = $matches[2];
     126
     127                                // Sets the header field value.
     128                                $this->raw_fields[ $new_header ] = $value;
     129                                $current_header                  = $new_header;
     130                        } else {
     131                                // more lines related to the current header.
     132
     133                                if ( $current_header ) {
     134                                        // to prevent notice from empty lines.
     135                                        $this->raw_fields[ $current_header ] .= substr( $line, 1 );
     136                                }
     137                        }
     138
     139                        $i++;
     140                }
     141        }
     142
     143        /**
     144         * Gets the Email subject.
     145         *
     146         * @throws Exception if a subject header is not found.
     147         * @return string An UTF-8 formatted string.
     148         */
     149        public function get_subject() {
     150                if ( ! isset( $this->raw_fields['subject'] ) ) {
     151                        throw new Exception( "Couldn't find the subject of the email" );
     152                }
     153
     154                $ret = '';
     155
     156                if ( $this->is_imap_extension_available ) {
     157                        // Subject can span into several lines.
     158                        foreach ( imap_mime_header_decode( $this->raw_fields['subject'] ) as $h ) {
     159                                $charset = ( 'default' === $h->charset ) ? 'US-ASCII' : $h->charset;
     160                                $ret    .= iconv( $charset, 'UTF-8//TRANSLIT', $h->text );
     161                        }
     162                } else {
     163                        $ret = utf8_encode( iconv_mime_decode( $this->raw_fields['subject'] ) );
     164                }
     165
     166                return $ret;
     167        }
     168
     169        /**
     170         * Gets the list of recipients in copy.
     171         *
     172         * @return array The list of recipients in copy.
     173         */
     174        public function get_cc() {
     175                if ( ! isset( $this->raw_fields['cc'] ) ) {
     176                        return array();
     177                }
     178
     179                return explode( ',', $this->raw_fields['cc'] );
     180        }
     181
     182        /**
     183         * Gets the list of recipients.
     184         *
     185         * @throws Exception if a to header is not found or if there are no recipient.
     186         * @return array The list of recipients.
     187         */
     188        public function get_to() {
     189                if ( ! isset( $this->raw_fields['to'] ) || ! count( $this->raw_fields['to'] ) ) {
     190                        throw new Exception( "Couldn't find the recipients of the email" );
     191                }
     192
     193                return explode( ',', $this->raw_fields['to'] );
     194        }
     195
     196        /**
     197         * Returns the email body, UTF8 encoded.
     198         *
     199         * Example of an email body
     200         *
     201         * --0016e65b5ec22721580487cb20fd
     202         * Content-Type: text/plain; charset=ISO-8859-1
     203         * Hi all. I am new to Android development.
     204         * Please help me.
     205         *
     206         * --
     207         * My signature
     208         * email: myemail@gmail.com
     209         * web: http://www.example.com
     210         * --0016e65b5ec22721580487cb20fd
     211         * Content-Type: text/html; charset=ISO-8859-1
     212         *
     213         * @param int $return_type The code of the type of content to return.
     214         *                         Default: 1.
     215         * @return string The email body, UTF8 encoded.
     216         */
     217        public function get_body( $return_type = self::PLAINTEXT ) {
     218                $body                      = '';
     219                $detected_content_type     = false;
     220                $content_transfer_encoding = null;
     221                $charset                   = 'ASCII';
     222                $waiting_for_content_start = true;
     223
     224                if ( self::HTML === $return_type ) {
     225                        $content_type_regex = '/^Content-Type: ?text\/html/i';
     226                } else {
     227                        $content_type_regex = '/^Content-Type: ?text\/plain/i';
     228                }
     229
     230                // There could be more than one boundary.
     231                preg_match_all( '!boundary=(.*)$!mi', $this->email_raw_content, $matches );
     232                $boundaries = array();
     233
     234                if ( isset( $matches[1] ) && $matches[1] ) {
     235                        // Sometimes boundaries are delimited by quotes - we want to remove them.
     236                        foreach ( $matches[1] as $boundary ) {
     237                                $boundaries[] = trim( str_replace( array( "'", '"' ), '', $boundary ) );
     238                        }
     239                }
     240
     241                foreach ( $this->raw_body_lines as $line ) {
     242                        if ( null === $content_transfer_encoding && preg_match( '/^Content-Transfer-Encoding: ?(.*)/i', $line, $matches ) ) {
     243                                $content_transfer_encoding = $matches[1];
     244                        }
     245
     246                        if ( ! $detected_content_type ) {
     247
     248                                if ( preg_match( $content_type_regex, $line, $matches ) ) {
     249                                        $detected_content_type = true;
     250                                }
     251
     252                                if ( preg_match( '/charset=(.*)/i', $line, $matches ) ) {
     253                                        $charset = strtoupper( trim( $matches[1], '"' ) );
     254                                }
     255                        } elseif ( $detected_content_type && $waiting_for_content_start ) {
     256
     257                                if ( preg_match( '/charset=(.*)/i', $line, $matches ) ) {
     258                                        $charset = strtoupper( trim( $matches[1], '"' ) );
     259                                }
     260
     261                                if ( self::is_new_line( $line ) ) {
     262                                        $waiting_for_content_start = false;
     263                                }
     264                        } else {
     265                                /*
     266                                 * Collecting the actual content until we find the delimiter.
     267                                 * If the delimited is AAAAA, the line will be --AAAAA  - that's why we use substr.
     268                                 */
     269                                if ( is_array( $boundaries ) && in_array( substr( $line, 2 ), $boundaries, true ) ) {
     270                                        break;
     271                                }
     272
     273                                $body .= $line . "\n";
     274                        }
     275                }
     276
     277                if ( ! $detected_content_type ) {
     278                        /*
     279                         * If here, we missed the text/plain content-type (probably it was
     280                         * in the header), thus we assume the whole body is what we are after.
     281                         */
     282                        $body = implode( "\n", $this->raw_body_lines );
     283                }
     284
     285                // removing trailing new lines
     286                $body = preg_replace( '/((\r?\n)*)$/', '', $body );
     287
     288                if ( 'base64' === $content_transfer_encoding ) {
     289                        $body = base64_decode( $body );
     290                } elseif ( 'quoted-printable' === $content_transfer_encoding ) {
     291                        $body = quoted_printable_decode( $body );
     292                }
     293
     294                if ( 'UTF-8' !== $charset ) {
     295                        /*
     296                         * FORMAT=FLOWED, despite being popular in emails, it is not
     297                         * supported by iconv
     298                         */
     299                        $charset   = str_replace( 'FORMAT=FLOWED', '', $charset );
     300                        $body_copy = $body;
     301                        $body      = iconv( $charset, 'UTF-8//TRANSLIT', $body );
     302
     303                        // iconv returns false on failure.
     304                        if ( false === $body ) {
     305                                $body = utf8_encode( $body_copy );
     306                        }
     307                }
     308
     309                return $body;
     310        }
     311
     312        /**
     313         * Gets the text/plain body, UTF8 encoded.
     314         *
     315         * @return string The text/plain body, UTF8 encoded.
     316         */
     317        public function get_plain_body() {
     318                return $this->get_body( self::PLAINTEXT );
     319        }
     320
     321        /**
     322         * Gets the text/html body, UTF8 encoded.
     323         *
     324         * @return string The text/html body, UTF8 encoded.
     325         */
     326        public function get_html_body() {
     327                return $this->get_body( self::HTML );
     328        }
     329
     330        /**
     331         * Gets the value of a specific email header.
     332         *
     333         * N.B.: if the header doesn't exist an empty string is returned.
     334         *
     335         * @param string $header_name - the header we want to retrieve
     336         * @return string - the value of the header
     337         */
     338        public function get_header( $header_name ) {
     339                $header_name = strtolower( $header_name );
     340
     341                if ( isset( $this->raw_fields[ $header_name ] ) ) {
     342                        return $this->raw_fields[ $header_name ];
     343                }
     344
     345                return '';
     346        }
     347
     348        /**
     349         * Checks if the current line is a new one.
     350         *
     351         * @param string $line One of the lines of the raw content.
     352         * @return bool True if it's a new line. False otherwise.
     353         */
     354        public static function is_new_line( $line ) {
     355                $line = str_replace( "\r", '', $line );
     356                $line = str_replace( "\n", '', $line );
     357
     358                return ( strlen( $line ) === 0 );
     359        }
     360
     361        /**
     362         * Checks if the current line starts with a printable char.
     363         *
     364         * @param string $line One of the lines of the raw content.
     365         * @return bool True if the line starts with a printable char.
     366         *              False otherwise.
     367         * @access private
     368         */
     369        private function is_line_starting_with_printable_char( $line ) {
     370                return preg_match( '/^[A-Za-z]/', $line );
     371        }
     372}
  • src/wp-includes/comment.php

    diff --git src/wp-includes/comment.php src/wp-includes/comment.php
    index 116e66a57c..b9a89209fb 100644
    function _wp_check_for_scheduled_update_comment_type() { 
    39033903                wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_update_comment_type_batch' );
    39043904        }
    39053905}
     3906
     3907/**
     3908 * Checks the mail server (if enabled) is not restricted to posts.
     3909 *
     3910 * @since 5.7.0
     3911 *
     3912 * @return bool True if the mail server can be used for comments. False otherwise.
     3913 */
     3914function wp_check_comment_mailserver_usability() {
     3915        /**
     3916         * Filter here to disable mail server usage to comments.
     3917         *
     3918         * @since 5.7.0
     3919         *
     3920         * @param $value bool Whether to enable mail server usage to comments.
     3921         */
     3922        return apply_filters( 'wp_check_comment_mailserver_usability', wp_is_mailserver_enabled() );
     3923}
  • src/wp-includes/functions.php

    diff --git src/wp-includes/functions.php src/wp-includes/functions.php
    index eb4c4b6961..577f926846 100644
    function is_php_version_compatible( $required ) { 
    77797779function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) {
    77807780        return abs( (float) $expected - (float) $actual ) <= $precision;
    77817781}
     7782
     7783/**
     7784 * Check if the mail server to fetch posts or comments sent by email is enabled.
     7785 *
     7786 * @since 5.7.0
     7787 *
     7788 * @return bool Whether the mail server to fetch posts or comments sent by email is enabled.
     7789 */
     7790function wp_is_mailserver_enabled() {
     7791        $mailserver_url = get_option( 'mailserver_url', 'mail.example.com' );
     7792
     7793        return $mailserver_url && 'mail.example.com' !== $mailserver_url;
     7794}
  • src/wp-includes/pluggable.php

    diff --git src/wp-includes/pluggable.php src/wp-includes/pluggable.php
    index 7dee482170..024319e14a 100644
    if ( ! function_exists( 'wp_notify_postauthor' ) ) : 
    16721672                }
    16731673
    16741674                $notify_message .= get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n";
     1675
     1676                // Can authors reply to the comment directly by email?
     1677                $can_reply_by_email = wp_check_comment_mailserver_usability();
     1678
     1679                if ( $can_reply_by_email ) {
     1680                        /* translators: %s: Site name. */
     1681                        $notify_message .= sprintf( __( 'Reply to this email directly or view it on %s:' ), $blogname ) . "\r\n";
     1682                }
     1683
    16751684                /* translators: %s: Comment URL. */
    16761685                $notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n";
    16771686
    if ( ! function_exists( 'wp_notify_postauthor' ) ) : 
    16871696                        $notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
    16881697                }
    16891698
    1690                 $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
     1699                $domain   = preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
     1700                $wp_email = 'wordpress@' . $domain;
    16911701
    16921702                if ( '' === $comment->comment_author ) {
    16931703                        $from = "From: \"$blogname\" <$wp_email>";
    if ( ! function_exists( 'wp_notify_postauthor' ) ) : 
    17041714                $message_headers = "$from\n"
    17051715                . 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . "\"\n";
    17061716
    1707                 if ( isset( $reply_to ) ) {
     1717                /**
     1718                 * Use the Reply by Email feature if enabled.
     1719                 */
     1720                if ( $can_reply_by_email ) {
     1721                        $mailserver_login = get_option( 'mailserver_login' );
     1722                        $message_headers .= "Reply-To: \"$blogname\" <$mailserver_login>\n";
     1723
     1724                        $message_id = sprintf(
     1725                                /**
     1726                                 * This Message-ID header is unique for the domain.
     1727                                 * It identifies the author, the post type and the comment type.
     1728                                 * It will be transported into the email reply.
     1729                                 */
     1730                                '%1$s/type/%2$s/%3$s/comment-type/%4$s/%5$s@%6$s',
     1731                                $author->user_nicename,
     1732                                get_post_type( $post ),
     1733                                $post->ID,
     1734                                get_comment_type( $comment ),
     1735                                $comment->comment_ID,
     1736                                $domain
     1737                        );
     1738
     1739                        // Use the Message ID as one of the references to make it available into the email reply.
     1740                        $message_headers .= "References: <$message_id>\n";
     1741                } elseif ( isset( $reply_to ) ) {
    17081742                        $message_headers .= $reply_to . "\n";
    17091743                }
    17101744
  • src/wp-mail.php

    diff --git src/wp-mail.php src/wp-mail.php
    index 1d5fbedf03..91fb26271b 100644
    if ( ! apply_filters( 'enable_post_by_email_configuration', true ) ) { 
    1515        wp_die( __( 'This action has been disabled by the administrator.' ), 403 );
    1616}
    1717
    18 $mailserver_url = get_option( 'mailserver_url' );
    19 
    20 if ( 'mail.example.com' === $mailserver_url || empty( $mailserver_url ) ) {
     18if ( ! wp_is_mailserver_enabled() ) {
    2119        wp_die( __( 'This action has been disabled by the administrator.' ), 403 );
    2220}
    23 
    2421/**
    2522 * Fires to allow a plugin to do a complete takeover of Post by Email.
    2623 *
    do_action( 'wp-mail.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHoo 
    3027
    3128/** Get the POP3 class with which to access the mailbox. */
    3229require_once ABSPATH . WPINC . '/class-pop3.php';
     30require_once ABSPATH . WPINC . '/class-plancake-email-parser.php';
    3331
    3432/** Only check at this interval for new messages. */
    3533if ( ! defined( 'WP_MAIL_INTERVAL' ) ) {
    if ( 0 === $count ) { 
    6563        wp_die( __( 'There doesn&#8217;t seem to be any new mail.' ) );
    6664}
    6765
    68 for ( $i = 1; $i <= $count; $i++ ) {
     66$domain             = preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
     67$can_reply_by_email = wp_check_comment_mailserver_usability();
    6968
     69for ( $i = 1; $i <= $count; $i++ ) {
     70        // Set the message.
    7071        $message = $pop3->get( $i );
    7172
     73        if ( $can_reply_by_email ) {
     74                // Possibly set the comment reply.
     75                $reply = implode( '', $message );
     76
     77                // Parse the reply.
     78                $email_parser  = new Plancake_Email_Parser( $reply );
     79                $email_address = preg_replace( '/(.*?)\<(.*?)\>/', '$2', iconv_mime_decode( $email_parser->get_header( 'From' ) ) );
     80                $author_email  = sanitize_email( $email_address );
     81                $body          = $email_parser->get_plain_body();
     82                $wp_references = array();
     83                $references    = iconv_mime_decode( $email_parser->get_header( 'References' ) );
     84                $content_type  = explode( ';', $email_parser->get_header( 'Content-Type' ) );
     85
     86                $author = get_user_by( 'email', $author_email );
     87                if ( $author ) {
     88                        // Try to get the references.
     89                        preg_match( "/\<(.*?)\/type\/(.*?)\/(\d*?)\/comment-type\/(.*?)\/(\d*?)@$domain\>/", $references, $matches );
     90                        array_shift( $matches );
     91                        $wp_references = array_filter( $matches );
     92                        $comment       = null;
     93
     94                        // Fallback to the comment's link inside the quoted message.
     95                        if ( ! $wp_references ) {
     96                                $url = addcslashes( site_url(), '/.' );
     97
     98                                // Reset matches.
     99                                $matches = array();
     100
     101                                /**
     102                                 * @todo
     103                                 * If the comment parent does not exist anymore and this is used,
     104                                 * a post might be created. This needs extra checks.
     105                                 */
     106                                preg_match( "/$url(.*)#comment-(\d*)/", $body, $matches );
     107                                if ( ! empty( $matches[0] ) && ! empty( $matches[2] ) ) {
     108                                        $post_id = url_to_postid( $matches[0] );
     109
     110                                        if ( $post_id ) {
     111                                                $comment = get_comment( $matches[2] );
     112                                                if ( ! empty( $comment->comment_post_ID ) && $post_id === (int) $comment->comment_post_ID ) {
     113                                                        $wp_references = array(
     114                                                                $author->user_nicename,
     115                                                                get_post_type( $post_id ),
     116                                                                $post_id,
     117                                                                get_comment_type( $comment ),
     118                                                                $comment->comment_ID,
     119                                                        );
     120                                                }
     121                                        }
     122                                }
     123                        }
     124
     125                        // Let's process.
     126                        if ( 5 === count( $wp_references ) ) {
     127                                list( $author_user_nicename, $post_type, $post_id, $comment_type, $comment_id ) = $wp_references;
     128
     129                                if ( null === $comment ) {
     130                                        $comment = get_comment( $comment_id );
     131                                }
     132
     133                                // Set comment data.
     134                                if ( null !== $comment && $author_user_nicename === $author->user_nicename ) {
     135                                        // Set comment author.
     136                                        $comment_author       = $author->display_name;
     137                                        $comment_author_email = $author->user_email;
     138                                        $comment_author_url   = $author->user_url;
     139
     140                                        // Set comment content.
     141                                        $comment_content = $body;
     142                                        $content_type    = reset( $content_type );
     143
     144                                        // Most of the time emails are multipart, but some are not (eg: gmx.com).
     145                                        if ( 'text/html' === strtolower( $content_type ) ) {
     146                                                $comment_content = str_replace( '&nbsp;', ' ', wp_kses( $body, array() ) );
     147                                                $comment_content = html_entity_decode( $comment_content, ENT_QUOTES, get_bloginfo( 'charset' ) );
     148                                        }
     149
     150                                        // Reset matches.
     151                                        $matches = array();
     152
     153                                        // Look for <email> into the content to only keep what's above.
     154                                        preg_match( '/^(.*)\<(.*)@(.*)\>(.*)$/im', $comment_content, $matches );
     155                                        if ( ! empty( $matches[0] ) ) {
     156                                                $comment_content_parts = explode( $matches[0], $comment_content );
     157                                                if ( 2 === count( $comment_content_parts ) ) {
     158                                                        $comment_content = reset( $comment_content_parts );
     159                                                }
     160                                        } else {
     161                                                $commented_post = get_post( $post_id );
     162
     163                                                // Look for the post title into the content to only keep what's above.
     164                                                preg_match( "/^\>(.*)$commented_post->post_title(.*)$/im", $comment_content, $matches );
     165                                                if ( ! empty( $matches[0] ) ) {
     166                                                        $comment_content_parts = explode( $matches[0], $comment_content );
     167                                                        if ( 2 === count( $comment_content_parts ) ) {
     168                                                                $comment_content = reset( $comment_content_parts );
     169                                                        }
     170                                                }
     171                                        }
     172
     173                                        // Reset matches.
     174                                        $matches = array();
     175
     176                                        // Look for specific separators into the content to only keep what's above.
     177                                        preg_match( '/---*|___*|Sent:.*$/', $comment_content, $matches );
     178                                        if ( ! empty( $matches[0] ) ) {
     179                                                $comment_content_parts = explode( $matches[0], $comment_content );
     180
     181                                                if ( 2 === count( $comment_content_parts ) ) {
     182                                                        $comment_content = reset( $comment_content_parts );
     183                                                }
     184                                        }
     185
     186                                        // Trim extra characters from the content.
     187                                        $comment_content = trim( $comment_content, "\n \t\r" );
     188
     189                                        // Set comment date and GMT date.
     190                                        $reply_date       = iconv_mime_decode( $email_parser->get_header( 'Date' ) );
     191                                        $r_date           = preg_replace( '!\s*\(.+\)\s*$!', '', $reply_date );
     192                                        $reply_timestamp  = strtotime( $r_date );
     193                                        $comment_date     = gmdate( 'Y-m-d H:i:s', $reply_timestamp + $time_difference );
     194                                        $comment_date_gmt = gmdate( 'Y-m-d H:i:s', $reply_timestamp );
     195
     196                                        // The comment parent is the refernces' $comment_id.
     197                                        $comment_parent = (int) $comment_id;
     198
     199                                        // Set the comment post ID.
     200                                        $comment_post_ID = (int) $post_id; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
     201
     202                                        // Set the user ID.
     203                                        $user_id = $author->ID;
     204
     205                                        // Build arguments.
     206                                        $commentdata = compact(
     207                                                'comment_author',
     208                                                'comment_author_email',
     209                                                'comment_author_url',
     210                                                'comment_content',
     211                                                'comment_date',
     212                                                'comment_date_gmt',
     213                                                'comment_type',
     214                                                'comment_parent',
     215                                                'comment_post_ID',
     216                                                'user_id'
     217                                        );
     218
     219                                        $comment_reply_id = wp_new_comment( wp_slash( $commentdata ), true );
     220                                        if ( ! is_wp_error( $comment_reply_id ) ) {
     221                                                echo "\n<p><strong>" . __( 'Author:' ) . '</strong> ' . esc_html( $comment_author ) . '</p>';
     222                                                echo "\n<p><strong>" . __( 'Replied:' ) . '</strong> ' . esc_html( $comment_content ) . '</p>';
     223
     224                                                // Delete the email.
     225                                                if ( ! $pop3->delete( $i ) ) {
     226                                                        echo '<p>' . sprintf(
     227                                                                /* translators: %s: POP3 error. */
     228                                                                __( 'Oops: %s' ),
     229                                                                esc_html( $pop3->ERROR )
     230                                                        ) . '</p>';
     231                                                        $pop3->reset();
     232                                                        exit;
     233                                                } else {
     234                                                        echo '<p>' . sprintf(
     235                                                                /* translators: %s: The message ID. */
     236                                                                __( 'Mission complete. Reply %s deleted.' ),
     237                                                                '<strong>' . $i . '</strong>'
     238                                                        ) . '</p>';
     239                                                }
     240                                        } else {
     241                                                echo "\n" . $comment_reply_id->get_error_message();
     242                                        }
     243                                }
     244
     245                                // Jump to next message/reply to avoid creating a post.
     246                                continue;
     247                        }
     248                }
     249        }
     250
    72251        $bodysignal                = false;
    73252        $boundary                  = '';
    74253        $charset                   = '';