/ src / library / mail / MailComposeBuilder.php
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\library\mail;


/**
 * Builds the starting contents of the compose form when a user
 * replies to or forwards a message. Given the already-parsed source
 * message, it works out the recipients, the subject (adding the
 * Re:/Fwd: prefix), and a quoted or forwarded body, plus the threading
 * context (message id and references) so the sent reply links back to
 * the original. It does only this assembly from values handed to it,
 * so it touches no network, database, or session.
 *
 * @author Chris Pollett
 */
class MailComposeBuilder
{
    /**
     * Works out the subject line for a reply or forward by adding the
     * given prefix ("Re: " or "Fwd: ") unless the subject already
     * starts with it, so prefixes do not pile up over a long thread.
     *
     * @param string $prefix the prefix to ensure, e.g. "Re: "
     * @param string $subject the original subject line
     * @return string the subject with exactly one such prefix
     */
    public static function replySubject($prefix, $subject)
    {
        $stripped = ltrim($subject);
        $prefix_len = strlen($prefix);
        if (strncasecmp($stripped, $prefix, $prefix_len) === 0) {
            return $stripped;
        }
        return $prefix . $stripped;
    }
    /**
     * Builds the compose-form starting values for replying to or
     * forwarding a message. For a forward it leaves the recipients
     * empty and quotes the original under a "Begin forwarded message"
     * banner; for a reply it addresses the original sender (and, for
     * reply-all, the other recipients minus yourself) and quotes the
     * body with "> " markers under an attribution line. It also
     * returns the threading context so the sent message references the
     * original.
     *
     * @param object $source the parsed source message (a MimeMessage)
     * @param string $my_email the current user's own address, lower-
     *      cased, excluded from reply-all recipients
     * @param string $kind one of 'forward', 'reply', or 'reply_all'
     * @param string $folder the source message's folder, kept for
     *      context
     * @param int $uid the source message's uid, kept for context
     * @return array a two-part array: FORM_VALUES (TO, CC, SUBJECT,
     *      BODY) and CONTEXT (KIND, UID, FOLDER, MESSAGE_ID,
     *      REFERENCES)
     */
    public static function replyPrefill($source, $my_email, $kind,
        $folder, $uid)
    {
        $headers = $source->headers;
        $source_subject = $headers['subject'] ?? '';
        $source_from = $headers['from'] ?? '';
        $source_reply_to = $headers['reply-to'] ?? '';
        $source_to = $headers['to'] ?? '';
        $source_cc = $headers['cc'] ?? '';
        $source_date = $headers['date'] ?? '';
        $source_msgid = trim($headers['message-id'] ?? '');
        $source_refs = trim($headers['references'] ?? '');
        if ($kind === 'forward') {
            $to_value = '';
            $cc_value = '';
            $subject_value = self::replySubject('Fwd: ',
                $source_subject);
            $forward_header = "Begin forwarded message:\n" .
                "From: " . trim($source_from) . "\n" .
                "Date: " . trim($source_date) . "\n" .
                "Subject: " . trim($source_subject) . "\n" .
                "To: " . trim($source_to) . "\n";
            if (trim($source_cc) !== '') {
                $forward_header .= "Cc: " . trim($source_cc) . "\n";
            }
            $body_value = "\n\n" . $forward_header . "\n" .
                $source->body_text . "\n";
        } else {
            $to_value = $source_reply_to !== '' ? $source_reply_to
                : $source_from;
            $cc_value = '';
            if ($kind === 'reply_all') {
                $remaining_to = MailHeaderParser::filterAddressList(
                    $source_to, $my_email, $to_value);
                $cc_value = MailHeaderParser::joinAddressLists(
                    $source_cc, $remaining_to, $my_email);
            }
            $subject_value = self::replySubject('Re: ',
                $source_subject);
            $reply_attribution = "On " . trim($source_date) .
                ", " . trim($source_from) . " wrote:";
            $reply_lines = preg_split('/\r\n|\r|\n/',
                $source->body_text);
            $reply_quoted = [];
            foreach ($reply_lines as $reply_line) {
                $reply_quoted[] = "> " . $reply_line;
            }
            $body_value = "\n\n" . $reply_attribution . "\n" .
                implode("\n", $reply_quoted) . "\n";
        }
        $references_value = $source_refs !== '' ?
            $source_refs . ' ' . $source_msgid : $source_msgid;
        return [
            'FORM_VALUES' => [
                'TO' => $to_value,
                'CC' => $cc_value,
                'SUBJECT' => $subject_value,
                'BODY' => $body_value,
            ],
            'CONTEXT' => [
                'KIND' => $kind,
                'UID' => $uid,
                'FOLDER' => $folder,
                'MESSAGE_ID' => $source_msgid,
                'REFERENCES' => $references_value,
            ],
        ];
    }
}
X