settings send test mail frontend

This commit is contained in:
Roman 2024-04-05 14:17:50 +02:00
parent 98fcd2822c
commit 964b98c22a
9 changed files with 95 additions and 10 deletions

@ -185,6 +185,7 @@ namespace Core\API\Mail {
$mail->addStringAttachment("Version: 1", null, PHPMailer::ENCODING_BASE64, "application/pgp-encrypted", ""); $mail->addStringAttachment("Version: 1", null, PHPMailer::ENCODING_BASE64, "application/pgp-encrypted", "");
$mail->addStringAttachment($encryptedBody, "encrypted.asc", PHPMailer::ENCODING_7BIT, "application/octet-stream", ""); $mail->addStringAttachment($encryptedBody, "encrypted.asc", PHPMailer::ENCODING_7BIT, "application/octet-stream", "");
} else { } else {
$this->logger->error("Error encrypting with gpg: " . $res["error"]);
return $this->createError($res["error"]); return $this->createError($res["error"]);
} }
} else { } else {
@ -237,6 +238,7 @@ namespace Core\API\Mail {
if ($this->success && is_array($mailQueueItems)) { if ($this->success && is_array($mailQueueItems)) {
if ($debug) { if ($debug) {
echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL; echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL;
$this->logger->debug("Found " . count($mailQueueItems) . " mails to send");
} }
$successfulMails = 0; $successfulMails = 0;
@ -249,6 +251,7 @@ namespace Core\API\Mail {
if ($debug) { if ($debug) {
echo "Sending subject=$mailQueueItem->subject to=$mailQueueItem->to" . PHP_EOL; echo "Sending subject=$mailQueueItem->subject to=$mailQueueItem->to" . PHP_EOL;
$this->logger->debug("Sending subject=$mailQueueItem->subject to=$mailQueueItem->to");
} }
if ($mailQueueItem->send($this->context)) { if ($mailQueueItem->send($this->context)) {
@ -257,6 +260,9 @@ namespace Core\API\Mail {
} }
$this->success = $successfulMails === count($mailQueueItems); $this->success = $successfulMails === count($mailQueueItems);
if ($successfulMails > 0) {
$this->logger->debug("Sent $successfulMails emails successfully");
}
} }
return $this->success; return $this->success;

@ -117,6 +117,10 @@ namespace Core\API\Settings {
$this->success = ($query->execute() !== FALSE); $this->success = ($query->execute() !== FALSE);
$this->lastError = $sql->getLastError(); $this->lastError = $sql->getLastError();
if ($this->success) {
$this->logger->info("The site settings were changed");
}
} }
return $this->success; return $this->success;

@ -183,6 +183,8 @@ namespace Core\API\User {
$groups = []; $groups = [];
$sql = $this->context->getSQL(); $sql = $this->context->getSQL();
$currentUser = $this->context->getUser();
$currentUserId = $currentUser->getId();
$requestedGroups = array_unique($this->getParam("groups")); $requestedGroups = array_unique($this->getParam("groups"));
if (!empty($requestedGroups)) { if (!empty($requestedGroups)) {
@ -190,7 +192,7 @@ namespace Core\API\User {
foreach ($requestedGroups as $groupId) { foreach ($requestedGroups as $groupId) {
if (!isset($availableGroups[$groupId])) { if (!isset($availableGroups[$groupId])) {
return $this->createError("Group with id=$groupId does not exist."); return $this->createError("Group with id=$groupId does not exist.");
} else if ($groupId === Group::ADMIN && !$this->context->getUser()->hasGroup(Group::ADMIN)) { } else if ($groupId === Group::ADMIN && !$currentUser->hasGroup(Group::ADMIN)) {
return $this->createError("You cannot create users with administrator groups."); return $this->createError("You cannot create users with administrator groups.");
} }
} }
@ -202,6 +204,7 @@ namespace Core\API\User {
if ($user !== false) { if ($user !== false) {
$this->user = $user; $this->user = $user;
$this->result["userId"] = $user->getId(); $this->result["userId"] = $user->getId();
$this->logger->info("A new user with username='$username' and email='$email' was created by userId='$currentUserId'");
} }
return $this->success; return $this->success;
@ -423,6 +426,9 @@ namespace Core\API\User {
return false; return false;
} }
$currentUserId = $this->context->getUser()->getId();
$this->logger->info("A new user with username='$username' and email='$email' was invited by userId='$currentUserId'");
// Create Token // Create Token
$token = generateRandomString(36); $token = generateRandomString(36);
$validDays = 7; $validDays = 7;
@ -724,6 +730,8 @@ namespace Core\API\User {
return false; return false;
} }
$this->logger->info("A new user with username='$username' and email='$email' was created");
$validHours = 48; $validHours = 48;
$token = generateRandomString(36); $token = generateRandomString(36);
$userToken = new UserToken($user, $token, UserToken::TYPE_EMAIL_CONFIRM, $validHours); $userToken = new UserToken($user, $token, UserToken::TYPE_EMAIL_CONFIRM, $validHours);
@ -756,12 +764,12 @@ namespace Core\API\User {
$this->lastError = $request->getLastError(); $this->lastError = $request->getLastError();
} }
} else { } else {
$this->lastError = "Could create user token: " . $sql->getLastError(); $this->lastError = "Could not create user token: " . $sql->getLastError();
$this->success = false; $this->success = false;
} }
if (!$this->success) { if (!$this->success) {
$this->logger->error("Could not deliver email to=$email type=register reason=" . $this->lastError); $this->logger->error("Could not deliver email to='$email' type='register' reason='" . $this->lastError . "'");
$this->lastError = "Your account was registered but the confirmation email could not be sent. " . $this->lastError = "Your account was registered but the confirmation email could not be sent. " .
"Please contact the server administration. This issue has been automatically logged. Reason: " . $this->lastError; "Please contact the server administration. This issue has been automatically logged. Reason: " . $this->lastError;
} }
@ -1007,7 +1015,7 @@ namespace Core\API\User {
"gpgFingerprint" => $gpgFingerprint "gpgFingerprint" => $gpgFingerprint
)); ));
$this->lastError = $request->getLastError(); $this->lastError = $request->getLastError();
$this->logger->info("Requested password reset for user id=" . $user->getId() . " by ip_address=" . $_SERVER["REMOTE_ADDR"]); $this->logger->info("Requested password reset for user id='" . $user->getId() . "' by ip_address='" . $_SERVER["REMOTE_ADDR"] . "'");
} }
} }
} }

