<?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,
],
];
}
}