diff --git a/agent/invoice.php b/agent/invoice.php index 96fee66ad..3eba035fb 100644 --- a/agent/invoice.php +++ b/agent/invoice.php @@ -246,9 +246,13 @@ if (isset($_GET['invoice_id'])) { + + = 2 && !empty($config_smtp_provider) && $emailable_contacts > 0) { ?> + +
+ + + +
+ +
- Send Email + Send Email... @@ -468,6 +472,24 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); += 2 && !empty($config_smtp_provider)) { ?> + +
+ + + +
+ + @@ -74,10 +90,7 @@ ob_start(); $contact_primary = intval($row['contact_primary']); $contact_billing = intval($row['contact_billing']); - // Default selection reproduces the old link exactly: the - // primary contact was the recipient and every billing - // contact got a copy - $contact_checked = ($contact_primary == 1 || $contact_billing == 1); + $contact_checked = in_array($contact_id, $default_contact_ids, true); ?> diff --git a/agent/modals/quote/quote_email.php b/agent/modals/quote/quote_email.php index 5863a3005..a86fbd219 100644 --- a/agent/modals/quote/quote_email.php +++ b/agent/modals/quote/quote_email.php @@ -33,6 +33,22 @@ $sql_contacts = mysqli_query( $contact_count = mysqli_num_rows($sql_contacts); +// Which boxes open ticked. Read from the same helper Quick Send uses, so the +// modal and the one-click path can never disagree about who "the defaults" are. +$default_contact_ids = []; +$sql_defaults = mysqli_query( + $mysqli, + "SELECT contact_id FROM contacts + WHERE contact_client_id = $client_id + AND contact_archived_at IS NULL + AND contact_email IS NOT NULL + AND contact_email != '' + " . documentDefaultContactFilterSql('quote') +); +while ($row = mysqli_fetch_assoc($sql_defaults)) { + $default_contact_ids[] = intval($row['contact_id']); +} + ob_start(); ?> @@ -75,11 +91,7 @@ ob_start(); $contact_technical = intval($row['contact_technical']); $contact_important = intval($row['contact_important']); - // Default selection reproduces the old link: quotes went to - // the primary contact only. Billing contacts are listed and - // one click away, but stay unchecked - a quote is a sales - // conversation, not a bill - $contact_checked = ($contact_primary == 1); + $contact_checked = in_array($contact_id, $default_contact_ids, true); ?> diff --git a/agent/post/invoice.php b/agent/post/invoice.php index e5da23ba7..f0333d939 100644 --- a/agent/post/invoice.php +++ b/agent/post/invoice.php @@ -544,35 +544,49 @@ if (isset($_POST['email_invoice'])) { enforceClientAccess(); - // Recipients come from the Send Email modal's contact picker. Scoping the - // lookup to this invoice's client is what makes a tampered contact_id - // harmless - it simply matches nothing. - $selected_contacts = $_POST['contacts'] ?? []; - if (!is_array($selected_contacts)) { - $selected_contacts = []; - } - $selected_contact_ids = array_filter(array_unique(array_map('intval', $selected_contacts))); + // Two ways in. Quick Send skips the modal and resolves the default + // recipients server-side; the modal posts an explicit contacts[] list. + // Scoping either lookup to this invoice's client is what makes a tampered + // contact_id harmless - it simply matches nothing. + if (!empty($_POST['quick_send'])) { - if (empty($selected_contact_ids)) { - flashAlert("Select at least one contact to send to", 'error'); - redirect(); - } + $recipient_filter = documentDefaultContactFilterSql('invoice'); - $selected_contact_id_list = implode(',', $selected_contact_ids); + } else { + + $selected_contacts = $_POST['contacts'] ?? []; + if (!is_array($selected_contacts)) { + $selected_contacts = []; + } + $selected_contact_ids = array_filter(array_unique(array_map('intval', $selected_contacts))); + + if (empty($selected_contact_ids)) { + flashAlert("Select at least one contact to send to", 'error'); + redirect(); + } + + $selected_contact_id_list = implode(',', $selected_contact_ids); + $recipient_filter = "AND contact_id IN ($selected_contact_id_list)"; + + } $sql_recipients = mysqli_query( $mysqli, "SELECT contact_email, contact_name FROM contacts - WHERE contact_id IN ($selected_contact_id_list) - AND contact_client_id = $client_id + WHERE contact_client_id = $client_id AND contact_archived_at IS NULL AND contact_email IS NOT NULL AND contact_email != '' + $recipient_filter ORDER BY contact_primary DESC, contact_billing DESC, contact_name ASC" ); if (mysqli_num_rows($sql_recipients) == 0) { - flashAlert("None of the selected contacts have a usable email address", 'error'); + if (!empty($_POST['quick_send'])) { + flashAlert("No default contacts to quick send to - use Send Email to choose recipients", 'error'); + } else { + flashAlert("None of the selected contacts have a usable email address", 'error'); + } redirect(); } diff --git a/agent/post/quote.php b/agent/post/quote.php index 16c7f46e9..bccbbf0a2 100644 --- a/agent/post/quote.php +++ b/agent/post/quote.php @@ -596,35 +596,49 @@ if (isset($_POST['email_quote'])) { enforceClientAccess(); - // Recipients come from the Send Email modal's contact picker. Scoping the - // lookup to this quote's client is what makes a tampered contact_id - // harmless - it simply matches nothing. - $selected_contacts = $_POST['contacts'] ?? []; - if (!is_array($selected_contacts)) { - $selected_contacts = []; - } - $selected_contact_ids = array_filter(array_unique(array_map('intval', $selected_contacts))); + // Two ways in. Quick Send skips the modal and resolves the default + // recipients server-side; the modal posts an explicit contacts[] list. + // Scoping either lookup to this quote's client is what makes a tampered + // contact_id harmless - it simply matches nothing. + if (!empty($_POST['quick_send'])) { - if (empty($selected_contact_ids)) { - flashAlert("Select at least one contact to send to", 'error'); - redirect(); - } + $recipient_filter = documentDefaultContactFilterSql('quote'); - $selected_contact_id_list = implode(',', $selected_contact_ids); + } else { + + $selected_contacts = $_POST['contacts'] ?? []; + if (!is_array($selected_contacts)) { + $selected_contacts = []; + } + $selected_contact_ids = array_filter(array_unique(array_map('intval', $selected_contacts))); + + if (empty($selected_contact_ids)) { + flashAlert("Select at least one contact to send to", 'error'); + redirect(); + } + + $selected_contact_id_list = implode(',', $selected_contact_ids); + $recipient_filter = "AND contact_id IN ($selected_contact_id_list)"; + + } $sql_recipients = mysqli_query( $mysqli, "SELECT contact_email, contact_name FROM contacts - WHERE contact_id IN ($selected_contact_id_list) - AND contact_client_id = $client_id + WHERE contact_client_id = $client_id AND contact_archived_at IS NULL AND contact_email IS NOT NULL AND contact_email != '' + $recipient_filter ORDER BY contact_primary DESC, contact_billing DESC, contact_name ASC" ); if (mysqli_num_rows($sql_recipients) == 0) { - flashAlert("None of the selected contacts have a usable email address", 'error'); + if (!empty($_POST['quick_send'])) { + flashAlert("No default contacts to quick send to - use Send Email to choose recipients", 'error'); + } else { + flashAlert("None of the selected contacts have a usable email address", 'error'); + } redirect(); } diff --git a/agent/quote.php b/agent/quote.php index c19ec6a0a..5fe6dc91e 100644 --- a/agent/quote.php +++ b/agent/quote.php @@ -168,9 +168,13 @@ if (isset($_GET['quote_id'])) { + = 2 && !empty($config_smtp_provider) && $emailable_contacts > 0) { ?> + +
+ + + +
+ + +
- Email + Email... = 3) { ?> @@ -254,5 +258,23 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); ?> += 2 && !empty($config_smtp_provider)) { ?> + +
+ + + +
+ +