diff --git a/htdocs/core/class/html.formticket.class.php b/htdocs/core/class/html.formticket.class.php
index 9e00fc35f907f..d2dde32ce692e 100644
--- a/htdocs/core/class/html.formticket.class.php
+++ b/htdocs/core/class/html.formticket.class.php
@@ -1697,53 +1697,102 @@ public function showMessageForm($width = '40%')
print '';
// Recipients / adressed-to
- print '
'.$langs->trans('MailRecipients');
- print ' '.$form->textwithpicto('', $langs->trans("TicketMessageRecipientsHelp"), 1, 'help');
+ // @ATM-PATCH BEGIN ticket-editable-recipients — editable TO/CC recipient fields, backport of Dolibarr core PR #38391 (adapted to 21.0)
+ // grep: git grep '@ATM-PATCH' -- htdocs/
+ print ' '.$langs->trans('MailRecipients');
+ print ' '.$form->textwithpicto('', $langs->trans("YouCanUseCommaSeparatorForSeveralRecipients"), 1, 'help');
print ' ';
+ $withto = array();
if ($res) {
- // Retrieve email of all contacts (internal and external)
+ $sendto_free = GETPOSTISSET('sendto') ? GETPOST('sendto', 'alphawithlgt') : '';
+ print ' ';
+
+ // Build recipient list; keys are email addresses (used directly in newMessage())
$contacts = $ticketstat->getInfosTicketInternalContact(1);
$contacts = array_merge($contacts, $ticketstat->getInfosTicketExternalContact(1));
+ $seen_emails = array();
- $sendto = array();
-
- // Build array to display recipient list
if (is_array($contacts) && count($contacts) > 0) {
- foreach ($contacts as $key => $info_sendto) {
+ foreach ($contacts as $info_sendto) {
if ($info_sendto['email'] != '') {
- $sendto[] = dol_escape_htmltag(trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">").' ('.dol_escape_htmltag($info_sendto['libelle']).") ";
+ $email_lc = strtolower($info_sendto['email']);
+ if (!in_array($email_lc, $seen_emails)) {
+ $label_text = trim($info_sendto['firstname'].' '.$info_sendto['lastname']).' <'.$info_sendto['email'].'>';
+ $label_html = dol_htmlentities(trim($info_sendto['firstname'].' '.$info_sendto['lastname']), ENT_QUOTES, 'UTF-8')
+ .' ('.dol_htmlentities($info_sendto['libelle'], ENT_QUOTES, 'UTF-8').') ';
+ $withto[$info_sendto['email']] = array(
+ 'id' => $info_sendto['email'],
+ 'label' => str_replace(array('<', '>'), array('(', ')'), $label_text),
+ 'labelhtml' => $label_html,
+ );
+ $seen_emails[] = $email_lc;
+ }
}
}
}
- if (!empty($ticketstat->origin_replyto) && !in_array($ticketstat->origin_replyto, $sendto)) {
- $sendto[] = dol_escape_htmltag($ticketstat->origin_replyto).' ('.$langs->trans("TicketEmailOriginIssuer").") ";
- } elseif ($ticketstat->origin_email && !in_array($ticketstat->origin_email, $sendto)) {
- $sendto[] = dol_escape_htmltag($ticketstat->origin_email).' ('.$langs->trans("TicketEmailOriginIssuer").") ";
+ if (!empty($ticketstat->origin_replyto) && !in_array(strtolower($ticketstat->origin_replyto), $seen_emails)) {
+ $email = (string) $ticketstat->origin_replyto;
+ $withto[$email] = array('id' => $email, 'label' => $email, 'labelhtml' => dol_escape_htmltag($email).' ('.$langs->trans('TicketEmailOriginIssuer').') ');
+ $seen_emails[] = strtolower($email);
+ } elseif ($ticketstat->origin_email && !in_array(strtolower((string) $ticketstat->origin_email), $seen_emails)) {
+ $email = (string) $ticketstat->origin_email;
+ $withto[$email] = array('id' => $email, 'label' => $email, 'labelhtml' => dol_escape_htmltag($email).' ('.$langs->trans('TicketEmailOriginIssuer').') ');
+ $seen_emails[] = strtolower($email);
}
if ($ticketstat->fk_soc > 0) {
$ticketstat->socid = $ticketstat->fk_soc;
$ticketstat->fetch_thirdparty();
+ if (!empty($ticketstat->thirdparty->email) && !in_array(strtolower((string) $ticketstat->thirdparty->email), $seen_emails)) {
+ $email = (string) $ticketstat->thirdparty->email;
+ $withto[$email] = array('id' => $email, 'label' => $email, 'labelhtml' => dol_escape_htmltag($email).' ('.$langs->trans('Customer').') ');
+ $seen_emails[] = strtolower($email);
+ }
+ }
- if (!empty($ticketstat->thirdparty->email) && !in_array($ticketstat->thirdparty->email, $sendto)) {
- $sendto[] = $ticketstat->thirdparty->email.' ('.$langs->trans('Customer').') ';
+ if (getDolGlobalInt('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') && getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
+ $generic_email = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
+ if (!in_array(strtolower($generic_email), $seen_emails)) {
+ $withto[$generic_email] = array('id' => $generic_email, 'label' => $generic_email, 'labelhtml' => dol_escape_htmltag($generic_email).' (generic) ');
}
}
- if (getDolGlobalInt('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS')) {
- $sendto[] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO').' (generic email) ';
+ // Preselect all contacts on first load; restore POST selection on re-display
+ if (GETPOSTISSET('receiver_multiselect')) {
+ $withtoselected = GETPOST('receiver', 'array');
+ } else {
+ $withtoselected = array_keys($withto);
}
- // Print recipient list
- if (is_array($sendto) && count($sendto) > 0) {
- print img_picto('', 'email', 'class="pictofixedwidth"');
- print implode(', ', $sendto);
+ if (!empty($withto)) {
+ print ' '.$langs->trans("and").'/'.$langs->trans("or").' ';
+ print $form->multiselectarray('receiver', $withto, $withtoselected, 0, 0, 'inline-block minwidth500', 0, 0, '', '', '', 1);
} else {
- print ''.$langs->trans('WarningNoEMailsAdded').' '.$langs->trans('TicketGoIntoContactTab').'
';
+ // No contact linked: emit the flag hidden so newMessage() still reads the free sendto input
+ print ' ';
+ print ' '.$langs->trans('WarningNoEMailsAdded').' ';
}
}
print ' ';
+
+ // CC field: free input + multiselect of the same contact list, nothing preselected
+ print '';
+ print $form->textwithpicto($langs->trans("MailCC"), $langs->trans("YouCanUseCommaSeparatorForSeveralRecipients"), 1, 'help');
+ print ' ';
+ if (GETPOSTISSET('receivercc_multiselect')) {
+ $sendtocc_free = GETPOST('sendtocc', 'alphawithlgt');
+ } else {
+ $sendtocc_free = getDolGlobalString('TICKET_SEND_INTERNAL_CC');
+ }
+ print ' ';
+ if (!empty($withto)) {
+ print ' '.$langs->trans("and").'/'.$langs->trans("or").' ';
+ $withtocc_selected = GETPOSTISSET('receivercc_multiselect') ? GETPOST('receivercc', 'array') : array();
+ print $form->multiselectarray('receivercc', $withto, $withtocc_selected, 0, 0, 'inline-block minwidth500', 0, 0, '', '', '', 1);
+ }
+ print ' ';
+ // @ATM-PATCH END ticket-editable-recipients
}
$uselocalbrowser = false;
@@ -1833,6 +1882,12 @@ public function showMessageForm($width = '40%')
// Clean first \n and br (to avoid empty line when CONTACTCIVNAME is empty)
$defaultmessage = preg_replace("/^( )+/", "", $defaultmessage);
$defaultmessage = preg_replace("/^\n+/", "", $defaultmessage);
+ // @ATM-PATCH BEGIN ticket-private-empty-compose — a private ticket message opens with an empty compose field (was prefilled with the template/personal signature); an email message keeps the template, the outgoing signature is still appended at send time
+ // grep: git grep '@ATM-PATCH' -- htdocs/
+ if (empty($send_email)) {
+ $defaultmessage = '';
+ }
+ // @ATM-PATCH END ticket-private-empty-compose
}
print ''.$langs->trans("Message").' ';
diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php
index bc72624655715..bcf67554a136b 100644
--- a/htdocs/ticket/class/ticket.class.php
+++ b/htdocs/ticket/class/ticket.class.php
@@ -3023,9 +3023,79 @@ public function newMessage($user, &$action, $private = 1, $public_area = 0)
}
}
+ // @ATM-PATCH BEGIN ticket-editable-recipients — when the standardised send form is used (PR #38391 backport), replace auto-computed recipients with the user selection and add the CC list
+ // grep: git grep '@ATM-PATCH' -- htdocs/
+ $sendtocc_form = '';
+ if (GETPOSTISSET('receiver_multiselect')) {
+ $sendto = array();
+ $receiver_selected = GETPOST('receiver', 'array');
+ if (is_array($receiver_selected)) {
+ foreach ($receiver_selected as $email) {
+ $email = trim((string) $email);
+ if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $sendto[$email] = $email;
+ }
+ }
+ }
+ // Free input: plain email or "Name ", comma-separated
+ $sendto_free = GETPOST('sendto', 'alphawithlgt');
+ if ($sendto_free !== '') {
+ foreach (explode(',', $sendto_free) as $entry) {
+ $entry = trim($entry);
+ if ($entry === '') {
+ continue;
+ }
+ if (preg_match('/.*<\s*([^>]+)\s*>/', $entry, $matches)) {
+ $email = trim($matches[1]);
+ } else {
+ $email = $entry;
+ }
+ if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $sendto[$email] = $entry;
+ }
+ }
+ }
+
+ // CC list built from the form (multiselect + free input), case-insensitive dedup
+ $sendtocc_list = array();
+ $sendtocc_seen = array();
+ if (GETPOSTISSET('receivercc_multiselect')) {
+ $receivercc_selected = GETPOST('receivercc', 'array');
+ if (is_array($receivercc_selected)) {
+ foreach ($receivercc_selected as $email) {
+ $email = trim((string) $email);
+ if ($email && filter_var($email, FILTER_VALIDATE_EMAIL) && !in_array(strtolower($email), $sendtocc_seen)) {
+ $sendtocc_list[] = $email;
+ $sendtocc_seen[] = strtolower($email);
+ }
+ }
+ }
+ }
+ $sendtocc_free = GETPOST('sendtocc', 'alphawithlgt');
+ if ($sendtocc_free !== '') {
+ foreach (explode(',', $sendtocc_free) as $entry) {
+ $entry = trim($entry);
+ if ($entry === '') {
+ continue;
+ }
+ if (preg_match('/.*<\s*([^>]+)\s*>/', $entry, $matches)) {
+ $email = trim($matches[1]);
+ } else {
+ $email = $entry;
+ }
+ if ($email && filter_var($email, FILTER_VALIDATE_EMAIL) && !in_array(strtolower($email), $sendtocc_seen)) {
+ $sendtocc_list[] = $entry;
+ $sendtocc_seen[] = strtolower($email);
+ }
+ }
+ }
+ $sendtocc_form = implode(',', $sendtocc_list);
+ }
+ // @ATM-PATCH END ticket-editable-recipients
+
// Don't try to send email when no recipient
if (!empty($sendto)) {
- $result = $this->sendTicketMessageByEmail($subject, $message, 0, $sendto, $listofpaths, $listofmimes, $listofnames);
+ $result = $this->sendTicketMessageByEmail($subject, $message, 0, $sendto, $listofpaths, $listofmimes, $listofnames, $sendtocc_form);
if ($result) {
// update last_msg_sent date (for last message sent to external users)
$this->date_last_msg_sent = dol_now();
@@ -3092,7 +3162,7 @@ protected function applyMailSubstitutions($content, $langs, $object = null)
* @param string[] $mimefilename_list List of attached file name in message
* @return boolean True if mail sent to at least one receiver, false otherwise
*/
- public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array())
+ public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $sendtocc_extra = '')
{
global $conf, $langs, $user;
@@ -3116,6 +3186,12 @@ public function sendTicketMessageByEmail($subject, $message, $send_internal_cc =
if ($send_internal_cc) {
$sendtocc = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
}
+ // @ATM-PATCH BEGIN ticket-editable-recipients — allow an explicit CC list (from the standardised send form, PR #38391 backport) in addition to the internal CC
+ // grep: git grep '@ATM-PATCH' -- htdocs/
+ if (!empty($sendtocc_extra)) {
+ $sendtocc = ($sendtocc !== '' ? $sendtocc.',' : '').$sendtocc_extra;
+ }
+ // @ATM-PATCH END ticket-editable-recipients
$from = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
$is_sent = false;