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")} + + + + + + + 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) {