From 3192d404dba91298f39bf3ba5f1f79cc4fbf76c0 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 9 Jun 2025 18:03:16 +0530 Subject: [PATCH 01/58] Send email and restricts accounts due to no recovery email --- appinfo/info.xml | 1 + l10n/de.json | 4 +- l10n/de_DE.json | 4 +- l10n/en.json | 4 +- l10n/es.json | 4 +- l10n/fr.json | 4 +- l10n/it.json | 4 +- .../DailyRecoveryWarningNotificationJob.php | 317 ++++++++++++++++++ 8 files changed, 336 insertions(+), 6 deletions(-) create mode 100644 lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php diff --git a/appinfo/info.xml b/appinfo/info.xml index d839897..caa1517 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -19,6 +19,7 @@ OCA\EmailRecovery\BackgroundJob\WeeklyRecoveryNotificationJob + OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob OCA\EmailRecovery\Command\UpdateBlacklistedDomains diff --git a/l10n/de.json b/l10n/de.json index d7b79ce..db55beb 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -36,6 +36,8 @@ "The email could not be verified. Please try again later.": "Die E-Mail konnte nicht verifiziert werden. Bitte versuchen Sie es später noch einmal.", "The email address is disposable. Please provide another recovery address." : "Die E-Mail-Adresse ist eine Wegwerfadresse. Bitte geben Sie eine andere Wiederherstellungsadresse an.", "The email address is not deliverable. Please provide another recovery address.": "Die E-Mail Adresse ist nicht zustellbar. Bitte geben Sie eine andere Wiederherstellungsadresse an.", - "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace wird bald wieder vollständig verfügbar sein! Bitte lies diesen Leitfaden, um mehr zu erfahren." + "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace wird bald wieder vollständig verfügbar sein! Bitte lies diesen Leitfaden, um mehr zu erfahren.", + "20250610_account_recovery_warning_subject": "Erforderliche Maßnahme: Richten Sie Ihre Wiederherstellungs-E-Mail vor der Kontolöschung ein.", + "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{deletion_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/de_DE.json b/l10n/de_DE.json index ee6e86a..1f85c04 100644 --- a/l10n/de_DE.json +++ b/l10n/de_DE.json @@ -37,6 +37,8 @@ "The email address is disposable. Please provide another recovery address." : "Die E-Mail-Adresse ist eine Wegwerfadresse. Bitte geben Sie eine andere Wiederherstellungsadresse an.", "The email address is not deliverable. Please provide another recovery address.": "Die E-Mail Adresse ist nicht zustellbar. Bitte geben Sie eine andere Wiederherstellungsadresse an.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace wird bald wieder vollständig verfügbar sein! Bitte lesen Sie diesen Leitfaden, um mehr zu erfahren.", - "This email address in invalid, please use another one.":"Diese E-Mail-Adresse ist ungültig, bitte verwenden Sie eine andere." + "This email address in invalid, please use another one.":"Diese E-Mail-Adresse ist ungültig, bitte verwenden Sie eine andere.", + "20250610_account_recovery_warning_subject": "Erforderliche Maßnahme: Richten Sie Ihre Wiederherstellungs-E-Mail vor der Kontolöschung ein.", + "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{deletion_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/en.json b/l10n/en.json index 5d2ae37..2bf22fb 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -36,7 +36,9 @@ "The email could not be verified. Please try again later.": "The email could not be verified. Please try again later.", "The email address is disposable. Please provide another recovery address." : "The email address is disposable. Please provide another recovery address.", "The email address is not deliverable. Please provide another recovery address.": "The email address is not deliverable. Please provide another recovery address.", - "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace will be back fully soon! Please read this guide to know more." + "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace will be back fully soon! Please read this guide to know more.", + "20250610_account_recovery_warning_subject": "Action required: Set up your recovery email before account deletion", + "20250610_account_recovery_warning_body": "Dear **{username}**,\n\nFor security reasons you need to set a recovery email address for your Murena Cloud account. As you haven't done so yet, the usage of your email address is restricted. Please set and validate a recovery email address to remove all usage restriction on your email address. If you fail to do so, your account will be disabled on **{deletion_date}** and then deleted.\n\n[SET MY RECOVERY EMAIL ADDRESS](https://murena.io/settings/user/security#recovery-email-form)\n\nBest regards,\nThe Murena Team" }, "pluralForm": "nplurals=2; plural=(n != 1);" } \ No newline at end of file diff --git a/l10n/es.json b/l10n/es.json index 79d25d7..9642bc1 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -37,6 +37,8 @@ "The email address is disposable. Please provide another recovery address." : "La dirección de correo electrónico es desechable. Por favor, proporcione otra dirección de recuperación.", "The email address is not deliverable. Please provide another recovery address.": "La dirección de correo electrónico no se puede entregar. Por favor, proporcione otra dirección de recuperación.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "¡Murena Workspace estará de vuelta al 100% pronto! Por favor lee esta guía para saber más.", - "This email address in invalid, please use another one.":"Esta dirección de correo electrónico no es válida, por favor utiliza otra." + "This email address in invalid, please use another one.":"Esta dirección de correo electrónico no es válida, por favor utiliza otra.", + "20250610_account_recovery_warning_subject": "Acción necesaria: Configure su correo electrónico de recuperación antes de eliminar la cuenta", + "20250610_account_recovery_warning_body": "Estimado **{username}**,\n\nPor motivos de seguridad, debe establecer una dirección de correo electrónico de recuperación para su cuenta de Murena Cloud. Como aún no lo ha hecho, el uso de su dirección de correo electrónico está restringido. Configure y valide una dirección de correo electrónico de recuperación para eliminar todas las restricciones de uso de su dirección de correo electrónico. Si no lo hace, su cuenta se desactivará el **{deletion_date}** y se eliminará.\n\n[ESTABLECER MI DIRECCIÓN DE CORREO ELECTRÓNICO DE RECUPERACIÓN](https://murena.io/settings/user/security#recovery-email-form)\n\nSaludos cordiales,\nEl equipo Murena" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/fr.json b/l10n/fr.json index ccb6999..886c9a0 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -37,6 +37,8 @@ "The email address is disposable. Please provide another recovery address." : "L'adresse électronique est jetable. Veuillez fournir une autre adresse de récupération.", "The email address is not deliverable. Please provide another recovery address.": "L'adresse électronique ne peut être délivrée. Veuillez fournir une autre adresse de recouvrement.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace sera bientôt complètement de retour ! Veuillez lire ce guide pour en savoir plus.", - "This email address in invalid, please use another one.":"Cette adresse e-mail est invalide, veuillez en utiliser une autre." + "This email address in invalid, please use another one.":"Cette adresse e-mail est invalide, veuillez en utiliser une autre.", + "20250610_account_recovery_warning_subject": "Action requise : Configurez votre e-mail de récupération avant la suppression de votre compte", + "20250610_account_recovery_warning_body": "Cher **{username}**,\n\nPour des raisons de sécurité, vous devez définir une adresse électronique de récupération pour votre compte Murena Cloud. Comme vous ne l'avez pas encore fait, l'utilisation de votre adresse e-mail est restreinte. Veuillez définir et valider une adresse e-mail de récupération afin de supprimer toute restriction d'utilisation de votre adresse e-mail. Si vous ne le faites pas, votre compte sera désactivé le **{deletion_date}** puis supprimé.\n\n[DÉFINIR MON ADRESSE E-MAIL DE RÉCUPÉRATION](https://murena.io/settings/user/security#recovery-email-form)\n\nMeilleures salutations,\nL'équipe Murena" },"pluralForm" :"nplurals=2; plural=n > 1;" } \ No newline at end of file diff --git a/l10n/it.json b/l10n/it.json index 21e38a0..8511f70 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -37,6 +37,8 @@ "The email address is disposable. Please provide another recovery address." : "L'indirizzo e-mail è monouso. Si prega di fornire un altro indirizzo di recupero.", "The email address is not deliverable. Please provide another recovery address.": "The email address is not deliverable. Please provide another recovery address.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace tornerà presto completamente operativo! Si prega di leggere questa guida per saperne di più.", - "This email address in invalid, please use another one.":"Questo indirizzo email non è valido, per favore usane un altro." + "This email address in invalid, please use another one.":"Questo indirizzo email non è valido, per favore usane un altro.", + "20250610_account_recovery_warning_subject": "Azione necessaria: Impostare l'e-mail di recupero prima dell'eliminazione dell'account.", + "20250610_account_recovery_warning_body": "Caro **{username}**,\n\nPer motivi di sicurezza è necessario impostare un indirizzo e-mail di recupero per il proprio account Murena Cloud. Poiché non l'avete ancora fatto, l'uso del vostro indirizzo e-mail è limitato. Si prega di impostare e convalidare un indirizzo e-mail di ripristino per rimuovere tutte le restrizioni all'uso del proprio indirizzo e-mail. In caso contrario, l'account verrà disattivato alla **{deletion_date}** e quindi cancellato.\n\n[IMPOSTARE L'INDIRIZZO E-MAIL DI RECUPERO](https://murena.io/settings/user/security#recovery-email-form)\n\nCordiali saluti,\nIl team Murena" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php new file mode 100644 index 0000000..464fe45 --- /dev/null +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -0,0 +1,317 @@ + + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\EmailRecovery\BackgroundJob; + +use OCA\EmailRecovery\AppInfo\Application; +use OCA\EmailRecovery\Service\NotificationService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Mail\IEMailTemplate; +use OCP\Mail\IMailer; +use OCP\Notification\IManager as INotificationManager; +use OCP\Notification\INotification; +use Psr\Log\LoggerInterface; + +class DailyRecoveryWarningNotificationJob extends TimedJob { + private IConfig $config; + private IMailer $mailer; + private LoggerInterface $logger; + private IUserManager $userManager; + private NotificationService $notificationService; + private $uids = []; + private ITimeFactory $timeFactory; + private INotificationManager $notificationManager; + private IURLGenerator $urlGenerator; + + public function __construct( + IConfig $config, + IMailer $mailer, + IUserManager $userManager, + LoggerInterface $logger, + NotificationService $notificationService, + ITimeFactory $timeFactory, + INotificationManager $notificationManager, + IURLGenerator $urlGenerator + ) { + parent::__construct($timeFactory); + + // $this->setInterval(2 * 60); // Run every 2 minutes + $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days + $this->setTimeSensitivity(self::TIME_INSENSITIVE); + + $this->config = $config; + $this->mailer = $mailer; + $this->logger = $logger; + $this->userManager = $userManager; + $this->notificationService = $notificationService; + $this->timeFactory = $timeFactory; + $this->notificationManager = $notificationManager; + $this->urlGenerator = $urlGenerator; + } + + protected function run($argument): void { + try { + $this->prepareValidUserIds(); + $messageId = $this->config->getSystemValue('account_recovery_warning_subject_messageid'); // 20250610_account_recovery_warning + $this->sendCloudNotifications($messageId); + $this->sendEmails($messageId); + $this->disableUsersWithoutRecoveryEmail(); + $this->deleteUsersAfterGracePeriod(); + } catch (\Exception $e) { + $this->logger->error('Error sending recovery warning notification and emails to users', ['exception' => $e]); + return; + } + } + + /** + * Prepares valid user IDs and stores them in the 'uids' array. + * @return void + */ + private function prepareValidUserIds(): void { + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + foreach ($users as $username) { + $user = $this->userManager->get($username); + if ($this->isUserValid($user)) { + $uid = $user->getUID(); + $age = $this->getAccountAgeDays($uid); + if (in_array($age, [15, 22, 26, 29])) { + $this->uids[] = $uid; + } + } + } + } + /** + * Method getAccountAgeDays + * + * @param string $uid [explicite description] + * + * @return int + */ + private function getAccountAgeDays(string $uid): int + { + $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); + if (!$createdAt) { + return -1; // Invalid or missing + } + return (int)(($this->timeFactory->getTime() - strtotime($createdAt)) / 86400); + } + /** + * Method disableUsersWithoutRecoveryEmail + * + * @return void + */ + private function disableUsersWithoutRecoveryEmail(): void + { + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + foreach ($users as $username) { + $user = $this->userManager->get($username); + if (!$this->isUserValid($user)) { + continue; + } + $uid = $user->getUID(); + $age = $this->getAccountAgeDays($uid); + if ($age === 30) { + // Disable account + $user->setEnabled(false); + $this->config->setUserValue($uid, Application::APP_ID, 'disabled_at', (string)$this->timeFactory->getTime()); + $this->logger->info("User $uid disabled due to missing recovery email at 30 days."); + } + } + } + /** + * Method deleteUsersAfterGracePeriod + * + * @return void + */ + private function deleteUsersAfterGracePeriod(): void + { + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'disabled_at', ''); + foreach ($users as $uid) { + $disabledAt = (int)$this->config->getUserValue($uid, Application::APP_ID, 'disabled_at', '0'); + if (($this->timeFactory->getTime() - $disabledAt) > 15 * 86400) { // remove account after 15 days since disable + $user = $this->userManager->get($uid); + if ($user) { + $user->delete(); + $this->logger->info("User $uid permanently deleted after grace period."); + } + } + } + } + /** + * Send cloud notification to users + * + * @return void + */ + private function sendCloudNotifications(string $messageId): void { + try { + $datetime = $this->timeFactory->getDateTime(); + $notification = $this->notificationManager->createNotification(); + $notificationType = 'important'; + $notification->setApp(Application::APP_ID) + ->setDateTime($datetime) + ->setObject(Application::APP_ID . '-' . strtolower($notificationType), $messageId) + ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath(Application::APP_ID, strtolower($notificationType) . '.svg'))); + + $this->sendNotificationToUsers($messageId, $notification, $this->uids); + } catch (\Exception $e) { + $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + } + } + /** + * Sends a notification with the specified messageId and notification object to a list of users. + * + * @param string $messageId The identifier for the notification message. + * @param INotification $notification The notification object to be sent. + * @param array $users An array of usernames to whom the notification will be sent. + * + * @return void + */ + protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { + foreach ($users as $username) { + try { + $user = $this->userManager->get($username); + $this->sendNotificationToUser($messageId, $user, $notification); + } catch (\Exception $e) { + $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + } + } + } + /** + * Sends a notification with the specified messageId and notification object to a user. + * + * @param string $messageId The identifier for the notification message. + * @param IUser $user The user to whom the notification will be sent. + * @param INotification $notification The notification object to be sent. + * + * @return void + */ + protected function sendNotificationToUser(string $messageId, IUser $user, INotification $notification): void { + $uid = $user->getUID(); + $displayName = $user->getDisplayName(); + try { + $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); + $this->notificationManager->notify($notification); + $this->logger->debug('Notificaiton sent to ' . $uid . ' successfully.'); + } catch (\Exception $e) { + $this->logger->error('Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + } + } + /** + * Sends recovery emails to all users. + * + * @param string $messageId The identifier for the notification message. + * + * @return void + */ + private function sendEmails(string $messageId): void { + foreach ($this->uids as $uid) { + try { + $user = $this->userManager->get($uid); + $username = $user->getDisplayName(); + $emailAddress = $user->getEMailAddress(); + + $language = $this->config->getUserValue($uid, 'core', 'lang', null); + $translations = $this->notificationService->getTranslatedSubjectAndMessage($messageId, $language); + $subject = $translations['subject']; + $message = $translations['message']; + + $parsedSubject = $this->notificationService->getParsedString($subject, $username); + $subject = $parsedSubject['message']; + $parsedMessage = $this->notificationService->getParsedString($message, $username); + $message = $parsedMessage['message']; + + $this->sendEmail($subject, $message, $emailAddress); + $this->logger->debug('Recovery email sent to ' . $emailAddress . ' successfully.'); + } catch (\Exception $e) { + $this->logger->error('Error sending notification email to user ' . $uid.'. Error:' . $e->getMessage(), ['exception' => $e]); + } + } + } + + /** + * Send an email. + * + * @param string $subject The subject of the email. + * @param string $message The body/content of the email. + * @param string $emailAddress The recipient's email address. + * + * @return void + */ + private function sendEmail(string $subject, string $message, string $emailAddress): void { + // Convert Markdown-style links to HTML anchor tags + $message = preg_replace('/\[(.*?)\]\((.*?)\)/', "$1", $message); + $template = $this->mailer->createEMailTemplate(Application::APP_ID . '::sendMail'); + $template->setSubject($subject); + $template->addHeader(); + $template->addHeading($subject); + $this->setMailBody($template, $message); + $template->addFooter(); + + $email = $this->mailer->createMessage(); + $email->useTemplate($template); + $email->setTo([$emailAddress]); + + $this->mailer->send($email); + } + /** + * Special-treat list items and strip empty lines + * + * @param IEMailTemplate $template + * @param string $message + * + * @return void + */ + private function setMailBody(IEMailTemplate $template, string $message): void { + $lines = explode("\n", $message); + $finalHtml = ""; + $finalText = ""; + foreach ($lines as $line) { + if (trim($line) === '') { + continue; + } + $finalHtml .= "

" . $line . "

"; + $finalText .= $line; + } + $template->addBodyText($finalHtml, $finalText); + } + /** + * Validate a user. + * + * @param IUser|null $user The user to be validated. + * + * @return bool Returns true if the user is valid, false otherwise. + */ + private function isUserValid(?IUser $user): bool { + if (!($user instanceof IUser)) { + return false; + } + $emailAddress = $user->getEMailAddress(); + return ($emailAddress && $user->isEnabled() && $this->mailer->validateMailAddress($emailAddress)); + } +} -- GitLab From d707263fc90a25e278de13829d01894ca379841f Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 9 Jun 2025 19:48:03 +0530 Subject: [PATCH 02/58] getCreatedAt changes --- .../DailyRecoveryWarningNotificationJob.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 464fe45..f5a378e 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -37,6 +37,7 @@ use OCP\Mail\IMailer; use OCP\Notification\IManager as INotificationManager; use OCP\Notification\INotification; use Psr\Log\LoggerInterface; +use OCA\EcloudAccounts\Service\LDAPConnectionService; class DailyRecoveryWarningNotificationJob extends TimedJob { private IConfig $config; @@ -48,6 +49,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private ITimeFactory $timeFactory; private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; + private LDAPConnectionService $LDAPConnectionService; public function __construct( IConfig $config, @@ -57,7 +59,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { NotificationService $notificationService, ITimeFactory $timeFactory, INotificationManager $notificationManager, - IURLGenerator $urlGenerator + IURLGenerator $urlGenerator, + LDAPConnectionService $LDAPConnectionService ) { parent::__construct($timeFactory); @@ -73,6 +76,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->timeFactory = $timeFactory; $this->notificationManager = $notificationManager; $this->urlGenerator = $urlGenerator; + $this->LDAPConnectionService = $LDAPConnectionService; } protected function run($argument): void { @@ -115,7 +119,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { */ private function getAccountAgeDays(string $uid): int { - $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); + // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); + $createdAt = $this->getCreatedAt($uid); if (!$createdAt) { return -1; // Invalid or missing } @@ -314,4 +319,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $emailAddress = $user->getEMailAddress(); return ($emailAddress && $user->isEnabled() && $this->mailer->validateMailAddress($emailAddress)); } + + private function getCreatedAt($username): string { + return $this->LDAPConnectionService->getCreateTimestamp($username); + } + } -- GitLab From c0d11537489cacaa9b24f2d7261fca33502b7e9c Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 9 Jun 2025 23:49:48 +0530 Subject: [PATCH 03/58] background job commented temp --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index caa1517..760e268 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -19,7 +19,7 @@ OCA\EmailRecovery\BackgroundJob\WeeklyRecoveryNotificationJob - OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob + OCA\EmailRecovery\Command\UpdateBlacklistedDomains -- GitLab From d737309bae078c3fa844646587d317329a723ec1 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 9 Jun 2025 23:59:59 +0530 Subject: [PATCH 04/58] PHP FIXER --- appinfo/info.xml | 1 - .../DailyRecoveryWarningNotificationJob.php | 14 +++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 760e268..d839897 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -19,7 +19,6 @@ OCA\EmailRecovery\BackgroundJob\WeeklyRecoveryNotificationJob - OCA\EmailRecovery\Command\UpdateBlacklistedDomains diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index f5a378e..bc98cfc 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -109,7 +109,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { } } } - } + } /** * Method getAccountAgeDays * @@ -117,22 +117,20 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * * @return int */ - private function getAccountAgeDays(string $uid): int - { + private function getAccountAgeDays(string $uid): int { // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); $createdAt = $this->getCreatedAt($uid); if (!$createdAt) { return -1; // Invalid or missing } return (int)(($this->timeFactory->getTime() - strtotime($createdAt)) / 86400); - } + } /** * Method disableUsersWithoutRecoveryEmail * * @return void */ - private function disableUsersWithoutRecoveryEmail(): void - { + private function disableUsersWithoutRecoveryEmail(): void { $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); foreach ($users as $username) { $user = $this->userManager->get($username); @@ -154,8 +152,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * * @return void */ - private function deleteUsersAfterGracePeriod(): void - { + private function deleteUsersAfterGracePeriod(): void { $users = $this->config->getUsersForUserValue(Application::APP_ID, 'disabled_at', ''); foreach ($users as $uid) { $disabledAt = (int)$this->config->getUserValue($uid, Application::APP_ID, 'disabled_at', '0'); @@ -323,5 +320,4 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private function getCreatedAt($username): string { return $this->LDAPConnectionService->getCreateTimestamp($username); } - } -- GitLab From f2a5071cf3f765c26058ba1a278886246a62ae62 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 10:26:48 +0530 Subject: [PATCH 05/58] background job updated at info.xml --- appinfo/info.xml | 1 + lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index d839897..52d80fe 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -18,6 +18,7 @@ OCA\EmailRecovery\Settings\RecoveryEmailSettings + OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob OCA\EmailRecovery\BackgroundJob\WeeklyRecoveryNotificationJob diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index bc98cfc..b4686b2 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -82,7 +82,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { protected function run($argument): void { try { $this->prepareValidUserIds(); - $messageId = $this->config->getSystemValue('account_recovery_warning_subject_messageid'); // 20250610_account_recovery_warning + $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUsersWithoutRecoveryEmail(); @@ -104,6 +104,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($this->isUserValid($user)) { $uid = $user->getUID(); $age = $this->getAccountAgeDays($uid); + $this->logger->info("Ager $age for ".$uid); if (in_array($age, [15, 22, 26, 29])) { $this->uids[] = $uid; } -- GitLab From 50eaf583a2b1f461285e6978001494f26d5e2c6c Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 10:28:40 +0530 Subject: [PATCH 06/58] Commented some code temporariliy --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index b4686b2..d196613 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -84,9 +84,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->prepareValidUserIds(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning $this->sendCloudNotifications($messageId); - $this->sendEmails($messageId); - $this->disableUsersWithoutRecoveryEmail(); - $this->deleteUsersAfterGracePeriod(); + // $this->sendEmails($messageId); + // $this->disableUsersWithoutRecoveryEmail(); + // $this->deleteUsersAfterGracePeriod(); } catch (\Exception $e) { $this->logger->error('Error sending recovery warning notification and emails to users', ['exception' => $e]); return; -- GitLab From 05140d61dbe36c4fa94e227a6313a20b3104bba2 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 10:58:51 +0530 Subject: [PATCH 07/58] Added more logs for debug --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index d196613..aa7be81 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -81,6 +81,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { protected function run($argument): void { try { + $this->logger->error('DailyRecoveryWarningNotificationJob called.', ['exception' => '']); $this->prepareValidUserIds(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning $this->sendCloudNotifications($messageId); @@ -98,6 +99,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function prepareValidUserIds(): void { + $this->logger->error('DailyRecoveryWarningNotificationJob prepareValidUserIds called.', ['exception' => '']); $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); foreach ($users as $username) { $user = $this->userManager->get($username); @@ -119,8 +121,10 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return int */ private function getAccountAgeDays(string $uid): int { + $this->logger->error('DailyRecoveryWarningNotificationJob getAccountAgeDays called.', ['exception' => '']); // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); $createdAt = $this->getCreatedAt($uid); + $this->logger->error('DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); if (!$createdAt) { return -1; // Invalid or missing } @@ -319,6 +323,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { } private function getCreatedAt($username): string { + $this->logger->error('DailyRecoveryWarningNotificationJob getCreatedAt called.', ['exception' => '']); return $this->LDAPConnectionService->getCreateTimestamp($username); } } -- GitLab From d571e0fcb3f75b12bacea6f96bd048ed912b6ed6 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 11:08:10 +0530 Subject: [PATCH 08/58] more loggers added --- .../DailyRecoveryWarningNotificationJob.php | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index aa7be81..60e44f2 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -84,12 +84,14 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->logger->error('DailyRecoveryWarningNotificationJob called.', ['exception' => '']); $this->prepareValidUserIds(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning + $this->logger->error('DailyRecoveryWarningNotificationJob: messageId: '.$messageId, ['exception' => '']); + $this->sendCloudNotifications($messageId); // $this->sendEmails($messageId); // $this->disableUsersWithoutRecoveryEmail(); // $this->deleteUsersAfterGracePeriod(); } catch (\Exception $e) { - $this->logger->error('Error sending recovery warning notification and emails to users', ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery warning notification and emails to users', ['exception' => $e]); return; } } @@ -106,7 +108,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($this->isUserValid($user)) { $uid = $user->getUID(); $age = $this->getAccountAgeDays($uid); - $this->logger->info("Ager $age for ".$uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age for ".$uid); if (in_array($age, [15, 22, 26, 29])) { $this->uids[] = $uid; } @@ -148,7 +150,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { // Disable account $user->setEnabled(false); $this->config->setUserValue($uid, Application::APP_ID, 'disabled_at', (string)$this->timeFactory->getTime()); - $this->logger->info("User $uid disabled due to missing recovery email at 30 days."); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); } } } @@ -165,7 +167,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($uid); if ($user) { $user->delete(); - $this->logger->info("User $uid permanently deleted after grace period."); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted after grace period."); } } } @@ -187,7 +189,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->sendNotificationToUsers($messageId, $notification, $this->uids); } catch (\Exception $e) { - $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -205,7 +207,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($username); $this->sendNotificationToUser($messageId, $user, $notification); } catch (\Exception $e) { - $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -224,9 +226,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); $this->notificationManager->notify($notification); - $this->logger->debug('Notificaiton sent to ' . $uid . ' successfully.'); + $this->logger->debug('DailyRecoveryWarningNotificationJob: Notificaiton sent to ' . $uid . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -254,9 +256,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); - $this->logger->debug('Recovery email sent to ' . $emailAddress . ' successfully.'); + $this->logger->debug('DailyRecoveryWarningNotificationJob: Recovery email sent to ' . $emailAddress . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('Error sending notification email to user ' . $uid.'. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending notification email to user ' . $uid.'. Error:' . $e->getMessage(), ['exception' => $e]); } } } -- GitLab From 8dcce8645dc9ca2bae71bd1a8a83c4439e49c476 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 12:50:00 +0530 Subject: [PATCH 09/58] deletion_date dynamically add --- .../DailyRecoveryWarningNotificationJob.php | 23 +++++++++---------- lib/Notification/Notifier.php | 13 ++++++++--- lib/Service/NotificationService.php | 16 +++++++++++-- lib/Service/RecoveryEmailService.php | 23 ++++++++++++++++++- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 60e44f2..9dc4ad1 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -37,7 +37,7 @@ use OCP\Mail\IMailer; use OCP\Notification\IManager as INotificationManager; use OCP\Notification\INotification; use Psr\Log\LoggerInterface; -use OCA\EcloudAccounts\Service\LDAPConnectionService; +use OCA\EmailRecovery\Service\RecoveryEmailService; class DailyRecoveryWarningNotificationJob extends TimedJob { private IConfig $config; @@ -49,7 +49,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private ITimeFactory $timeFactory; private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; - private LDAPConnectionService $LDAPConnectionService; + private RecoveryEmailService $recoveryEmailService; public function __construct( IConfig $config, @@ -60,7 +60,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { ITimeFactory $timeFactory, INotificationManager $notificationManager, IURLGenerator $urlGenerator, - LDAPConnectionService $LDAPConnectionService + RecoveryEmailService $recoveryEmailService ) { parent::__construct($timeFactory); @@ -76,7 +76,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->timeFactory = $timeFactory; $this->notificationManager = $notificationManager; $this->urlGenerator = $urlGenerator; - $this->LDAPConnectionService = $LDAPConnectionService; + $this->recoveryEmailService = $recoveryEmailService; } protected function run($argument): void { @@ -111,6 +111,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age for ".$uid); if (in_array($age, [15, 22, 26, 29])) { $this->uids[] = $uid; + } elseif ($age > 29) { + $this->uids[] = $uid; } } } @@ -125,8 +127,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private function getAccountAgeDays(string $uid): int { $this->logger->error('DailyRecoveryWarningNotificationJob getAccountAgeDays called.', ['exception' => '']); // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); - $createdAt = $this->getCreatedAt($uid); - $this->logger->error('DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); + $createdAt = $this->recoveryEmailService->getCreatedAt($uid); + $this->logger->error(message: 'DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); if (!$createdAt) { return -1; // Invalid or missing } @@ -252,7 +254,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $parsedSubject = $this->notificationService->getParsedString($subject, $username); $subject = $parsedSubject['message']; - $parsedMessage = $this->notificationService->getParsedString($message, $username); + + $delection_date = $this->recoveryEmailService->getDeletionDate($username); + $parsedMessage = $this->notificationService->getParsedString($message, $username, $delection_date); $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); @@ -323,9 +327,4 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $emailAddress = $user->getEMailAddress(); return ($emailAddress && $user->isEnabled() && $this->mailer->validateMailAddress($emailAddress)); } - - private function getCreatedAt($username): string { - $this->logger->error('DailyRecoveryWarningNotificationJob getCreatedAt called.', ['exception' => '']); - return $this->LDAPConnectionService->getCreateTimestamp($username); - } } diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index 15acaec..a47176a 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -31,6 +31,7 @@ use OCP\L10N\IFactory; use OCP\Notification\INotification; use OCP\Notification\INotifier; use Psr\Log\LoggerInterface; +use OCA\EmailRecovery\Service\RecoveryEmailService; class Notifier implements INotifier { /** @var IFactory */ @@ -41,14 +42,19 @@ class Notifier implements INotifier { /** @var NotificationService */ private $NotificationService; + /** @var RecoveryEmailService */ + private $RecoveryEmailService; + public function __construct( IFactory $l10nFactory, LoggerInterface $logger, - NotificationService $NotificationService + NotificationService $NotificationService, + RecoveryEmailService $RecoveryEmailService ) { $this->l10nFactory = $l10nFactory; $this->logger = $logger; $this->NotificationService = $NotificationService; + $this->RecoveryEmailService = $RecoveryEmailService; } /** @@ -95,8 +101,9 @@ class Notifier implements INotifier { $subjectParameters = $parsedSubject['parameters']; $plainSubject = $parsedSubject['message']; $richSubject = $parsedSubject['richString']; - - $parsedMessage = $this->NotificationService->getParsedString($message, $username); + + $delection_date = $this->RecoveryEmailService->getDeletionDate($username); + $parsedMessage = $this->NotificationService->getParsedString($message, $username, $delection_date); $messageParameters = $parsedMessage['parameters']; $plainMessage = $parsedMessage['message']; $richMessage = $parsedMessage['richString']; diff --git a/lib/Service/NotificationService.php b/lib/Service/NotificationService.php index 376df29..887117d 100644 --- a/lib/Service/NotificationService.php +++ b/lib/Service/NotificationService.php @@ -19,7 +19,7 @@ class NotificationService { * @param string $message * @param string $username */ - public function getParsedString(string $message, string $username) { + public function getParsedString(string $message, string $username, string $deletion_date = '') { $richString = $message; $data = $this->prepareRichString($message, $richString, $username, 'url'); @@ -33,6 +33,13 @@ class NotificationService { $data = $this->prepareRichString($message, $richString, $username, 'bold'); $message = $data['message']; $richString = $data['richString']; + + if ($deletion_date != '') { + $data = $this->prepareRichString($message, $richString, $username, 'deletion_date', $deletion_date); + $message = $data['message']; + $richString = $data['richString']; + } + return $this->assignVariables($message, $richString, $username); } @@ -42,7 +49,7 @@ class NotificationService { * @param string $username * @param string $type */ - private function prepareRichString($message, $richString, $username, $type) { + private function prepareRichString($message, $richString, $username, $type, $deletion_date = '') { switch ($type) { case 'url': $richString = preg_replace('/\[(.*?)\]\((.*?)\)/', '{$1[$2]}', $message); @@ -62,6 +69,11 @@ class NotificationService { $richString = str_replace('{username}', $username, $richString); $message = str_replace('{username}', $username, $message); break; + + case 'deletion_date': + $richString = str_replace('{deletion_date}', $deletion_date, $richString); + $message = str_replace('{deletion_date}', $deletion_date, $message); + break; } return ['message' => $message, 'richString' => $richString]; } diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index d4b7c7f..bce4333 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -27,6 +27,7 @@ use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Security\VerificationToken\IVerificationToken; use OCP\Util; +use OCA\EcloudAccounts\Service\LDAPConnectionService; class RecoveryEmailService { private ILogger $logger; @@ -54,8 +55,9 @@ class RecoveryEmailService { private DomainService $domainService; private IL10N $l; private ISession $session; + private LDAPConnectionService $LDAPConnectionService; - public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper) { + public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper, LDAPConnectionService $LDAPConnectionService) { $this->logger = $logger; $this->config = $config; $this->appName = $appName; @@ -70,6 +72,7 @@ class RecoveryEmailService { $this->domainService = $domainService; $this->httpClientService = $httpClientService; $this->l = $l; + $this->LDAPConnectionService = $LDAPConnectionService; $this->cacheFactory = $cacheFactory; // Initialize the cache factory $this->cache = $this->cacheFactory->createDistributed(self::CACHE_KEY); // Initialize the cache $this->configMapper = $configMapper; @@ -560,4 +563,22 @@ class RecoveryEmailService { $requests[] = $now; $this->cache->set($key, $requests, 2); } + public function getCreatedAt($username): string { + $this->logger->error('DailyRecoveryWarningNotificationJob getCreatedAt called.', ['exception' => '']); + return $this->LDAPConnectionService->getCreateTimestamp($username); + } + + public function getDeletionDate(string $username): string { + try { + // Fetch LDAP timestamp (e.g., 20220817084557Z) + $date = $this->getCreatedAt($username); + if (!$date) { + throw new \Exception("No date found."); + } + + return date('Y-m-d H:i:s', strtotime($date . ' +30 days')); + } catch (\Exception $e) { + throw new \Exception("Error getting deletion date"); + } + } } -- GitLab From 38ffb04e010b7c339b3041480fcd4b3c20297863 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 16:15:31 +0530 Subject: [PATCH 10/58] run every 2 minutes for testing --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 9dc4ad1..b350353 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -64,8 +64,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { ) { parent::__construct($timeFactory); - // $this->setInterval(2 * 60); // Run every 2 minutes - $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days + $this->setInterval(2 * 60); // Run every 2 minutes + // $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days $this->setTimeSensitivity(self::TIME_INSENSITIVE); $this->config = $config; @@ -128,7 +128,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->logger->error('DailyRecoveryWarningNotificationJob getAccountAgeDays called.', ['exception' => '']); // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); $createdAt = $this->recoveryEmailService->getCreatedAt($uid); - $this->logger->error(message: 'DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); + $this->logger->error( 'DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); if (!$createdAt) { return -1; // Invalid or missing } -- GitLab From 993aaa7c6dbaae6eb43da15b5f31ec3616791653 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 16:19:49 +0530 Subject: [PATCH 11/58] Background job: php fixer --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index b350353..203b0c9 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -128,7 +128,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->logger->error('DailyRecoveryWarningNotificationJob getAccountAgeDays called.', ['exception' => '']); // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); $createdAt = $this->recoveryEmailService->getCreatedAt($uid); - $this->logger->error( 'DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); + $this->logger->error('DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); if (!$createdAt) { return -1; // Invalid or missing } -- GitLab From d9c285788daed63886a91537b08154611181ffe8 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 11 Jun 2025 16:23:12 +0530 Subject: [PATCH 12/58] Exception added and changed date format to YMD --- lib/Service/RecoveryEmailService.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index bce4333..76b2211 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -263,7 +263,7 @@ class RecoveryEmailService { $this->logger->info("User ID $username's requested recovery email address is not deliverable."); throw new BlacklistedEmailException($l->t('The email address is not deliverable. Please provide another recovery address.')); } - } catch (\Exception $e) { + } catch (Exception $e) { // Optionally handle specific exceptions if needed here (e.g., timeouts, network errors) $this->logger->error("Error while validating email for user $username: " . $e->getMessage()); throw $e; // Re-throw if necessary @@ -407,7 +407,7 @@ class RecoveryEmailService { $email->setTo([$recoveryEmailAddress]); $email->setFrom([Util::getDefaultEmailAddress('no-reply') => $this->themingDefaults->getName()]); $this->mailer->send($email); - } catch (\Exception $e) { + } catch (Exception $e) { $this->logger->error('Error sending notification email to user ' . $uid, ['exception' => $e]); } } @@ -573,12 +573,12 @@ class RecoveryEmailService { // Fetch LDAP timestamp (e.g., 20220817084557Z) $date = $this->getCreatedAt($username); if (!$date) { - throw new \Exception("No date found."); + throw new Exception("No date found."); } - return date('Y-m-d H:i:s', strtotime($date . ' +30 days')); - } catch (\Exception $e) { - throw new \Exception("Error getting deletion date"); + return date('Y-m-d', strtotime($date . ' +30 days')); + } catch (Exception $e) { + throw new Exception("Error getting deletion date"); } } } -- GitLab From 51b55174db6a7745a89ea1a0326270ccd73ac5fa Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 17:56:08 +0530 Subject: [PATCH 13/58] Added config values to trace disabled and deletion date --- .../DailyRecoveryWarningNotificationJob.php | 70 ++++++++----------- lib/Service/RecoveryEmailService.php | 27 +++++-- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 203b0c9..184f99f 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -4,7 +4,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * - * @author Joas Schilling + * @author Ronak Patel * * @license AGPL-3.0 * @@ -50,6 +50,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; + private $now; + private const DISABLE_AFTER_DAYS = 30; + private const DELETE_AFTER_DAYS = 15; public function __construct( IConfig $config, @@ -77,6 +80,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->notificationManager = $notificationManager; $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; + $this->now = $this->timeFactory->getTime(); // string time } protected function run($argument): void { @@ -87,7 +91,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->logger->error('DailyRecoveryWarningNotificationJob: messageId: '.$messageId, ['exception' => '']); $this->sendCloudNotifications($messageId); - // $this->sendEmails($messageId); + $this->sendEmails($messageId); // $this->disableUsersWithoutRecoveryEmail(); // $this->deleteUsersAfterGracePeriod(); } catch (\Exception $e) { @@ -103,55 +107,38 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private function prepareValidUserIds(): void { $this->logger->error('DailyRecoveryWarningNotificationJob prepareValidUserIds called.', ['exception' => '']); $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + foreach ($users as $username) { $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); - $age = $this->getAccountAgeDays($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age for ".$uid); - if (in_array($age, [15, 22, 26, 29])) { - $this->uids[] = $uid; - } elseif ($age > 29) { + + $disableDate = $this->recoveryEmailService->getDeletionDate($uid); + + $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); + $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age (from disable-unverified-users-date) for " . $uid); + + if (in_array($age, [15, 22, 26, 29]) || $age > 29) { $this->uids[] = $uid; } } } } - /** - * Method getAccountAgeDays - * - * @param string $uid [explicite description] - * - * @return int - */ - private function getAccountAgeDays(string $uid): int { - $this->logger->error('DailyRecoveryWarningNotificationJob getAccountAgeDays called.', ['exception' => '']); - // $createdAt = $this->config->getUserValue($uid, 'core', 'created_at', null); - $createdAt = $this->recoveryEmailService->getCreatedAt($uid); - $this->logger->error('DailyRecoveryWarningNotificationJob createdAt found.'.$createdAt, ['exception' => '']); - if (!$createdAt) { - return -1; // Invalid or missing - } - return (int)(($this->timeFactory->getTime() - strtotime($createdAt)) / 86400); - } /** * Method disableUsersWithoutRecoveryEmail * * @return void */ private function disableUsersWithoutRecoveryEmail(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); - foreach ($users as $username) { + foreach ($this->uids as $username) { $user = $this->userManager->get($username); - if (!$this->isUserValid($user)) { - continue; - } $uid = $user->getUID(); - $age = $this->getAccountAgeDays($uid); - if ($age === 30) { + $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); + $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); + if ($age === self::DISABLE_AFTER_DAYS) { // Disable account $user->setEnabled(false); - $this->config->setUserValue($uid, Application::APP_ID, 'disabled_at', (string)$this->timeFactory->getTime()); + $this->config->setUserValue($uid, Application::APP_ID, 'disabled-at', date('Y-m-d', $this->now)); $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); } } @@ -162,14 +149,19 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function deleteUsersAfterGracePeriod(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'disabled_at', ''); + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'disabled-at', ''); foreach ($users as $uid) { - $disabledAt = (int)$this->config->getUserValue($uid, Application::APP_ID, 'disabled_at', '0'); - if (($this->timeFactory->getTime() - $disabledAt) > 15 * 86400) { // remove account after 15 days since disable - $user = $this->userManager->get($uid); - if ($user) { - $user->delete(); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted after grace period."); + $disabledAt = $this->config->getUserValue($uid, Application::APP_ID, 'disabled-at', null); + if ($disabledAt && strtotime($disabledAt) !== false) { + $disabledAtTs = strtotime($disabledAt); + if (($this->now - $disabledAtTs) > self::DELETE_AFTER_DAYS * 86400) { + $user = $this->userManager->get($uid); + if ($user) { + $user->delete(); + $this->config->deleteUserValue($uid, Application::APP_ID, 'disable-unverified-users-date'); + $this->config->deleteUserValue($uid, Application::APP_ID, 'disabled-at'); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted after grace period."); + } } } } diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 76b2211..ce1aed0 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -563,15 +563,19 @@ class RecoveryEmailService { $requests[] = $now; $this->cache->set($key, $requests, 2); } - public function getCreatedAt($username): string { - $this->logger->error('DailyRecoveryWarningNotificationJob getCreatedAt called.', ['exception' => '']); - return $this->LDAPConnectionService->getCreateTimestamp($username); + public function getUnverifiedUserDate(string $uid): string { + $disableDate = $this->config->getUserValue($uid, $this->appName, 'disable-unverified-users-date', null); + if ($disableDate === null) { + $disableDate = date('Y-m-d'); + $this->config->setUserValue($uid, $this->appName, 'disable-unverified-users-date', $disableDate); + } + return $disableDate; } public function getDeletionDate(string $username): string { try { // Fetch LDAP timestamp (e.g., 20220817084557Z) - $date = $this->getCreatedAt($username); + $date = $this->getUnverifiedUserDate($username); if (!$date) { throw new Exception("No date found."); } @@ -581,4 +585,19 @@ class RecoveryEmailService { throw new Exception("Error getting deletion date"); } } + + public function getAccountAgeDaysFromDisableDate($disableDate): int { + if (!$disableDate || strtotime($disableDate) === false) { + return -1; + } + + $disableDateDay = strtotime(date('Y-m-d', strtotime($disableDate))); + $today = strtotime(date('Y-m-d')); + + return (int)(($today - $disableDateDay) / 86400); + } + + public function deletedisableUnverifiedUsersDate($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'disable-unverified-users-date'); + } } -- GitLab From 8e48290c2b2c84a31a0f7488bcba645bb995d24e Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 17:57:46 +0530 Subject: [PATCH 14/58] remvoed LDAP which is unncessary --- lib/Service/RecoveryEmailService.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index ce1aed0..312d45c 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -27,7 +27,6 @@ use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Security\VerificationToken\IVerificationToken; use OCP\Util; -use OCA\EcloudAccounts\Service\LDAPConnectionService; class RecoveryEmailService { private ILogger $logger; @@ -55,9 +54,8 @@ class RecoveryEmailService { private DomainService $domainService; private IL10N $l; private ISession $session; - private LDAPConnectionService $LDAPConnectionService; - public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper, LDAPConnectionService $LDAPConnectionService) { + public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper) { $this->logger = $logger; $this->config = $config; $this->appName = $appName; @@ -72,7 +70,6 @@ class RecoveryEmailService { $this->domainService = $domainService; $this->httpClientService = $httpClientService; $this->l = $l; - $this->LDAPConnectionService = $LDAPConnectionService; $this->cacheFactory = $cacheFactory; // Initialize the cache factory $this->cache = $this->cacheFactory->createDistributed(self::CACHE_KEY); // Initialize the cache $this->configMapper = $configMapper; @@ -574,7 +571,6 @@ class RecoveryEmailService { public function getDeletionDate(string $username): string { try { - // Fetch LDAP timestamp (e.g., 20220817084557Z) $date = $this->getUnverifiedUserDate($username); if (!$date) { throw new Exception("No date found."); -- GitLab From 64f63c01ff493bafb6c6a46f45f1cce69c16fae5 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 18:16:54 +0530 Subject: [PATCH 15/58] disabled weekly notification as it will no longer needed --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 52d80fe..ddd2198 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -19,7 +19,7 @@ OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob - OCA\EmailRecovery\BackgroundJob\WeeklyRecoveryNotificationJob + OCA\EmailRecovery\Command\UpdateBlacklistedDomains -- GitLab From 7c54974aafb90d0d77ecfe8c8c440801054ecd10 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 18:23:42 +0530 Subject: [PATCH 16/58] check unverifiedUserDate when preparingValidUserIDs --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 4 ++-- lib/Service/RecoveryEmailService.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 184f99f..79fd640 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -113,10 +113,10 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($this->isUserValid($user)) { $uid = $user->getUID(); - $disableDate = $this->recoveryEmailService->getDeletionDate($uid); + $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); - $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age (from disable-unverified-users-date) for " . $uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($disableDate) (from disable-unverified-users-date) for " . $uid); if (in_array($age, [15, 22, 26, 29]) || $age > 29) { $this->uids[] = $uid; diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 312d45c..3d23b62 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -583,6 +583,7 @@ class RecoveryEmailService { } public function getAccountAgeDaysFromDisableDate($disableDate): int { + $this->logger->info("$disableDate -- ".date('Y-m-d')); if (!$disableDate || strtotime($disableDate) === false) { return -1; } -- GitLab From 400604ee37f0a20e19d498020843302290859f85 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 18:50:31 +0530 Subject: [PATCH 17/58] Created new get-set functions --- .../DailyRecoveryWarningNotificationJob.php | 7 ++++++- lib/Service/RecoveryEmailService.php | 18 +++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 79fd640..c6e9a53 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -114,11 +114,16 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $uid = $user->getUID(); $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: disableDate: $disableDate for " . $uid); + if ($disableDate === null) { + $disableDate = date('Y-m-d', $this->now); + $this->recoveryEmailService->setUnverifiedUserDate($uid, $disableDate); + } $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($disableDate) (from disable-unverified-users-date) for " . $uid); - if (in_array($age, [15, 22, 26, 29]) || $age > 29) { + if (in_array($age, [0, 15, 22, 26, 29]) || $age > 29) { $this->uids[] = $uid; } } diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 3d23b62..32c03ad 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -561,14 +561,14 @@ class RecoveryEmailService { $this->cache->set($key, $requests, 2); } public function getUnverifiedUserDate(string $uid): string { - $disableDate = $this->config->getUserValue($uid, $this->appName, 'disable-unverified-users-date', null); - if ($disableDate === null) { - $disableDate = date('Y-m-d'); - $this->config->setUserValue($uid, $this->appName, 'disable-unverified-users-date', $disableDate); - } - return $disableDate; + return $this->config->getUserValue($uid, $this->appName, 'disable-unverified-users-date', null); + } + public function setUnverifiedUserDate(string $uid, string $disableDate): void { + $this->config->setUserValue($uid, $this->appName, 'disable-unverified-users-date', $disableDate); + } + public function deletedisableUnverifiedUsersDate($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'disable-unverified-users-date'); } - public function getDeletionDate(string $username): string { try { $date = $this->getUnverifiedUserDate($username); @@ -593,8 +593,4 @@ class RecoveryEmailService { return (int)(($today - $disableDateDay) / 86400); } - - public function deletedisableUnverifiedUsersDate($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'disable-unverified-users-date'); - } } -- GitLab From 44e33725d2add0c21e62225495fae503e11a5827 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 23:29:15 +0530 Subject: [PATCH 18/58] renamed some function, added const, added test mode, created more functions --- .../DailyRecoveryWarningNotificationJob.php | 92 ++++++++++++------- lib/Service/RecoveryEmailService.php | 13 ++- 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index c6e9a53..7350334 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -51,8 +51,11 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; private $now; - private const DISABLE_AFTER_DAYS = 30; - private const DELETE_AFTER_DAYS = 15; + private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; + private const DISABLE_AFTER_DAYS = 1; //30 to be + private const DELETE_AFTER_DISABLED_DAYS = 2; // 15 days to be + // at the top of the class: + private ?string $testUsername = null; public function __construct( IConfig $config, @@ -67,8 +70,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { ) { parent::__construct($timeFactory); - $this->setInterval(2 * 60); // Run every 2 minutes - // $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days + // $this->setInterval(2 * 60); // Run every 2 minutes + $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days $this->setTimeSensitivity(self::TIME_INSENSITIVE); $this->config = $config; @@ -81,19 +84,20 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; $this->now = $this->timeFactory->getTime(); // string time + $this->testUsername = 'testwithdev01_11'; } protected function run($argument): void { try { - $this->logger->error('DailyRecoveryWarningNotificationJob called.', ['exception' => '']); - $this->prepareValidUserIds(); + $this->logger->info('DailyRecoveryWarningNotificationJob called.', ['exception' => '']); + $this->identifyUnverifiedUsers(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning - $this->logger->error('DailyRecoveryWarningNotificationJob: messageId: '.$messageId, ['exception' => '']); + $this->logger->info('DailyRecoveryWarningNotificationJob: messageId: ' . $messageId, ['exception' => '']); $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); - // $this->disableUsersWithoutRecoveryEmail(); - // $this->deleteUsersAfterGracePeriod(); + $this->disableUnverifiedUsers(); + $this->deleteExpiredDisabledUsers(); } catch (\Exception $e) { $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery warning notification and emails to users', ['exception' => $e]); return; @@ -104,11 +108,15 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * Prepares valid user IDs and stores them in the 'uids' array. * @return void */ - private function prepareValidUserIds(): void { - $this->logger->error('DailyRecoveryWarningNotificationJob prepareValidUserIds called.', ['exception' => '']); + private function identifyUnverifiedUsers(): void { + $this->logger->info('DailyRecoveryWarningNotificationJob identifyUnverifiedUsers called.', ['exception' => '']); $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); foreach ($users as $username) { + // Test purpose + if ($this->testUsername && $username !== $this->testUsername) { + continue; + } $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); @@ -123,54 +131,65 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($disableDate) (from disable-unverified-users-date) for " . $uid); - if (in_array($age, [0, 15, 22, 26, 29]) || $age > 29) { + if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } } } } /** - * Method disableUsersWithoutRecoveryEmail + * Method disableUnverifiedUsers * * @return void */ - private function disableUsersWithoutRecoveryEmail(): void { + private function disableUnverifiedUsers(): void { foreach ($this->uids as $username) { $user = $this->userManager->get($username); $uid = $user->getUID(); $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); if ($age === self::DISABLE_AFTER_DAYS) { - // Disable account - $user->setEnabled(false); - $this->config->setUserValue($uid, Application::APP_ID, 'disabled-at', date('Y-m-d', $this->now)); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); + try { + // Disable account + $user->setEnabled(false); + + $deleteAt = date('Y-m-d', $this->now + self::DELETE_AFTER_DISABLED_DAYS * 86400); + $this->recoveryEmailService->setUserDeletionDate($username, $deleteAt); + + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); + } catch (\Throwable $e) { + $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); + } } } } /** - * Method deleteUsersAfterGracePeriod + * Method deleteExpiredDisabledUsers * * @return void */ - private function deleteUsersAfterGracePeriod(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'disabled-at', ''); - foreach ($users as $uid) { - $disabledAt = $this->config->getUserValue($uid, Application::APP_ID, 'disabled-at', null); - if ($disabledAt && strtotime($disabledAt) !== false) { - $disabledAtTs = strtotime($disabledAt); - if (($this->now - $disabledAtTs) > self::DELETE_AFTER_DAYS * 86400) { + private function deleteExpiredDisabledUsers(): void { + foreach ($this->uids as $uid) { + $deleteAt = $this->recoveryEmailService->getUserDeletionDate($uid); + if ($deleteAt && strtotime($deleteAt) !== false) { + $deleteAtTs = strtotime($deleteAt); + if ($this->now >= $deleteAtTs) { $user = $this->userManager->get($uid); if ($user) { - $user->delete(); - $this->config->deleteUserValue($uid, Application::APP_ID, 'disable-unverified-users-date'); - $this->config->deleteUserValue($uid, Application::APP_ID, 'disabled-at'); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted after grace period."); + try { + $user->delete(); + $this->recoveryEmailService->deletedisableUnverifiedUsersDate($uid); + $this->recoveryEmailService->deleteUserDeletionDate($uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted on or after delete-at date ($deleteAt)."); + } catch (\Throwable $e) { + $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); + } } } } } } + /** * Send cloud notification to users * @@ -225,7 +244,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); $this->notificationManager->notify($notification); - $this->logger->debug('DailyRecoveryWarningNotificationJob: Notificaiton sent to ' . $uid . ' successfully.'); + $this->logger->info('DailyRecoveryWarningNotificationJob: Notificaiton sent to ' . $uid . ' successfully.'); } catch (\Exception $e) { $this->logger->error('DailyRecoveryWarningNotificationJob: Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } @@ -243,7 +262,10 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($uid); $username = $user->getDisplayName(); $emailAddress = $user->getEMailAddress(); - + if (!$emailAddress) { + $this->logger->warning("No email address found for $uid, skipping."); + continue; + } $language = $this->config->getUserValue($uid, 'core', 'lang', null); $translations = $this->notificationService->getTranslatedSubjectAndMessage($messageId, $language); $subject = $translations['subject']; @@ -251,15 +273,15 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $parsedSubject = $this->notificationService->getParsedString($subject, $username); $subject = $parsedSubject['message']; - + $delection_date = $this->recoveryEmailService->getDeletionDate($username); $parsedMessage = $this->notificationService->getParsedString($message, $username, $delection_date); $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); - $this->logger->debug('DailyRecoveryWarningNotificationJob: Recovery email sent to ' . $emailAddress . ' successfully.'); + $this->logger->info('DailyRecoveryWarningNotificationJob: Recovery email sent to ' . $emailAddress . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending notification email to user ' . $uid.'. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } } diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 32c03ad..16c2e8a 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -569,6 +569,15 @@ class RecoveryEmailService { public function deletedisableUnverifiedUsersDate($uid) { $this->config->deleteUserValue($uid, $this->appName, 'disable-unverified-users-date'); } + public function getUserDeletionDate(string $uid): string { + return $this->config->getUserValue($uid, $this->appName, 'delete-at', null); + } + public function setUserDeletionDate(string $uid, string $deleteDate): void { + $this->config->setUserValue($uid, $this->appName, 'delete-at', $deleteDate); + } + public function deleteUserDeletionDate($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'delete-at'); + } public function getDeletionDate(string $username): string { try { $date = $this->getUnverifiedUserDate($username); @@ -583,7 +592,6 @@ class RecoveryEmailService { } public function getAccountAgeDaysFromDisableDate($disableDate): int { - $this->logger->info("$disableDate -- ".date('Y-m-d')); if (!$disableDate || strtotime($disableDate) === false) { return -1; } @@ -591,6 +599,9 @@ class RecoveryEmailService { $disableDateDay = strtotime(date('Y-m-d', strtotime($disableDate))); $today = strtotime(date('Y-m-d')); + return $this->calculateDateDifference($today, $disableDateDay); + } + public function calculateDateDifference($disableDateDay, $today): int { return (int)(($today - $disableDateDay) / 86400); } } -- GitLab From 07bd226e76d080fa0ffc79d100e1d455f05eb51a Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 23:34:09 +0530 Subject: [PATCH 19/58] renamed some function, added const, added test mode, created more functions --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 7350334..0f5ad98 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -52,8 +52,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private RecoveryEmailService $recoveryEmailService; private $now; private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; - private const DISABLE_AFTER_DAYS = 1; //30 to be - private const DELETE_AFTER_DISABLED_DAYS = 2; // 15 days to be + private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 1; //30 to be // Days after which a user without recovery email is disabled + private const DELETE_USER_AFTER_DISABLE_DAYS = 2; // 15 days to be // Days after which a disabled user is deleted // at the top of the class: private ?string $testUsername = null; @@ -148,12 +148,12 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $uid = $user->getUID(); $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); - if ($age === self::DISABLE_AFTER_DAYS) { + if ($age === self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { try { // Disable account $user->setEnabled(false); - $deleteAt = date('Y-m-d', $this->now + self::DELETE_AFTER_DISABLED_DAYS * 86400); + $deleteAt = date('Y-m-d', $this->now + self::DELETE_USER_AFTER_DISABLE_DAYS * 86400); $this->recoveryEmailService->setUserDeletionDate($username, $deleteAt); $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); -- GitLab From 2899809fb5eab1d93f49fa2e7ec088ad2870a20d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Fri, 13 Jun 2025 23:40:18 +0530 Subject: [PATCH 20/58] When recovery email is verified, flags are removed --- lib/Controller/EmailRecoveryController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Controller/EmailRecoveryController.php b/lib/Controller/EmailRecoveryController.php index 8f95571..bb80f31 100644 --- a/lib/Controller/EmailRecoveryController.php +++ b/lib/Controller/EmailRecoveryController.php @@ -104,7 +104,8 @@ class EmailRecoveryController extends Controller { $this->recoveryEmailService->makeRecoveryEmailVerified($userId); $this->recoveryEmailService->deleteVerificationToken($token, $user, $verificationKey); - + $this->recoveryEmailService->deletedisableUnverifiedUsersDate($userId); + $this->recoveryEmailService->deleteUserDeletionDate($userId); $responseParams = [ 'title' => $this->l->t('You verified recovery email successfully.'), 'message' => $this->l->t('You verified recovery email successfully.'), -- GitLab From ba7a43391fb2e8248acd0dbd0467d1728413f0e6 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 13:30:30 +0530 Subject: [PATCH 21/58] Added debugged, and corrected code; renamed function/variable names --- l10n/de.json | 2 +- l10n/de_DE.json | 2 +- l10n/en.json | 2 +- l10n/es.json | 2 +- l10n/fr.json | 2 +- l10n/it.json | 2 +- .../DailyRecoveryWarningNotificationJob.php | 53 ++++++++-------- lib/Controller/EmailRecoveryController.php | 5 +- lib/Notification/Notifier.php | 10 +-- lib/Service/NotificationService.php | 14 ++--- lib/Service/RecoveryEmailService.php | 61 ++++++++++++------- 11 files changed, 89 insertions(+), 66 deletions(-) diff --git a/l10n/de.json b/l10n/de.json index db55beb..8ccd999 100644 --- a/l10n/de.json +++ b/l10n/de.json @@ -38,6 +38,6 @@ "The email address is not deliverable. Please provide another recovery address.": "Die E-Mail Adresse ist nicht zustellbar. Bitte geben Sie eine andere Wiederherstellungsadresse an.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace wird bald wieder vollständig verfügbar sein! Bitte lies diesen Leitfaden, um mehr zu erfahren.", "20250610_account_recovery_warning_subject": "Erforderliche Maßnahme: Richten Sie Ihre Wiederherstellungs-E-Mail vor der Kontolöschung ein.", - "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{deletion_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" + "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{disable_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/de_DE.json b/l10n/de_DE.json index 1f85c04..baef5b6 100644 --- a/l10n/de_DE.json +++ b/l10n/de_DE.json @@ -39,6 +39,6 @@ "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace wird bald wieder vollständig verfügbar sein! Bitte lesen Sie diesen Leitfaden, um mehr zu erfahren.", "This email address in invalid, please use another one.":"Diese E-Mail-Adresse ist ungültig, bitte verwenden Sie eine andere.", "20250610_account_recovery_warning_subject": "Erforderliche Maßnahme: Richten Sie Ihre Wiederherstellungs-E-Mail vor der Kontolöschung ein.", - "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{deletion_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" + "20250610_account_recovery_warning_body": "Sehr geehrter **{username}**,\n\nAus Sicherheitsgründen müssen Sie eine Wiederherstellungs-E-Mail-Adresse für Ihr Murena Cloud-Konto festlegen. Da Sie dies noch nicht getan haben, ist die Nutzung Ihrer E-Mail-Adresse eingeschränkt. Bitte richten Sie eine Wiederherstellungs-E-Mail-Adresse ein und bestätigen Sie diese, um alle Nutzungsbeschränkungen für Ihre E-Mail-Adresse aufzuheben. Wenn Sie dies nicht tun, wird Ihr Konto am **{disable_date}** deaktiviert und anschließend gelöscht.\n\n[MEINE WIEDERHERSTELLUNGS-E-MAIL-ADRESSE FESTLEGEN](https://murena.io/settings/user/security#recovery-email-form)\n\nMit freundlichen Grüßen,\nDas Murena-Team" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/en.json b/l10n/en.json index 2bf22fb..23c2d1b 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -38,7 +38,7 @@ "The email address is not deliverable. Please provide another recovery address.": "The email address is not deliverable. Please provide another recovery address.", "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace will be back fully soon! Please read this guide to know more.", "20250610_account_recovery_warning_subject": "Action required: Set up your recovery email before account deletion", - "20250610_account_recovery_warning_body": "Dear **{username}**,\n\nFor security reasons you need to set a recovery email address for your Murena Cloud account. As you haven't done so yet, the usage of your email address is restricted. Please set and validate a recovery email address to remove all usage restriction on your email address. If you fail to do so, your account will be disabled on **{deletion_date}** and then deleted.\n\n[SET MY RECOVERY EMAIL ADDRESS](https://murena.io/settings/user/security#recovery-email-form)\n\nBest regards,\nThe Murena Team" + "20250610_account_recovery_warning_body": "Dear **{username}**,\n\nFor security reasons you need to set a recovery email address for your Murena Cloud account. As you haven't done so yet, the usage of your email address is restricted. Please set and validate a recovery email address to remove all usage restriction on your email address. If you fail to do so, your account will be disabled on **{disable_date}** and then deleted.\n\n[SET MY RECOVERY EMAIL ADDRESS](https://murena.io/settings/user/security#recovery-email-form)\n\nBest regards,\nThe Murena Team" }, "pluralForm": "nplurals=2; plural=(n != 1);" } \ No newline at end of file diff --git a/l10n/es.json b/l10n/es.json index 9642bc1..59bfc22 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -39,6 +39,6 @@ "Murena Workspace will be back fully soon! Please read this guide to know more.": "¡Murena Workspace estará de vuelta al 100% pronto! Por favor lee esta guía para saber más.", "This email address in invalid, please use another one.":"Esta dirección de correo electrónico no es válida, por favor utiliza otra.", "20250610_account_recovery_warning_subject": "Acción necesaria: Configure su correo electrónico de recuperación antes de eliminar la cuenta", - "20250610_account_recovery_warning_body": "Estimado **{username}**,\n\nPor motivos de seguridad, debe establecer una dirección de correo electrónico de recuperación para su cuenta de Murena Cloud. Como aún no lo ha hecho, el uso de su dirección de correo electrónico está restringido. Configure y valide una dirección de correo electrónico de recuperación para eliminar todas las restricciones de uso de su dirección de correo electrónico. Si no lo hace, su cuenta se desactivará el **{deletion_date}** y se eliminará.\n\n[ESTABLECER MI DIRECCIÓN DE CORREO ELECTRÓNICO DE RECUPERACIÓN](https://murena.io/settings/user/security#recovery-email-form)\n\nSaludos cordiales,\nEl equipo Murena" + "20250610_account_recovery_warning_body": "Estimado **{username}**,\n\nPor motivos de seguridad, debe establecer una dirección de correo electrónico de recuperación para su cuenta de Murena Cloud. Como aún no lo ha hecho, el uso de su dirección de correo electrónico está restringido. Configure y valide una dirección de correo electrónico de recuperación para eliminar todas las restricciones de uso de su dirección de correo electrónico. Si no lo hace, su cuenta se desactivará el **{disable_date}** y se eliminará.\n\n[ESTABLECER MI DIRECCIÓN DE CORREO ELECTRÓNICO DE RECUPERACIÓN](https://murena.io/settings/user/security#recovery-email-form)\n\nSaludos cordiales,\nEl equipo Murena" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/l10n/fr.json b/l10n/fr.json index 886c9a0..31a45d0 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -39,6 +39,6 @@ "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace sera bientôt complètement de retour ! Veuillez lire ce guide pour en savoir plus.", "This email address in invalid, please use another one.":"Cette adresse e-mail est invalide, veuillez en utiliser une autre.", "20250610_account_recovery_warning_subject": "Action requise : Configurez votre e-mail de récupération avant la suppression de votre compte", - "20250610_account_recovery_warning_body": "Cher **{username}**,\n\nPour des raisons de sécurité, vous devez définir une adresse électronique de récupération pour votre compte Murena Cloud. Comme vous ne l'avez pas encore fait, l'utilisation de votre adresse e-mail est restreinte. Veuillez définir et valider une adresse e-mail de récupération afin de supprimer toute restriction d'utilisation de votre adresse e-mail. Si vous ne le faites pas, votre compte sera désactivé le **{deletion_date}** puis supprimé.\n\n[DÉFINIR MON ADRESSE E-MAIL DE RÉCUPÉRATION](https://murena.io/settings/user/security#recovery-email-form)\n\nMeilleures salutations,\nL'équipe Murena" + "20250610_account_recovery_warning_body": "Cher **{username}**,\n\nPour des raisons de sécurité, vous devez définir une adresse électronique de récupération pour votre compte Murena Cloud. Comme vous ne l'avez pas encore fait, l'utilisation de votre adresse e-mail est restreinte. Veuillez définir et valider une adresse e-mail de récupération afin de supprimer toute restriction d'utilisation de votre adresse e-mail. Si vous ne le faites pas, votre compte sera désactivé le **{disable_date}** puis supprimé.\n\n[DÉFINIR MON ADRESSE E-MAIL DE RÉCUPÉRATION](https://murena.io/settings/user/security#recovery-email-form)\n\nMeilleures salutations,\nL'équipe Murena" },"pluralForm" :"nplurals=2; plural=n > 1;" } \ No newline at end of file diff --git a/l10n/it.json b/l10n/it.json index 8511f70..135dda8 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -39,6 +39,6 @@ "Murena Workspace will be back fully soon! Please read this guide to know more.": "Murena Workspace tornerà presto completamente operativo! Si prega di leggere questa guida per saperne di più.", "This email address in invalid, please use another one.":"Questo indirizzo email non è valido, per favore usane un altro.", "20250610_account_recovery_warning_subject": "Azione necessaria: Impostare l'e-mail di recupero prima dell'eliminazione dell'account.", - "20250610_account_recovery_warning_body": "Caro **{username}**,\n\nPer motivi di sicurezza è necessario impostare un indirizzo e-mail di recupero per il proprio account Murena Cloud. Poiché non l'avete ancora fatto, l'uso del vostro indirizzo e-mail è limitato. Si prega di impostare e convalidare un indirizzo e-mail di ripristino per rimuovere tutte le restrizioni all'uso del proprio indirizzo e-mail. In caso contrario, l'account verrà disattivato alla **{deletion_date}** e quindi cancellato.\n\n[IMPOSTARE L'INDIRIZZO E-MAIL DI RECUPERO](https://murena.io/settings/user/security#recovery-email-form)\n\nCordiali saluti,\nIl team Murena" + "20250610_account_recovery_warning_body": "Caro **{username}**,\n\nPer motivi di sicurezza è necessario impostare un indirizzo e-mail di recupero per il proprio account Murena Cloud. Poiché non l'avete ancora fatto, l'uso del vostro indirizzo e-mail è limitato. Si prega di impostare e convalidare un indirizzo e-mail di ripristino per rimuovere tutte le restrizioni all'uso del proprio indirizzo e-mail. In caso contrario, l'account verrà disattivato alla **{disable_date}** e quindi cancellato.\n\n[IMPOSTARE L'INDIRIZZO E-MAIL DI RECUPERO](https://murena.io/settings/user/security#recovery-email-form)\n\nCordiali saluti,\nIl team Murena" },"pluralForm" :"nplurals=2; plural=n != 1;" } \ No newline at end of file diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 0f5ad98..ad41fe9 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -109,10 +109,10 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function identifyUnverifiedUsers(): void { - $this->logger->info('DailyRecoveryWarningNotificationJob identifyUnverifiedUsers called.', ['exception' => '']); $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); - foreach ($users as $username) { + $this->logger->info("DailyRecoveryWarningNotificationJob: username: $username"); + // Test purpose if ($this->testUsername && $username !== $this->testUsername) { continue; @@ -121,16 +121,23 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($this->isUserValid($user)) { $uid = $user->getUID(); - $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob: disableDate: $disableDate for " . $uid); - if ($disableDate === null) { - $disableDate = date('Y-m-d', $this->now); - $this->recoveryEmailService->setUnverifiedUserDate($uid, $disableDate); + $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: startDate: $startDate for " . $uid); + if ($startDate === null) { + $startDate = date('Y-m-d', $this->now); + $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); + } + if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { + $disableAt = date('Y-m-d', $this->now + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); + $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); + } + if (!$this->recoveryEmailService->getUnverifiedUserDeleteAt($username)) { + $deleteAt = date('Y-m-d', $this->now + self::DELETE_USER_AFTER_DISABLE_DAYS * 86400); + $this->recoveryEmailService->setUnverifiedUserDeleteAt($username, $deleteAt); } - $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); - $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($disableDate) (from disable-unverified-users-date) for " . $uid); - + $age = $this->recoveryEmailService->getDaysSinceDate($startDate); + $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($startDate) (from recovery-email-reminder-start-date) for " . $uid); if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } @@ -143,19 +150,14 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function disableUnverifiedUsers(): void { - foreach ($this->uids as $username) { - $user = $this->userManager->get($username); - $uid = $user->getUID(); - $disableDate = $this->recoveryEmailService->getUnverifiedUserDate($uid); - $age = $this->recoveryEmailService->getAccountAgeDaysFromDisableDate($disableDate); + foreach ($this->uids as $uid) { + $disableDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); + $age = $this->recoveryEmailService->getDaysSinceDate($disableDate); if ($age === self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { + $user = $this->userManager->get($uid); try { // Disable account $user->setEnabled(false); - - $deleteAt = date('Y-m-d', $this->now + self::DELETE_USER_AFTER_DISABLE_DAYS * 86400); - $this->recoveryEmailService->setUserDeletionDate($username, $deleteAt); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); @@ -170,7 +172,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { */ private function deleteExpiredDisabledUsers(): void { foreach ($this->uids as $uid) { - $deleteAt = $this->recoveryEmailService->getUserDeletionDate($uid); + $deleteAt = $this->recoveryEmailService->getUnverifiedUserDeleteAt($uid); if ($deleteAt && strtotime($deleteAt) !== false) { $deleteAtTs = strtotime($deleteAt); if ($this->now >= $deleteAtTs) { @@ -178,8 +180,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($user) { try { $user->delete(); - $this->recoveryEmailService->deletedisableUnverifiedUsersDate($uid); - $this->recoveryEmailService->deleteUserDeletionDate($uid); + $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); + $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); + $this->recoveryEmailService->deleteUnverifiedUserDeleteAt($uid); $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted on or after delete-at date ($deleteAt)."); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); @@ -274,8 +277,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $parsedSubject = $this->notificationService->getParsedString($subject, $username); $subject = $parsedSubject['message']; - $delection_date = $this->recoveryEmailService->getDeletionDate($username); - $parsedMessage = $this->notificationService->getParsedString($message, $username, $delection_date); + $disableDate = $this->recoveryEmailService->getUnverifiedUserDisableAt($username); + $parsedMessage = $this->notificationService->getParsedString($message, $username, $disableDate); $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); @@ -344,6 +347,6 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { return false; } $emailAddress = $user->getEMailAddress(); - return ($emailAddress && $user->isEnabled() && $this->mailer->validateMailAddress($emailAddress)); + return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } } diff --git a/lib/Controller/EmailRecoveryController.php b/lib/Controller/EmailRecoveryController.php index bb80f31..a3fac27 100644 --- a/lib/Controller/EmailRecoveryController.php +++ b/lib/Controller/EmailRecoveryController.php @@ -104,8 +104,9 @@ class EmailRecoveryController extends Controller { $this->recoveryEmailService->makeRecoveryEmailVerified($userId); $this->recoveryEmailService->deleteVerificationToken($token, $user, $verificationKey); - $this->recoveryEmailService->deletedisableUnverifiedUsersDate($userId); - $this->recoveryEmailService->deleteUserDeletionDate($userId); + $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($userId); + $this->recoveryEmailService->deleteUnverifiedUserDisableAt($userId); + $this->recoveryEmailService->deleteUnverifiedUserDeleteAt($userId); $responseParams = [ 'title' => $this->l->t('You verified recovery email successfully.'), 'message' => $this->l->t('You verified recovery email successfully.'), diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index a47176a..f218135 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -43,18 +43,18 @@ class Notifier implements INotifier { private $NotificationService; /** @var RecoveryEmailService */ - private $RecoveryEmailService; + private $recoveryEmailService; public function __construct( IFactory $l10nFactory, LoggerInterface $logger, NotificationService $NotificationService, - RecoveryEmailService $RecoveryEmailService + RecoveryEmailService $recoveryEmailService ) { $this->l10nFactory = $l10nFactory; $this->logger = $logger; $this->NotificationService = $NotificationService; - $this->RecoveryEmailService = $RecoveryEmailService; + $this->recoveryEmailService = $recoveryEmailService; } /** @@ -102,8 +102,8 @@ class Notifier implements INotifier { $plainSubject = $parsedSubject['message']; $richSubject = $parsedSubject['richString']; - $delection_date = $this->RecoveryEmailService->getDeletionDate($username); - $parsedMessage = $this->NotificationService->getParsedString($message, $username, $delection_date); + $disableDate = $this->recoveryEmailService->getUnverifiedUserDisableAt($username); + $parsedMessage = $this->NotificationService->getParsedString($message, $username, $disableDate); $messageParameters = $parsedMessage['parameters']; $plainMessage = $parsedMessage['message']; $richMessage = $parsedMessage['richString']; diff --git a/lib/Service/NotificationService.php b/lib/Service/NotificationService.php index 887117d..51a1950 100644 --- a/lib/Service/NotificationService.php +++ b/lib/Service/NotificationService.php @@ -19,7 +19,7 @@ class NotificationService { * @param string $message * @param string $username */ - public function getParsedString(string $message, string $username, string $deletion_date = '') { + public function getParsedString(string $message, string $username, string $disable_date = '') { $richString = $message; $data = $this->prepareRichString($message, $richString, $username, 'url'); @@ -34,8 +34,8 @@ class NotificationService { $message = $data['message']; $richString = $data['richString']; - if ($deletion_date != '') { - $data = $this->prepareRichString($message, $richString, $username, 'deletion_date', $deletion_date); + if ($disable_date != '') { + $data = $this->prepareRichString($message, $richString, $username, 'disable_date', $disable_date); $message = $data['message']; $richString = $data['richString']; } @@ -49,7 +49,7 @@ class NotificationService { * @param string $username * @param string $type */ - private function prepareRichString($message, $richString, $username, $type, $deletion_date = '') { + private function prepareRichString($message, $richString, $username, $type, $disable_date = '') { switch ($type) { case 'url': $richString = preg_replace('/\[(.*?)\]\((.*?)\)/', '{$1[$2]}', $message); @@ -70,9 +70,9 @@ class NotificationService { $message = str_replace('{username}', $username, $message); break; - case 'deletion_date': - $richString = str_replace('{deletion_date}', $deletion_date, $richString); - $message = str_replace('{deletion_date}', $deletion_date, $message); + case 'disable_date': + $richString = str_replace('{disable_date}', $disable_date, $richString); + $message = str_replace('{disable_date}', $disable_date, $message); break; } return ['message' => $message, 'richString' => $richString]; diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 16c2e8a..7101e11 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -560,27 +560,39 @@ class RecoveryEmailService { $requests[] = $now; $this->cache->set($key, $requests, 2); } - public function getUnverifiedUserDate(string $uid): string { - return $this->config->getUserValue($uid, $this->appName, 'disable-unverified-users-date', null); + /** Recovery email reminder start date **/ + public function getRecoveryEmailReminderStartDate(string $uid): string { + return $this->config->getUserValue($uid, $this->appName, 'recovery-email-reminder-start-date', null); } - public function setUnverifiedUserDate(string $uid, string $disableDate): void { - $this->config->setUserValue($uid, $this->appName, 'disable-unverified-users-date', $disableDate); + public function setRecoveryEmailReminderStartDate(string $uid, string $startDate): void { + $this->config->setUserValue($uid, $this->appName, 'recovery-email-reminder-start-date', $startDate); } - public function deletedisableUnverifiedUsersDate($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'disable-unverified-users-date'); + public function deleteRecoveryEmailReminderStartDate($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'recovery-email-reminder-start-date'); } - public function getUserDeletionDate(string $uid): string { - return $this->config->getUserValue($uid, $this->appName, 'delete-at', null); + /** Unverified user disable At **/ + public function getUnverifiedUserDisableAt(string $uid): string { + return $this->config->getUserValue($uid, $this->appName, 'unverified-user-disable-at', null); } - public function setUserDeletionDate(string $uid, string $deleteDate): void { - $this->config->setUserValue($uid, $this->appName, 'delete-at', $deleteDate); + public function setUnverifiedUserDisableAt(string $uid, string $disableDate): void { + $this->config->setUserValue($uid, $this->appName, 'unverified-user-disable-at', $disableDate); } - public function deleteUserDeletionDate($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'delete-at'); + public function deleteUnverifiedUserDisableAt($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'unverified-user-disable-at'); } - public function getDeletionDate(string $username): string { + /** Unverified user delete At **/ + public function getUnverifiedUserDeleteAt(string $uid): string { + return $this->config->getUserValue($uid, $this->appName, 'unverified-user-delete-at', null); + } + public function setUnverifiedUserDeleteAt(string $uid, string $deleteDate): void { + $this->config->setUserValue($uid, $this->appName, 'unverified-user-delete-at', $deleteDate); + } + public function deleteUnverifiedUserDeleteAt($uid) { + $this->config->deleteUserValue($uid, $this->appName, 'unverified-user-delete-at'); + } + public function getUserDisableDate(string $username): string { try { - $date = $this->getUnverifiedUserDate($username); + $date = $this->getRecoveryEmailReminderStartDate($username); if (!$date) { throw new Exception("No date found."); } @@ -591,17 +603,24 @@ class RecoveryEmailService { } } - public function getAccountAgeDaysFromDisableDate($disableDate): int { - if (!$disableDate || strtotime($disableDate) === false) { + /** + * Calculates how many full days have passed since the given start date. + */ + public function getDaysSinceDate($startDate): int { + if (!$startDate || strtotime($startDate) === false) { return -1; } - $disableDateDay = strtotime(date('Y-m-d', strtotime($disableDate))); - $today = strtotime(date('Y-m-d')); + $startDateTimestamp = strtotime(date('Y-m-d', strtotime($startDate))); + $todayTimestamp = strtotime(date('Y-m-d')); - return $this->calculateDateDifference($today, $disableDateDay); + return $this->calculateDayDifference($startDateTimestamp, $todayTimestamp); } - public function calculateDateDifference($disableDateDay, $today): int { - return (int)(($today - $disableDateDay) / 86400); + + /** + * Calculates the number of days between two timestamps. + */ + public function calculateDayDifference($fromTimestamp, $toTimestamp): int { + return (int)(($toTimestamp - $fromTimestamp) / 86400); } } -- GitLab From c5cb26326e4aa525c5e6e48fcb4478baad2262c7 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 13:31:34 +0530 Subject: [PATCH 22/58] 23jan02 set --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index ad41fe9..26e76b6 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -84,7 +84,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; $this->now = $this->timeFactory->getTime(); // string time - $this->testUsername = 'testwithdev01_11'; + $this->testUsername = '23jan02'; } protected function run($argument): void { -- GitLab From 52dff8d8944b2e214b7eee32b2b94fbafe684e7d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 13:31:54 +0530 Subject: [PATCH 23/58] run every 2 minutes for testing --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 26e76b6..96206eb 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -70,8 +70,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { ) { parent::__construct($timeFactory); - // $this->setInterval(2 * 60); // Run every 2 minutes - $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days + $this->setInterval(2 * 60); // Run every 2 minutes + // $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days $this->setTimeSensitivity(self::TIME_INSENSITIVE); $this->config = $config; -- GitLab From e8679b6a2647d8c9fc01b8dd10351b432269689a Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 13:32:40 +0530 Subject: [PATCH 24/58] added more loggers --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 96206eb..89484cd 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -124,14 +124,17 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); $this->logger->info("DailyRecoveryWarningNotificationJob: startDate: $startDate for " . $uid); if ($startDate === null) { + $this->logger->info("DailyRecoveryWarningNotificationJob: startDate NOT SET for " . $uid); $startDate = date('Y-m-d', $this->now); $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); } if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { + $this->logger->info("DailyRecoveryWarningNotificationJob: DISABLE_USER_AFTER_UNVERIFIED_DAYS NOT SET for " . $uid); $disableAt = date('Y-m-d', $this->now + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); } if (!$this->recoveryEmailService->getUnverifiedUserDeleteAt($username)) { + $this->logger->info("DailyRecoveryWarningNotificationJob: DELETE_USER_AFTER_DISABLE_DAYS NOT SET for " . $uid); $deleteAt = date('Y-m-d', $this->now + self::DELETE_USER_AFTER_DISABLE_DAYS * 86400); $this->recoveryEmailService->setUnverifiedUserDeleteAt($username, $deleteAt); } -- GitLab From ad43ece19bd52e299800b7ff631e3145f197ce68 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 13:45:29 +0530 Subject: [PATCH 25/58] more logs added --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 89484cd..9b1adb1 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -89,7 +89,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { protected function run($argument): void { try { - $this->logger->info('DailyRecoveryWarningNotificationJob called.', ['exception' => '']); + $this->logger->info('DailyRecoveryWarningNotificationJob: called.', ['exception' => '']); $this->identifyUnverifiedUsers(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning $this->logger->info('DailyRecoveryWarningNotificationJob: messageId: ' . $messageId, ['exception' => '']); @@ -144,6 +144,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } + } else { + $this->logger->info("DailyRecoveryWarningNotificationJob: $username not valid.", ['exception' => '']); } } } @@ -350,6 +352,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { return false; } $emailAddress = $user->getEMailAddress(); + $this->logger->info("DailyRecoveryWarningNotificationJob: $emailAddress found.", ['exception' => '']); return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } } -- GitLab From 726ad6651dd58d4072ba8cf2402de129e4fe9394 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 14:16:22 +0530 Subject: [PATCH 26/58] null returned, so chahed return type --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 5 ----- lib/Service/RecoveryEmailService.php | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 9b1adb1..47e5e13 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -112,11 +112,6 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); foreach ($users as $username) { $this->logger->info("DailyRecoveryWarningNotificationJob: username: $username"); - - // Test purpose - if ($this->testUsername && $username !== $this->testUsername) { - continue; - } $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 7101e11..ab95e06 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -561,7 +561,7 @@ class RecoveryEmailService { $this->cache->set($key, $requests, 2); } /** Recovery email reminder start date **/ - public function getRecoveryEmailReminderStartDate(string $uid): string { + public function getRecoveryEmailReminderStartDate(string $uid): ?string { return $this->config->getUserValue($uid, $this->appName, 'recovery-email-reminder-start-date', null); } public function setRecoveryEmailReminderStartDate(string $uid, string $startDate): void { @@ -571,7 +571,7 @@ class RecoveryEmailService { $this->config->deleteUserValue($uid, $this->appName, 'recovery-email-reminder-start-date'); } /** Unverified user disable At **/ - public function getUnverifiedUserDisableAt(string $uid): string { + public function getUnverifiedUserDisableAt(string $uid): ?string { return $this->config->getUserValue($uid, $this->appName, 'unverified-user-disable-at', null); } public function setUnverifiedUserDisableAt(string $uid, string $disableDate): void { @@ -581,7 +581,7 @@ class RecoveryEmailService { $this->config->deleteUserValue($uid, $this->appName, 'unverified-user-disable-at'); } /** Unverified user delete At **/ - public function getUnverifiedUserDeleteAt(string $uid): string { + public function getUnverifiedUserDeleteAt(string $uid): ?string { return $this->config->getUserValue($uid, $this->appName, 'unverified-user-delete-at', null); } public function setUnverifiedUserDeleteAt(string $uid, string $deleteDate): void { -- GitLab From 86e35ca082a4268e653ef18d21f1e835f99efe4d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 14:18:46 +0530 Subject: [PATCH 27/58] added const for consistancy --- lib/Service/RecoveryEmailService.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index ab95e06..aafc0e4 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -49,6 +49,10 @@ class RecoveryEmailService { private const VERIFYMAIL_API_URL = 'https://verifymail.io/api/%s?key=%s'; private const RATE_LIMIT_EMAIL = 'verifymail_email_ratelimit'; private const RATE_LIMIT_DOMAIN = 'verifymail_domain_ratelimit'; + private const RECOVERY_EMAIL_REMINDER_START_DATE = 'recovery-email-reminder-start-date'; + private const UNVERIFIED_USER_DISABLE_AT = 'unverified-user-disable-at'; + private const UNVERIFIED_USER_DELETE_AT = 'unverified-user-delete-at'; + private $cache; private DomainService $domainService; @@ -562,33 +566,33 @@ class RecoveryEmailService { } /** Recovery email reminder start date **/ public function getRecoveryEmailReminderStartDate(string $uid): ?string { - return $this->config->getUserValue($uid, $this->appName, 'recovery-email-reminder-start-date', null); + return $this->config->getUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE, null); } public function setRecoveryEmailReminderStartDate(string $uid, string $startDate): void { - $this->config->setUserValue($uid, $this->appName, 'recovery-email-reminder-start-date', $startDate); + $this->config->setUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE, $startDate); } public function deleteRecoveryEmailReminderStartDate($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'recovery-email-reminder-start-date'); + $this->config->deleteUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE); } /** Unverified user disable At **/ public function getUnverifiedUserDisableAt(string $uid): ?string { - return $this->config->getUserValue($uid, $this->appName, 'unverified-user-disable-at', null); + return $this->config->getUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT, null); } public function setUnverifiedUserDisableAt(string $uid, string $disableDate): void { - $this->config->setUserValue($uid, $this->appName, 'unverified-user-disable-at', $disableDate); + $this->config->setUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT, $disableDate); } public function deleteUnverifiedUserDisableAt($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'unverified-user-disable-at'); + $this->config->deleteUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT); } /** Unverified user delete At **/ public function getUnverifiedUserDeleteAt(string $uid): ?string { - return $this->config->getUserValue($uid, $this->appName, 'unverified-user-delete-at', null); + return $this->config->getUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT, null); } public function setUnverifiedUserDeleteAt(string $uid, string $deleteDate): void { - $this->config->setUserValue($uid, $this->appName, 'unverified-user-delete-at', $deleteDate); + $this->config->setUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT, $deleteDate); } public function deleteUnverifiedUserDeleteAt($uid) { - $this->config->deleteUserValue($uid, $this->appName, 'unverified-user-delete-at'); + $this->config->deleteUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT); } public function getUserDisableDate(string $username): string { try { -- GitLab From f1db8ac0c14f25df387c6bf61edfbbf1ce0026a1 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 14:29:03 +0530 Subject: [PATCH 28/58] testUsername added to not delete all accounts (keeping for other testing) --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 47e5e13..412a333 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -112,6 +112,12 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); foreach ($users as $username) { $this->logger->info("DailyRecoveryWarningNotificationJob: username: $username"); + + // Test purpose + if ($this->testUsername && $username !== $this->testUsername) { + $this->logger->info("DailyRecoveryWarningNotificationJob: Skipping $username due to testUsername set to {$this->testUsername}"); + continue; + } $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); -- GitLab From be05c9eb4bfa9127f861641f716cb918d4465b05 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Sat, 14 Jun 2025 14:33:23 +0530 Subject: [PATCH 29/58] set for one day --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 412a333..b9f0448 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -70,8 +70,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { ) { parent::__construct($timeFactory); - $this->setInterval(2 * 60); // Run every 2 minutes - // $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days + // $this->setInterval(2 * 60); // Run every 2 minutes + $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days $this->setTimeSensitivity(self::TIME_INSENSITIVE); $this->config = $config; -- GitLab From f6b82f6f20af8faad64d5f45ec936eff27330259 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 16 Jun 2025 00:35:11 +0530 Subject: [PATCH 30/58] delete-at not require so removed it; also added new arrays for disable,delete --- .../DailyRecoveryWarningNotificationJob.php | 63 +++++++++---------- lib/Controller/EmailRecoveryController.php | 1 - lib/Service/RecoveryEmailService.php | 10 --- 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index b9f0448..f4b352f 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -46,14 +46,16 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private IUserManager $userManager; private NotificationService $notificationService; private $uids = []; + private $disableUids = []; + private $deleteUids = []; private ITimeFactory $timeFactory; private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; private $now; - private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 1; //30 to be // Days after which a user without recovery email is disabled private const DELETE_USER_AFTER_DISABLE_DAYS = 2; // 15 days to be // Days after which a disabled user is deleted + private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; // at the top of the class: private ?string $testUsername = null; @@ -134,17 +136,18 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $disableAt = date('Y-m-d', $this->now + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); } - if (!$this->recoveryEmailService->getUnverifiedUserDeleteAt($username)) { - $this->logger->info("DailyRecoveryWarningNotificationJob: DELETE_USER_AFTER_DISABLE_DAYS NOT SET for " . $uid); - $deleteAt = date('Y-m-d', $this->now + self::DELETE_USER_AFTER_DISABLE_DAYS * 86400); - $this->recoveryEmailService->setUnverifiedUserDeleteAt($username, $deleteAt); - } $age = $this->recoveryEmailService->getDaysSinceDate($startDate); $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($startDate) (from recovery-email-reminder-start-date) for " . $uid); if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } + if ($age == self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { + $this->disableUids[] = $uid; + } + if ($age == self::DELETE_USER_AFTER_DISABLE_DAYS) { + $this->deleteUids[] = $uid; + } } else { $this->logger->info("DailyRecoveryWarningNotificationJob: $username not valid.", ['exception' => '']); } @@ -156,18 +159,14 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function disableUnverifiedUsers(): void { - foreach ($this->uids as $uid) { - $disableDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); - $age = $this->recoveryEmailService->getDaysSinceDate($disableDate); - if ($age === self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { - $user = $this->userManager->get($uid); - try { - // Disable account - $user->setEnabled(false); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at 30 days."); - } catch (\Throwable $e) { - $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); - } + foreach ($this->disableUids as $uid) { + $user = $this->userManager->get($uid); + try { + // Disable account + $user->setEnabled(false); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at ".self::DISABLE_USER_AFTER_UNVERIFIED_DAYS." days."); + } catch (\Throwable $e) { + $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } } } @@ -177,23 +176,17 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function deleteExpiredDisabledUsers(): void { - foreach ($this->uids as $uid) { - $deleteAt = $this->recoveryEmailService->getUnverifiedUserDeleteAt($uid); - if ($deleteAt && strtotime($deleteAt) !== false) { - $deleteAtTs = strtotime($deleteAt); - if ($this->now >= $deleteAtTs) { - $user = $this->userManager->get($uid); - if ($user) { - try { - $user->delete(); - $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); - $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); - $this->recoveryEmailService->deleteUnverifiedUserDeleteAt($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted on or after delete-at date ($deleteAt)."); - } catch (\Throwable $e) { - $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); - } - } + foreach ($this->deleteUids as $uid) { + $user = $this->userManager->get($uid); + if ($user) { + try { + // Delete account + $user->delete(); + $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); + $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); + $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted on ".date('Y-m-d', $this->now)); + } catch (\Throwable $e) { + $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } } } diff --git a/lib/Controller/EmailRecoveryController.php b/lib/Controller/EmailRecoveryController.php index a3fac27..58604be 100644 --- a/lib/Controller/EmailRecoveryController.php +++ b/lib/Controller/EmailRecoveryController.php @@ -106,7 +106,6 @@ class EmailRecoveryController extends Controller { $this->recoveryEmailService->deleteVerificationToken($token, $user, $verificationKey); $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($userId); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($userId); - $this->recoveryEmailService->deleteUnverifiedUserDeleteAt($userId); $responseParams = [ 'title' => $this->l->t('You verified recovery email successfully.'), 'message' => $this->l->t('You verified recovery email successfully.'), diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index aafc0e4..40dd93e 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -584,16 +584,6 @@ class RecoveryEmailService { public function deleteUnverifiedUserDisableAt($uid) { $this->config->deleteUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT); } - /** Unverified user delete At **/ - public function getUnverifiedUserDeleteAt(string $uid): ?string { - return $this->config->getUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT, null); - } - public function setUnverifiedUserDeleteAt(string $uid, string $deleteDate): void { - $this->config->setUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT, $deleteDate); - } - public function deleteUnverifiedUserDeleteAt($uid) { - $this->config->deleteUserValue($uid, $this->appName, self::UNVERIFIED_USER_DELETE_AT); - } public function getUserDisableDate(string $username): string { try { $date = $this->getRecoveryEmailReminderStartDate($username); -- GitLab From 324bbc548b60a0c378f46e0b4d980dbc7c853d73 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 16 Jun 2025 16:52:36 +0530 Subject: [PATCH 31/58] removed loggers --- .../DailyRecoveryWarningNotificationJob.php | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index f4b352f..9407eae 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -53,8 +53,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; private $now; - private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 1; //30 to be // Days after which a user without recovery email is disabled - private const DELETE_USER_AFTER_DISABLE_DAYS = 2; // 15 days to be // Days after which a disabled user is deleted + private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; //30 to be // Days after which a user without recovery email is disabled + private const DELETE_USER_AFTER_DISABLE_DAYS = 45; // 30+15 days to be // Days after which a disabled user is deleted private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; // at the top of the class: private ?string $testUsername = null; @@ -86,22 +86,25 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; $this->now = $this->timeFactory->getTime(); // string time - $this->testUsername = '23jan02'; + $this->testUsername = '23jan03'; } protected function run($argument): void { try { - $this->logger->info('DailyRecoveryWarningNotificationJob: called.', ['exception' => '']); + $this->logger->info('DailyRecoveryWarningNotificationJob job started.'); $this->identifyUnverifiedUsers(); - $messageId = $this->config->getSystemValue('account_recovery_warning_messageid'); // 20250610_account_recovery_warning - $this->logger->info('DailyRecoveryWarningNotificationJob: messageId: ' . $messageId, ['exception' => '']); - + $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); // 20250610_account_recovery_warning + if (!$messageId) { + $this->logger->error('DailyRecoveryWarningNotificationJob messageId not set!'); + return; + } $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUnverifiedUsers(); $this->deleteExpiredDisabledUsers(); + $this->logger->info('DailyRecoveryWarningNotificationJob job Finished ', ['exception' => '']); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery warning notification and emails to users', ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); return; } } @@ -111,13 +114,13 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { * @return void */ private function identifyUnverifiedUsers(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', []); foreach ($users as $username) { - $this->logger->info("DailyRecoveryWarningNotificationJob: username: $username"); + $this->logger->info("DailyRecoveryWarningNotificationJob username: $username"); // Test purpose if ($this->testUsername && $username !== $this->testUsername) { - $this->logger->info("DailyRecoveryWarningNotificationJob: Skipping $username due to testUsername set to {$this->testUsername}"); + $this->logger->info("DailyRecoveryWarningNotificationJob skipping $username due to testUsername set to {$this->testUsername}"); continue; } $user = $this->userManager->get($username); @@ -125,20 +128,16 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $uid = $user->getUID(); $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob: startDate: $startDate for " . $uid); if ($startDate === null) { - $this->logger->info("DailyRecoveryWarningNotificationJob: startDate NOT SET for " . $uid); $startDate = date('Y-m-d', $this->now); $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); } if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { - $this->logger->info("DailyRecoveryWarningNotificationJob: DISABLE_USER_AFTER_UNVERIFIED_DAYS NOT SET for " . $uid); $disableAt = date('Y-m-d', $this->now + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); } $age = $this->recoveryEmailService->getDaysSinceDate($startDate); - $this->logger->info("DailyRecoveryWarningNotificationJob: Age $age ($startDate) (from recovery-email-reminder-start-date) for " . $uid); if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } @@ -149,7 +148,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->deleteUids[] = $uid; } } else { - $this->logger->info("DailyRecoveryWarningNotificationJob: $username not valid.", ['exception' => '']); + $this->logger->info("DailyRecoveryWarningNotificationJob $username not valid.", ['exception' => '']); } } } @@ -164,7 +163,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { // Disable account $user->setEnabled(false); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid disabled due to missing recovery email at ".self::DISABLE_USER_AFTER_UNVERIFIED_DAYS." days."); + $this->logger->info("DailyRecoveryWarningNotificationJob User $uid disabled due to missing recovery email at ".date('Y-m-d', $this->now)." days."); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } @@ -184,7 +183,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user->delete(); $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob: User $uid permanently deleted on ".date('Y-m-d', $this->now)); + $this->logger->info("DailyRecoveryWarningNotificationJob User $uid permanently deleted on ".date('Y-m-d', $this->now)); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } @@ -209,7 +208,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->sendNotificationToUsers($messageId, $notification, $this->uids); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -227,7 +226,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($username); $this->sendNotificationToUser($messageId, $user, $notification); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -246,9 +245,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); $this->notificationManager->notify($notification); - $this->logger->info('DailyRecoveryWarningNotificationJob: Notificaiton sent to ' . $uid . ' successfully.'); + $this->logger->info('DailyRecoveryWarningNotificationJob Notificaiton sent to ' . $uid . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -281,9 +280,9 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); - $this->logger->info('DailyRecoveryWarningNotificationJob: Recovery email sent to ' . $emailAddress . ' successfully.'); + $this->logger->info('DailyRecoveryWarningNotificationJob Recovery email sent to ' . $emailAddress . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob: Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('DailyRecoveryWarningNotificationJob Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -346,7 +345,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { return false; } $emailAddress = $user->getEMailAddress(); - $this->logger->info("DailyRecoveryWarningNotificationJob: $emailAddress found.", ['exception' => '']); + $this->logger->info("DailyRecoveryWarningNotificationJob $emailAddress found.", ['exception' => '']); return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } } -- GitLab From 4115dfaa0868bde011615b52314c2dbaf686f36d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 16 Jun 2025 16:58:06 +0530 Subject: [PATCH 32/58] removed messageid comment --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index 9407eae..f88cdaf 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -93,7 +93,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { $this->logger->info('DailyRecoveryWarningNotificationJob job started.'); $this->identifyUnverifiedUsers(); - $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); // 20250610_account_recovery_warning + $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); if (!$messageId) { $this->logger->error('DailyRecoveryWarningNotificationJob messageId not set!'); return; -- GitLab From 62ed2a22bef1bee65ce0276df61a45a7aa7f7efd Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 17 Jun 2025 13:13:52 +0530 Subject: [PATCH 33/58] added >= --- lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index f88cdaf..c587687 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -86,7 +86,6 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; $this->now = $this->timeFactory->getTime(); // string time - $this->testUsername = '23jan03'; } protected function run($argument): void { @@ -118,11 +117,6 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { foreach ($users as $username) { $this->logger->info("DailyRecoveryWarningNotificationJob username: $username"); - // Test purpose - if ($this->testUsername && $username !== $this->testUsername) { - $this->logger->info("DailyRecoveryWarningNotificationJob skipping $username due to testUsername set to {$this->testUsername}"); - continue; - } $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); @@ -144,7 +138,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if ($age == self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { $this->disableUids[] = $uid; } - if ($age == self::DELETE_USER_AFTER_DISABLE_DAYS) { + if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { $this->deleteUids[] = $uid; } } else { -- GitLab From b21119adab0928a52055ac0f2e806ee3477bc1c6 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 17 Jun 2025 13:20:59 +0530 Subject: [PATCH 34/58] created function to check if disable/delete users --- .../DailyRecoveryWarningNotificationJob.php | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index c587687..f073c58 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -135,17 +135,45 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } - if ($age == self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { - $this->disableUids[] = $uid; - } - if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { - $this->deleteUids[] = $uid; - } + $this->shouldDisableUser($user, $age); + $this->shouldDeleteUser($user, $age); } else { $this->logger->info("DailyRecoveryWarningNotificationJob $username not valid.", ['exception' => '']); } } } + /** + * Method shouldDisableUser + * + * @param IUser $user [explicite description] + * @param int $age [explicite description] + * + * @return void + */ + private function shouldDisableUser(IUser $user, int $age): void { + if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { + if ($user->isEnabled()) { + $this->disableUids[] = $user->getUID(); + $this->logger->info("User {$user->getUID()} is enabled and will be added to disableUids."); + } else { + $this->logger->info("User {$user->getUID()} is already disabled. Skipping."); + } + } + } + /** + * Method shouldDeleteUser + * + * @param IUser $user [explicite description] + * @param int $age [explicite description] + * + * @return void + */ + private function shouldDeleteUser(IUser $user, int $age): void { + if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { + $this->deleteUids[] = $user->getUID(); + $this->logger->info("User {$user->getUID()} reached delete threshold and will be added to deleteUids."); + } + } /** * Method disableUnverifiedUsers * -- GitLab From ddd3fd194d7d277b3e33269f201513d4d68eba0b Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 17 Jun 2025 13:23:16 +0530 Subject: [PATCH 35/58] removed function and added simple condition to check enable --- .../DailyRecoveryWarningNotificationJob.php | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index f073c58..fd6f9f6 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -135,45 +135,20 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } - $this->shouldDisableUser($user, $age); - $this->shouldDeleteUser($user, $age); + if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { + if ($user->isEnabled()) { + $this->disableUids[] = $uid; + } + } + if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { + $this->deleteUids[] = $uid; + } } else { $this->logger->info("DailyRecoveryWarningNotificationJob $username not valid.", ['exception' => '']); } } } - /** - * Method shouldDisableUser - * - * @param IUser $user [explicite description] - * @param int $age [explicite description] - * - * @return void - */ - private function shouldDisableUser(IUser $user, int $age): void { - if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { - if ($user->isEnabled()) { - $this->disableUids[] = $user->getUID(); - $this->logger->info("User {$user->getUID()} is enabled and will be added to disableUids."); - } else { - $this->logger->info("User {$user->getUID()} is already disabled. Skipping."); - } - } - } - /** - * Method shouldDeleteUser - * - * @param IUser $user [explicite description] - * @param int $age [explicite description] - * - * @return void - */ - private function shouldDeleteUser(IUser $user, int $age): void { - if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { - $this->deleteUids[] = $user->getUID(); - $this->logger->info("User {$user->getUID()} reached delete threshold and will be added to deleteUids."); - } - } + /** * Method disableUnverifiedUsers * -- GitLab From 11750b8b27ade73a23c6e871d64326a7752ea176 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 17 Jun 2025 15:04:09 +0530 Subject: [PATCH 36/58] Added code to check if subscription active --- .../DailyRecoveryWarningNotificationJob.php | 33 +++++++++++++------ lib/Service/RecoveryEmailService.php | 26 +++++++++++++-- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php index fd6f9f6..9658644 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php @@ -120,7 +120,10 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($username); if ($this->isUserValid($user)) { $uid = $user->getUID(); - + $emailAddress = $user->getEMailAddress(); + if ($this->recoveryEmailService->checkIfSubscriptionActive($emailAddress)) { + continue; // skip if subscription active + } $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); if ($startDate === null) { $startDate = date('Y-m-d', $this->now); @@ -135,20 +138,30 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { if (in_array($age, self::REMINDER_DAYS)) { $this->uids[] = $uid; } - if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS) { - if ($user->isEnabled()) { - $this->disableUids[] = $uid; - } - } - if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { - $this->deleteUids[] = $uid; - } + $this->shouldDisableUser($user, $age); + $this->shouldDeleteUser($user, $age); } else { $this->logger->info("DailyRecoveryWarningNotificationJob $username not valid.", ['exception' => '']); } } } - + private function shouldDisableUser(IUser $user, int $age): void { + if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { + if ($user->isEnabled()) { + $this->disableUids[] = $user->getUID(); + $this->logger->info("User {$user->getUID()} is enabled and will be added to disableUids."); + } else { + $this->logger->info("User {$user->getUID()} is already disabled. Skipping."); + } + } + } + + private function shouldDeleteUser(IUser $user, int $age): void { + if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { + $this->deleteUids[] = $user->getUID(); + $this->logger->info("User {$user->getUID()} reached delete threshold and will be added to deleteUids."); + } + } /** * Method disableUnverifiedUsers * diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index 40dd93e..c9aeee1 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -11,6 +11,7 @@ use OCA\EmailRecovery\Exception\MurenaDomainDisallowedException; use OCA\EmailRecovery\Exception\RecoveryEmailAlreadyFoundException; use OCA\EmailRecovery\Exception\SameRecoveryEmailAsEmailException; use OCA\EmailRecovery\Exception\TooManyVerificationAttemptsException; +use OCA\EcloudAccounts\Service\ShopAccountService; use OCA\EmailRecovery\Db\ConfigMapper; use OCP\Defaults; use OCP\Http\Client\IClientService; @@ -51,15 +52,15 @@ class RecoveryEmailService { private const RATE_LIMIT_DOMAIN = 'verifymail_domain_ratelimit'; private const RECOVERY_EMAIL_REMINDER_START_DATE = 'recovery-email-reminder-start-date'; private const UNVERIFIED_USER_DISABLE_AT = 'unverified-user-disable-at'; - private const UNVERIFIED_USER_DELETE_AT = 'unverified-user-delete-at'; private $cache; private DomainService $domainService; private IL10N $l; private ISession $session; + private ShopAccountService $shopAccountService; - public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper) { + public function __construct(string $appName, ILogger $logger, IConfig $config, ISession $session, IUserManager $userManager, IMailer $mailer, IFactory $l10nFactory, IURLGenerator $urlGenerator, Defaults $themingDefaults, IVerificationToken $verificationToken, CurlService $curlService, DomainService $domainService, IL10N $l, ICacheFactory $cacheFactory, IClientService $httpClientService, ConfigMapper $configMapper, ShopAccountService $shopAccountService) { $this->logger = $logger; $this->config = $config; $this->appName = $appName; @@ -77,6 +78,7 @@ class RecoveryEmailService { $this->cacheFactory = $cacheFactory; // Initialize the cache factory $this->cache = $this->cacheFactory->createDistributed(self::CACHE_KEY); // Initialize the cache $this->configMapper = $configMapper; + $this->shopAccountService = $shopAccountService; $commonServiceURL = $this->config->getSystemValue('common_services_url', ''); if (!empty($commonServiceURL)) { @@ -617,4 +619,24 @@ class RecoveryEmailService { public function calculateDayDifference($fromTimestamp, $toTimestamp): int { return (int)(($toTimestamp - $fromTimestamp) / 86400); } + + + /** + * Method checkIfSubscriptionActive + * + * @param string $email [explicite description] + * + * @return bool + */ + public function checkIfSubscriptionActive(string $email): bool { + $shopUsers = $this->shopAccountService->getUsers($email); + if (!empty($shopUsers)) { + foreach ($shopUsers as $shopUser) { + if ($shopUser['has_active_subscription']) { + return true; + } + } + } + return false; + } } -- GitLab From c2a78aca7068dd253a759633525a50a758e4cc17 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Thu, 19 Jun 2025 15:53:57 +0530 Subject: [PATCH 37/58] Edit RecoveryEmailService.php --- lib/Service/RecoveryEmailService.php | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index ebbb279..cb75f1d 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -619,4 +619,77 @@ class RecoveryEmailService { } return $spamAccounts; } + /** Recovery email reminder start date **/ + public function getRecoveryEmailReminderStartDate(string $uid): ?string { + return $this->config->getUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE, null); + } + public function setRecoveryEmailReminderStartDate(string $uid, string $startDate): void { + $this->config->setUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE, $startDate); + } + public function deleteRecoveryEmailReminderStartDate($uid) { + $this->config->deleteUserValue($uid, $this->appName, self::RECOVERY_EMAIL_REMINDER_START_DATE); + } + /** Unverified user disable At **/ + public function getUnverifiedUserDisableAt(string $uid): ?string { + return $this->config->getUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT, null); + } + public function setUnverifiedUserDisableAt(string $uid, string $disableDate): void { + $this->config->setUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT, $disableDate); + } + public function deleteUnverifiedUserDisableAt($uid) { + $this->config->deleteUserValue($uid, $this->appName, self::UNVERIFIED_USER_DISABLE_AT); + } + public function getUserDisableDate(string $username): string { + try { + $date = $this->getRecoveryEmailReminderStartDate($username); + if (!$date) { + throw new Exception("No date found."); + } + + return date('Y-m-d', strtotime($date . ' +30 days')); + } catch (Exception $e) { + throw new Exception("Error getting deletion date"); + } + } + + /** + * Calculates how many full days have passed since the given start date. + */ + public function getDaysSinceDate($startDate): int { + if (!$startDate || strtotime($startDate) === false) { + return -1; + } + + $startDateTimestamp = strtotime(date('Y-m-d', strtotime($startDate))); + $todayTimestamp = strtotime(date('Y-m-d')); + + return $this->calculateDayDifference($startDateTimestamp, $todayTimestamp); + } + + /** + * Calculates the number of days between two timestamps. + */ + public function calculateDayDifference($fromTimestamp, $toTimestamp): int { + return (int)(($toTimestamp - $fromTimestamp) / 86400); + } + + + /** + * Method checkIfSubscriptionActive + * + * @param string $email [explicite description] + * + * @return bool + */ + public function checkIfSubscriptionActive(string $email): bool { + $shopUsers = $this->shopAccountService->getUsers($email); + if (!empty($shopUsers)) { + foreach ($shopUsers as $shopUser) { + if ($shopUser['has_active_subscription']) { + return true; + } + } + } + return false; + } } -- GitLab From 2280d2a6652e4bad3974d07016d6f93bb492a15a Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 23 Jun 2025 12:48:27 +0530 Subject: [PATCH 38/58] Removed older Notification job --- appinfo/info.xml | 1 - .../WeeklyRecoveryNotificationJob.php | 255 ------------------ 2 files changed, 256 deletions(-) delete mode 100644 lib/BackgroundJob/WeeklyRecoveryNotificationJob.php diff --git a/appinfo/info.xml b/appinfo/info.xml index 3348f79..6ed7c33 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -19,7 +19,6 @@ OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob - OCA\EmailRecovery\Command\UpdateBlacklistedDomains diff --git a/lib/BackgroundJob/WeeklyRecoveryNotificationJob.php b/lib/BackgroundJob/WeeklyRecoveryNotificationJob.php deleted file mode 100644 index 6e128f0..0000000 --- a/lib/BackgroundJob/WeeklyRecoveryNotificationJob.php +++ /dev/null @@ -1,255 +0,0 @@ - - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OCA\EmailRecovery\BackgroundJob; - -use OCA\EmailRecovery\AppInfo\Application; -use OCA\EmailRecovery\Service\NotificationService; -use OCP\AppFramework\Utility\ITimeFactory; -use OCP\BackgroundJob\TimedJob; -use OCP\IConfig; -use OCP\IURLGenerator; -use OCP\IUser; -use OCP\IUserManager; -use OCP\Mail\IEMailTemplate; -use OCP\Mail\IMailer; -use OCP\Notification\IManager as INotificationManager; -use OCP\Notification\INotification; -use Psr\Log\LoggerInterface; - -class WeeklyRecoveryNotificationJob extends TimedJob { - private IConfig $config; - private IMailer $mailer; - private LoggerInterface $logger; - private IUserManager $userManager; - private NotificationService $notificationService; - private $uids = []; - private ITimeFactory $timeFactory; - private INotificationManager $notificationManager; - private IURLGenerator $urlGenerator; - - public function __construct( - IConfig $config, - IMailer $mailer, - IUserManager $userManager, - LoggerInterface $logger, - NotificationService $notificationService, - ITimeFactory $timeFactory, - INotificationManager $notificationManager, - IURLGenerator $urlGenerator - ) { - parent::__construct($timeFactory); - - // $this->setInterval(2 * 60); // Run every 2 minutes - // $this->setInterval(60 * 60 * 24); // Run once per day - $this->setInterval(7 * 24 * 60 * 60); // Run for 7 days - $this->setTimeSensitivity(self::TIME_INSENSITIVE); - - $this->config = $config; - $this->mailer = $mailer; - $this->logger = $logger; - $this->userManager = $userManager; - $this->notificationService = $notificationService; - $this->timeFactory = $timeFactory; - $this->notificationManager = $notificationManager; - $this->urlGenerator = $urlGenerator; - } - - protected function run($argument): void { - try { - $this->prepareValidUserIds(); - $messageId = $this->config->getSystemValue('weekly_reminder_messageid'); - $this->sendCloudNotifications($messageId); - $this->sendEmails($messageId); - } catch (\Exception $e) { - $this->logger->error('Error sending notification emails to users', ['exception' => $e]); - return; - } - } - - /** - * Prepares valid user IDs and stores them in the 'uids' array. - * @return void - */ - private function prepareValidUserIds(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); - foreach ($users as $username) { - $user = $this->userManager->get($username); - if ($this->isUserValid($user)) { - array_push($this->uids, $user->getUID()); - } - } - } - /** - * Send cloud notification to users - * - * @return void - */ - private function sendCloudNotifications(string $messageId): void { - try { - $datetime = $this->timeFactory->getDateTime(); - $notification = $this->notificationManager->createNotification(); - $notificationType = 'important'; - $notification->setApp(Application::APP_ID) - ->setDateTime($datetime) - ->setObject(Application::APP_ID . '-' . strtolower($notificationType), $messageId) - ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath(Application::APP_ID, strtolower($notificationType) . '.svg'))); - - $this->sendNotificationToUsers($messageId, $notification, $this->uids); - } catch (\Exception $e) { - $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); - } - } - /** - * Sends a notification with the specified messageId and notification object to a list of users. - * - * @param string $messageId The identifier for the notification message. - * @param INotification $notification The notification object to be sent. - * @param array $users An array of usernames to whom the notification will be sent. - * - * @return void - */ - protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { - foreach ($users as $username) { - try { - $user = $this->userManager->get($username); - $this->sendNotificationToUser($messageId, $user, $notification); - } catch (\Exception $e) { - $this->logger->error('Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); - } - } - } - /** - * Sends a notification with the specified messageId and notification object to a user. - * - * @param string $messageId The identifier for the notification message. - * @param IUser $user The user to whom the notification will be sent. - * @param INotification $notification The notification object to be sent. - * - * @return void - */ - protected function sendNotificationToUser(string $messageId, IUser $user, INotification $notification): void { - $uid = $user->getUID(); - $displayName = $user->getDisplayName(); - try { - $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); - $this->notificationManager->notify($notification); - $this->logger->debug('Notificaiton sent to ' . $uid . ' successfully.'); - } catch (\Exception $e) { - $this->logger->error('Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); - } - } - /** - * Sends recovery emails to all users. - * - * @param string $messageId The identifier for the notification message. - * - * @return void - */ - private function sendEmails(string $messageId): void { - foreach ($this->uids as $uid) { - try { - $user = $this->userManager->get($uid); - $username = $user->getDisplayName(); - $emailAddress = $user->getEMailAddress(); - - $language = $this->config->getUserValue($uid, 'core', 'lang', null); - $translations = $this->notificationService->getTranslatedSubjectAndMessage($messageId, $language); - $subject = $translations['subject']; - $message = $translations['message']; - - $parsedSubject = $this->notificationService->getParsedString($subject, $username); - $subject = $parsedSubject['message']; - $parsedMessage = $this->notificationService->getParsedString($message, $username); - $message = $parsedMessage['message']; - - $this->sendEmail($subject, $message, $emailAddress); - $this->logger->debug('Recovery email sent to ' . $emailAddress . ' successfully.'); - } catch (\Exception $e) { - $this->logger->error('Error sending notification email to user ' . $uid.'. Error:' . $e->getMessage(), ['exception' => $e]); - } - } - } - - /** - * Send an email. - * - * @param string $subject The subject of the email. - * @param string $message The body/content of the email. - * @param string $emailAddress The recipient's email address. - * - * @return void - */ - private function sendEmail(string $subject, string $message, string $emailAddress): void { - // Convert Markdown-style links to HTML anchor tags - $message = preg_replace('/\[(.*?)\]\((.*?)\)/', "$1", $message); - $template = $this->mailer->createEMailTemplate(Application::APP_ID . '::sendMail'); - $template->setSubject($subject); - $template->addHeader(); - $template->addHeading($subject); - $this->setMailBody($template, $message); - $template->addFooter(); - - $email = $this->mailer->createMessage(); - $email->useTemplate($template); - $email->setTo([$emailAddress]); - - $this->mailer->send($email); - } - /** - * Special-treat list items and strip empty lines - * - * @param IEMailTemplate $template - * @param string $message - * - * @return void - */ - private function setMailBody(IEMailTemplate $template, string $message): void { - $lines = explode("\n", $message); - $finalHtml = ""; - $finalText = ""; - foreach ($lines as $line) { - if (trim($line) === '') { - continue; - } - $finalHtml .= "

" . $line . "

"; - $finalText .= $line; - } - $template->addBodyText($finalHtml, $finalText); - } - /** - * Validate a user. - * - * @param IUser|null $user The user to be validated. - * - * @return bool Returns true if the user is valid, false otherwise. - */ - private function isUserValid(?IUser $user): bool { - if (!($user instanceof IUser)) { - return false; - } - $emailAddress = $user->getEMailAddress(); - return ($emailAddress && $user->isEnabled() && $this->mailer->validateMailAddress($emailAddress)); - } -} -- GitLab From 2da854305be2655941f2d9c654a011cfc7b731cb Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 23 Jun 2025 15:25:10 +0530 Subject: [PATCH 39/58] Changes as per feedback; removed some code, handled in better way --- appinfo/info.xml | 2 +- ...php => RecoveryWarningNotificationJob.php} | 146 ++++++++++-------- lib/Service/RecoveryEmailService.php | 11 +- 3 files changed, 90 insertions(+), 69 deletions(-) rename lib/BackgroundJob/{DailyRecoveryWarningNotificationJob.php => RecoveryWarningNotificationJob.php} (69%) diff --git a/appinfo/info.xml b/appinfo/info.xml index 6ed7c33..f12eca2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -18,7 +18,7 @@ OCA\EmailRecovery\Settings\RecoveryEmailSettings - OCA\EmailRecovery\BackgroundJob\DailyRecoveryWarningNotificationJob + OCA\EmailRecovery\BackgroundJob\RecoveryWarningNotificationJob OCA\EmailRecovery\Command\UpdateBlacklistedDomains diff --git a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php similarity index 69% rename from lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php rename to lib/BackgroundJob/RecoveryWarningNotificationJob.php index 9658644..946d5fa 100644 --- a/lib/BackgroundJob/DailyRecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -2,23 +2,24 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2025 Florent VINCENT * - * @author Ronak Patel + * @author Ronak Patel * - * @license AGPL-3.0 + * @license GNU AGPL version 3 or any later version * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . * */ @@ -39,7 +40,7 @@ use OCP\Notification\INotification; use Psr\Log\LoggerInterface; use OCA\EmailRecovery\Service\RecoveryEmailService; -class DailyRecoveryWarningNotificationJob extends TimedJob { +class RecoveryWarningNotificationJob extends TimedJob { private IConfig $config; private IMailer $mailer; private LoggerInterface $logger; @@ -52,12 +53,11 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; - private $now; + private $currentTime; private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; //30 to be // Days after which a user without recovery email is disabled private const DELETE_USER_AFTER_DISABLE_DAYS = 45; // 30+15 days to be // Days after which a disabled user is deleted private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; // at the top of the class: - private ?string $testUsername = null; public function __construct( IConfig $config, @@ -85,78 +85,98 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->notificationManager = $notificationManager; $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; - $this->now = $this->timeFactory->getTime(); // string time + $this->currentTime = $this->timeFactory->getTime(); // string time } protected function run($argument): void { try { - $this->logger->info('DailyRecoveryWarningNotificationJob job started.'); + $this->logger->info('RecoveryWarningNotificationJob job started.'); $this->identifyUnverifiedUsers(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); if (!$messageId) { - $this->logger->error('DailyRecoveryWarningNotificationJob messageId not set!'); + $this->logger->error('RecoveryWarningNotificationJob messageId not set!'); return; } $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUnverifiedUsers(); $this->deleteExpiredDisabledUsers(); - $this->logger->info('DailyRecoveryWarningNotificationJob job Finished ', ['exception' => '']); + $this->logger->info('RecoveryWarningNotificationJob job Finished '); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); return; } } - /** - * Prepares valid user IDs and stores them in the 'uids' array. + * Method identifyUnverifiedUsers + * This is used to identify unverfied users + * * @return void */ private function identifyUnverifiedUsers(): void { - $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', []); + $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + if (!is_array($users)) { + $this->logger->warning("RecoveryWarningNotificationJob Expected array for recovery-email users, got " . gettype($users)); + return; + } foreach ($users as $username) { - $this->logger->info("DailyRecoveryWarningNotificationJob username: $username"); - $user = $this->userManager->get($username); - if ($this->isUserValid($user)) { - $uid = $user->getUID(); - $emailAddress = $user->getEMailAddress(); - if ($this->recoveryEmailService->checkIfSubscriptionActive($emailAddress)) { - continue; // skip if subscription active - } - $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); - if ($startDate === null) { - $startDate = date('Y-m-d', $this->now); - $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); - } - if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { - $disableAt = date('Y-m-d', $this->now + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); - $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); - } - - $age = $this->recoveryEmailService->getDaysSinceDate($startDate); - if (in_array($age, self::REMINDER_DAYS)) { - $this->uids[] = $uid; - } - $this->shouldDisableUser($user, $age); - $this->shouldDeleteUser($user, $age); - } else { - $this->logger->info("DailyRecoveryWarningNotificationJob $username not valid.", ['exception' => '']); + if (!$this->isUserValid($user)) { + $this->logger->debug("RecoveryWarningNotificationJob $username not valid."); + continue; + } + $uid = $user->getUID(); + $emailAddress = $user->getEMailAddress(); + if ($this->recoveryEmailService->checkIfSubscriptionActive($emailAddress)) { + continue; // skip if subscription active + } + $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); + if ($startDate === null) { + $startDate = date('Y-m-d', $this->currentTime); + $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); + } + if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { + $disableAt = date('Y-m-d', $this->currentTime + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); + $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); + } + $age = $this->recoveryEmailService->getDaysSinceDate($startDate); + if (in_array($age, self::REMINDER_DAYS)) { + $this->uids[] = $uid; } + $this->identifyForDisable($user, $age); + $this->identifyForDelete($user, $age); } } - private function shouldDisableUser(IUser $user, int $age): void { + /** + * Method identifyForDisable + * Identifies if the user qualifies for disabling and queues them. + * + * @param IUser $user [explicite description] + * @param int $age [explicite description] + * + * @return void + */ + private function identifyForDisable(IUser $user, int $age): void { if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { if ($user->isEnabled()) { $this->disableUids[] = $user->getUID(); - $this->logger->info("User {$user->getUID()} is enabled and will be added to disableUids."); - } else { - $this->logger->info("User {$user->getUID()} is already disabled. Skipping."); + $this->logger->debug("User {$user->getUID()} is enabled and will be added to disableUids."); + return; } + $this->logger->debug("User {$user->getUID()} is already disabled. Skipping."); } } - - private function shouldDeleteUser(IUser $user, int $age): void { + + /** + * Method identifyForDelete + * Identifies if the user qualifies for deletion and queues them. + * + * @param IUser $user [explicite description] + * @param int $age [explicite description] + * + * @return void + */ + private function identifyForDelete(IUser $user, int $age): void { if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { $this->deleteUids[] = $user->getUID(); $this->logger->info("User {$user->getUID()} reached delete threshold and will be added to deleteUids."); @@ -164,6 +184,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { } /** * Method disableUnverifiedUsers + * This method is used to disable unverified users * * @return void */ @@ -173,7 +194,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { // Disable account $user->setEnabled(false); - $this->logger->info("DailyRecoveryWarningNotificationJob User $uid disabled due to missing recovery email at ".date('Y-m-d', $this->now)." days."); + $this->logger->info("RecoveryWarningNotificationJob User $uid disabled due to missing recovery email at ".date('Y-m-d', $this->currentTime)." days."); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } @@ -181,6 +202,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { } /** * Method deleteExpiredDisabledUsers + * This method is used to delete expired disabled users * * @return void */ @@ -189,11 +211,11 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($uid); if ($user) { try { - // Delete account - $user->delete(); $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); - $this->logger->info("DailyRecoveryWarningNotificationJob User $uid permanently deleted on ".date('Y-m-d', $this->now)); + // Delete account + $user->delete(); + $this->logger->info("RecoveryWarningNotificationJob User $uid permanently deleted on ".date('Y-m-d', $this->currentTime)); } catch (\Throwable $e) { $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); } @@ -202,6 +224,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { } /** + * Method sendCloudNotifications * Send cloud notification to users * * @return void @@ -218,7 +241,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $this->sendNotificationToUsers($messageId, $notification, $this->uids); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -236,7 +259,7 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $user = $this->userManager->get($username); $this->sendNotificationToUser($messageId, $user, $notification); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -255,9 +278,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { try { $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); $this->notificationManager->notify($notification); - $this->logger->info('DailyRecoveryWarningNotificationJob Notificaiton sent to ' . $uid . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } /** @@ -290,9 +312,8 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); - $this->logger->info('DailyRecoveryWarningNotificationJob Recovery email sent to ' . $emailAddress . ' successfully.'); } catch (\Exception $e) { - $this->logger->error('DailyRecoveryWarningNotificationJob Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -355,7 +376,6 @@ class DailyRecoveryWarningNotificationJob extends TimedJob { return false; } $emailAddress = $user->getEMailAddress(); - $this->logger->info("DailyRecoveryWarningNotificationJob $emailAddress found.", ['exception' => '']); return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } } diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index cb75f1d..c1484b7 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -683,11 +683,12 @@ class RecoveryEmailService { */ public function checkIfSubscriptionActive(string $email): bool { $shopUsers = $this->shopAccountService->getUsers($email); - if (!empty($shopUsers)) { - foreach ($shopUsers as $shopUser) { - if ($shopUser['has_active_subscription']) { - return true; - } + if (empty($shopUsers)) { + return false; + } + foreach ($shopUsers as $shopUser) { + if ($shopUser['has_active_subscription']) { + return true; } } return false; -- GitLab From a35626e56b63411f47c1b9f3d59ac4efe42de9fe Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 23 Jun 2025 15:27:06 +0530 Subject: [PATCH 40/58] added type for every variable --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 946d5fa..aa9a835 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -46,14 +46,14 @@ class RecoveryWarningNotificationJob extends TimedJob { private LoggerInterface $logger; private IUserManager $userManager; private NotificationService $notificationService; - private $uids = []; - private $disableUids = []; - private $deleteUids = []; + private array $uids = []; + private array $disableUids = []; + private array $deleteUids = []; private ITimeFactory $timeFactory; private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; - private $currentTime; + private int $currentTime; private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; //30 to be // Days after which a user without recovery email is disabled private const DELETE_USER_AFTER_DISABLE_DAYS = 45; // 30+15 days to be // Days after which a disabled user is deleted private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; -- GitLab From 73970c6c78dd9fd1c5c7654dc09be9ca6850684f Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 23 Jun 2025 16:22:28 +0530 Subject: [PATCH 41/58] Murena SAS as Copyright --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index aa9a835..843f440 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -2,7 +2,7 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2025 Florent VINCENT + * @copyright Copyright (c) 2025 Murena SAS * * @author Ronak Patel * -- GitLab From 2b2e0ec12218bf990f521b8b5cfb0611c31ad434 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Mon, 23 Jun 2025 16:40:54 +0530 Subject: [PATCH 42/58] removed more debuggeer --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 843f440..c331697 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -160,10 +160,8 @@ class RecoveryWarningNotificationJob extends TimedJob { if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { if ($user->isEnabled()) { $this->disableUids[] = $user->getUID(); - $this->logger->debug("User {$user->getUID()} is enabled and will be added to disableUids."); return; } - $this->logger->debug("User {$user->getUID()} is already disabled. Skipping."); } } @@ -179,7 +177,6 @@ class RecoveryWarningNotificationJob extends TimedJob { private function identifyForDelete(IUser $user, int $age): void { if ($age >= self::DELETE_USER_AFTER_DISABLE_DAYS) { $this->deleteUids[] = $user->getUID(); - $this->logger->info("User {$user->getUID()} reached delete threshold and will be added to deleteUids."); } } /** @@ -194,9 +191,8 @@ class RecoveryWarningNotificationJob extends TimedJob { try { // Disable account $user->setEnabled(false); - $this->logger->info("RecoveryWarningNotificationJob User $uid disabled due to missing recovery email at ".date('Y-m-d', $this->currentTime)." days."); } catch (\Throwable $e) { - $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); + $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); } } } @@ -215,9 +211,8 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); // Delete account $user->delete(); - $this->logger->info("RecoveryWarningNotificationJob User $uid permanently deleted on ".date('Y-m-d', $this->currentTime)); } catch (\Throwable $e) { - $this->logger->error("Failed to delete user $uid. Error: " . $e->getMessage()); + $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); } } } @@ -296,7 +291,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $username = $user->getDisplayName(); $emailAddress = $user->getEMailAddress(); if (!$emailAddress) { - $this->logger->warning("No email address found for $uid, skipping."); + $this->logger->warning("RecoveryWarningNotificationJob No email address found for $uid, skipping."); continue; } $language = $this->config->getUserValue($uid, 'core', 'lang', null); -- GitLab From 01a2cb0b9f1dc81bf40b58256b24acf10d751c4b Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 24 Jun 2025 15:05:36 +0530 Subject: [PATCH 43/58] Dry-run added; and new config for disabledForRecoveryEmail added --- .../RecoveryWarningNotificationJob.php | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index c331697..868ed10 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -56,8 +56,9 @@ class RecoveryWarningNotificationJob extends TimedJob { private int $currentTime; private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; //30 to be // Days after which a user without recovery email is disabled private const DELETE_USER_AFTER_DISABLE_DAYS = 45; // 30+15 days to be // Days after which a disabled user is deleted - private const REMINDER_DAYS = [0, 15, 22, 26, 29, 30]; + private const REMINDER_DAYS_SINCE_START = [0, 15, 22, 26, 29, 30]; // at the top of the class: + private bool $dryRun = false; public function __construct( IConfig $config, @@ -97,6 +98,10 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->logger->error('RecoveryWarningNotificationJob messageId not set!'); return; } + $this->dryRun = $this->config->getSystemValueBool('recovery_email_dry_run', false); + if ($this->dryRun) { + $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); + } $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUnverifiedUsers(); @@ -140,7 +145,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); } $age = $this->recoveryEmailService->getDaysSinceDate($startDate); - if (in_array($age, self::REMINDER_DAYS)) { + if (in_array($age, self::REMINDER_DAYS_SINCE_START)) { $this->uids[] = $uid; } $this->identifyForDisable($user, $age); @@ -188,11 +193,19 @@ class RecoveryWarningNotificationJob extends TimedJob { private function disableUnverifiedUsers(): void { foreach ($this->disableUids as $uid) { $user = $this->userManager->get($uid); + if (!$user) { + continue; + } + if ($this->dryRun) { + $this->logger->info("[Dry Run] Would disable user: $uid"); + continue; + } try { - // Disable account $user->setEnabled(false); + $this->config->setUserValue($uid, Application::APP_ID, 'disabledForRecoveryEmail', '1'); + $this->logger->info("User $uid has been disabled due to missing recovery email."); } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); + $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage()); } } } @@ -205,19 +218,25 @@ class RecoveryWarningNotificationJob extends TimedJob { private function deleteExpiredDisabledUsers(): void { foreach ($this->deleteUids as $uid) { $user = $this->userManager->get($uid); - if ($user) { - try { - $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); - $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); - // Delete account - $user->delete(); - } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); - } + if (!$user) { + continue; + } + if ($this->dryRun) { + $this->logger->info("[Dry Run] Would delete user: $uid"); + continue; + } + try { + $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); + $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); + $user->delete(); + $this->logger->info("User $uid has been deleted."); + } catch (\Throwable $e) { + $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); } } } + /** * Method sendCloudNotifications * Send cloud notification to users @@ -250,6 +269,10 @@ class RecoveryWarningNotificationJob extends TimedJob { */ protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { foreach ($users as $username) { + if ($this->dryRun) { + $this->logger->info("[Dry Run] Would send notification to user: $username"); + continue; + } try { $user = $this->userManager->get($username); $this->sendNotificationToUser($messageId, $user, $notification); @@ -286,6 +309,10 @@ class RecoveryWarningNotificationJob extends TimedJob { */ private function sendEmails(string $messageId): void { foreach ($this->uids as $uid) { + if ($this->dryRun) { + $this->logger->info("[Dry Run] Would send notification to user: $uid"); + continue; + } try { $user = $this->userManager->get($uid); $username = $user->getDisplayName(); -- GitLab From 7cc23766af740281d83690c8029543462876bbb1 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 24 Jun 2025 17:56:29 +0530 Subject: [PATCH 44/58] Added PHPDoc --- .../RecoveryWarningNotificationJob.php | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 868ed10..48d4062 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -40,6 +40,14 @@ use OCP\Notification\INotification; use Psr\Log\LoggerInterface; use OCA\EmailRecovery\Service\RecoveryEmailService; +/** + * Class RecoveryWarningNotificationJob + * + * This background job handles sending warning notifications and emails to users who have not set a recovery email. + * It also disables or deletes users after certain periods if they remain unverified. + * + * @package OCA\EmailRecovery\BackgroundJob + */ class RecoveryWarningNotificationJob extends TimedJob { private IConfig $config; private IMailer $mailer; @@ -54,10 +62,21 @@ class RecoveryWarningNotificationJob extends TimedJob { private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; private int $currentTime; - private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; //30 to be // Days after which a user without recovery email is disabled - private const DELETE_USER_AFTER_DISABLE_DAYS = 45; // 30+15 days to be // Days after which a disabled user is deleted + /** + * @var int Days after which a user without recovery email is disabled + */ + private const DISABLE_USER_AFTER_UNVERIFIED_DAYS = 30; + /** + * @var int Days after which a disabled user is deleted + */ + private const DELETE_USER_AFTER_DISABLE_DAYS = 45; + /** + * @var int[] Days since start to send reminders + */ private const REMINDER_DAYS_SINCE_START = [0, 15, 22, 26, 29, 30]; - // at the top of the class: + /** + * @var bool If true, actions are logged but not performed + */ private bool $dryRun = false; public function __construct( @@ -89,6 +108,13 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->currentTime = $this->timeFactory->getTime(); // string time } + /** + * Run the background job to process recovery warnings, notifications, and user status changes. + * + * @param mixed $argument Not used. + * + * @return void + */ protected function run($argument): void { try { $this->logger->info('RecoveryWarningNotificationJob job started.'); @@ -113,8 +139,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } } /** - * Method identifyUnverifiedUsers - * This is used to identify unverfied users + * Identify users who have not set a recovery email and queue them for notifications, disabling, or deletion. * * @return void */ @@ -153,11 +178,10 @@ class RecoveryWarningNotificationJob extends TimedJob { } } /** - * Method identifyForDisable * Identifies if the user qualifies for disabling and queues them. * - * @param IUser $user [explicite description] - * @param int $age [explicite description] + * @param IUser $user The user to check + * @param int $age Days since reminder start * * @return void */ @@ -171,11 +195,10 @@ class RecoveryWarningNotificationJob extends TimedJob { } /** - * Method identifyForDelete * Identifies if the user qualifies for deletion and queues them. * - * @param IUser $user [explicite description] - * @param int $age [explicite description] + * @param IUser $user The user to check + * @param int $age Days since reminder start * * @return void */ @@ -185,8 +208,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } } /** - * Method disableUnverifiedUsers - * This method is used to disable unverified users + * Disable users who have not set a recovery email after the configured period. * * @return void */ @@ -210,8 +232,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } } /** - * Method deleteExpiredDisabledUsers - * This method is used to delete expired disabled users + * Delete users who have been disabled for longer than the configured period. * * @return void */ @@ -238,8 +259,9 @@ class RecoveryWarningNotificationJob extends TimedJob { /** - * Method sendCloudNotifications - * Send cloud notification to users + * Send cloud notifications to users who need to be reminded. + * + * @param string $messageId The identifier for the notification message. * * @return void */ @@ -263,7 +285,7 @@ class RecoveryWarningNotificationJob extends TimedJob { * * @param string $messageId The identifier for the notification message. * @param INotification $notification The notification object to be sent. - * @param array $users An array of usernames to whom the notification will be sent. + * @param string[] $users An array of user IDs to whom the notification will be sent. * * @return void */ @@ -301,7 +323,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } } /** - * Sends recovery emails to all users. + * Sends recovery emails to all users who need to be reminded. * * @param string $messageId The identifier for the notification message. * @@ -341,7 +363,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } /** - * Send an email. + * Send an email to a user. * * @param string $subject The subject of the email. * @param string $message The body/content of the email. @@ -366,10 +388,10 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->mailer->send($email); } /** - * Special-treat list items and strip empty lines + * Add the message body to the email template, handling line breaks and formatting. * - * @param IEMailTemplate $template - * @param string $message + * @param IEMailTemplate $template The email template object. + * @param string $message The message body. * * @return void */ @@ -387,7 +409,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $template->addBodyText($finalHtml, $finalText); } /** - * Validate a user. + * Validate a user for notification and email purposes. * * @param IUser|null $user The user to be validated. * -- GitLab From 2c781f4cf79ffd52d025ebd2196193de1f829af2 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 24 Jun 2025 18:00:00 +0530 Subject: [PATCH 45/58] added disabled and deleted logs --- .../RecoveryWarningNotificationJob.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 48d4062..d63d3a7 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -226,6 +226,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $user->setEnabled(false); $this->config->setUserValue($uid, Application::APP_ID, 'disabledForRecoveryEmail', '1'); $this->logger->info("User $uid has been disabled due to missing recovery email."); + $this->logDisabledUser($uid); } catch (\Throwable $e) { $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage()); } @@ -251,6 +252,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); $user->delete(); $this->logger->info("User $uid has been deleted."); + $this->logDeletedUser($uid); } catch (\Throwable $e) { $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); } @@ -422,4 +424,37 @@ class RecoveryWarningNotificationJob extends TimedJob { $emailAddress = $user->getEMailAddress(); return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } + /** + * Log a disabled user to the custom log file. + * + * @param string $uid The user ID that was disabled. + * @return void + */ + private function logDisabledUser(string $uid): void { + $logDir = dirname(__DIR__, 2) . '/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0770, true); + } + $logFile = $logDir . '/disabled_users.log'; + $timestamp = date('Y-m-d H:i:s'); + $entry = "[$timestamp] Disabled user: $uid\n"; + file_put_contents($logFile, $entry, FILE_APPEND); + } + + /** + * Log a deleted user to the custom log file. + * + * @param string $uid The user ID that was deleted. + * @return void + */ + private function logDeletedUser(string $uid): void { + $logDir = dirname(__DIR__, 2) . '/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0770, true); + } + $logFile = $logDir . '/deleted_users.log'; + $timestamp = date('Y-m-d H:i:s'); + $entry = "[$timestamp] Deleted user: $uid\n"; + file_put_contents($logFile, $entry, FILE_APPEND); + } } -- GitLab From 0a4ebd5f6c4f56d0fd849a7b9f2658e355bfcc8c Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 24 Jun 2025 18:02:37 +0530 Subject: [PATCH 46/58] Refactor: Add custom logging for disabled and deleted users, optimize logging logic, and improve maintainability. Logs are now written to logs/disabled_users.log and logs/deleted_users.log outside of Nextcloud log rotation. --- .../RecoveryWarningNotificationJob.php | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index d63d3a7..94cd75e 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -425,22 +425,34 @@ class RecoveryWarningNotificationJob extends TimedJob { return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } /** - * Log a disabled user to the custom log file. + * Log a user action to the custom log file. * - * @param string $uid The user ID that was disabled. + * @param string $uid The user ID. + * @param string $action The action performed (e.g., 'Disabled', 'Deleted'). + * @param string $filename The log file name. * @return void */ - private function logDisabledUser(string $uid): void { + private function logUserAction(string $uid, string $action, string $filename): void { $logDir = dirname(__DIR__, 2) . '/logs'; if (!is_dir($logDir)) { mkdir($logDir, 0770, true); } - $logFile = $logDir . '/disabled_users.log'; + $logFile = $logDir . '/' . $filename; $timestamp = date('Y-m-d H:i:s'); - $entry = "[$timestamp] Disabled user: $uid\n"; + $entry = "[$timestamp] $action user: $uid\n"; file_put_contents($logFile, $entry, FILE_APPEND); } + /** + * Log a disabled user to the custom log file. + * + * @param string $uid The user ID that was disabled. + * @return void + */ + private function logDisabledUser(string $uid): void { + $this->logUserAction($uid, 'Disabled', 'disabled_users.log'); + } + /** * Log a deleted user to the custom log file. * @@ -448,13 +460,6 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logDeletedUser(string $uid): void { - $logDir = dirname(__DIR__, 2) . '/logs'; - if (!is_dir($logDir)) { - mkdir($logDir, 0770, true); - } - $logFile = $logDir . '/deleted_users.log'; - $timestamp = date('Y-m-d H:i:s'); - $entry = "[$timestamp] Deleted user: $uid\n"; - file_put_contents($logFile, $entry, FILE_APPEND); + $this->logUserAction($uid, 'Deleted', 'deleted_users.log'); } } -- GitLab From 9f3c0f08118e2eb46e11b581644877da290439ca Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Tue, 24 Jun 2025 23:43:13 +0530 Subject: [PATCH 47/58] camelcase issue, public functions --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 2 +- lib/Service/NotificationService.php | 12 ++++++------ lib/Service/RecoveryEmailService.php | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 94cd75e..b6623a9 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -157,7 +157,7 @@ class RecoveryWarningNotificationJob extends TimedJob { } $uid = $user->getUID(); $emailAddress = $user->getEMailAddress(); - if ($this->recoveryEmailService->checkIfSubscriptionActive($emailAddress)) { + if ($this->recoveryEmailService->hasActiveSubscription($emailAddress)) { continue; // skip if subscription active } $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); diff --git a/lib/Service/NotificationService.php b/lib/Service/NotificationService.php index 51a1950..cc0fa89 100644 --- a/lib/Service/NotificationService.php +++ b/lib/Service/NotificationService.php @@ -19,7 +19,7 @@ class NotificationService { * @param string $message * @param string $username */ - public function getParsedString(string $message, string $username, string $disable_date = '') { + public function getParsedString(string $message, string $username, string $disableDate = '') { $richString = $message; $data = $this->prepareRichString($message, $richString, $username, 'url'); @@ -34,8 +34,8 @@ class NotificationService { $message = $data['message']; $richString = $data['richString']; - if ($disable_date != '') { - $data = $this->prepareRichString($message, $richString, $username, 'disable_date', $disable_date); + if ($disableDate != '') { + $data = $this->prepareRichString($message, $richString, $username, 'disable_date', $disableDate); $message = $data['message']; $richString = $data['richString']; } @@ -49,7 +49,7 @@ class NotificationService { * @param string $username * @param string $type */ - private function prepareRichString($message, $richString, $username, $type, $disable_date = '') { + private function prepareRichString($message, $richString, $username, $type, $disableDate = '') { switch ($type) { case 'url': $richString = preg_replace('/\[(.*?)\]\((.*?)\)/', '{$1[$2]}', $message); @@ -71,8 +71,8 @@ class NotificationService { break; case 'disable_date': - $richString = str_replace('{disable_date}', $disable_date, $richString); - $message = str_replace('{disable_date}', $disable_date, $message); + $richString = str_replace('{disable_date}', $disableDate, $richString); + $message = str_replace('{disable_date}', $disableDate, $message); break; } return ['message' => $message, 'richString' => $richString]; diff --git a/lib/Service/RecoveryEmailService.php b/lib/Service/RecoveryEmailService.php index c1484b7..10af329 100644 --- a/lib/Service/RecoveryEmailService.php +++ b/lib/Service/RecoveryEmailService.php @@ -51,7 +51,7 @@ class RecoveryEmailService { private const RATE_LIMIT_EMAIL = 'verifymail_email_ratelimit'; private const RATE_LIMIT_DOMAIN = 'verifymail_domain_ratelimit'; private const RECOVERY_EMAIL_REMINDER_START_DATE = 'recovery-email-reminder-start-date'; - private const UNVERIFIED_USER_DISABLE_AT = 'unverified-user-disable-at'; + private const UNVERIFIED_USER_DISABLE_AT = 'unverified-user-disabled-at'; private $cache; @@ -669,19 +669,19 @@ class RecoveryEmailService { /** * Calculates the number of days between two timestamps. */ - public function calculateDayDifference($fromTimestamp, $toTimestamp): int { + private function calculateDayDifference($fromTimestamp, $toTimestamp): int { return (int)(($toTimestamp - $fromTimestamp) / 86400); } /** - * Method checkIfSubscriptionActive + * Method hasActiveSubscription * * @param string $email [explicite description] * * @return bool */ - public function checkIfSubscriptionActive(string $email): bool { + public function hasActiveSubscription(string $email): bool { $shopUsers = $this->shopAccountService->getUsers($email); if (empty($shopUsers)) { return false; -- GitLab From ca542e317b1cffa779063e60fb9051e76a4c827d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 25 Jun 2025 00:35:40 +0530 Subject: [PATCH 48/58] ordered correctly --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index b6623a9..9ea416d 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -118,16 +118,16 @@ class RecoveryWarningNotificationJob extends TimedJob { protected function run($argument): void { try { $this->logger->info('RecoveryWarningNotificationJob job started.'); - $this->identifyUnverifiedUsers(); $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); if (!$messageId) { $this->logger->error('RecoveryWarningNotificationJob messageId not set!'); return; } - $this->dryRun = $this->config->getSystemValueBool('recovery_email_dry_run', false); + $this->dryRun = $this->config->getSystemValue('recovery_email_dry_run', false); if ($this->dryRun) { $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); } + $this->identifyUnverifiedUsers(); $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUnverifiedUsers(); -- GitLab From 4c4d7c45e3693dec5aa1d47cc200592a65ae5c01 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 25 Jun 2025 01:13:55 +0530 Subject: [PATCH 49/58] added log path --- .../RecoveryWarningNotificationJob.php | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 9ea416d..7bcbc3c 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -92,7 +92,6 @@ class RecoveryWarningNotificationJob extends TimedJob { ) { parent::__construct($timeFactory); - // $this->setInterval(2 * 60); // Run every 2 minutes $this->setInterval(1 * 24 * 60 * 60); // Run for 1 days $this->setTimeSensitivity(self::TIME_INSENSITIVE); @@ -128,11 +127,14 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); } $this->identifyUnverifiedUsers(); - $this->sendCloudNotifications($messageId); - $this->sendEmails($messageId); + // $this->sendCloudNotifications($messageId); + // $this->sendEmails($messageId); $this->disableUnverifiedUsers(); - $this->deleteExpiredDisabledUsers(); + // $this->deleteExpiredDisabledUsers(); $this->logger->info('RecoveryWarningNotificationJob job Finished '); + $this->logger->info('RecoveryWarningNotificationJob : uids'.json_encode($this->uids)); + $this->logger->info('RecoveryWarningNotificationJob : disableUids'.json_encode($this->disableUids)); + $this->logger->info('RecoveryWarningNotificationJob : deleteUids'.json_encode($this->deleteUids)); } catch (\Exception $e) { $this->logger->error('RecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); return; @@ -151,6 +153,10 @@ class RecoveryWarningNotificationJob extends TimedJob { } foreach ($users as $username) { $user = $this->userManager->get($username); + if ($username != 'ronak') { + $this->logger->info('RecoveryWarningNotificationJob : user'.json_encode($user)); + continue; + } if (!$this->isUserValid($user)) { $this->logger->debug("RecoveryWarningNotificationJob $username not valid."); continue; @@ -175,6 +181,8 @@ class RecoveryWarningNotificationJob extends TimedJob { } $this->identifyForDisable($user, $age); $this->identifyForDelete($user, $age); + $this->disableUids[] = '23jan01'; + $this->deleteUids[] = '23jan01'; } } /** @@ -219,7 +227,7 @@ class RecoveryWarningNotificationJob extends TimedJob { continue; } if ($this->dryRun) { - $this->logger->info("[Dry Run] Would disable user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would disable user: $uid"); continue; } try { @@ -244,7 +252,7 @@ class RecoveryWarningNotificationJob extends TimedJob { continue; } if ($this->dryRun) { - $this->logger->info("[Dry Run] Would delete user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would delete user: $uid"); continue; } try { @@ -294,7 +302,7 @@ class RecoveryWarningNotificationJob extends TimedJob { protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { foreach ($users as $username) { if ($this->dryRun) { - $this->logger->info("[Dry Run] Would send notification to user: $username"); + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $username"); continue; } try { @@ -334,7 +342,7 @@ class RecoveryWarningNotificationJob extends TimedJob { private function sendEmails(string $messageId): void { foreach ($this->uids as $uid) { if ($this->dryRun) { - $this->logger->info("[Dry Run] Would send notification to user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $uid"); continue; } try { @@ -433,11 +441,12 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $logDir = dirname(__DIR__, 2) . '/logs'; - if (!is_dir($logDir)) { - mkdir($logDir, 0770, true); + $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', \OC::$server->getConfig()->getSystemValue('datadirectory') . '/email-recovery-logs'); + if (!is_dir($defaultLogDir)) { + mkdir($defaultLogDir, 0770, true); } - $logFile = $logDir . '/' . $filename; + + $logFile = rtrim($defaultLogDir, '/') . '/' . $filename; $timestamp = date('Y-m-d H:i:s'); $entry = "[$timestamp] $action user: $uid\n"; file_put_contents($logFile, $entry, FILE_APPEND); -- GitLab From 996f9c05179554c69f5fe2fe0dbc203ef2d32443 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 25 Jun 2025 23:56:41 +0530 Subject: [PATCH 50/58] changed in getSystemValue --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 7bcbc3c..0d429ac 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -441,7 +441,7 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', \OC::$server->getConfig()->getSystemValue('datadirectory') . '/email-recovery-logs'); + $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', $this->config->getSystemValue('datadirectory') . '/email-recovery-logs'); if (!is_dir($defaultLogDir)) { mkdir($defaultLogDir, 0770, true); } -- GitLab From d831e30138610d7eb54430d166e2472dd63b538c Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Wed, 25 Jun 2025 23:58:16 +0530 Subject: [PATCH 51/58] default log path changes --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 0d429ac..4e3b300 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -441,7 +441,7 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', $this->config->getSystemValue('datadirectory') . '/email-recovery-logs'); + $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); if (!is_dir($defaultLogDir)) { mkdir($defaultLogDir, 0770, true); } -- GitLab From b1ee5ccdc7ef3807305ee7165d6e62ca9e7d9fc5 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 00:06:08 +0530 Subject: [PATCH 52/58] removed unncessary things --- .../RecoveryWarningNotificationJob.php | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 4e3b300..5927484 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -61,7 +61,6 @@ class RecoveryWarningNotificationJob extends TimedJob { private INotificationManager $notificationManager; private IURLGenerator $urlGenerator; private RecoveryEmailService $recoveryEmailService; - private int $currentTime; /** * @var int Days after which a user without recovery email is disabled */ @@ -104,7 +103,6 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->notificationManager = $notificationManager; $this->urlGenerator = $urlGenerator; $this->recoveryEmailService = $recoveryEmailService; - $this->currentTime = $this->timeFactory->getTime(); // string time } /** @@ -127,14 +125,11 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); } $this->identifyUnverifiedUsers(); - // $this->sendCloudNotifications($messageId); - // $this->sendEmails($messageId); + $this->sendCloudNotifications($messageId); + $this->sendEmails($messageId); $this->disableUnverifiedUsers(); - // $this->deleteExpiredDisabledUsers(); + $this->deleteExpiredDisabledUsers(); $this->logger->info('RecoveryWarningNotificationJob job Finished '); - $this->logger->info('RecoveryWarningNotificationJob : uids'.json_encode($this->uids)); - $this->logger->info('RecoveryWarningNotificationJob : disableUids'.json_encode($this->disableUids)); - $this->logger->info('RecoveryWarningNotificationJob : deleteUids'.json_encode($this->deleteUids)); } catch (\Exception $e) { $this->logger->error('RecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); return; @@ -153,10 +148,6 @@ class RecoveryWarningNotificationJob extends TimedJob { } foreach ($users as $username) { $user = $this->userManager->get($username); - if ($username != 'ronak') { - $this->logger->info('RecoveryWarningNotificationJob : user'.json_encode($user)); - continue; - } if (!$this->isUserValid($user)) { $this->logger->debug("RecoveryWarningNotificationJob $username not valid."); continue; @@ -168,11 +159,11 @@ class RecoveryWarningNotificationJob extends TimedJob { } $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); if ($startDate === null) { - $startDate = date('Y-m-d', $this->currentTime); + $startDate = date('Y-m-d', time()); $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); } if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { - $disableAt = date('Y-m-d', $this->currentTime + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); + $disableAt = date('Y-m-d', time() + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); } $age = $this->recoveryEmailService->getDaysSinceDate($startDate); @@ -181,8 +172,6 @@ class RecoveryWarningNotificationJob extends TimedJob { } $this->identifyForDisable($user, $age); $this->identifyForDelete($user, $age); - $this->disableUids[] = '23jan01'; - $this->deleteUids[] = '23jan01'; } } /** @@ -233,7 +222,6 @@ class RecoveryWarningNotificationJob extends TimedJob { try { $user->setEnabled(false); $this->config->setUserValue($uid, Application::APP_ID, 'disabledForRecoveryEmail', '1'); - $this->logger->info("User $uid has been disabled due to missing recovery email."); $this->logDisabledUser($uid); } catch (\Throwable $e) { $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage()); @@ -259,7 +247,6 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); $user->delete(); - $this->logger->info("User $uid has been deleted."); $this->logDeletedUser($uid); } catch (\Throwable $e) { $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); @@ -441,12 +428,11 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); - if (!is_dir($defaultLogDir)) { - mkdir($defaultLogDir, 0770, true); + $logDir = dirname(__DIR__, 2) . '/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0770, true); } - - $logFile = rtrim($defaultLogDir, '/') . '/' . $filename; + $logFile = $logDir . '/' . $filename; $timestamp = date('Y-m-d H:i:s'); $entry = "[$timestamp] $action user: $uid\n"; file_put_contents($logFile, $entry, FILE_APPEND); -- GitLab From 78140ac4b653d03f941e33a9bc444598c0e66937 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 00:11:08 +0530 Subject: [PATCH 53/58] log issue --- lib/BackgroundJob/RecoveryWarningNotificationJob.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index 5927484..da17a03 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -428,11 +428,12 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $logDir = dirname(__DIR__, 2) . '/logs'; - if (!is_dir($logDir)) { - mkdir($logDir, 0770, true); + $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); + if (!is_dir($defaultLogDir)) { + mkdir($defaultLogDir, 0770, true); } - $logFile = $logDir . '/' . $filename; + + $logFile = rtrim($defaultLogDir, '/') . '/' . $filename; $timestamp = date('Y-m-d H:i:s'); $entry = "[$timestamp] $action user: $uid\n"; file_put_contents($logFile, $entry, FILE_APPEND); -- GitLab From af9260fd2e238c576f0c578ea4a28e0d8bc4e348 Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 00:20:11 +0530 Subject: [PATCH 54/58] feat: improve error handling and logging in RecoveryWarningNotificationJob - Replace getSystemValue with getSystemValueBool for dry-run setting - Add comprehensive try-catch blocks around all operations - Add missing success logging for user disable/delete operations - Improve error messages with better context and exception details - Add user existence validation before operations - Enhance file operation error handling in logUserAction - Standardize exception handling using \Throwable throughout - Add proper error handling for directory creation and file writing --- .../RecoveryWarningNotificationJob.php | 132 +++++++++++------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index da17a03..bbff4f9 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -120,7 +120,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->logger->error('RecoveryWarningNotificationJob messageId not set!'); return; } - $this->dryRun = $this->config->getSystemValue('recovery_email_dry_run', false); + $this->dryRun = $this->config->getSystemValueBool('recovery_email_dry_run', false); if ($this->dryRun) { $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); } @@ -129,8 +129,8 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->sendEmails($messageId); $this->disableUnverifiedUsers(); $this->deleteExpiredDisabledUsers(); - $this->logger->info('RecoveryWarningNotificationJob job Finished '); - } catch (\Exception $e) { + $this->logger->info('RecoveryWarningNotificationJob job finished.'); + } catch (\Throwable $e) { $this->logger->error('RecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); return; } @@ -211,20 +211,21 @@ class RecoveryWarningNotificationJob extends TimedJob { */ private function disableUnverifiedUsers(): void { foreach ($this->disableUids as $uid) { - $user = $this->userManager->get($uid); - if (!$user) { - continue; - } - if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would disable user: $uid"); - continue; - } try { + $user = $this->userManager->get($uid); + if (!$user) { + $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for disabling."); + continue; + } + if ($this->dryRun) { + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would disable user: $uid"); + continue; + } $user->setEnabled(false); $this->config->setUserValue($uid, Application::APP_ID, 'disabledForRecoveryEmail', '1'); $this->logDisabledUser($uid); } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage()); + $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage(), ['exception' => $e]); } } } @@ -235,21 +236,22 @@ class RecoveryWarningNotificationJob extends TimedJob { */ private function deleteExpiredDisabledUsers(): void { foreach ($this->deleteUids as $uid) { - $user = $this->userManager->get($uid); - if (!$user) { - continue; - } - if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would delete user: $uid"); - continue; - } try { + $user = $this->userManager->get($uid); + if (!$user) { + $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for deletion."); + continue; + } + if ($this->dryRun) { + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would delete user: $uid"); + continue; + } $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); $user->delete(); $this->logDeletedUser($uid); } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage()); + $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage(), ['exception' => $e]); } } } @@ -273,7 +275,7 @@ class RecoveryWarningNotificationJob extends TimedJob { ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath(Application::APP_ID, strtolower($notificationType) . '.svg'))); $this->sendNotificationToUsers($messageId, $notification, $this->uids); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); } } @@ -288,15 +290,19 @@ class RecoveryWarningNotificationJob extends TimedJob { */ protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { foreach ($users as $username) { - if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $username"); - continue; - } try { + if ($this->dryRun) { + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $username"); + continue; + } $user = $this->userManager->get($username); + if (!$user) { + $this->logger->warning("RecoveryWarningNotificationJob User $username not found for notification."); + continue; + } $this->sendNotificationToUser($messageId, $user, $notification); - } catch (\Exception $e) { - $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + } catch (\Throwable $e) { + $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to user ' . $username . '. Error:' . $e->getMessage(), ['exception' => $e]); } } } @@ -315,7 +321,7 @@ class RecoveryWarningNotificationJob extends TimedJob { try { $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); $this->notificationManager->notify($notification); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->error('RecoveryWarningNotificationJob Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } @@ -328,12 +334,16 @@ class RecoveryWarningNotificationJob extends TimedJob { */ private function sendEmails(string $messageId): void { foreach ($this->uids as $uid) { - if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $uid"); - continue; - } try { + if ($this->dryRun) { + $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $uid"); + continue; + } $user = $this->userManager->get($uid); + if (!$user) { + $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for email."); + continue; + } $username = $user->getDisplayName(); $emailAddress = $user->getEMailAddress(); if (!$emailAddress) { @@ -353,7 +363,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->error('RecoveryWarningNotificationJob Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); } } @@ -369,20 +379,24 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function sendEmail(string $subject, string $message, string $emailAddress): void { - // Convert Markdown-style links to HTML anchor tags - $message = preg_replace('/\[(.*?)\]\((.*?)\)/', "$1", $message); - $template = $this->mailer->createEMailTemplate(Application::APP_ID . '::sendMail'); - $template->setSubject($subject); - $template->addHeader(); - $template->addHeading($subject); - $this->setMailBody($template, $message); - $template->addFooter(); + try { + // Convert Markdown-style links to HTML anchor tags + $message = preg_replace('/\[(.*?)\]\((.*?)\)/', "$1", $message); + $template = $this->mailer->createEMailTemplate(Application::APP_ID . '::sendMail'); + $template->setSubject($subject); + $template->addHeader(); + $template->addHeading($subject); + $this->setMailBody($template, $message); + $template->addFooter(); - $email = $this->mailer->createMessage(); - $email->useTemplate($template); - $email->setTo([$emailAddress]); + $email = $this->mailer->createMessage(); + $email->useTemplate($template); + $email->setTo([$emailAddress]); - $this->mailer->send($email); + $this->mailer->send($email); + } catch (\Throwable $e) { + $this->logger->error('RecoveryWarningNotificationJob Failed to send email to ' . $emailAddress . '. Error:' . $e->getMessage(), ['exception' => $e]); + } } /** * Add the message body to the email template, handling line breaks and formatting. @@ -428,15 +442,25 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function logUserAction(string $uid, string $action, string $filename): void { - $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); - if (!is_dir($defaultLogDir)) { - mkdir($defaultLogDir, 0770, true); + try { + $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); + if (!is_dir($defaultLogDir)) { + if (!mkdir($defaultLogDir, 0770, true)) { + $this->logger->error("RecoveryWarningNotificationJob Failed to create log directory: $defaultLogDir"); + return; + } + } + + $logFile = rtrim($defaultLogDir, '/') . '/' . $filename; + $timestamp = date('Y-m-d H:i:s'); + $entry = "[$timestamp] $action user: $uid\n"; + + if (file_put_contents($logFile, $entry, FILE_APPEND) === false) { + $this->logger->error("RecoveryWarningNotificationJob Failed to write to log file: $logFile"); + } + } catch (\Throwable $e) { + $this->logger->error("RecoveryWarningNotificationJob Failed to log user action for $uid. Error: " . $e->getMessage(), ['exception' => $e]); } - - $logFile = rtrim($defaultLogDir, '/') . '/' . $filename; - $timestamp = date('Y-m-d H:i:s'); - $entry = "[$timestamp] $action user: $uid\n"; - file_put_contents($logFile, $entry, FILE_APPEND); } /** -- GitLab From 67c164a4a312a9eb946a270c3e200ee53a5fc62c Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 07:46:41 +0530 Subject: [PATCH 55/58] Test Command file added --- README.md | 5 + appinfo/info.xml | 1 + lib/Command/TestRecoveryWarningJob.php | 177 +++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 lib/Command/TestRecoveryWarningJob.php diff --git a/README.md b/README.md index 1449852..7551795 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,9 @@ `occ email-recovery:create-popular-domains` +### Test Weekly Email Report BG as Command +# Test in dry-run mode (recommended) +occ email-recovery:test-recovery-warning-job --dry-run +# Run the actual job +occ email-recovery:test-recovery-warning-job \ No newline at end of file diff --git a/appinfo/info.xml b/appinfo/info.xml index f12eca2..75d8f15 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -24,5 +24,6 @@ OCA\EmailRecovery\Command\UpdateBlacklistedDomains OCA\EmailRecovery\Command\CreatePopularDomain OCA\EmailRecovery\Command\SpamAccountDetection + OCA\EmailRecovery\Command\TestRecoveryWarningJob diff --git a/lib/Command/TestRecoveryWarningJob.php b/lib/Command/TestRecoveryWarningJob.php new file mode 100644 index 0000000..52be536 --- /dev/null +++ b/lib/Command/TestRecoveryWarningJob.php @@ -0,0 +1,177 @@ +config = $config; + $this->mailer = $mailer; + $this->logger = $logger; + $this->userManager = $userManager; + $this->notificationService = $notificationService; + $this->timeFactory = $timeFactory; + $this->notificationManager = $notificationManager; + $this->urlGenerator = $urlGenerator; + $this->recoveryEmailService = $recoveryEmailService; + } + + protected function configure() { + $this + ->setName(Application::APP_ID.':test-recovery-warning-job') + ->setDescription('Test the recovery warning notification background job') + ->addOption( + 'dry-run', + 'd', + InputOption::VALUE_NONE, + 'Run in dry-run mode (no actual actions performed)' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $dryRun = $input->getOption('dry-run'); + + $output->writeln('Testing Recovery Warning Notification Job'); + $output->writeln('=========================================='); + + // Check if messageId is configured + $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); + if (!$messageId) { + $output->writeln('Error: account_recovery_warning_messageid not configured!'); + $output->writeln('Set it with: occ config:system:set account_recovery_warning_messageid --value="20250610_account_recovery_warning"'); + return Command::FAILURE; + } + + $output->writeln("Message ID: $messageId"); + $output->writeln("Dry run mode: " . ($dryRun ? 'Yes' : 'No')); + + // Set dry run mode if requested + if ($dryRun) { + $this->config->setSystemValue('recovery_email_dry_run', true); + $output->writeln('Dry run mode enabled - no actual actions will be performed'); + } + + try { + // Create and run the background job + $job = new RecoveryWarningNotificationJob( + $this->config, + $this->mailer, + $this->userManager, + $this->logger, + $this->notificationService, + $this->timeFactory, + $this->notificationManager, + $this->urlGenerator, + $this->recoveryEmailService + ); + + $output->writeln('Executing recovery warning notification job...'); + $job->run(null); + + $output->writeln('Job completed successfully!'); + + // Show summary + $this->showSummary($output, $dryRun); + + } catch (\Throwable $e) { + $output->writeln('Error running job: ' . $e->getMessage() . ''); + $this->logger->error('Error running recovery warning notification job', ['exception' => $e]); + return Command::FAILURE; + } finally { + // Clean up dry run mode + if ($dryRun) { + $this->config->setSystemValue('recovery_email_dry_run', false); + } + } + + return Command::SUCCESS; + } + + private function showSummary(OutputInterface $output, bool $dryRun): void { + $output->writeln(''); + $output->writeln('Summary:'); + $output->writeln('--------'); + + // Count users without recovery email + $usersWithoutRecoveryEmail = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); + $countWithoutRecoveryEmail = is_array($usersWithoutRecoveryEmail) ? count($usersWithoutRecoveryEmail) : 0; + + // Count total users + $allUsers = $this->userManager->search(''); + $countWithRecoveryEmail = 0; + $countEnabled = 0; + $countDisabled = 0; + + foreach ($allUsers as $user) { + $recoveryEmail = $this->recoveryEmailService->getRecoveryEmail($user->getUID()); + if ($recoveryEmail) { + $countWithRecoveryEmail++; + } + if ($user->isEnabled()) { + $countEnabled++; + } else { + $countDisabled++; + } + } + + $output->writeln("Total users: " . count($allUsers)); + $output->writeln("Users with recovery email: $countWithRecoveryEmail"); + $output->writeln("Users without recovery email: $countWithoutRecoveryEmail"); + $output->writeln("Enabled users: $countEnabled"); + $output->writeln("Disabled users: $countDisabled"); + + if ($dryRun) { + $output->writeln(''); + $output->writeln('This was a dry run. Check the logs for detailed information.'); + } else { + $output->writeln(''); + $output->writeln('Check the logs for detailed job execution information.'); + } + + // Show log file locations + $logPath = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); + $output->writeln(''); + $output->writeln('Log files:'); + $output->writeln("- Custom logs: $logPath/disabled_users.log"); + $output->writeln("- Custom logs: $logPath/deleted_users.log"); + $output->writeln('- Nextcloud logs: Check your Nextcloud log files'); + } +} \ No newline at end of file -- GitLab From f2610465e9aceca39f1b7d4d8a7cf9926dbe6c5e Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 07:48:17 +0530 Subject: [PATCH 56/58] lint php --- lib/Command/TestRecoveryWarningJob.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Command/TestRecoveryWarningJob.php b/lib/Command/TestRecoveryWarningJob.php index 52be536..a2f02a1 100644 --- a/lib/Command/TestRecoveryWarningJob.php +++ b/lib/Command/TestRecoveryWarningJob.php @@ -110,7 +110,6 @@ class TestRecoveryWarningJob extends Command { // Show summary $this->showSummary($output, $dryRun); - } catch (\Throwable $e) { $output->writeln('Error running job: ' . $e->getMessage() . ''); $this->logger->error('Error running recovery warning notification job', ['exception' => $e]); @@ -174,4 +173,4 @@ class TestRecoveryWarningJob extends Command { $output->writeln("- Custom logs: $logPath/deleted_users.log"); $output->writeln('- Nextcloud logs: Check your Nextcloud log files'); } -} \ No newline at end of file +} -- GitLab From 6ddd9996414588abdc06d3d7e2e2a275c77ddfdd Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 08:10:58 +0530 Subject: [PATCH 57/58] removed command --- README.md | 5 - appinfo/info.xml | 1 - lib/Command/TestRecoveryWarningJob.php | 176 ------------------------- 3 files changed, 182 deletions(-) delete mode 100644 lib/Command/TestRecoveryWarningJob.php diff --git a/README.md b/README.md index 7551795..1449852 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,4 @@ `occ email-recovery:create-popular-domains` -### Test Weekly Email Report BG as Command -# Test in dry-run mode (recommended) -occ email-recovery:test-recovery-warning-job --dry-run -# Run the actual job -occ email-recovery:test-recovery-warning-job \ No newline at end of file diff --git a/appinfo/info.xml b/appinfo/info.xml index 75d8f15..f12eca2 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -24,6 +24,5 @@ OCA\EmailRecovery\Command\UpdateBlacklistedDomains OCA\EmailRecovery\Command\CreatePopularDomain OCA\EmailRecovery\Command\SpamAccountDetection - OCA\EmailRecovery\Command\TestRecoveryWarningJob
diff --git a/lib/Command/TestRecoveryWarningJob.php b/lib/Command/TestRecoveryWarningJob.php deleted file mode 100644 index a2f02a1..0000000 --- a/lib/Command/TestRecoveryWarningJob.php +++ /dev/null @@ -1,176 +0,0 @@ -config = $config; - $this->mailer = $mailer; - $this->logger = $logger; - $this->userManager = $userManager; - $this->notificationService = $notificationService; - $this->timeFactory = $timeFactory; - $this->notificationManager = $notificationManager; - $this->urlGenerator = $urlGenerator; - $this->recoveryEmailService = $recoveryEmailService; - } - - protected function configure() { - $this - ->setName(Application::APP_ID.':test-recovery-warning-job') - ->setDescription('Test the recovery warning notification background job') - ->addOption( - 'dry-run', - 'd', - InputOption::VALUE_NONE, - 'Run in dry-run mode (no actual actions performed)' - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): int { - $dryRun = $input->getOption('dry-run'); - - $output->writeln('Testing Recovery Warning Notification Job'); - $output->writeln('=========================================='); - - // Check if messageId is configured - $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); - if (!$messageId) { - $output->writeln('Error: account_recovery_warning_messageid not configured!'); - $output->writeln('Set it with: occ config:system:set account_recovery_warning_messageid --value="20250610_account_recovery_warning"'); - return Command::FAILURE; - } - - $output->writeln("Message ID: $messageId"); - $output->writeln("Dry run mode: " . ($dryRun ? 'Yes' : 'No')); - - // Set dry run mode if requested - if ($dryRun) { - $this->config->setSystemValue('recovery_email_dry_run', true); - $output->writeln('Dry run mode enabled - no actual actions will be performed'); - } - - try { - // Create and run the background job - $job = new RecoveryWarningNotificationJob( - $this->config, - $this->mailer, - $this->userManager, - $this->logger, - $this->notificationService, - $this->timeFactory, - $this->notificationManager, - $this->urlGenerator, - $this->recoveryEmailService - ); - - $output->writeln('Executing recovery warning notification job...'); - $job->run(null); - - $output->writeln('Job completed successfully!'); - - // Show summary - $this->showSummary($output, $dryRun); - } catch (\Throwable $e) { - $output->writeln('Error running job: ' . $e->getMessage() . ''); - $this->logger->error('Error running recovery warning notification job', ['exception' => $e]); - return Command::FAILURE; - } finally { - // Clean up dry run mode - if ($dryRun) { - $this->config->setSystemValue('recovery_email_dry_run', false); - } - } - - return Command::SUCCESS; - } - - private function showSummary(OutputInterface $output, bool $dryRun): void { - $output->writeln(''); - $output->writeln('Summary:'); - $output->writeln('--------'); - - // Count users without recovery email - $usersWithoutRecoveryEmail = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); - $countWithoutRecoveryEmail = is_array($usersWithoutRecoveryEmail) ? count($usersWithoutRecoveryEmail) : 0; - - // Count total users - $allUsers = $this->userManager->search(''); - $countWithRecoveryEmail = 0; - $countEnabled = 0; - $countDisabled = 0; - - foreach ($allUsers as $user) { - $recoveryEmail = $this->recoveryEmailService->getRecoveryEmail($user->getUID()); - if ($recoveryEmail) { - $countWithRecoveryEmail++; - } - if ($user->isEnabled()) { - $countEnabled++; - } else { - $countDisabled++; - } - } - - $output->writeln("Total users: " . count($allUsers)); - $output->writeln("Users with recovery email: $countWithRecoveryEmail"); - $output->writeln("Users without recovery email: $countWithoutRecoveryEmail"); - $output->writeln("Enabled users: $countEnabled"); - $output->writeln("Disabled users: $countDisabled"); - - if ($dryRun) { - $output->writeln(''); - $output->writeln('This was a dry run. Check the logs for detailed information.'); - } else { - $output->writeln(''); - $output->writeln('Check the logs for detailed job execution information.'); - } - - // Show log file locations - $logPath = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); - $output->writeln(''); - $output->writeln('Log files:'); - $output->writeln("- Custom logs: $logPath/disabled_users.log"); - $output->writeln("- Custom logs: $logPath/deleted_users.log"); - $output->writeln('- Nextcloud logs: Check your Nextcloud log files'); - } -} -- GitLab From 3440f1d0308675996ed2668d353d3d25b68ca42d Mon Sep 17 00:00:00 2001 From: theronakpatel Date: Thu, 26 Jun 2025 10:02:48 +0530 Subject: [PATCH 58/58] Added Test mode- Resolved Dirty query issue --- .../RecoveryWarningNotificationJob.php | 365 +++++++++++++++--- 1 file changed, 315 insertions(+), 50 deletions(-) diff --git a/lib/BackgroundJob/RecoveryWarningNotificationJob.php b/lib/BackgroundJob/RecoveryWarningNotificationJob.php index bbff4f9..a8a8cbf 100644 --- a/lib/BackgroundJob/RecoveryWarningNotificationJob.php +++ b/lib/BackgroundJob/RecoveryWarningNotificationJob.php @@ -77,6 +77,10 @@ class RecoveryWarningNotificationJob extends TimedJob { * @var bool If true, actions are logged but not performed */ private bool $dryRun = false; + /** + * @var bool If true, process only 10 users for testing + */ + private bool $testMode = false; public function __construct( IConfig $config, @@ -114,27 +118,51 @@ class RecoveryWarningNotificationJob extends TimedJob { */ protected function run($argument): void { try { - $this->logger->info('RecoveryWarningNotificationJob job started.'); + $this->logger->info('RecoveryWarningNotificationJob: Job started.'); + + // Check messageId configuration $messageId = $this->config->getSystemValue('account_recovery_warning_messageid', ''); if (!$messageId) { - $this->logger->error('RecoveryWarningNotificationJob messageId not set!'); + $this->logger->error('RecoveryWarningNotificationJob: messageId not set! Job will exit.'); return; } + + // Check dry run mode $this->dryRun = $this->config->getSystemValueBool('recovery_email_dry_run', false); + + // Check test mode + $this->testMode = $this->config->getSystemValueBool('recovery_email_test_mode', false); + if ($this->dryRun) { - $this->logger->info('RecoveryWarningNotificationJob dry-run enabled!'); + $this->logger->info('RecoveryWarningNotificationJob: Dry-run enabled!'); + } + + if ($this->testMode) { + $this->logger->info('RecoveryWarningNotificationJob: Test mode enabled - processing only 10 users!'); } + + // Initialize arrays + $this->uids = []; + $this->disableUids = []; + $this->deleteUids = []; + + // Execute main processing steps $this->identifyUnverifiedUsers(); $this->sendCloudNotifications($messageId); $this->sendEmails($messageId); $this->disableUnverifiedUsers(); $this->deleteExpiredDisabledUsers(); - $this->logger->info('RecoveryWarningNotificationJob job finished.'); + + $this->logger->info('RecoveryWarningNotificationJob: Job finished successfully.'); } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Error sending recovery warning notification and emails to users', ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Error during job execution', [ + 'exception' => $e, + 'message' => $e->getMessage() + ]); return; } } + /** * Identify users who have not set a recovery email and queue them for notifications, disabling, or deletion. * @@ -143,37 +171,151 @@ class RecoveryWarningNotificationJob extends TimedJob { private function identifyUnverifiedUsers(): void { $users = $this->config->getUsersForUserValue(Application::APP_ID, 'recovery-email', ''); if (!is_array($users)) { - $this->logger->warning("RecoveryWarningNotificationJob Expected array for recovery-email users, got " . gettype($users)); + $this->logger->warning("RecoveryWarningNotificationJob: Expected array for recovery-email users, got " . gettype($users)); return; } + + // Limit to 10 users for dry-run mode for quick testing + if ($this->dryRun && count($users) > 10) { + $users = array_slice($users, 0, 10); + $this->logger->info('RecoveryWarningNotificationJob: Limited to 10 users for dry-run testing'); + } + + // Limit to 10 users for test mode (regardless of dry-run status) + if ($this->testMode && count($users) > 10) { + $users = array_slice($users, 0, 10); + $this->logger->info('RecoveryWarningNotificationJob: Limited to 10 users for test mode'); + } + + $processedCount = 0; + $skippedCount = 0; + $invalidCount = 0; + $subscriptionActiveCount = 0; + $errorCount = 0; + foreach ($users as $username) { - $user = $this->userManager->get($username); - if (!$this->isUserValid($user)) { - $this->logger->debug("RecoveryWarningNotificationJob $username not valid."); + try { + // Add retry logic for user retrieval to handle dirty table reads + $user = $this->getUserWithRetry($username); + if (!$user) { + $invalidCount++; + continue; + } + + if (!$this->isUserValid($user)) { + $invalidCount++; + continue; + } + + $uid = $user->getUID(); + $emailAddress = $user->getEMailAddress(); + + // Check for active subscription + if ($this->recoveryEmailService->hasActiveSubscription($emailAddress)) { + $subscriptionActiveCount++; + continue; + } + + // Get or set reminder start date + $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); + if ($startDate === null) { + $startDate = date('Y-m-d', time()); + $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); + } + + // Set disable date if not set + if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { + $disableAt = date('Y-m-d', time() + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); + $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); + } + + // Calculate age and process + $age = $this->recoveryEmailService->getDaysSinceDate($startDate); + + if (in_array($age, self::REMINDER_DAYS_SINCE_START)) { + $this->uids[] = $uid; + } + + $this->identifyForDisable($user, $age); + $this->identifyForDelete($user, $age); + + $processedCount++; + + } catch (\Throwable $e) { + $errorCount++; + $this->logger->error('RecoveryWarningNotificationJob: Error processing user', [ + 'username' => $username, + 'message' => $e->getMessage() + ]); + + // If we get too many errors, log a warning but continue + if ($errorCount > 5) { + $this->logger->warning('RecoveryWarningNotificationJob: Too many errors, continuing with remaining users'); + } + continue; } - $uid = $user->getUID(); - $emailAddress = $user->getEMailAddress(); - if ($this->recoveryEmailService->hasActiveSubscription($emailAddress)) { - continue; // skip if subscription active - } - $startDate = $this->recoveryEmailService->getRecoveryEmailReminderStartDate($uid); - if ($startDate === null) { - $startDate = date('Y-m-d', time()); - $this->recoveryEmailService->setRecoveryEmailReminderStartDate($uid, $startDate); - } - if (!$this->recoveryEmailService->getUnverifiedUserDisableAt($username)) { - $disableAt = date('Y-m-d', time() + self::DISABLE_USER_AFTER_UNVERIFIED_DAYS * 86400); - $this->recoveryEmailService->setUnverifiedUserDisableAt($username, $disableAt); - } - $age = $this->recoveryEmailService->getDaysSinceDate($startDate); - if (in_array($age, self::REMINDER_DAYS_SINCE_START)) { - $this->uids[] = $uid; + } + + $this->logger->info('RecoveryWarningNotificationJob: User identification completed', [ + 'totalUsers' => count($users), + 'processedCount' => $processedCount, + 'usersForNotifications' => count($this->uids), + 'usersForDisable' => count($this->disableUids), + 'usersForDelete' => count($this->deleteUids) + ]); + } + + /** + * Get user with retry logic to handle dirty table reads + * + * @param string $username The username to retrieve + * @param int $maxRetries Maximum number of retries + * @param int $retryDelay Delay between retries in microseconds + * @return IUser|null The user object or null if not found + */ + private function getUserWithRetry(string $username, int $maxRetries = 3, int $retryDelay = 100000): ?IUser { + for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { + try { + $user = $this->userManager->get($username); + if ($user) { + return $user; + } + + // If user doesn't exist, no need to retry + return null; + + } catch (\Throwable $e) { + $errorMessage = $e->getMessage(); + + // Check if it's a dirty table read error + if (strpos($errorMessage, 'dirty table reads') !== false || + strpos($errorMessage, 'deadlock') !== false || + strpos($errorMessage, 'lock wait timeout') !== false) { + + if ($attempt < $maxRetries) { + usleep($retryDelay); + continue; + } + } + + // For other errors, log and return null + $this->logger->error('RecoveryWarningNotificationJob: Error retrieving user', [ + 'username' => $username, + 'message' => $errorMessage + ]); + + return null; } - $this->identifyForDisable($user, $age); - $this->identifyForDelete($user, $age); } + + $this->logger->warning('RecoveryWarningNotificationJob: Failed to retrieve user after all retries', [ + 'username' => $username + ]); + + return null; } + /** * Identifies if the user qualifies for disabling and queues them. * @@ -186,7 +328,6 @@ class RecoveryWarningNotificationJob extends TimedJob { if ($age >= self::DISABLE_USER_AFTER_UNVERIFIED_DAYS && $age < self::DELETE_USER_AFTER_DISABLE_DAYS) { if ($user->isEnabled()) { $this->disableUids[] = $user->getUID(); - return; } } } @@ -204,59 +345,104 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->deleteUids[] = $user->getUID(); } } + /** * Disable users who have not set a recovery email after the configured period. * * @return void */ private function disableUnverifiedUsers(): void { + $disabledCount = 0; + $failedCount = 0; + foreach ($this->disableUids as $uid) { try { $user = $this->userManager->get($uid); if (!$user) { - $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for disabling."); + $this->logger->warning("RecoveryWarningNotificationJob: User not found for disabling", ['uid' => $uid]); + $failedCount++; continue; } + if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would disable user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob: [Dry Run] Would disable user", ['uid' => $uid]); + $disabledCount++; continue; } + $user->setEnabled(false); $this->config->setUserValue($uid, Application::APP_ID, 'disabledForRecoveryEmail', '1'); $this->logDisabledUser($uid); + + $this->logger->info('RecoveryWarningNotificationJob: Successfully disabled user', ['uid' => $uid]); + $disabledCount++; + } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to disable user $uid. Error: " . $e->getMessage(), ['exception' => $e]); + $this->logger->error("RecoveryWarningNotificationJob: Failed to disable user", [ + 'uid' => $uid, + 'message' => $e->getMessage() + ]); + $failedCount++; } } + + if ($disabledCount > 0 || $failedCount > 0) { + $this->logger->info('RecoveryWarningNotificationJob: Disable users completed', [ + 'disabledCount' => $disabledCount, + 'failedCount' => $failedCount + ]); + } } + /** * Delete users who have been disabled for longer than the configured period. * * @return void */ private function deleteExpiredDisabledUsers(): void { + $deletedCount = 0; + $failedCount = 0; + foreach ($this->deleteUids as $uid) { try { $user = $this->userManager->get($uid); if (!$user) { - $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for deletion."); + $this->logger->warning("RecoveryWarningNotificationJob: User not found for deletion", ['uid' => $uid]); + $failedCount++; continue; } + if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would delete user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob: [Dry Run] Would delete user", ['uid' => $uid]); + $deletedCount++; continue; } + $this->recoveryEmailService->deleteRecoveryEmailReminderStartDate($uid); $this->recoveryEmailService->deleteUnverifiedUserDisableAt($uid); $user->delete(); $this->logDeletedUser($uid); + + $this->logger->info('RecoveryWarningNotificationJob: Successfully deleted user', ['uid' => $uid]); + $deletedCount++; + } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to delete user $uid. Error: " . $e->getMessage(), ['exception' => $e]); + $this->logger->error("RecoveryWarningNotificationJob: Failed to delete user", [ + 'uid' => $uid, + 'message' => $e->getMessage() + ]); + $failedCount++; } } + + if ($deletedCount > 0 || $failedCount > 0) { + $this->logger->info('RecoveryWarningNotificationJob: Delete users completed', [ + 'deletedCount' => $deletedCount, + 'failedCount' => $failedCount + ]); + } } - /** * Send cloud notifications to users who need to be reminded. * @@ -269,6 +455,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $datetime = $this->timeFactory->getDateTime(); $notification = $this->notificationManager->createNotification(); $notificationType = 'important'; + $notification->setApp(Application::APP_ID) ->setDateTime($datetime) ->setObject(Application::APP_ID . '-' . strtolower($notificationType), $messageId) @@ -276,9 +463,12 @@ class RecoveryWarningNotificationJob extends TimedJob { $this->sendNotificationToUsers($messageId, $notification, $this->uids); } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to users. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Error sending cloud notifications', [ + 'message' => $e->getMessage() + ]); } } + /** * Sends a notification with the specified messageId and notification object to a list of users. * @@ -289,23 +479,44 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ protected function sendNotificationToUsers(string $messageId, INotification $notification, array $users): void { + $sentCount = 0; + $failedCount = 0; + foreach ($users as $username) { try { if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $username"); + $this->logger->info("RecoveryWarningNotificationJob: [Dry Run] Would send notification to user", ['username' => $username]); + $sentCount++; continue; } + $user = $this->userManager->get($username); if (!$user) { - $this->logger->warning("RecoveryWarningNotificationJob User $username not found for notification."); + $this->logger->warning("RecoveryWarningNotificationJob: User not found for notification", ['username' => $username]); + $failedCount++; continue; } + $this->sendNotificationToUser($messageId, $user, $notification); + $sentCount++; + } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Error sending recovery email cloud notifications to user ' . $username . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Error sending notification to user', [ + 'username' => $username, + 'message' => $e->getMessage() + ]); + $failedCount++; } } + + if ($sentCount > 0 || $failedCount > 0) { + $this->logger->info('RecoveryWarningNotificationJob: Send notifications completed', [ + 'sentCount' => $sentCount, + 'failedCount' => $failedCount + ]); + } } + /** * Sends a notification with the specified messageId and notification object to a user. * @@ -318,13 +529,22 @@ class RecoveryWarningNotificationJob extends TimedJob { protected function sendNotificationToUser(string $messageId, IUser $user, INotification $notification): void { $uid = $user->getUID(); $displayName = $user->getDisplayName(); + try { - $notification->setSubject('cli', [$messageId, $displayName])->setMessage('cli', [$messageId, $displayName])->setUser($uid); + $notification->setSubject('cli', [$messageId, $displayName]) + ->setMessage('cli', [$messageId, $displayName]) + ->setUser($uid); + $this->notificationManager->notify($notification); } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Failed to send notification to ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Failed to send notification to user', [ + 'uid' => $uid, + 'message' => $e->getMessage() + ]); + throw $e; } } + /** * Sends recovery emails to all users who need to be reminded. * @@ -333,23 +553,34 @@ class RecoveryWarningNotificationJob extends TimedJob { * @return void */ private function sendEmails(string $messageId): void { + $sentCount = 0; + $failedCount = 0; + $noEmailCount = 0; + foreach ($this->uids as $uid) { try { if ($this->dryRun) { - $this->logger->info("RecoveryWarningNotificationJob [Dry Run] Would send notification to user: $uid"); + $this->logger->info("RecoveryWarningNotificationJob: [Dry Run] Would send email to user", ['uid' => $uid]); + $sentCount++; continue; } + $user = $this->userManager->get($uid); if (!$user) { - $this->logger->warning("RecoveryWarningNotificationJob User $uid not found for email."); + $this->logger->warning("RecoveryWarningNotificationJob: User not found for email", ['uid' => $uid]); + $failedCount++; continue; } + $username = $user->getDisplayName(); $emailAddress = $user->getEMailAddress(); + if (!$emailAddress) { - $this->logger->warning("RecoveryWarningNotificationJob No email address found for $uid, skipping."); + $this->logger->warning("RecoveryWarningNotificationJob: No email address found for user", ['uid' => $uid]); + $noEmailCount++; continue; } + $language = $this->config->getUserValue($uid, 'core', 'lang', null); $translations = $this->notificationService->getTranslatedSubjectAndMessage($messageId, $language); $subject = $translations['subject']; @@ -363,10 +594,24 @@ class RecoveryWarningNotificationJob extends TimedJob { $message = $parsedMessage['message']; $this->sendEmail($subject, $message, $emailAddress); + $sentCount++; + } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Error sending notification email to user ' . $uid . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Error sending email to user', [ + 'uid' => $uid, + 'message' => $e->getMessage() + ]); + $failedCount++; } } + + if ($sentCount > 0 || $failedCount > 0 || $noEmailCount > 0) { + $this->logger->info('RecoveryWarningNotificationJob: Send emails completed', [ + 'sentCount' => $sentCount, + 'failedCount' => $failedCount, + 'noEmailCount' => $noEmailCount + ]); + } } /** @@ -382,6 +627,7 @@ class RecoveryWarningNotificationJob extends TimedJob { try { // Convert Markdown-style links to HTML anchor tags $message = preg_replace('/\[(.*?)\]\((.*?)\)/', "$1", $message); + $template = $this->mailer->createEMailTemplate(Application::APP_ID . '::sendMail'); $template->setSubject($subject); $template->addHeader(); @@ -394,10 +640,19 @@ class RecoveryWarningNotificationJob extends TimedJob { $email->setTo([$emailAddress]); $this->mailer->send($email); + + $this->logger->info('RecoveryWarningNotificationJob: Email sent successfully', [ + 'emailAddress' => $emailAddress + ]); } catch (\Throwable $e) { - $this->logger->error('RecoveryWarningNotificationJob Failed to send email to ' . $emailAddress . '. Error:' . $e->getMessage(), ['exception' => $e]); + $this->logger->error('RecoveryWarningNotificationJob: Failed to send email', [ + 'emailAddress' => $emailAddress, + 'message' => $e->getMessage() + ]); + throw $e; } } + /** * Add the message body to the email template, handling line breaks and formatting. * @@ -410,6 +665,7 @@ class RecoveryWarningNotificationJob extends TimedJob { $lines = explode("\n", $message); $finalHtml = ""; $finalText = ""; + foreach ($lines as $line) { if (trim($line) === '') { continue; @@ -417,8 +673,10 @@ class RecoveryWarningNotificationJob extends TimedJob { $finalHtml .= "

" . $line . "

"; $finalText .= $line; } + $template->addBodyText($finalHtml, $finalText); } + /** * Validate a user for notification and email purposes. * @@ -430,9 +688,11 @@ class RecoveryWarningNotificationJob extends TimedJob { if (!($user instanceof IUser)) { return false; } + $emailAddress = $user->getEMailAddress(); return ($emailAddress && $this->mailer->validateMailAddress($emailAddress)); } + /** * Log a user action to the custom log file. * @@ -444,9 +704,10 @@ class RecoveryWarningNotificationJob extends TimedJob { private function logUserAction(string $uid, string $action, string $filename): void { try { $defaultLogDir = $this->config->getSystemValue('recovery_email_log_path', '/var/log/email-recovery-logs'); + if (!is_dir($defaultLogDir)) { if (!mkdir($defaultLogDir, 0770, true)) { - $this->logger->error("RecoveryWarningNotificationJob Failed to create log directory: $defaultLogDir"); + $this->logger->error("RecoveryWarningNotificationJob: Failed to create log directory", ['logDir' => $defaultLogDir]); return; } } @@ -456,10 +717,14 @@ class RecoveryWarningNotificationJob extends TimedJob { $entry = "[$timestamp] $action user: $uid\n"; if (file_put_contents($logFile, $entry, FILE_APPEND) === false) { - $this->logger->error("RecoveryWarningNotificationJob Failed to write to log file: $logFile"); + $this->logger->error("RecoveryWarningNotificationJob: Failed to write to log file", ['logFile' => $logFile]); } } catch (\Throwable $e) { - $this->logger->error("RecoveryWarningNotificationJob Failed to log user action for $uid. Error: " . $e->getMessage(), ['exception' => $e]); + $this->logger->error("RecoveryWarningNotificationJob: Failed to log user action", [ + 'uid' => $uid, + 'action' => $action, + 'message' => $e->getMessage() + ]); } } -- GitLab