@ -159,7 +159,8 @@ class Settings {
->addRow("mail_password", "", true, false) ->addRow("mail_password", "", true, false)
->addRow("mail_from", "", false, false) ->addRow("mail_from", "", false, false)
->addRow("mail_last_sync", "", false, false) ->addRow("mail_last_sync", "", false, false)
->addRow("mail_footer", "", false, false); ->addRow("mail_footer", "", false, false)
->addRow("mail_async", false, false, false);
} }
public function getSiteName(): string { public function getSiteName(): string {

@ -50,6 +50,8 @@ return [
"info" => "Info", "info" => "Info",
"reload" => "Aktualisieren", "reload" => "Aktualisieren",
"success" => "Erfolg", "success" => "Erfolg",
"send" => "Senden",
"sending" => "Sende ab",
# file # file
"choose_file" => "Datei auswählen", "choose_file" => "Datei auswählen",

@ -47,6 +47,9 @@ return [
"mail_username" => "Mail-Server Benutzername", "mail_username" => "Mail-Server Benutzername",
"mail_password" => "Mail-Server Passwort", "mail_password" => "Mail-Server Passwort",
"mail_footer" => "Pfad zum E-Mail-Footer", "mail_footer" => "Pfad zum E-Mail-Footer",
"mail_async" => "E-Mails asynchron senden (erfordert einen Cron-Job)",
"mail_address" => "E-Mail Adresse",
"send_test_email" => "Test E-Mail senden",
# recaptcha # recaptcha
"recaptcha_enabled" => "Aktiviere Google reCaptcha", "recaptcha_enabled" => "Aktiviere Google reCaptcha",
@ -57,4 +60,6 @@ return [
"fetch_settings_error" => "Fehler beim Holen der Einstellungen", "fetch_settings_error" => "Fehler beim Holen der Einstellungen",
"save_settings_success" => "Einstellungen erfolgreich gespeichert", "save_settings_success" => "Einstellungen erfolgreich gespeichert",
"save_settings_error" => "Fehler beim Speichern der Einstellungen", "save_settings_error" => "Fehler beim Speichern der Einstellungen",
"send_test_email_error" => "Fehler beim Senden der Test E-Mail",
"send_test_email_success" => "Test E-Mail erfolgreich versendet, überprüfen Sie Ihren Posteingang!",
]; ];

@ -48,6 +48,8 @@ return [
"info" => "Info", "info" => "Info",
"reload" => "Reload", "reload" => "Reload",
"success" => "Success", "success" => "Success",
"send" => "Send",
"sending" => "Sending",
# file # file
"choose_file" => "Choose File", "choose_file" => "Choose File",

@ -47,6 +47,9 @@ return [
"mail_username" => "Mail server username", "mail_username" => "Mail server username",
"mail_password" => "Mail server password", "mail_password" => "Mail server password",
"mail_footer" => "Path to e-mail footer", "mail_footer" => "Path to e-mail footer",
"mail_async" => "Send e-mails asynchronously (requires a cron-job)",
"mail_address" => "Mail address",
"send_test_email" => "Send test e-mail",
# recaptcha # recaptcha
"recaptcha_enabled" => "Enable Google reCaptcha", "recaptcha_enabled" => "Enable Google reCaptcha",
@ -57,4 +60,6 @@ return [
"fetch_settings_error" => "Error fetching settings", "fetch_settings_error" => "Error fetching settings",
"save_settings_success" => "Settings saved successfully", "save_settings_success" => "Settings saved successfully",
"save_settings_error" => "Error saving settings", "save_settings_error" => "Error saving settings",
"send_test_email_error" => "Error sending test email",
"send_test_email_success" => "Test email successfully sent. Please check your inbox!",
]; ];

@ -3,7 +3,7 @@ import {LocaleContext} from "shared/locale";
import { import {
Box, Button, Checkbox, Box, Button, Checkbox,
CircularProgress, FormControl, FormControlLabel, CircularProgress, FormControl, FormControlLabel,
FormGroup, FormLabel, IconButton, FormGroup, FormLabel, Grid, IconButton,
Paper, Select, styled, Paper, Select, styled,
Tab, Tab,
Table, Table,
@ -14,7 +14,17 @@ import {
Tabs, TextField Tabs, TextField
} from "@mui/material"; } from "@mui/material";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Add, Delete, Google, LibraryBooks, Mail, RestartAlt, Save, SettingsApplications} from "@mui/icons-material"; import {
Add,
Delete,
Google,
LibraryBooks,
Mail,
RestartAlt,
Save,
Send,
SettingsApplications
} from "@mui/icons-material";
import {TableContainer} from "@material-ui/core"; import {TableContainer} from "@material-ui/core";
import TIME_ZONES from "shared/time-zones"; import TIME_ZONES from "shared/time-zones";
@ -50,6 +60,7 @@ export default function SettingsView(props) {
"mail_port", "mail_port",
"mail_username", "mail_username",
"mail_password", "mail_password",
"mail_async",
], ],
"recaptcha": [ "recaptcha": [
"recaptcha_enabled", "recaptcha_enabled",
@ -69,6 +80,8 @@ export default function SettingsView(props) {
const [hasChanged, setChanged] = useState(false); const [hasChanged, setChanged] = useState(false);
const [isSaving, setSaving] = useState(false); const [isSaving, setSaving] = useState(false);
const [newKey, setNewKey] = useState(""); const [newKey, setNewKey] = useState("");
const [testMailAddress, setTestMailAddress] = useState("");
const [isSending, setSending] = useState(false);
const isUncategorized = (key) => { const isUncategorized = (key) => {
return !(Object.values(KNOWN_SETTING_KEYS).reduce((acc, arr) => { return !(Object.values(KNOWN_SETTING_KEYS).reduce((acc, arr) => {
@ -161,11 +174,26 @@ export default function SettingsView(props) {
} }
}, [settings]); }, [settings]);
const onSendTestMail = useCallback(() => {
if (!isSending) {
setSending(true);
api.sendTestMail(testMailAddress).then(data => {
setSending(false);
if (!data.success) {
showDialog(data.msg, L("settings.send_test_email_error"));
} else {
showDialog(L("settings.send_test_email_success"), L("general.success"));
setTestMailAddress("");
}
});
}
}, [api, showDialog, testMailAddress, isSending]);
if (settings === null) { if (settings === null) {
return <CircularProgress /> return <CircularProgress />
} }
const parseBool = (v) => v === true || v === 1 || ["true", "1", "yes"].includes(v.toString().toLowerCase()); const parseBool = (v) => v !== undefined && (v === true || v === 1 || ["true", "1", "yes"].includes(v.toString().toLowerCase()));
const renderTextInput = (key_name, disabled=false, props={}) => { const renderTextInput = (key_name, disabled=false, props={}) => {
return <SettingsFormGroup key={"form-" + key_name} {...props}> return <SettingsFormGroup key={"form-" + key_name} {...props}>
@ -254,6 +282,30 @@ export default function SettingsView(props) {
renderTextInput("mail_username", !parseBool(settings.mail_enabled)), renderTextInput("mail_username", !parseBool(settings.mail_enabled)),
renderPasswordInput("mail_password", !parseBool(settings.mail_enabled)), renderPasswordInput("mail_password", !parseBool(settings.mail_enabled)),
renderTextInput("mail_footer", !parseBool(settings.mail_enabled)), renderTextInput("mail_footer", !parseBool(settings.mail_enabled)),
renderCheckBox("mail_async", !parseBool(settings.mail_enabled)),
<FormGroup key={"mail-test"}>
<FormLabel>{L("settings.send_test_email")}</FormLabel>
<FormControl disabled={!parseBool(settings.mail_enabled)}>
<Grid container spacing={1}>
<Grid item xs={1}>
<Button startIcon={isSending ? <CircularProgress size={14} /> : <Send />}
variant={"outlined"} onClick={onSendTestMail}
fullWidth={true}
disabled={!parseBool(settings.mail_enabled) || isSending || !api.hasPermission("mail/test")}>
{isSending ? L("general.sending") + "…" : L("general.send")}
</Button>
</Grid>
<Grid item xs={11}>
<TextField disabled={!parseBool(settings.mail_enabled)}
fullWidth={true}
variant={"outlined"} value={testMailAddress}
onChange={e => setTestMailAddress(e.target.value)}
size={"small"} type={"email"}
placeholder={L("settings.mail_address")} />
</Grid>
</Grid>
</FormControl>
</FormGroup>
]; ];
} else if (selectedTab === "recaptcha") { } else if (selectedTab === "recaptcha") {
return [ return [
@ -348,7 +400,7 @@ export default function SettingsView(props) {
<ButtonBar> <ButtonBar>
<Button color={"primary"} <Button color={"primary"}
onClick={onSaveSettings} onClick={onSaveSettings}
disabled={isSaving} disabled={isSaving || !api.hasPermission("settings/set")}
startIcon={isSaving ? <CircularProgress size={14} /> : <Save />} startIcon={isSaving ? <CircularProgress size={14} /> : <Save />}
variant={"outlined"} title={L(hasChanged ? "general.unsaved_changes" : "general.save")}> variant={"outlined"} title={L(hasChanged ? "general.unsaved_changes" : "general.save")}>
{isSaving ? L("general.saving") + "…" : (L("general.save") + (hasChanged ? " *" : ""))} {isSaving ? L("general.saving") + "…" : (L("general.save") + (hasChanged ? " *" : ""))}