Fix spam whoopsie.

This commit is contained in:
James Cole
2025-08-09 08:34:18 +02:00
parent 661e4e53e6
commit f50aa6b0ce
8 changed files with 144 additions and 105 deletions

View File

@@ -6,12 +6,13 @@ namespace FireflyIII\Events\Model\Bill;
use FireflyIII\Events\Event;
use FireflyIII\Models\Bill;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
class WarnUserAboutOverdueSubscription extends Event
class WarnUserAboutOverdueSubscriptions extends Event
{
use SerializesModels;
public function __construct(public Bill $bill, public array $dates) {}
public function __construct(public User $user, public array $overdue) {}
}

View File

@@ -26,9 +26,10 @@ namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\User\BillReminder;
use FireflyIII\Notifications\User\SubscriptionOverdueReminder;
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
@@ -38,47 +39,65 @@ use Illuminate\Support\Facades\Notification;
*/
class BillEventHandler
{
public function warnAboutOverdueSubscription(WarnUserAboutOverdueSubscription $event): void
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $event): void
{
$bill = $event->bill;
$dates = $event->dates;
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($dates['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
$pref = Preferences::getForUser($bill->user, $key, false);
if (true === $pref->data) {
Log::debug(sprintf('User %s has already been warned about overdue subscription %s.', $bill->user->id, $bill->id));
return;
Log::debug(sprintf('Now in %s', __METHOD__));
// make sure user does not get the warning twice.
$overdue = $event->overdue;
$user = $event->user;
$toBeWarned = [];
Log::debug(sprintf('%d bills to warn about.', count($overdue)));
foreach ($overdue as $item) {
/** @var Bill $bill */
$bill = $item['bill'];
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
$pref = Preferences::getForUser($bill->user, $key, false);
if (true === $pref->data) {
Log::debug(sprintf('User #%d has already been warned about overdue subscription #%d.', $bill->user->id, $bill->id));
continue;
}
$toBeWarned[] = $item;
}
unset($bill);
Log::debug(sprintf('Now %d bills to warn about.', count($toBeWarned)));
/** @var bool $sendNotification */
$sendNotification = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
if (true === $sendNotification) {
Log::debug('Will warning about overdue subscription.');
Preferences::setForUser($bill->user, $key, true);
try {
Notification::send($bill->user, new SubscriptionOverdueReminder($bill, $dates));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
$sendNotification = Preferences::getForUser($user, 'notification_bill_reminder', true)->data;
if (false === $sendNotification) {
Log::debug('User has disabled bill reminders.');
return;
}
Log::debug('User has disabled bill reminders.');
Log::debug(sprintf('Will warning about %d overdue subscription(s).', count($toBeWarned)));
if (0 === count($toBeWarned)) {
Log::debug('No overdue subscriptions to warn about.');
return;
}
foreach ($toBeWarned as $item) {
/** @var Bill $bill */
$bill = $item['bill'];
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
Preferences::setForUser($bill->user, $key, true);
}
Log::warning('should hit this ONCE');
try {
Notification::send($user, new SubscriptionsOverdueReminder($overdue));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
public function warnAboutBill(WarnUserAboutBill $event): void

View File

@@ -26,10 +26,11 @@ namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Models\Bill;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -55,12 +56,12 @@ class WarnAboutBills implements ShouldQueue
*/
public function __construct(?Carbon $date)
{
$newDate = new Carbon();
$newDate = new Carbon();
$newDate->startOfDay();
$this->date = $newDate;
$this->date = $newDate;
if ($date instanceof Carbon) {
$newDate = clone $date;
$newDate = clone $date;
$newDate->startOfDay();
$this->date = $newDate;
}
@@ -76,28 +77,29 @@ class WarnAboutBills implements ShouldQueue
public function handle(): void
{
Log::debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
$bills = Bill::all();
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getDates($bill);
if ($this->needsOverdueAlert($dates)) {
$this->sendOverdueAlert($bill, $dates);
}
if ($this->hasDateFields($bill)) {
if ($this->needsWarning($bill, 'end_date')) {
$this->sendWarning($bill, 'end_date');
foreach (User::all() as $user) {
$bills = $user->bills()->where('active', true)->get();
$overdue = [];
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getDates($bill);
if ($this->needsOverdueAlert($dates)) {
$overdue[] = ['bill' => $bill, 'dates' => $dates];
}
if ($this->needsWarning($bill, 'extension_date')) {
$this->sendWarning($bill, 'extension_date');
if ($this->hasDateFields($bill)) {
if ($this->needsWarning($bill, 'end_date')) {
$this->sendWarning($bill, 'end_date');
}
if ($this->needsWarning($bill, 'extension_date')) {
$this->sendWarning($bill, 'extension_date');
}
}
}
$this->sendOverdueAlerts($user, $overdue);
}
Log::debug('Done with handle()');
// clear cache:
app('preferences')->mark();
}
private function hasDateFields(Bill $bill): bool
@@ -148,7 +150,7 @@ class WarnAboutBills implements ShouldQueue
public function setDate(Carbon $date): void
{
$newDate = clone $date;
$newDate = clone $date;
$newDate->startOfDay();
$this->date = $newDate;
}
@@ -168,7 +170,7 @@ class WarnAboutBills implements ShouldQueue
$enrichment->setUser($bill->user);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$single = $enrichment->enrichSingle($bill);
$single = $enrichment->enrichSingle($bill);
return [
'pay_dates' => $single->meta['pay_dates'] ?? [],
@@ -178,7 +180,7 @@ class WarnAboutBills implements ShouldQueue
private function needsOverdueAlert(array $dates): bool
{
$count = count($dates['pay_dates']) - count($dates['paid_dates']);
$count = count($dates['pay_dates']) - count($dates['paid_dates']);
if (0 === $count || 0 === count($dates['pay_dates'])) {
return false;
}
@@ -186,7 +188,7 @@ class WarnAboutBills implements ShouldQueue
$earliest = new Carbon($dates['pay_dates'][0]);
$earliest->startOfDay();
Log::debug(sprintf('Earliest expected pay date is %s', $earliest->toAtomString()));
$diff = $earliest->diffInDays($this->date);
$diff = $earliest->diffInDays($this->date);
Log::debug(sprintf('Difference in days is %s', $diff));
if ($diff < 2) {
return false;
@@ -195,9 +197,11 @@ class WarnAboutBills implements ShouldQueue
return true;
}
private function sendOverdueAlert(Bill $bill, array $dates): void
private function sendOverdueAlerts(User $user, array $overdue): void
{
Log::debug('Will now send warning about overdue bill.');
event(new WarnUserAboutOverdueSubscription($bill, $dates));
if (count($overdue) > 0) {
Log::debug(sprintf('Will now send warning about overdue bill for user #%d.', $user->id));
event(new WarnUserAboutOverdueSubscriptions($user, $overdue));
}
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\User;
use Carbon\Carbon;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\ReturnsAvailableChannels;
use FireflyIII\Notifications\ReturnsSettings;
use FireflyIII\User;
@@ -15,11 +14,13 @@ use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Pushover\PushoverMessage;
class SubscriptionOverdueReminder extends Notification
class SubscriptionsOverdueReminder extends Notification
{
use Queueable;
public function __construct(private Bill $bill, private array $dates) {}
public function __construct(private array $overdue)
{
}
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
@@ -35,23 +36,31 @@ class SubscriptionOverdueReminder extends Notification
*/
public function toMail(User $notifiable): MailMessage
{
// format the dates in a human-readable way
$this->dates['pay_dates'] = array_map(
static function (string $date): string {
return new Carbon($date)->isoFormat((string) trans('config.month_and_day_moment_js'));
},
$this->dates['pay_dates']
);
// format the data
$info = [];
$count = 0;
foreach ($this->overdue as $item) {
$current = [
'bill' => $item['bill'],
];
$current['pay_dates'] = array_map(
static function (string $date): string {
return new Carbon($date)->isoFormat((string)trans('config.month_and_day_moment_js'));
}, $item['dates']['pay_dates']);
$info[] = $current;
$count++;
}
return new MailMessage()
->markdown('emails.subscription-overdue-warning', ['bill' => $this->bill, 'dates' => $this->dates])
->subject($this->getSubject())
;
->markdown('emails.subscriptions-overdue-warning', ['info' => $info,'count' => $count])
->subject($this->getSubject());
}
private function getSubject(): string
{
return (string) trans('email.subscription_overdue_subject', ['name' => $this->bill->name]);
if (count($this->overdue) > 1) {
return (string)trans('email.subscriptions_overdue_subject_multi', ['count' => count($this->overdue)]);
}
return (string)trans('email.subscriptions_overdue_subject_single');
}
public function toNtfy(User $notifiable): Message
@@ -60,7 +69,7 @@ class SubscriptionOverdueReminder extends Notification
$message = new Message();
$message->topic($settings['ntfy_topic']);
$message->title($this->getSubject());
$message->body((string) trans('email.bill_warning_please_action'));
$message->body((string)trans('email.bill_warning_please_action'));
return $message;
}
@@ -70,9 +79,8 @@ class SubscriptionOverdueReminder extends Notification
*/
public function toPushover(User $notifiable): PushoverMessage
{
return PushoverMessage::create((string) trans('email.bill_warning_please_action'))
->title($this->getSubject())
;
return PushoverMessage::create((string)trans('email.bill_warning_please_action'))
->title($this->getSubject());
}
/**
@@ -86,10 +94,9 @@ class SubscriptionOverdueReminder extends Notification
return new SlackMessage()
->warning()
->attachment(static function ($attachment) use ($bill, $url): void {
$attachment->title((string) trans('firefly.visit_bill', ['name' => $bill->name]), $url);
$attachment->title((string)trans('firefly.visit_bill', ['name' => $bill->name]), $url);
})
->content($this->getSubject())
;
->content($this->getSubject());
}
/**

View File

@@ -28,7 +28,7 @@ use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
@@ -203,8 +203,8 @@ class EventServiceProvider extends ServiceProvider
WarnUserAboutBill::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
],
WarnUserAboutOverdueSubscription::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscription',
WarnUserAboutOverdueSubscriptions::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
],
// audit log events:

View File

@@ -139,10 +139,11 @@ return [
'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:',
// subscription is overdue.
'subscription_overdue_subject' => 'Your subscription ":name" is overdue to be paid',
'subscription_overdue_warning_intro' => 'Your subscription ":name" is overdue to be paid. At the following date(s) a payment was expected, but it has not yet arrived.',
'subscription_overdue_please_action' => 'Perhaps you have simply not linked the transaction to subscription ":name". In that case, please do so. You will NOT get another warning about this overdue bill.',
'subscription_overdue_outro' => 'If you believe this message is wrong, please contact the Firefly III developer.',
'subscriptions_overdue_subject_multi' => 'You have :count subscriptions that are overdue to be paid',
'subscriptions_overdue_subject_single' => 'You have a subscription that is overdue to be paid',
'subscriptions_overdue_warning_intro' => 'You have :count subscription(s) that are overdue to be paid. At the following date(s) a payment was expected, but it has not yet arrived.',
'subscriptions_overdue_please_action' => 'Perhaps you have simply not linked a transaction to these subscription(s). In that case, please do so. You will NOT get another warning about these overdue bill(s).',
'subscriptions_overdue_outro' => 'If you believe this message is wrong, please contact the Firefly III developer.',
// bill warning
'bill_warning_subject_end_date' => 'Your subscription ":name" is due to end in :diff days',
'bill_warning_subject_now_end_date' => 'Your subscription ":name" is due to end TODAY',

View File

@@ -1,10 +0,0 @@
@component('mail::message')
{{ trans('email.subscription_overdue_warning_intro', ['name' => $bill->name]) }}
@foreach($dates['pay_dates'] as $date)
- {{ $date }}
@endforeach
{{ trans('email.subscription_overdue_please_action', ['name' => $bill->name]) }}
@endcomponent

View File

@@ -0,0 +1,17 @@
@component('mail::message')
{{ trans('email.subscriptions_overdue_warning_intro', ['count' => $count]) }}
@foreach($info as $row)
- {{ $row['bill']->name }}:
@foreach($row['pay_dates'] as $date)
- {{ $date }}
@endforeach
@endforeach
{{ trans('email.subscriptions_overdue_please_action') }}
{{ trans('email.subscriptions_overdue_outro') }}
@endcomponent