mirror of https://github.com/itflow-org/itflow
Merge pull request #1261 from cs2000/develop
Add full Microsoft 365 and Google OAuth mail support
This commit is contained in:
commit
4d895a56e4
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "../config.php";
|
||||||
|
require_once "../functions.php";
|
||||||
|
require_once "../includes/check_login.php";
|
||||||
|
|
||||||
|
$settings_mail_path = '/admin/settings_mail.php';
|
||||||
|
|
||||||
|
if (!isset($session_is_admin) || !$session_is_admin) {
|
||||||
|
flash_alert("Admin access required.", 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = sanitizeInput($_GET['state'] ?? '');
|
||||||
|
$code = $_GET['code'] ?? '';
|
||||||
|
$error = sanitizeInput($_GET['error'] ?? '');
|
||||||
|
$error_description = sanitizeInput($_GET['error_description'] ?? '');
|
||||||
|
|
||||||
|
$session_state = $_SESSION['mail_oauth_state'] ?? '';
|
||||||
|
$session_state_expires = intval($_SESSION['mail_oauth_state_expires_at'] ?? 0);
|
||||||
|
|
||||||
|
unset($_SESSION['mail_oauth_state'], $_SESSION['mail_oauth_state_expires_at']);
|
||||||
|
|
||||||
|
if (!empty($error)) {
|
||||||
|
$msg = "Microsoft OAuth authorization failed: $error";
|
||||||
|
if (!empty($error_description)) {
|
||||||
|
$msg .= " ($error_description)";
|
||||||
|
}
|
||||||
|
|
||||||
|
flash_alert($msg, 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($state) || empty($code) || empty($session_state) || !hash_equals($session_state, $state) || time() > $session_state_expires) {
|
||||||
|
flash_alert("Microsoft OAuth callback validation failed. Please try connecting again.", 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_tenant_id)) {
|
||||||
|
flash_alert("Microsoft OAuth settings are incomplete. Please fill Client ID, Client Secret, and Tenant ID.", 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined('BASE_URL') && !empty(BASE_URL)) {
|
||||||
|
$base_url = rtrim((string) BASE_URL, '/');
|
||||||
|
} else {
|
||||||
|
$base_url = 'https://' . rtrim((string) $config_base_url, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirect_uri = $base_url . '/admin/oauth_microsoft_mail_callback.php';
|
||||||
|
$token_url = 'https://login.microsoftonline.com/' . rawurlencode($config_mail_oauth_tenant_id) . '/oauth2/v2.0/token';
|
||||||
|
$scope = 'offline_access openid profile https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send';
|
||||||
|
|
||||||
|
$ch = curl_init($token_url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||||
|
'client_id' => $config_mail_oauth_client_id,
|
||||||
|
'client_secret' => $config_mail_oauth_client_secret,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'code' => $code,
|
||||||
|
'redirect_uri' => $redirect_uri,
|
||||||
|
'scope' => $scope,
|
||||||
|
], '', '&'));
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
|
||||||
|
$raw_body = curl_exec($ch);
|
||||||
|
$curl_err = curl_error($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($raw_body === false || $http_code < 200 || $http_code >= 300) {
|
||||||
|
$reason = !empty($curl_err) ? $curl_err : "HTTP $http_code";
|
||||||
|
flash_alert("Microsoft OAuth token exchange failed: $reason", 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($raw_body, true);
|
||||||
|
if (!is_array($json) || empty($json['refresh_token']) || empty($json['access_token'])) {
|
||||||
|
flash_alert("Microsoft OAuth token exchange failed: refresh token or access token missing.", 'error');
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$refresh_token = (string) $json['refresh_token'];
|
||||||
|
$access_token = (string) $json['access_token'];
|
||||||
|
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
|
||||||
|
|
||||||
|
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token);
|
||||||
|
$access_token_esc = mysqli_real_escape_string($mysqli, $access_token);
|
||||||
|
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE settings SET
|
||||||
|
config_imap_provider = 'microsoft_oauth',
|
||||||
|
config_smtp_provider = 'microsoft_oauth',
|
||||||
|
config_mail_oauth_refresh_token = '$refresh_token_esc',
|
||||||
|
config_mail_oauth_access_token = '$access_token_esc',
|
||||||
|
config_mail_oauth_access_token_expires_at = '$expires_at_esc'
|
||||||
|
WHERE company_id = 1
|
||||||
|
");
|
||||||
|
|
||||||
|
logAction("Settings", "Edit", "$session_name completed Microsoft OAuth connect flow for mail settings");
|
||||||
|
flash_alert("Microsoft OAuth connected successfully. Token expires at $expires_at.");
|
||||||
|
redirect($settings_mail_path);
|
||||||
|
|
@ -1,287 +1,582 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
|
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
|
||||||
|
|
||||||
if (isset($_POST['edit_mail_smtp_settings'])) {
|
if (!defined('MICROSOFT_OAUTH_BASE_URL')) {
|
||||||
|
define('MICROSOFT_OAUTH_BASE_URL', 'https://login.microsoftonline.com/');
|
||||||
validateCSRFToken($_POST['csrf_token']);
|
}
|
||||||
|
|
||||||
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider']);
|
if (isset($_POST['oauth_connect_microsoft_mail'])) {
|
||||||
$config_smtp_host = sanitizeInput($_POST['config_smtp_host']);
|
|
||||||
$config_smtp_port = intval($_POST['config_smtp_port'] ?? 0);
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
$config_smtp_encryption = sanitizeInput($_POST['config_smtp_encryption']);
|
|
||||||
$config_smtp_username = sanitizeInput($_POST['config_smtp_username']);
|
// Save current IMAP/OAuth form values first so auth flow always uses latest inputs.
|
||||||
$config_smtp_password = sanitizeInput($_POST['config_smtp_password']);
|
$config_imap_provider = sanitizeInput($_POST['config_imap_provider'] ?? '');
|
||||||
|
$config_imap_username = sanitizeInput($_POST['config_imap_username'] ?? '');
|
||||||
// Shared OAuth fields
|
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id'] ?? '');
|
||||||
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
|
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret'] ?? '');
|
||||||
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
|
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id'] ?? '');
|
||||||
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
|
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token'] ?? '');
|
||||||
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
|
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token'] ?? '');
|
||||||
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
|
|
||||||
|
mysqli_query($mysqli, "UPDATE settings SET
|
||||||
mysqli_query($mysqli, "
|
config_imap_provider = '$config_imap_provider',
|
||||||
UPDATE settings SET
|
config_imap_username = '$config_imap_username',
|
||||||
config_smtp_provider = '$config_smtp_provider',
|
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
|
||||||
config_smtp_host = '$config_smtp_host',
|
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
|
||||||
config_smtp_port = $config_smtp_port,
|
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
|
||||||
config_smtp_encryption = '$config_smtp_encryption',
|
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
|
||||||
config_smtp_username = '$config_smtp_username',
|
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
|
||||||
config_smtp_password = '$config_smtp_password',
|
WHERE company_id = 1
|
||||||
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
|
");
|
||||||
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
|
|
||||||
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
|
if ($config_imap_provider !== 'microsoft_oauth') {
|
||||||
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
|
flash_alert("Please set IMAP Provider to Microsoft 365 (OAuth) before connecting.", 'error');
|
||||||
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
|
redirect();
|
||||||
WHERE company_id = 1
|
}
|
||||||
");
|
|
||||||
|
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_tenant_id)) {
|
||||||
logAction("Settings", "Edit", "$session_name edited SMTP settings");
|
flash_alert("Missing Microsoft OAuth settings. Please provide Client ID, Client Secret, and Tenant ID first.", 'error');
|
||||||
|
redirect();
|
||||||
flash_alert("SMTP Mail Settings updated");
|
}
|
||||||
|
|
||||||
redirect();
|
if (defined('BASE_URL') && !empty(BASE_URL)) {
|
||||||
|
$base_url = rtrim((string) BASE_URL, '/');
|
||||||
}
|
} else {
|
||||||
|
$base_url = 'https://' . rtrim((string) $config_base_url, '/');
|
||||||
if (isset($_POST['edit_mail_imap_settings'])) {
|
}
|
||||||
|
|
||||||
validateCSRFToken($_POST['csrf_token']);
|
$redirect_uri = $base_url . '/admin/oauth_microsoft_mail_callback.php';
|
||||||
|
|
||||||
$config_imap_provider = sanitizeInput($_POST['config_imap_provider']);
|
try {
|
||||||
$config_imap_host = sanitizeInput($_POST['config_imap_host']);
|
$state = bin2hex(random_bytes(32));
|
||||||
$config_imap_port = intval($_POST['config_imap_port'] ?? 0);
|
} catch (Throwable $e) {
|
||||||
$config_imap_encryption = sanitizeInput($_POST['config_imap_encryption']);
|
$state = sha1(uniqid((string) mt_rand(), true));
|
||||||
$config_imap_username = sanitizeInput($_POST['config_imap_username']);
|
}
|
||||||
$config_imap_password = sanitizeInput($_POST['config_imap_password']);
|
|
||||||
|
$_SESSION['mail_oauth_state'] = $state;
|
||||||
// Shared OAuth fields
|
$_SESSION['mail_oauth_state_expires_at'] = time() + 600;
|
||||||
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
|
|
||||||
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
|
$scope = 'offline_access openid profile https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send';
|
||||||
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
|
|
||||||
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
|
$authorize_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($config_mail_oauth_tenant_id) . '/oauth2/v2.0/authorize?'
|
||||||
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
|
. http_build_query([
|
||||||
|
'client_id' => $config_mail_oauth_client_id,
|
||||||
mysqli_query($mysqli, "
|
'response_type' => 'code',
|
||||||
UPDATE settings SET
|
'redirect_uri' => $redirect_uri,
|
||||||
config_imap_provider = '$config_imap_provider',
|
'response_mode' => 'query',
|
||||||
config_imap_host = '$config_imap_host',
|
'scope' => $scope,
|
||||||
config_imap_port = $config_imap_port,
|
'state' => $state,
|
||||||
config_imap_encryption = '$config_imap_encryption',
|
'prompt' => 'consent',
|
||||||
config_imap_username = '$config_imap_username',
|
], '', '&', PHP_QUERY_RFC3986);
|
||||||
config_imap_password = '$config_imap_password',
|
|
||||||
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
|
logAction("Settings", "Edit", "$session_name started Microsoft OAuth connect flow for mail settings");
|
||||||
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
|
|
||||||
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
|
redirect($authorize_url);
|
||||||
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
|
}
|
||||||
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
|
|
||||||
WHERE company_id = 1
|
if (isset($_POST['edit_mail_smtp_settings'])) {
|
||||||
");
|
|
||||||
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
logAction("Settings", "Edit", "$session_name edited IMAP settings");
|
|
||||||
|
$config_smtp_provider = sanitizeInput($_POST['config_smtp_provider']);
|
||||||
flash_alert("IMAP Mail Settings updated");
|
$config_smtp_host = sanitizeInput($_POST['config_smtp_host']);
|
||||||
|
$config_smtp_port = intval($_POST['config_smtp_port'] ?? 0);
|
||||||
redirect();
|
$config_smtp_encryption = sanitizeInput($_POST['config_smtp_encryption']);
|
||||||
|
$config_smtp_username = sanitizeInput($_POST['config_smtp_username']);
|
||||||
}
|
$config_smtp_password = sanitizeInput($_POST['config_smtp_password']);
|
||||||
|
|
||||||
if (isset($_POST['edit_mail_from_settings'])) {
|
// Shared OAuth fields
|
||||||
|
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
|
||||||
validateCSRFToken($_POST['csrf_token']);
|
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
|
||||||
|
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
|
||||||
$config_mail_from_email = sanitizeInput(filter_var($_POST['config_mail_from_email'], FILTER_VALIDATE_EMAIL));
|
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
|
||||||
$config_mail_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_mail_from_name']));
|
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
|
||||||
|
|
||||||
$config_invoice_from_email = sanitizeInput(filter_var($_POST['config_invoice_from_email'], FILTER_VALIDATE_EMAIL));
|
mysqli_query($mysqli, "
|
||||||
$config_invoice_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_invoice_from_name']));
|
UPDATE settings SET
|
||||||
|
config_smtp_provider = '$config_smtp_provider',
|
||||||
$config_quote_from_email = sanitizeInput(filter_var($_POST['config_quote_from_email'], FILTER_VALIDATE_EMAIL));
|
config_smtp_host = '$config_smtp_host',
|
||||||
$config_quote_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_quote_from_name']));
|
config_smtp_port = $config_smtp_port,
|
||||||
|
config_smtp_encryption = '$config_smtp_encryption',
|
||||||
$config_ticket_from_email = sanitizeInput(filter_var($_POST['config_ticket_from_email'], FILTER_VALIDATE_EMAIL));
|
config_smtp_username = '$config_smtp_username',
|
||||||
$config_ticket_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_ticket_from_name']));
|
config_smtp_password = '$config_smtp_password',
|
||||||
|
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
|
||||||
mysqli_query($mysqli,"UPDATE settings SET config_mail_from_email = '$config_mail_from_email', config_mail_from_name = '$config_mail_from_name', config_invoice_from_email = '$config_invoice_from_email', config_invoice_from_name = '$config_invoice_from_name', config_quote_from_email = '$config_quote_from_email', config_quote_from_name = '$config_quote_from_name', config_ticket_from_email = '$config_ticket_from_email', config_ticket_from_name = '$config_ticket_from_name' WHERE company_id = 1");
|
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
|
||||||
|
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
|
||||||
logAction("Settings", "Edit", "$session_name edited mail from settings");
|
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
|
||||||
|
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
|
||||||
flash_alert("Mail From Settings updated");
|
WHERE company_id = 1
|
||||||
|
");
|
||||||
redirect();
|
|
||||||
|
logAction("Settings", "Edit", "$session_name edited SMTP settings");
|
||||||
}
|
|
||||||
|
flash_alert("SMTP Mail Settings updated");
|
||||||
if (isset($_POST['test_email_smtp'])) {
|
|
||||||
|
redirect();
|
||||||
validateCSRFToken($_POST['csrf_token']);
|
|
||||||
|
}
|
||||||
$test_email = intval($_POST['test_email']);
|
|
||||||
|
if (isset($_POST['edit_mail_imap_settings'])) {
|
||||||
if($test_email == 1) {
|
|
||||||
$email_from = sanitizeInput($config_mail_from_email);
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
$email_from_name = sanitizeInput($config_mail_from_name);
|
|
||||||
} elseif ($test_email == 2) {
|
$config_imap_provider = sanitizeInput($_POST['config_imap_provider']);
|
||||||
$email_from = sanitizeInput($config_invoice_from_email);
|
$config_imap_host = sanitizeInput($_POST['config_imap_host']);
|
||||||
$email_from_name = sanitizeInput($config_invoice_from_name);
|
$config_imap_port = intval($_POST['config_imap_port'] ?? 0);
|
||||||
} elseif ($test_email == 3) {
|
$config_imap_encryption = sanitizeInput($_POST['config_imap_encryption']);
|
||||||
$email_from = sanitizeInput($config_quote_from_email);
|
$config_imap_username = sanitizeInput($_POST['config_imap_username']);
|
||||||
$email_from_name = sanitizeInput($config_quote_from_name);
|
$config_imap_password = sanitizeInput($_POST['config_imap_password']);
|
||||||
} else {
|
|
||||||
$email_from = sanitizeInput($config_ticket_from_email);
|
// Shared OAuth fields
|
||||||
$email_from_name = sanitizeInput($config_ticket_from_name);
|
$config_mail_oauth_client_id = sanitizeInput($_POST['config_mail_oauth_client_id']);
|
||||||
}
|
$config_mail_oauth_client_secret = sanitizeInput($_POST['config_mail_oauth_client_secret']);
|
||||||
|
$config_mail_oauth_tenant_id = sanitizeInput($_POST['config_mail_oauth_tenant_id']);
|
||||||
$email_to = sanitizeInput($_POST['email_to']);
|
$config_mail_oauth_refresh_token = sanitizeInput($_POST['config_mail_oauth_refresh_token']);
|
||||||
$subject = "Test email from ITFlow";
|
$config_mail_oauth_access_token = sanitizeInput($_POST['config_mail_oauth_access_token']);
|
||||||
$body = "This is a test email from ITFlow. If you are reading this, it worked!";
|
|
||||||
|
mysqli_query($mysqli, "
|
||||||
$data = [
|
UPDATE settings SET
|
||||||
[
|
config_imap_provider = '$config_imap_provider',
|
||||||
'from' => $email_from,
|
config_imap_host = '$config_imap_host',
|
||||||
'from_name' => $email_from_name,
|
config_imap_port = $config_imap_port,
|
||||||
'recipient' => $email_to,
|
config_imap_encryption = '$config_imap_encryption',
|
||||||
'recipient_name' => 'Chap',
|
config_imap_username = '$config_imap_username',
|
||||||
'subject' => $subject,
|
config_imap_password = '$config_imap_password',
|
||||||
'body' => $body
|
config_mail_oauth_client_id = '$config_mail_oauth_client_id',
|
||||||
]
|
config_mail_oauth_client_secret = '$config_mail_oauth_client_secret',
|
||||||
];
|
config_mail_oauth_tenant_id = '$config_mail_oauth_tenant_id',
|
||||||
|
config_mail_oauth_refresh_token = '$config_mail_oauth_refresh_token',
|
||||||
$mail = addToMailQueue($data);
|
config_mail_oauth_access_token = '$config_mail_oauth_access_token'
|
||||||
|
WHERE company_id = 1
|
||||||
if ($mail === true) {
|
");
|
||||||
flash_alert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
|
|
||||||
} else {
|
logAction("Settings", "Edit", "$session_name edited IMAP settings");
|
||||||
flash_alert("Failed to add test mail to queue", 'error');
|
|
||||||
}
|
flash_alert("IMAP Mail Settings updated");
|
||||||
|
|
||||||
redirect();
|
redirect();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_POST['test_email_imap'])) {
|
if (isset($_POST['edit_mail_from_settings'])) {
|
||||||
|
|
||||||
validateCSRFToken($_POST['csrf_token']);
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
|
|
||||||
$host = $config_imap_host;
|
$config_mail_from_email = sanitizeInput(filter_var($_POST['config_mail_from_email'], FILTER_VALIDATE_EMAIL));
|
||||||
$port = (int) $config_imap_port;
|
$config_mail_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_mail_from_name']));
|
||||||
$encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
|
|
||||||
$username = $config_imap_username;
|
$config_invoice_from_email = sanitizeInput(filter_var($_POST['config_invoice_from_email'], FILTER_VALIDATE_EMAIL));
|
||||||
$password = $config_imap_password;
|
$config_invoice_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_invoice_from_name']));
|
||||||
|
|
||||||
// Build remote socket (implicit SSL vs plain TCP)
|
$config_quote_from_email = sanitizeInput(filter_var($_POST['config_quote_from_email'], FILTER_VALIDATE_EMAIL));
|
||||||
$transport = 'tcp';
|
$config_quote_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_quote_from_name']));
|
||||||
if ($encryption === 'ssl') {
|
|
||||||
$transport = 'ssl';
|
$config_ticket_from_email = sanitizeInput(filter_var($_POST['config_ticket_from_email'], FILTER_VALIDATE_EMAIL));
|
||||||
}
|
$config_ticket_from_name = sanitizeInput(preg_replace('/[^a-zA-Z0-9\s]/', '', $_POST['config_ticket_from_name']));
|
||||||
|
|
||||||
$remote_socket = $transport . '://' . $host . ':' . $port;
|
mysqli_query($mysqli,"UPDATE settings SET config_mail_from_email = '$config_mail_from_email', config_mail_from_name = '$config_mail_from_name', config_invoice_from_email = '$config_invoice_from_email', config_invoice_from_name = '$config_invoice_from_name', config_quote_from_email = '$config_quote_from_email', config_quote_from_name = '$config_quote_from_name', config_ticket_from_email = '$config_ticket_from_email', config_ticket_from_name = '$config_ticket_from_name' WHERE company_id = 1");
|
||||||
|
|
||||||
// Stream context (you can tighten these if you want strict validation)
|
logAction("Settings", "Edit", "$session_name edited mail from settings");
|
||||||
$contextOptions = [];
|
|
||||||
if (in_array($encryption, ['ssl', 'tls'], true)) {
|
flash_alert("Mail From Settings updated");
|
||||||
$contextOptions['ssl'] = [
|
|
||||||
'verify_peer' => false,
|
redirect();
|
||||||
'verify_peer_name' => false,
|
|
||||||
'allow_self_signed' => true,
|
}
|
||||||
];
|
|
||||||
}
|
if (isset($_POST['test_email_smtp'])) {
|
||||||
|
|
||||||
$context = stream_context_create($contextOptions);
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
|
|
||||||
try {
|
$test_email = intval($_POST['test_email']);
|
||||||
$errno = 0;
|
|
||||||
$errstr = '';
|
if($test_email == 1) {
|
||||||
|
$email_from = sanitizeInput($config_mail_from_email);
|
||||||
// 10-second timeout, adjust as needed
|
$email_from_name = sanitizeInput($config_mail_from_name);
|
||||||
$fp = @stream_socket_client(
|
} elseif ($test_email == 2) {
|
||||||
$remote_socket,
|
$email_from = sanitizeInput($config_invoice_from_email);
|
||||||
$errno,
|
$email_from_name = sanitizeInput($config_invoice_from_name);
|
||||||
$errstr,
|
} elseif ($test_email == 3) {
|
||||||
10,
|
$email_from = sanitizeInput($config_quote_from_email);
|
||||||
STREAM_CLIENT_CONNECT,
|
$email_from_name = sanitizeInput($config_quote_from_name);
|
||||||
$context
|
} else {
|
||||||
);
|
$email_from = sanitizeInput($config_ticket_from_email);
|
||||||
|
$email_from_name = sanitizeInput($config_ticket_from_name);
|
||||||
if (!$fp) {
|
}
|
||||||
throw new Exception("Could not connect to IMAP server: [$errno] $errstr");
|
|
||||||
}
|
$email_to = sanitizeInput($_POST['email_to']);
|
||||||
|
$subject = "Test email from ITFlow";
|
||||||
stream_set_timeout($fp, 10);
|
$body = "This is a test email from ITFlow. If you are reading this, it worked!";
|
||||||
|
|
||||||
// Read server greeting (IMAP servers send something like: * OK Dovecot ready)
|
$data = [
|
||||||
$greeting = fgets($fp, 1024);
|
[
|
||||||
if ($greeting === false || strpos($greeting, '* OK') !== 0) {
|
'from' => $email_from,
|
||||||
fclose($fp);
|
'from_name' => $email_from_name,
|
||||||
throw new Exception("Invalid IMAP greeting: " . trim((string) $greeting));
|
'recipient' => $email_to,
|
||||||
}
|
'recipient_name' => 'Chap',
|
||||||
|
'subject' => $subject,
|
||||||
// If you really want STARTTLS for "tls" (port 143), you can do it here
|
'body' => $body
|
||||||
if ($encryption === 'tls' && stripos($greeting, 'STARTTLS') !== false) {
|
]
|
||||||
// Request STARTTLS
|
];
|
||||||
fwrite($fp, "A0001 STARTTLS\r\n");
|
|
||||||
$line = fgets($fp, 1024);
|
$mail = addToMailQueue($data);
|
||||||
if ($line === false || stripos($line, 'A0001 OK') !== 0) {
|
|
||||||
fclose($fp);
|
if ($mail === true) {
|
||||||
throw new Exception("STARTTLS failed: " . trim((string) $line));
|
flash_alert("Test email queued! <a class='text-bold text-light' href='mail_queue.php'>Check Admin > Mail queue</a>");
|
||||||
}
|
} else {
|
||||||
|
flash_alert("Failed to add test mail to queue", 'error');
|
||||||
// Enable crypto on the stream
|
}
|
||||||
if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
|
|
||||||
fclose($fp);
|
redirect();
|
||||||
throw new Exception("Unable to enable TLS encryption on IMAP connection.");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (isset($_POST['test_email_imap'])) {
|
||||||
// --- Do LOGIN command ---
|
|
||||||
$tag = 'A0002';
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
|
|
||||||
// Simple quoting; this may fail with some special chars in username/password.
|
$provider = sanitizeInput($config_imap_provider ?? '');
|
||||||
$loginCmd = sprintf(
|
|
||||||
"%s LOGIN \"%s\" \"%s\"\r\n",
|
$host = $config_imap_host;
|
||||||
$tag,
|
$port = (int) $config_imap_port;
|
||||||
addcslashes($username, "\\\""),
|
$encryption = strtolower(trim($config_imap_encryption)); // e.g. "ssl", "tls", "none"
|
||||||
addcslashes($password, "\\\"")
|
$username = $config_imap_username;
|
||||||
);
|
$password = $config_imap_password;
|
||||||
|
|
||||||
fwrite($fp, $loginCmd);
|
// Shared OAuth fields
|
||||||
|
$config_mail_oauth_client_id = $config_mail_oauth_client_id ?? '';
|
||||||
$success = false;
|
$config_mail_oauth_client_secret = $config_mail_oauth_client_secret ?? '';
|
||||||
$errorLine = '';
|
$config_mail_oauth_tenant_id = $config_mail_oauth_tenant_id ?? '';
|
||||||
|
$config_mail_oauth_refresh_token = $config_mail_oauth_refresh_token ?? '';
|
||||||
while (!feof($fp)) {
|
$config_mail_oauth_access_token = $config_mail_oauth_access_token ?? '';
|
||||||
$line = fgets($fp, 2048);
|
$config_mail_oauth_access_token_expires_at = $config_mail_oauth_access_token_expires_at ?? '';
|
||||||
if ($line === false) {
|
|
||||||
break;
|
$is_oauth = ($provider === 'google_oauth' || $provider === 'microsoft_oauth');
|
||||||
}
|
|
||||||
|
if ($provider === 'google_oauth') {
|
||||||
// Look for tagged response for our LOGIN
|
if (empty($host)) {
|
||||||
if (strpos($line, $tag . ' ') === 0) {
|
$host = 'imap.gmail.com';
|
||||||
if (stripos($line, $tag . ' OK') === 0) {
|
}
|
||||||
$success = true;
|
if (empty($port)) {
|
||||||
} else {
|
$port = 993;
|
||||||
$errorLine = trim($line);
|
}
|
||||||
}
|
if (empty($encryption)) {
|
||||||
break;
|
$encryption = 'ssl';
|
||||||
}
|
}
|
||||||
}
|
} elseif ($provider === 'microsoft_oauth') {
|
||||||
|
if (empty($host)) {
|
||||||
// Always logout / close
|
$host = 'outlook.office365.com';
|
||||||
fwrite($fp, "A0003 LOGOUT\r\n");
|
}
|
||||||
fclose($fp);
|
if (empty($port)) {
|
||||||
|
$port = 993;
|
||||||
if ($success) {
|
}
|
||||||
flash_alert("Connected successfully");
|
if (empty($encryption)) {
|
||||||
} else {
|
$encryption = 'ssl';
|
||||||
if (!$errorLine) {
|
}
|
||||||
$errorLine = 'Unknown IMAP authentication error';
|
}
|
||||||
}
|
|
||||||
throw new Exception($errorLine);
|
if (empty($host) || empty($port) || empty($username)) {
|
||||||
}
|
flash_alert("<strong>IMAP connection failed:</strong> Missing host, port, or username.", 'error');
|
||||||
|
redirect();
|
||||||
} catch (Exception $e) {
|
}
|
||||||
flash_alert("<strong>IMAP connection failed:</strong> " . htmlspecialchars($e->getMessage()), 'error');
|
|
||||||
}
|
$token_is_expired = function (?string $expires_at): bool {
|
||||||
|
if (empty($expires_at)) {
|
||||||
redirect();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ts = strtotime($expires_at);
|
||||||
|
|
||||||
|
if ($ts === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($ts - 60) <= time();
|
||||||
|
};
|
||||||
|
|
||||||
|
$http_form_post = function (string $url, array $fields): array {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields, '', '&'));
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
|
||||||
|
$raw = curl_exec($ch);
|
||||||
|
$err = curl_error($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => ($raw !== false && $code >= 200 && $code < 300),
|
||||||
|
'body' => $raw,
|
||||||
|
'code' => $code,
|
||||||
|
'err' => $err,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($is_oauth) {
|
||||||
|
if (!empty($config_mail_oauth_access_token) && !$token_is_expired($config_mail_oauth_access_token_expires_at)) {
|
||||||
|
$password = $config_mail_oauth_access_token;
|
||||||
|
} else {
|
||||||
|
if (empty($config_mail_oauth_client_id) || empty($config_mail_oauth_client_secret) || empty($config_mail_oauth_refresh_token)) {
|
||||||
|
flash_alert("<strong>IMAP OAuth failed:</strong> Missing OAuth client credentials or refresh token.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($provider === 'google_oauth') {
|
||||||
|
$response = $http_form_post('https://oauth2.googleapis.com/token', [
|
||||||
|
'client_id' => $config_mail_oauth_client_id,
|
||||||
|
'client_secret' => $config_mail_oauth_client_secret,
|
||||||
|
'refresh_token' => $config_mail_oauth_refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if (empty($config_mail_oauth_tenant_id)) {
|
||||||
|
flash_alert("<strong>IMAP OAuth failed:</strong> Microsoft tenant ID is required.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($config_mail_oauth_tenant_id) . "/oauth2/v2.0/token";
|
||||||
|
$response = $http_form_post($token_url, [
|
||||||
|
'client_id' => $config_mail_oauth_client_id,
|
||||||
|
'client_secret' => $config_mail_oauth_client_secret,
|
||||||
|
'refresh_token' => $config_mail_oauth_refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['ok']) {
|
||||||
|
flash_alert("<strong>IMAP OAuth failed:</strong> Could not refresh access token.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($response['body'], true);
|
||||||
|
if (!is_array($json) || empty($json['access_token'])) {
|
||||||
|
flash_alert("<strong>IMAP OAuth failed:</strong> Token response did not include an access token.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$password = $json['access_token'];
|
||||||
|
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
|
||||||
|
$refresh_token_to_save = $json['refresh_token'] ?? null;
|
||||||
|
|
||||||
|
$token_esc = mysqli_real_escape_string($mysqli, $password);
|
||||||
|
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
|
||||||
|
|
||||||
|
$refresh_sql = '';
|
||||||
|
if (!empty($refresh_token_to_save)) {
|
||||||
|
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token_to_save);
|
||||||
|
$refresh_sql = ", config_mail_oauth_refresh_token = '{$refresh_token_esc}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '{$token_esc}', config_mail_oauth_access_token_expires_at = '{$expires_at_esc}'{$refresh_sql} WHERE company_id = 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build remote socket (implicit SSL vs plain TCP)
|
||||||
|
$transport = 'tcp';
|
||||||
|
if ($encryption === 'ssl') {
|
||||||
|
$transport = 'ssl';
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote_socket = $transport . '://' . $host . ':' . $port;
|
||||||
|
|
||||||
|
// Stream context (you can tighten these if you want strict validation)
|
||||||
|
$context_options = [];
|
||||||
|
if (in_array($encryption, ['ssl', 'tls'], true)) {
|
||||||
|
$context_options['ssl'] = [
|
||||||
|
'verify_peer' => false,
|
||||||
|
'verify_peer_name' => false,
|
||||||
|
'allow_self_signed' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = stream_context_create($context_options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$errno = 0;
|
||||||
|
$errstr = '';
|
||||||
|
|
||||||
|
// 10-second timeout, adjust as needed
|
||||||
|
$fp = @stream_socket_client(
|
||||||
|
$remote_socket,
|
||||||
|
$errno,
|
||||||
|
$errstr,
|
||||||
|
10,
|
||||||
|
STREAM_CLIENT_CONNECT,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$fp) {
|
||||||
|
throw new Exception("Could not connect to IMAP server: [$errno] $errstr");
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_set_timeout($fp, 10);
|
||||||
|
|
||||||
|
// Read server greeting (IMAP servers send something like: * OK Dovecot ready)
|
||||||
|
$greeting = fgets($fp, 1024);
|
||||||
|
if ($greeting === false || strpos($greeting, '* OK') !== 0) {
|
||||||
|
fclose($fp);
|
||||||
|
throw new Exception("Invalid IMAP greeting: " . trim((string) $greeting));
|
||||||
|
}
|
||||||
|
// If you really want STARTTLS for "tls" (port 143), you can do it here
|
||||||
|
if ($encryption === 'tls' && stripos($greeting, 'STARTTLS') !== false) {
|
||||||
|
fwrite($fp, "A0001 STARTTLS\r\n");
|
||||||
|
$line = fgets($fp, 1024);
|
||||||
|
if ($line === false || stripos($line, 'A0001 OK') !== 0) {
|
||||||
|
fclose($fp);
|
||||||
|
throw new Exception("STARTTLS failed: " . trim((string) $line));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
|
||||||
|
fclose($fp);
|
||||||
|
throw new Exception("Unable to enable TLS encryption on IMAP connection.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tag = 'A0002';
|
||||||
|
|
||||||
|
if ($is_oauth) {
|
||||||
|
$oauth_b64 = base64_encode("user={$username}\x01auth=Bearer {$password}\x01\x01");
|
||||||
|
$auth_cmd = sprintf("%s AUTHENTICATE XOAUTH2 %s\r\n", $tag, $oauth_b64);
|
||||||
|
fwrite($fp, $auth_cmd);
|
||||||
|
} else {
|
||||||
|
$login_cmd = sprintf(
|
||||||
|
"%s LOGIN \"%s\" \"%s\"\r\n",
|
||||||
|
$tag,
|
||||||
|
addcslashes($username, "\\\""),
|
||||||
|
addcslashes($password, "\\\"")
|
||||||
|
);
|
||||||
|
|
||||||
|
fwrite($fp, $login_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = false;
|
||||||
|
$error_line = '';
|
||||||
|
|
||||||
|
while (!feof($fp)) {
|
||||||
|
$line = fgets($fp, 2048);
|
||||||
|
if ($line === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($line, $tag . ' ') === 0) {
|
||||||
|
if (stripos($line, $tag . ' OK') === 0) {
|
||||||
|
$success = true;
|
||||||
|
} else {
|
||||||
|
$error_line = trim($line);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always logout / close
|
||||||
|
fwrite($fp, "A0003 LOGOUT\r\n");
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
if ($is_oauth) {
|
||||||
|
flash_alert("Connected successfully using OAuth");
|
||||||
|
} else {
|
||||||
|
flash_alert("Connected successfully");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$error_line) {
|
||||||
|
$error_line = 'Unknown IMAP authentication error';
|
||||||
|
}
|
||||||
|
throw new Exception($error_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
flash_alert("<strong>IMAP connection failed:</strong> " . htmlspecialchars($e->getMessage()), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($_POST['test_oauth_token_refresh'])) {
|
||||||
|
|
||||||
|
validateCSRFToken($_POST['csrf_token']);
|
||||||
|
|
||||||
|
$provider = sanitizeInput($_POST['oauth_provider'] ?? '');
|
||||||
|
|
||||||
|
if ($provider !== 'google_oauth' && $provider !== 'microsoft_oauth') {
|
||||||
|
flash_alert("OAuth token test failed: unsupported provider.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$oauth_client_id = sanitizeInput($config_mail_oauth_client_id ?? '');
|
||||||
|
$oauth_client_secret = sanitizeInput($config_mail_oauth_client_secret ?? '');
|
||||||
|
$oauth_tenant_id = sanitizeInput($config_mail_oauth_tenant_id ?? '');
|
||||||
|
$oauth_refresh_token = sanitizeInput($config_mail_oauth_refresh_token ?? '');
|
||||||
|
|
||||||
|
if (empty($oauth_client_id) || empty($oauth_client_secret) || empty($oauth_refresh_token)) {
|
||||||
|
flash_alert("OAuth token test failed: missing client ID, client secret, or refresh token.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($provider === 'microsoft_oauth' && empty($oauth_tenant_id)) {
|
||||||
|
flash_alert("OAuth token test failed: Microsoft tenant ID is required.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_url = 'https://oauth2.googleapis.com/token';
|
||||||
|
if ($provider === 'microsoft_oauth') {
|
||||||
|
$token_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($oauth_tenant_id) . "/oauth2/v2.0/token";
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_fields = http_build_query([
|
||||||
|
'client_id' => $oauth_client_id,
|
||||||
|
'client_secret' => $oauth_client_secret,
|
||||||
|
'refresh_token' => $oauth_refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init($token_url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
|
||||||
|
$raw_body = curl_exec($ch);
|
||||||
|
$curl_err = curl_error($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($raw_body === false || $http_code < 200 || $http_code >= 300) {
|
||||||
|
$err_msg = !empty($curl_err) ? $curl_err : "HTTP $http_code";
|
||||||
|
flash_alert("OAuth token test failed: $err_msg", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($raw_body, true);
|
||||||
|
|
||||||
|
if (!is_array($json) || empty($json['access_token'])) {
|
||||||
|
flash_alert("OAuth token test failed: access token missing in provider response.", 'error');
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_access_token = sanitizeInput($json['access_token']);
|
||||||
|
$new_expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
|
||||||
|
$new_refresh_token = !empty($json['refresh_token']) ? sanitizeInput($json['refresh_token']) : '';
|
||||||
|
|
||||||
|
$new_access_token_esc = mysqli_real_escape_string($mysqli, $new_access_token);
|
||||||
|
$new_expires_at_esc = mysqli_real_escape_string($mysqli, $new_expires_at);
|
||||||
|
|
||||||
|
$refresh_sql = '';
|
||||||
|
if (!empty($new_refresh_token)) {
|
||||||
|
$new_refresh_token_esc = mysqli_real_escape_string($mysqli, $new_refresh_token);
|
||||||
|
$refresh_sql = ", config_mail_oauth_refresh_token = '$new_refresh_token_esc'";
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '$new_access_token_esc', config_mail_oauth_access_token_expires_at = '$new_expires_at_esc'$refresh_sql WHERE company_id = 1");
|
||||||
|
|
||||||
|
$provider_label = $provider === 'microsoft_oauth' ? 'Microsoft 365' : 'Google Workspace';
|
||||||
|
logAction("Settings", "Edit", "$session_name tested OAuth token refresh for $provider_label mail settings");
|
||||||
|
|
||||||
|
flash_alert("OAuth token refresh successful for $provider_label. Access token expires at $new_expires_at.");
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,357 +1,462 @@
|
||||||
<?php
|
<?php
|
||||||
// Set working directory to the directory this cron script lives at.
|
// Set working directory to the directory this cron script lives at.
|
||||||
chdir(dirname(__FILE__));
|
chdir(dirname(__FILE__));
|
||||||
|
|
||||||
// Ensure we're running from command line
|
// Ensure we're running from command line
|
||||||
if (php_sapi_name() !== 'cli') {
|
if (php_sapi_name() !== 'cli') {
|
||||||
die("This script must be run from the command line.\n");
|
die("This script must be run from the command line.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once "../config.php";
|
require_once "../config.php";
|
||||||
require_once "../includes/inc_set_timezone.php";
|
require_once "../includes/inc_set_timezone.php";
|
||||||
require_once "../functions.php";
|
require_once "../functions.php";
|
||||||
require_once "../plugins/vendor/autoload.php";
|
require_once "../plugins/vendor/autoload.php";
|
||||||
|
|
||||||
// PHP Mailer Libs
|
// PHP Mailer Libs
|
||||||
require_once "../plugins/PHPMailer/src/Exception.php";
|
require_once "../plugins/PHPMailer/src/Exception.php";
|
||||||
require_once "../plugins/PHPMailer/src/PHPMailer.php";
|
require_once "../plugins/PHPMailer/src/PHPMailer.php";
|
||||||
require_once "../plugins/PHPMailer/src/SMTP.php";
|
require_once "../plugins/PHPMailer/src/SMTP.php";
|
||||||
require_once "../plugins/PHPMailer/src/OAuthTokenProvider.php";
|
require_once "../plugins/PHPMailer/src/OAuthTokenProvider.php";
|
||||||
require_once "../plugins/PHPMailer/src/OAuth.php";
|
require_once "../plugins/PHPMailer/src/OAuth.php";
|
||||||
|
|
||||||
use PHPMailer\PHPMailer\PHPMailer;
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
use PHPMailer\PHPMailer\Exception;
|
use PHPMailer\PHPMailer\Exception;
|
||||||
use PHPMailer\PHPMailer\OAuthTokenProvider;
|
use PHPMailer\PHPMailer\OAuthTokenProvider;
|
||||||
|
|
||||||
/** =======================================================================
|
if (!defined('GOOGLE_OAUTH_TOKEN_URL')) {
|
||||||
* XOAUTH2 Token Provider for PHPMailer (simple “static” provider)
|
define('GOOGLE_OAUTH_TOKEN_URL', 'https://oauth2.googleapis.com/token');
|
||||||
* ======================================================================= */
|
}
|
||||||
class StaticTokenProvider implements OAuthTokenProvider {
|
|
||||||
private string $email;
|
if (!defined('MICROSOFT_OAUTH_BASE_URL')) {
|
||||||
private string $accessToken;
|
define('MICROSOFT_OAUTH_BASE_URL', 'https://login.microsoftonline.com/');
|
||||||
public function __construct(string $email, string $accessToken) {
|
}
|
||||||
$this->email = $email;
|
|
||||||
$this->accessToken = $accessToken;
|
/** =======================================================================
|
||||||
}
|
* XOAUTH2 Token Provider for PHPMailer (simple “static” provider)
|
||||||
public function getOauth64(): string {
|
* ======================================================================= */
|
||||||
$auth = "user={$this->email}\x01auth=Bearer {$this->accessToken}\x01\x01";
|
class StaticTokenProvider implements OAuthTokenProvider {
|
||||||
return base64_encode($auth);
|
private string $email;
|
||||||
}
|
private string $accessToken;
|
||||||
}
|
public function __construct(string $email, string $accessToken) {
|
||||||
|
$this->email = $email;
|
||||||
/** =======================================================================
|
$this->accessToken = $accessToken;
|
||||||
* Load settings
|
}
|
||||||
* ======================================================================= */
|
public function getOauth64(): string {
|
||||||
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
|
$auth = "user={$this->email}\x01auth=Bearer {$this->accessToken}\x01\x01";
|
||||||
$row = mysqli_fetch_assoc($sql_settings);
|
return base64_encode($auth);
|
||||||
|
}
|
||||||
$config_enable_cron = intval($row['config_enable_cron']);
|
}
|
||||||
|
|
||||||
// SMTP baseline
|
/** =======================================================================
|
||||||
$config_smtp_host = $row['config_smtp_host'];
|
* Load settings
|
||||||
$config_smtp_username = $row['config_smtp_username'];
|
* ======================================================================= */
|
||||||
$config_smtp_password = $row['config_smtp_password'];
|
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
|
||||||
$config_smtp_port = intval($row['config_smtp_port']);
|
$row = mysqli_fetch_assoc($sql_settings);
|
||||||
$config_smtp_encryption = $row['config_smtp_encryption'];
|
|
||||||
|
$config_enable_cron = intval($row['config_enable_cron']);
|
||||||
// SMTP provider + shared OAuth fields
|
|
||||||
$config_smtp_provider = $row['config_smtp_provider']; // 'standard_smtp' | 'google_oauth' | 'microsoft_oauth'
|
// SMTP baseline
|
||||||
$config_mail_oauth_client_id = $row['config_mail_oauth_client_id'] ?? '';
|
$config_smtp_host = $row['config_smtp_host'];
|
||||||
$config_mail_oauth_client_secret = $row['config_mail_oauth_client_secret'] ?? '';
|
$config_smtp_username = $row['config_smtp_username'];
|
||||||
$config_mail_oauth_tenant_id = $row['config_mail_oauth_tenant_id'] ?? '';
|
$config_smtp_password = $row['config_smtp_password'];
|
||||||
$config_mail_oauth_refresh_token = $row['config_mail_oauth_refresh_token'] ?? '';
|
$config_smtp_port = intval($row['config_smtp_port']);
|
||||||
$config_mail_oauth_access_token = $row['config_mail_oauth_access_token'] ?? '';
|
$config_smtp_encryption = $row['config_smtp_encryption'];
|
||||||
$config_mail_oauth_access_token_expires_at = $row['config_mail_oauth_access_token_expires_at'] ?? '';
|
|
||||||
|
// SMTP provider + shared OAuth fields
|
||||||
if ($config_enable_cron == 0) {
|
$config_smtp_provider = $row['config_smtp_provider']; // 'standard_smtp' | 'google_oauth' | 'microsoft_oauth'
|
||||||
logApp("Cron-Mail-Queue", "error", "Cron Mail Queue unable to run - cron not enabled in admin settings.");
|
$config_mail_oauth_client_id = $row['config_mail_oauth_client_id'] ?? '';
|
||||||
exit("Cron: is not enabled -- Quitting..");
|
$config_mail_oauth_client_secret = $row['config_mail_oauth_client_secret'] ?? '';
|
||||||
}
|
$config_mail_oauth_tenant_id = $row['config_mail_oauth_tenant_id'] ?? '';
|
||||||
|
$config_mail_oauth_refresh_token = $row['config_mail_oauth_refresh_token'] ?? '';
|
||||||
if (empty($config_smtp_provider)) {
|
$config_mail_oauth_access_token = $row['config_mail_oauth_access_token'] ?? '';
|
||||||
logApp("Cron-Mail-Queue", "info", "SMTP sending skipped: provider not configured.");
|
$config_mail_oauth_access_token_expires_at = $row['config_mail_oauth_access_token_expires_at'] ?? '';
|
||||||
exit(0);
|
|
||||||
}
|
if ($config_enable_cron == 0) {
|
||||||
|
logApp("Cron-Mail-Queue", "error", "Cron Mail Queue unable to run - cron not enabled in admin settings.");
|
||||||
/** =======================================================================
|
exit("Cron: is not enabled -- Quitting..");
|
||||||
* Lock file
|
}
|
||||||
* ======================================================================= */
|
|
||||||
$temp_dir = sys_get_temp_dir();
|
if (empty($config_smtp_provider)) {
|
||||||
$lock_file_path = "{$temp_dir}/itflow_mail_queue_{$installation_id}.lock";
|
logApp("Cron-Mail-Queue", "info", "SMTP sending skipped: provider not configured.");
|
||||||
|
exit(0);
|
||||||
if (file_exists($lock_file_path)) {
|
}
|
||||||
$file_age = time() - filemtime($lock_file_path);
|
|
||||||
if ($file_age > 600) {
|
/** =======================================================================
|
||||||
unlink($lock_file_path);
|
* Lock file
|
||||||
logApp("Cron-Mail-Queue", "warning", "Cron Mail Queue detected a lock file was present but was over 10 minutes old so it removed it.");
|
* ======================================================================= */
|
||||||
} else {
|
$temp_dir = sys_get_temp_dir();
|
||||||
logApp("Cron-Mail-Queue", "info", "Cron Mail Queue attempted to execute but was already executing so instead it terminated.");
|
$lock_file_path = "{$temp_dir}/itflow_mail_queue_{$installation_id}.lock";
|
||||||
exit("Script is already running. Exiting.");
|
|
||||||
}
|
if (file_exists($lock_file_path)) {
|
||||||
}
|
$file_age = time() - filemtime($lock_file_path);
|
||||||
|
if ($file_age > 600) {
|
||||||
file_put_contents($lock_file_path, "Locked");
|
unlink($lock_file_path);
|
||||||
|
logApp("Cron-Mail-Queue", "warning", "Cron Mail Queue detected a lock file was present but was over 10 minutes old so it removed it.");
|
||||||
/** =======================================================================
|
} else {
|
||||||
* Mail sender function (defined inside this cron)
|
logApp("Cron-Mail-Queue", "info", "Cron Mail Queue attempted to execute but was already executing so instead it terminated.");
|
||||||
* - Handles standard SMTP and XOAUTH2 for Google/Microsoft
|
exit("Script is already running. Exiting.");
|
||||||
* - Reuses shared OAuth settings
|
}
|
||||||
* ======================================================================= */
|
}
|
||||||
function sendQueueEmail(
|
|
||||||
string $provider,
|
file_put_contents($lock_file_path, "Locked");
|
||||||
string $host,
|
|
||||||
int $port,
|
/** =======================================================================
|
||||||
string $encryption,
|
* Mail OAuth helpers + sender function
|
||||||
string $username,
|
* ======================================================================= */
|
||||||
string $password,
|
function tokenIsExpired(?string $expires_at): bool {
|
||||||
string $from_email,
|
if (empty($expires_at)) {
|
||||||
string $from_name,
|
return true;
|
||||||
string $to_email,
|
}
|
||||||
string $to_name,
|
|
||||||
string $subject,
|
$ts = strtotime($expires_at);
|
||||||
string $html_body,
|
|
||||||
string $ics_str,
|
if ($ts === false) {
|
||||||
string $oauth_client_id,
|
return true;
|
||||||
string $oauth_client_secret,
|
}
|
||||||
string $oauth_tenant_id,
|
|
||||||
string $oauth_refresh_token,
|
return ($ts - 60) <= time();
|
||||||
string $oauth_access_token,
|
}
|
||||||
string $oauth_access_token_expires_at
|
|
||||||
) {
|
function httpFormPost(string $url, array $fields): array {
|
||||||
// Sensible defaults for OAuth providers if fields were left blank
|
$ch = curl_init($url);
|
||||||
if ($provider === 'google_oauth') {
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
if (!$host) $host = 'smtp.gmail.com';
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
if (!$port) $port = 587;
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields, '', '&'));
|
||||||
if (!$encryption) $encryption = 'tls';
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
if (!$username) $username = $from_email;
|
|
||||||
} elseif ($provider === 'microsoft_oauth') {
|
$raw = curl_exec($ch);
|
||||||
if (!$host) $host = 'smtp.office365.com';
|
$err = curl_error($ch);
|
||||||
if (!$port) $port = 587;
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
if (!$encryption) $encryption = 'tls';
|
|
||||||
if (!$username) $username = $from_email;
|
curl_close($ch);
|
||||||
}
|
|
||||||
|
return [
|
||||||
$mail = new PHPMailer(true);
|
'ok' => ($raw !== false && $code >= 200 && $code < 300),
|
||||||
$mail->CharSet = "UTF-8";
|
'body' => $raw,
|
||||||
$mail->SMTPDebug = 0;
|
'code' => $code,
|
||||||
$mail->isSMTP();
|
'err' => $err,
|
||||||
$mail->Host = $host;
|
];
|
||||||
$mail->Port = $port;
|
}
|
||||||
|
|
||||||
$enc = strtolower($encryption);
|
function persistMailOauthTokens(string $access_token, string $expires_at, ?string $refresh_token = null): void {
|
||||||
if ($enc === '' || $enc === 'none') {
|
global $mysqli;
|
||||||
$mail->SMTPAutoTLS = false;
|
|
||||||
$mail->SMTPSecure = false;
|
$access_token_esc = mysqli_real_escape_string($mysqli, $access_token);
|
||||||
$mail->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
|
$expires_at_esc = mysqli_real_escape_string($mysqli, $expires_at);
|
||||||
} else {
|
|
||||||
$mail->SMTPSecure = $enc; // 'tls' | 'ssl'
|
$refresh_sql = '';
|
||||||
}
|
if (!empty($refresh_token)) {
|
||||||
|
$refresh_token_esc = mysqli_real_escape_string($mysqli, $refresh_token);
|
||||||
if ($provider === 'google_oauth' || $provider === 'microsoft_oauth') {
|
$refresh_sql = ", config_mail_oauth_refresh_token = '{$refresh_token_esc}'";
|
||||||
// XOAUTH2
|
}
|
||||||
$mail->SMTPAuth = true;
|
|
||||||
$mail->AuthType = 'XOAUTH2';
|
mysqli_query($mysqli, "UPDATE settings SET config_mail_oauth_access_token = '{$access_token_esc}', config_mail_oauth_access_token_expires_at = '{$expires_at_esc}'{$refresh_sql} WHERE company_id = 1");
|
||||||
$mail->Username = $username;
|
}
|
||||||
|
|
||||||
// Pick/refresh access token
|
function refreshMailOauthAccessToken(string $provider, string $oauth_client_id, string $oauth_client_secret, string $oauth_tenant_id, string $oauth_refresh_token): ?array {
|
||||||
$accessToken = trim($oauth_access_token);
|
$result = null;
|
||||||
$needsRefresh = empty($accessToken);
|
$response = null;
|
||||||
if (!$needsRefresh && !empty($oauth_access_token_expires_at)) {
|
|
||||||
$expTs = strtotime($oauth_access_token_expires_at);
|
if (!empty($oauth_client_id) && !empty($oauth_client_secret) && !empty($oauth_refresh_token)) {
|
||||||
if ($expTs && $expTs <= time() + 60) $needsRefresh = true;
|
if ($provider === 'google_oauth') {
|
||||||
}
|
$response = httpFormPost(GOOGLE_OAUTH_TOKEN_URL, [
|
||||||
|
'client_id' => $oauth_client_id,
|
||||||
if ($needsRefresh) {
|
'client_secret' => $oauth_client_secret,
|
||||||
if ($provider === 'google_oauth' && function_exists('getGoogleAccessToken')) {
|
'refresh_token' => $oauth_refresh_token,
|
||||||
$accessToken = getGoogleAccessToken($username);
|
'grant_type' => 'refresh_token',
|
||||||
} elseif ($provider === 'microsoft_oauth' && function_exists('getMicrosoftAccessToken')) {
|
]);
|
||||||
$accessToken = getMicrosoftAccessToken($username);
|
} elseif ($provider === 'microsoft_oauth' && !empty($oauth_tenant_id)) {
|
||||||
}
|
$token_url = MICROSOFT_OAUTH_BASE_URL . rawurlencode($oauth_tenant_id) . "/oauth2/v2.0/token";
|
||||||
}
|
$response = httpFormPost($token_url, [
|
||||||
|
'client_id' => $oauth_client_id,
|
||||||
if (empty($accessToken)) {
|
'client_secret' => $oauth_client_secret,
|
||||||
throw new Exception("Missing OAuth access token for XOAUTH2 SMTP.");
|
'refresh_token' => $oauth_refresh_token,
|
||||||
}
|
'grant_type' => 'refresh_token',
|
||||||
|
]);
|
||||||
$mail->setOAuth(new StaticTokenProvider($username, $accessToken));
|
}
|
||||||
} else {
|
}
|
||||||
// Standard SMTP (with or without auth)
|
|
||||||
$mail->SMTPAuth = !empty($username);
|
if (is_array($response) && !empty($response['ok'])) {
|
||||||
$mail->Username = $username ?: '';
|
$json = json_decode($response['body'], true);
|
||||||
$mail->Password = $password ?: '';
|
|
||||||
}
|
if (is_array($json) && !empty($json['access_token'])) {
|
||||||
|
$expires_at = date('Y-m-d H:i:s', time() + (int)($json['expires_in'] ?? 3600));
|
||||||
// Recipients & content
|
$result = [
|
||||||
$mail->setFrom($from_email, $from_name);
|
'access_token' => $json['access_token'],
|
||||||
$mail->addAddress($to_email, $to_name);
|
'expires_at' => $expires_at,
|
||||||
$mail->isHTML(true);
|
'refresh_token' => $json['refresh_token'] ?? null,
|
||||||
$mail->Subject = $subject;
|
];
|
||||||
$mail->Body = $html_body;
|
}
|
||||||
|
}
|
||||||
if (!empty($ics_str)) {
|
|
||||||
$mail->addStringAttachment($ics_str, 'Scheduled_ticket.ics', 'base64', 'text/calendar');
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mail->send();
|
function resolveMailOauthAccessToken(string $provider, string $oauth_client_id, string $oauth_client_secret, string $oauth_tenant_id, string $oauth_refresh_token, string $oauth_access_token, string $oauth_access_token_expires_at): ?string {
|
||||||
return true;
|
if (!empty($oauth_access_token) && !tokenIsExpired($oauth_access_token_expires_at)) {
|
||||||
}
|
return $oauth_access_token;
|
||||||
|
}
|
||||||
/** =======================================================================
|
|
||||||
* SEND: status = 0 (Queued)
|
$tokens = refreshMailOauthAccessToken($provider, $oauth_client_id, $oauth_client_secret, $oauth_tenant_id, $oauth_refresh_token);
|
||||||
* ======================================================================= */
|
|
||||||
$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()");
|
if (!is_array($tokens) || empty($tokens['access_token']) || empty($tokens['expires_at'])) {
|
||||||
|
return null;
|
||||||
if (mysqli_num_rows($sql_queue) > 0) {
|
}
|
||||||
while ($rowq = mysqli_fetch_assoc($sql_queue)) {
|
|
||||||
$email_id = (int)$rowq['email_id'];
|
persistMailOauthTokens($tokens['access_token'], $tokens['expires_at'], $tokens['refresh_token'] ?? null);
|
||||||
$email_from = $rowq['email_from'];
|
|
||||||
$email_from_name = $rowq['email_from_name'];
|
return $tokens['access_token'];
|
||||||
$email_recipient = $rowq['email_recipient'];
|
}
|
||||||
$email_recipient_name = $rowq['email_recipient_name'];
|
|
||||||
$email_subject = $rowq['email_subject'];
|
function sendQueueEmail(
|
||||||
$email_content = $rowq['email_content'];
|
string $provider,
|
||||||
$email_ics_str = $rowq['email_cal_str'];
|
string $host,
|
||||||
|
int $port,
|
||||||
// Check sender
|
string $encryption,
|
||||||
if (!filter_var($email_from, FILTER_VALIDATE_EMAIL)) {
|
string $username,
|
||||||
$email_from_logging = sanitizeInput($rowq['email_from']);
|
string $password,
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
string $from_email,
|
||||||
logApp("Cron-Mail-Queue", "Error", "Failed to send email #$email_id due to invalid sender address: $email_from_logging - check configuration in settings.");
|
string $from_name,
|
||||||
appNotify("Mail", "Failed to send email #$email_id due to invalid sender address");
|
string $to_email,
|
||||||
continue;
|
string $to_name,
|
||||||
}
|
string $subject,
|
||||||
|
string $html_body,
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
|
string $ics_str,
|
||||||
|
string $oauth_client_id,
|
||||||
// Basic recipient syntax check
|
string $oauth_client_secret,
|
||||||
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) {
|
string $oauth_tenant_id,
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
string $oauth_refresh_token,
|
||||||
$email_to_logging = sanitizeInput($email_recipient);
|
string $oauth_access_token,
|
||||||
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
string $oauth_access_token_expires_at
|
||||||
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_to_logging due to invalid recipient address. Email subject was: $email_subject_logging");
|
) {
|
||||||
appNotify("Mail", "Failed to send email #$email_id to $email_to_logging due to invalid recipient address: Email subject was: $email_subject_logging");
|
// Sensible defaults for OAuth providers if fields were left blank
|
||||||
continue;
|
if ($provider === 'google_oauth') {
|
||||||
}
|
if (!$host) $host = 'smtp.gmail.com';
|
||||||
|
if (!$port) $port = 587;
|
||||||
// More intelligent recipient MX check (if not disabled with --no-mx-validation)
|
if (!$encryption) $encryption = 'tls';
|
||||||
$domain = sanitizeInput(substr($email_recipient, strpos($email_recipient, '@') + 1));
|
if (!$username) $username = $from_email;
|
||||||
if (!in_array('--no-mx-validation', $argv) && !checkdnsrr($domain, 'MX')) {
|
} elseif ($provider === 'microsoft_oauth') {
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
if (!$host) $host = 'smtp.office365.com';
|
||||||
$email_to_logging = sanitizeInput($email_recipient);
|
if (!$port) $port = 587;
|
||||||
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
if (!$encryption) $encryption = 'tls';
|
||||||
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_to_logging due to invalid recipient domain (no MX). Email subject was: $email_subject_logging");
|
if (!$username) $username = $from_email;
|
||||||
appNotify("Mail", "Failed to send email #$email_id to $email_to_logging due to invalid recipient domain (no MX): Email subject was: $email_subject_logging");
|
}
|
||||||
continue;
|
|
||||||
}
|
$mail = new PHPMailer(true);
|
||||||
|
$mail->CharSet = "UTF-8";
|
||||||
try {
|
$mail->SMTPDebug = 0;
|
||||||
sendQueueEmail(
|
$mail->isSMTP();
|
||||||
($config_smtp_provider ?: 'standard_smtp'),
|
$mail->Host = $host;
|
||||||
$config_smtp_host,
|
$mail->Port = $port;
|
||||||
(int)$config_smtp_port,
|
|
||||||
(string)$config_smtp_encryption,
|
$enc = strtolower($encryption);
|
||||||
(string)$config_smtp_username,
|
if ($enc === '' || $enc === 'none') {
|
||||||
(string)$config_smtp_password,
|
$mail->SMTPAutoTLS = false;
|
||||||
(string)$email_from,
|
$mail->SMTPSecure = false;
|
||||||
(string)$email_from_name,
|
$mail->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
|
||||||
(string)$email_recipient,
|
} else {
|
||||||
(string)$email_recipient_name,
|
$mail->SMTPSecure = $enc; // 'tls' | 'ssl'
|
||||||
(string)$email_subject,
|
}
|
||||||
(string)$email_content,
|
|
||||||
(string)$email_ics_str,
|
if ($provider === 'google_oauth' || $provider === 'microsoft_oauth') {
|
||||||
(string)$config_mail_oauth_client_id,
|
// XOAUTH2
|
||||||
(string)$config_mail_oauth_client_secret,
|
$mail->SMTPAuth = true;
|
||||||
(string)$config_mail_oauth_tenant_id,
|
$mail->AuthType = 'XOAUTH2';
|
||||||
(string)$config_mail_oauth_refresh_token,
|
$mail->Username = $username;
|
||||||
(string)$config_mail_oauth_access_token,
|
|
||||||
(string)$config_mail_oauth_access_token_expires_at
|
$access_token = resolveMailOauthAccessToken(
|
||||||
);
|
$provider,
|
||||||
|
trim($oauth_client_id),
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
|
trim($oauth_client_secret),
|
||||||
|
trim($oauth_tenant_id),
|
||||||
} catch (Exception $e) {
|
trim($oauth_refresh_token),
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
|
trim($oauth_access_token),
|
||||||
|
trim($oauth_access_token_expires_at)
|
||||||
$email_recipient_logging = sanitizeInput($rowq['email_recipient']);
|
);
|
||||||
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
|
||||||
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
|
if (empty($access_token)) {
|
||||||
|
throw new Exception("Missing OAuth access token for XOAUTH2 SMTP.");
|
||||||
appNotify("Cron-Mail-Queue", "Failed to send email #$email_id to $email_recipient_logging");
|
}
|
||||||
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_recipient_logging regarding $email_subject_logging. $err");
|
|
||||||
}
|
$mail->setOAuth(new StaticTokenProvider($username, $access_token));
|
||||||
}
|
} else {
|
||||||
}
|
// Standard SMTP (with or without auth)
|
||||||
|
$mail->SMTPAuth = !empty($username);
|
||||||
/** =======================================================================
|
$mail->Username = $username ?: '';
|
||||||
* RETRIES: status = 2 (Failed), attempts < 4, wait 30 min
|
$mail->Password = $password ?: '';
|
||||||
* NOTE: Backoff is `email_failed_at <= NOW() - INTERVAL 30 MINUTE`
|
}
|
||||||
* =======================================================================
|
|
||||||
*/
|
// Recipients & content
|
||||||
$sql_failed_queue = mysqli_query(
|
$mail->setFrom($from_email, $from_name);
|
||||||
$mysqli,
|
$mail->addAddress($to_email, $to_name);
|
||||||
"SELECT * FROM email_queue
|
$mail->isHTML(true);
|
||||||
WHERE email_status = 2
|
$mail->Subject = $subject;
|
||||||
AND email_attempts < 4
|
$mail->Body = $html_body;
|
||||||
AND email_failed_at <= NOW() - INTERVAL 30 MINUTE"
|
|
||||||
);
|
if (!empty($ics_str)) {
|
||||||
|
$mail->addStringAttachment($ics_str, 'Scheduled_ticket.ics', 'base64', 'text/calendar');
|
||||||
if (mysqli_num_rows($sql_failed_queue) > 0) {
|
}
|
||||||
while ($rowf = mysqli_fetch_assoc($sql_failed_queue)) {
|
|
||||||
$email_id = (int)$rowf['email_id'];
|
$mail->send();
|
||||||
$email_from = $rowf['email_from'];
|
return true;
|
||||||
$email_from_name = $rowf['email_from_name'];
|
}
|
||||||
$email_recipient = $rowf['email_recipient'];
|
|
||||||
$email_recipient_name = $rowf['email_recipient_name'];
|
/** =======================================================================
|
||||||
$email_subject = $rowf['email_subject'];
|
* SEND: status = 0 (Queued)
|
||||||
$email_content = $rowf['email_content'];
|
* ======================================================================= */
|
||||||
$email_ics_str = $rowf['email_cal_str'];
|
$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()");
|
||||||
$email_attempts = (int)$rowf['email_attempts'] + 1;
|
|
||||||
|
if (mysqli_num_rows($sql_queue) > 0) {
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
|
while ($rowq = mysqli_fetch_assoc($sql_queue)) {
|
||||||
|
$email_id = (int)$rowq['email_id'];
|
||||||
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) {
|
$email_from = $rowq['email_from'];
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = $email_attempts WHERE email_id = $email_id");
|
$email_from_name = $rowq['email_from_name'];
|
||||||
continue;
|
$email_recipient = $rowq['email_recipient'];
|
||||||
}
|
$email_recipient_name = $rowq['email_recipient_name'];
|
||||||
|
$email_subject = $rowq['email_subject'];
|
||||||
try {
|
$email_content = $rowq['email_content'];
|
||||||
sendQueueEmail(
|
$email_ics_str = $rowq['email_cal_str'];
|
||||||
($config_smtp_provider ?: 'standard_smtp'),
|
|
||||||
$config_smtp_host,
|
// Check sender
|
||||||
(int)$config_smtp_port,
|
if (!filter_var($email_from, FILTER_VALIDATE_EMAIL)) {
|
||||||
(string)$config_smtp_encryption,
|
$email_from_logging = sanitizeInput($rowq['email_from']);
|
||||||
(string)$config_smtp_username,
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
||||||
(string)$config_smtp_password,
|
logApp("Cron-Mail-Queue", "Error", "Failed to send email #$email_id due to invalid sender address: $email_from_logging - check configuration in settings.");
|
||||||
(string)$email_from,
|
appNotify("Mail", "Failed to send email #$email_id due to invalid sender address");
|
||||||
(string)$email_from_name,
|
continue;
|
||||||
(string)$email_recipient,
|
}
|
||||||
(string)$email_recipient_name,
|
|
||||||
(string)$email_subject,
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
|
||||||
(string)$email_content,
|
|
||||||
(string)$email_ics_str,
|
// Basic recipient syntax check
|
||||||
(string)$config_mail_oauth_client_id,
|
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) {
|
||||||
(string)$config_mail_oauth_client_secret,
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
||||||
(string)$config_mail_oauth_tenant_id,
|
$email_to_logging = sanitizeInput($email_recipient);
|
||||||
(string)$config_mail_oauth_refresh_token,
|
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
||||||
(string)$config_mail_oauth_access_token,
|
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_to_logging due to invalid recipient address. Email subject was: $email_subject_logging");
|
||||||
(string)$config_mail_oauth_access_token_expires_at
|
appNotify("Mail", "Failed to send email #$email_id to $email_to_logging due to invalid recipient address: Email subject was: $email_subject_logging");
|
||||||
);
|
continue;
|
||||||
|
}
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
|
|
||||||
|
// More intelligent recipient MX check (if not disabled with --no-mx-validation)
|
||||||
} catch (Exception $e) {
|
$domain = sanitizeInput(substr($email_recipient, strpos($email_recipient, '@') + 1));
|
||||||
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
|
if (!in_array('--no-mx-validation', $argv) && !checkdnsrr($domain, 'MX')) {
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
|
||||||
$email_recipient_logging = sanitizeInput($rowf['email_recipient']);
|
$email_to_logging = sanitizeInput($email_recipient);
|
||||||
$email_subject_logging = sanitizeInput($rowf['email_subject']);
|
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
||||||
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
|
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_to_logging due to invalid recipient domain (no MX). Email subject was: $email_subject_logging");
|
||||||
|
appNotify("Mail", "Failed to send email #$email_id to $email_to_logging due to invalid recipient domain (no MX): Email subject was: $email_subject_logging");
|
||||||
logApp("Cron-Mail-Queue", "Error", "Failed to re-send email #$email_id to $email_recipient_logging regarding $email_subject_logging. $err");
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
sendQueueEmail(
|
||||||
/** =======================================================================
|
($config_smtp_provider ?: 'standard_smtp'),
|
||||||
* Unlock
|
$config_smtp_host,
|
||||||
* ======================================================================= */
|
(int)$config_smtp_port,
|
||||||
unlink($lock_file_path);
|
(string)$config_smtp_encryption,
|
||||||
|
(string)$config_smtp_username,
|
||||||
|
(string)$config_smtp_password,
|
||||||
|
(string)$email_from,
|
||||||
|
(string)$email_from_name,
|
||||||
|
(string)$email_recipient,
|
||||||
|
(string)$email_recipient_name,
|
||||||
|
(string)$email_subject,
|
||||||
|
(string)$email_content,
|
||||||
|
(string)$email_ics_str,
|
||||||
|
(string)$config_mail_oauth_client_id,
|
||||||
|
(string)$config_mail_oauth_client_secret,
|
||||||
|
(string)$config_mail_oauth_tenant_id,
|
||||||
|
(string)$config_mail_oauth_refresh_token,
|
||||||
|
(string)$config_mail_oauth_access_token,
|
||||||
|
(string)$config_mail_oauth_access_token_expires_at
|
||||||
|
);
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
|
||||||
|
|
||||||
|
$email_recipient_logging = sanitizeInput($rowq['email_recipient']);
|
||||||
|
$email_subject_logging = sanitizeInput($rowq['email_subject']);
|
||||||
|
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
|
||||||
|
|
||||||
|
appNotify("Cron-Mail-Queue", "Failed to send email #$email_id to $email_recipient_logging");
|
||||||
|
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_recipient_logging regarding $email_subject_logging. $err");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** =======================================================================
|
||||||
|
* RETRIES: status = 2 (Failed), attempts < 4, wait 30 min
|
||||||
|
* NOTE: Backoff is `email_failed_at <= NOW() - INTERVAL 30 MINUTE`
|
||||||
|
* =======================================================================
|
||||||
|
*/
|
||||||
|
$sql_failed_queue = mysqli_query(
|
||||||
|
$mysqli,
|
||||||
|
"SELECT * FROM email_queue
|
||||||
|
WHERE email_status = 2
|
||||||
|
AND email_attempts < 4
|
||||||
|
AND email_failed_at <= NOW() - INTERVAL 30 MINUTE"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mysqli_num_rows($sql_failed_queue) > 0) {
|
||||||
|
while ($rowf = mysqli_fetch_assoc($sql_failed_queue)) {
|
||||||
|
$email_id = (int)$rowf['email_id'];
|
||||||
|
$email_from = $rowf['email_from'];
|
||||||
|
$email_from_name = $rowf['email_from_name'];
|
||||||
|
$email_recipient = $rowf['email_recipient'];
|
||||||
|
$email_recipient_name = $rowf['email_recipient_name'];
|
||||||
|
$email_subject = $rowf['email_subject'];
|
||||||
|
$email_content = $rowf['email_content'];
|
||||||
|
$email_ics_str = $rowf['email_cal_str'];
|
||||||
|
$email_attempts = (int)$rowf['email_attempts'] + 1;
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
|
||||||
|
|
||||||
|
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = $email_attempts WHERE email_id = $email_id");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
sendQueueEmail(
|
||||||
|
($config_smtp_provider ?: 'standard_smtp'),
|
||||||
|
$config_smtp_host,
|
||||||
|
(int)$config_smtp_port,
|
||||||
|
(string)$config_smtp_encryption,
|
||||||
|
(string)$config_smtp_username,
|
||||||
|
(string)$config_smtp_password,
|
||||||
|
(string)$email_from,
|
||||||
|
(string)$email_from_name,
|
||||||
|
(string)$email_recipient,
|
||||||
|
(string)$email_recipient_name,
|
||||||
|
(string)$email_subject,
|
||||||
|
(string)$email_content,
|
||||||
|
(string)$email_ics_str,
|
||||||
|
(string)$config_mail_oauth_client_id,
|
||||||
|
(string)$config_mail_oauth_client_secret,
|
||||||
|
(string)$config_mail_oauth_tenant_id,
|
||||||
|
(string)$config_mail_oauth_refresh_token,
|
||||||
|
(string)$config_mail_oauth_access_token,
|
||||||
|
(string)$config_mail_oauth_access_token_expires_at
|
||||||
|
);
|
||||||
|
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
|
||||||
|
|
||||||
|
$email_recipient_logging = sanitizeInput($rowf['email_recipient']);
|
||||||
|
$email_subject_logging = sanitizeInput($rowf['email_subject']);
|
||||||
|
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
|
||||||
|
|
||||||
|
logApp("Cron-Mail-Queue", "Error", "Failed to re-send email #$email_id to $email_recipient_logging regarding $email_subject_logging. $err");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** =======================================================================
|
||||||
|
* Unlock
|
||||||
|
* ======================================================================= */
|
||||||
|
unlink($lock_file_path);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue