Files
ignacionelson 6e47d76ba6 ProjectSend 2.0.0
Client file sharing, rebuilt from the ground up: a private area per
client, resumable uploads, folders, groups and categories, sharing with
expiry dates and download limits, comments, file versions, an activity
log, a REST API, and sixteen languages.

This repository begins here. ProjectSend 2 was developed privately, and
that development history is not published — the previous generation
remains available, with its own history, at projectsend/legacy.

Free software under the GNU General Public License v2, or (at your
option) any later version.
2026-08-14 01:38:12 -03:00

80 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Notifications;
use App\Models\User;
use App\Modules\Notifications\Jobs\SendNotificationDigest;
use App\Modules\Platform\Settings\Setting;
use App\Modules\Platform\Settings\Settings;
/**
* Collapses a burst of the same kind of notification to the same person
* into one email.
*
* Sharing five files with a client one at a time used to mean five emails;
* so would answering five comments. This debounces both, without needing a
* "is a send already scheduled" flag — see SendNotificationDigest for why.
*
* Generalised from the file-share pipeline it grew out of. What made it
* share-specific was the payload, not the mechanism: it is keyed by
* notification type now, and asks the registry which mail class to use for
* one item and which for several.
*
* **Like Notifier, this performs no authorization.** `$recipients` must
* already be the exact, permission-checked list — the same contract, for
* the same reason. It *does* apply the two email gates (the installation's
* master switch and the recipient's own preference), because those are
* questions about email rather than about who may see a thing, and every
* caller was otherwise repeating them.
*/
class NotificationDigester
{
/**
* How long a recipient's pending items wait for more to accumulate.
* Deliberately short — this is about collapsing a burst a few seconds
* apart, not batching over an afternoon.
*/
private const DEBOUNCE_SECONDS = 120;
public function __construct(
private readonly NotificationTypeRegistry $types,
private readonly NotificationPreferences $preferences,
private readonly Settings $settings,
) {}
/**
* @param iterable<User> $recipients Already permission-checked by the caller.
* @param array<string, mixed> $context Whatever the mail class needs.
*/
public function queue(string $typeKey, iterable $recipients, string $subjectName, array $context = []): void
{
$type = $this->types->get($typeKey);
if ($type?->digestMail === null) {
return;
}
if ($this->settings->get(Setting::EmailNotificationsEnabled) !== true) {
return;
}
foreach ($recipients as $recipient) {
if (! $this->preferences->emailEnabledFor($recipient, $type)) {
continue;
}
PendingNotification::query()->create([
'user_id' => $recipient->getKey(),
'type' => $type->key,
'subject_name' => $subjectName,
'context' => $context === [] ? null : $context,
]);
SendNotificationDigest::dispatch($recipient->getKey(), $type->key)
->delay(now()->addSeconds(self::DEBOUNCE_SECONDS));
}
}
}