usersWithLegacyEncryption()->get(); // Always report the total scope of legacy encrypted data (soft-deleted users are // excluded by the model's default scope). This is the signal for deciding whether // the browser-side encryption code can finally be removed, independent of who we // actually email below. $this->info("{$encryptedUsers->count()} non-deleted user(s) still have encrypted data."); $users = $this->excludeBilledUsers($encryptedUsers); if ($users->isEmpty()) { $this->info('No users to warn.'); return self::SUCCESS; } if ($this->option('dry-run')) { $this->renderTable($users); $this->info('[dry-run] No emails sent.'); return self::SUCCESS; } if (! $this->option('force') && ! $this->confirm("Send the deletion warning to {$users->count()} user(s)?", true)) { $this->info('Cancelled.'); return self::SUCCESS; } $progressBar = $this->output->createProgressBar($users->count()); $progressBar->start(); foreach ($users->values() as $index => $user) { // Spread over the 'emails' queue at 50/day to match the existing bulk-email convention. SendUpdateEmailJob::dispatch($user, self::VIEW, self::IDENTIFIER, self::SUBJECT) ->delay(now()->addDays((int) floor($index / 50))); $progressBar->advance(); } $progressBar->finish(); $this->newLine(); $this->info("Queued {$users->count()} warning email(s) to the 'emails' queue (50/day)."); return self::SUCCESS; } /** * @param Collection $users */ private function renderTable($users): void { $this->table(['Email', 'Last active'], $users->map(fn (User $user) => [ $user->email, $user->last_active_at?->toDateTimeString() ?? 'never', ])->all()); } }