diff --git a/Core/API/MailAPI.class.php b/Core/API/MailAPI.class.php
index 6d9cc31..d89312b 100644
--- a/Core/API/MailAPI.class.php
+++ b/Core/API/MailAPI.class.php
@@ -185,6 +185,7 @@ namespace Core\API\Mail {
$mail->addStringAttachment("Version: 1", null, PHPMailer::ENCODING_BASE64, "application/pgp-encrypted", "");
$mail->addStringAttachment($encryptedBody, "encrypted.asc", PHPMailer::ENCODING_7BIT, "application/octet-stream", "");
} else {
+ $this->logger->error("Error encrypting with gpg: " . $res["error"]);
return $this->createError($res["error"]);
}
} else {
@@ -237,6 +238,7 @@ namespace Core\API\Mail {
if ($this->success && is_array($mailQueueItems)) {
if ($debug) {
echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL;
+ $this->logger->debug("Found " . count($mailQueueItems) . " mails to send");
}
$successfulMails = 0;
@@ -249,6 +251,7 @@ namespace Core\API\Mail {
if ($debug) {
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)) {
@@ -257,6 +260,9 @@ namespace Core\API\Mail {
}
$this->success = $successfulMails === count($mailQueueItems);
+ if ($successfulMails > 0) {
+ $this->logger->debug("Sent $successfulMails emails successfully");
+ }
}
return $this->success;
diff --git a/Core/API/SettingsAPI.class.php b/Core/API/SettingsAPI.class.php
index 6c55dc7..241842f 100644
--- a/Core/API/SettingsAPI.class.php
+++ b/Core/API/SettingsAPI.class.php
@@ -117,6 +117,10 @@ namespace Core\API\Settings {
$this->success = ($query->execute() !== FALSE);
$this->lastError = $sql->getLastError();
+
+ if ($this->success) {
+ $this->logger->info("The site settings were changed");
+ }
}
return $this->success;
diff --git a/Core/API/UserAPI.class.php b/Core/API/UserAPI.class.php
index d463979..cb7cd3e 100644
--- a/Core/API/UserAPI.class.php
+++ b/Core/API/UserAPI.class.php
@@ -183,6 +183,8 @@ namespace Core\API\User {
$groups = [];
$sql = $this->context->getSQL();
+ $currentUser = $this->context->getUser();
+ $currentUserId = $currentUser->getId();
$requestedGroups = array_unique($this->getParam("groups"));
if (!empty($requestedGroups)) {
@@ -190,7 +192,7 @@ namespace Core\API\User {
foreach ($requestedGroups as $groupId) {
if (!isset($availableGroups[$groupId])) {
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.");
}
}
@@ -202,6 +204,7 @@ namespace Core\API\User {
if ($user !== false) {
$this->user = $user;
$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;
@@ -423,6 +426,9 @@ namespace Core\API\User {
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
$token = generateRandomString(36);
$validDays = 7;
@@ -724,6 +730,8 @@ namespace Core\API\User {
return false;
}
+ $this->logger->info("A new user with username='$username' and email='$email' was created");
+
$validHours = 48;
$token = generateRandomString(36);
$userToken = new UserToken($user, $token, UserToken::TYPE_EMAIL_CONFIRM, $validHours);
@@ -756,12 +764,12 @@ namespace Core\API\User {
$this->lastError = $request->getLastError();
}
} else {
- $this->lastError = "Could create user token: " . $sql->getLastError();
+ $this->lastError = "Could not create user token: " . $sql->getLastError();
$this->success = false;
}
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. " .
"Please contact the server administration. This issue has been automatically logged. Reason: " . $this->lastError;
}
@@ -1007,7 +1015,7 @@ namespace Core\API\User {
"gpgFingerprint" => $gpgFingerprint
));
$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"] . "'");
}
}
}
diff --git a/Core/Configuration/Settings.class.php b/Core/Configuration/Settings.class.php
index 76e8c15..fe6a8a1 100644
--- a/Core/Configuration/Settings.class.php
+++ b/Core/Configuration/Settings.class.php
@@ -159,7 +159,8 @@ class Settings {
->addRow("mail_password", "", true, false)
->addRow("mail_from", "", 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 {
diff --git a/Core/Localization/de_DE/general.php b/Core/Localization/de_DE/general.php
index 13496ff..f928b0c 100644
--- a/Core/Localization/de_DE/general.php
+++ b/Core/Localization/de_DE/general.php
@@ -50,6 +50,8 @@ return [
"info" => "Info",
"reload" => "Aktualisieren",
"success" => "Erfolg",
+ "send" => "Senden",
+ "sending" => "Sende ab",
# file
"choose_file" => "Datei auswählen",
diff --git a/Core/Localization/de_DE/settings.php b/Core/Localization/de_DE/settings.php
index baa337f..ac4a21e 100644
--- a/Core/Localization/de_DE/settings.php
+++ b/Core/Localization/de_DE/settings.php
@@ -47,6 +47,9 @@ return [
"mail_username" => "Mail-Server Benutzername",
"mail_password" => "Mail-Server Passwort",
"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_enabled" => "Aktiviere Google reCaptcha",
@@ -57,4 +60,6 @@ return [
"fetch_settings_error" => "Fehler beim Holen der Einstellungen",
"save_settings_success" => "Einstellungen erfolgreich gespeichert",
"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!",
];
\ No newline at end of file
diff --git a/Core/Localization/en_US/general.php b/Core/Localization/en_US/general.php
index a45ab35..4f4b76a 100644
--- a/Core/Localization/en_US/general.php
+++ b/Core/Localization/en_US/general.php
@@ -48,6 +48,8 @@ return [
"info" => "Info",
"reload" => "Reload",
"success" => "Success",
+ "send" => "Send",
+ "sending" => "Sending",
# file
"choose_file" => "Choose File",
diff --git a/Core/Localization/en_US/settings.php b/Core/Localization/en_US/settings.php
index d1939a3..b8f05d0 100644
--- a/Core/Localization/en_US/settings.php
+++ b/Core/Localization/en_US/settings.php
@@ -47,6 +47,9 @@ return [
"mail_username" => "Mail server username",
"mail_password" => "Mail server password",
"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_enabled" => "Enable Google reCaptcha",
@@ -57,4 +60,6 @@ return [
"fetch_settings_error" => "Error fetching settings",
"save_settings_success" => "Settings saved successfully",
"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!",
];
\ No newline at end of file
diff --git a/react/admin-panel/src/views/settings.js b/react/admin-panel/src/views/settings.js
index f635654..e8d207e 100644
--- a/react/admin-panel/src/views/settings.js
+++ b/react/admin-panel/src/views/settings.js
@@ -3,7 +3,7 @@ import {LocaleContext} from "shared/locale";
import {
Box, Button, Checkbox,
CircularProgress, FormControl, FormControlLabel,
- FormGroup, FormLabel, IconButton,
+ FormGroup, FormLabel, Grid, IconButton,
Paper, Select, styled,
Tab,
Table,
@@ -14,7 +14,17 @@ import {
Tabs, TextField
} from "@mui/material";
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 TIME_ZONES from "shared/time-zones";
@@ -50,6 +60,7 @@ export default function SettingsView(props) {
"mail_port",
"mail_username",
"mail_password",
+ "mail_async",
],
"recaptcha": [
"recaptcha_enabled",
@@ -69,6 +80,8 @@ export default function SettingsView(props) {
const [hasChanged, setChanged] = useState(false);
const [isSaving, setSaving] = useState(false);
const [newKey, setNewKey] = useState("");
+ const [testMailAddress, setTestMailAddress] = useState("");
+ const [isSending, setSending] = useState(false);
const isUncategorized = (key) => {
return !(Object.values(KNOWN_SETTING_KEYS).reduce((acc, arr) => {
@@ -161,11 +174,26 @@ export default function SettingsView(props) {
}
}, [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) {
return
}
- 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={}) => {
return
@@ -254,6 +282,30 @@ export default function SettingsView(props) {
renderTextInput("mail_username", !parseBool(settings.mail_enabled)),
renderPasswordInput("mail_password", !parseBool(settings.mail_enabled)),
renderTextInput("mail_footer", !parseBool(settings.mail_enabled)),
+ renderCheckBox("mail_async", !parseBool(settings.mail_enabled)),
+
+ {L("settings.send_test_email")}
+
+
+
+ : }
+ variant={"outlined"} onClick={onSendTestMail}
+ fullWidth={true}
+ disabled={!parseBool(settings.mail_enabled) || isSending || !api.hasPermission("mail/test")}>
+ {isSending ? L("general.sending") + "…" : L("general.send")}
+
+
+
+ setTestMailAddress(e.target.value)}
+ size={"small"} type={"email"}
+ placeholder={L("settings.mail_address")} />
+
+
+
+
];
} else if (selectedTab === "recaptcha") {
return [
@@ -291,7 +343,7 @@ export default function SettingsView(props) {
setNewKey(e.target.value)}
- onBlur={() => onAddKey(newKey)} value={newKey}/>
+ onBlur={() => onAddKey(newKey)} value={newKey} />
@@ -348,7 +400,7 @@ export default function SettingsView(props) {
: }
variant={"outlined"} title={L(hasChanged ? "general.unsaved_changes" : "general.save")}>
{isSaving ? L("general.saving") + "…" : (L("general.save") + (hasChanged ? " *" : ""))}