Files
timebank-cc-public/app/Console/Commands/SendTestEmail.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

458 lines
16 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
use App\Models\User;
use App\Models\Organization;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Account;
use App\Mail\CallBlockedMail;
use App\Mail\CallExpiredMail;
use App\Mail\CallExpiringMail;
use App\Mail\InactiveProfileWarning1Mail;
use App\Models\Call;
use App\Mail\InactiveProfileWarning2Mail;
use App\Mail\InactiveProfileWarningFinalMail;
use App\Mail\UserDeletedMail;
use App\Mail\TransferReceived;
use App\Mail\ProfileLinkChangedMail;
use App\Mail\ProfileEditedByAdminMail;
use App\Mail\VerifyProfileEmailMailable;
use App\Mail\ReservationCreatedMail;
use App\Mail\ReservationCancelledMail;
use App\Mail\ReservationUpdateMail;
use App\Mail\ReactionCreatedMail;
use App\Mail\TagAddedMail;
class SendTestEmail extends Command
{
protected $signature = 'email:send-test
{--type= : Email type to send (use --list to see all types)}
{--receiver= : Receiver type (user, organization, admin, bank)}
{--id= : Receiver ID}
{--list : List all available email types}
{--queue : Send via queue instead of immediately}';
protected $description = 'Send test mailing transactional emails for testing and review';
protected $emailTypes = [
'inactive-warning-1' => [
'class' => InactiveProfileWarning1Mail::class,
'description' => 'Inactive profile warning 1 (first warning)',
'supports' => ['user', 'organization'],
],
'inactive-warning-2' => [
'class' => InactiveProfileWarning2Mail::class,
'description' => 'Inactive profile warning 2 (second warning)',
'supports' => ['user', 'organization'],
],
'inactive-warning-final' => [
'class' => InactiveProfileWarningFinalMail::class,
'description' => 'Inactive Profile Final Warning (last warning)',
'supports' => ['user', 'organization'],
],
'user-deleted' => [
'class' => UserDeletedMail::class,
'description' => 'User Deleted Notification',
'supports' => ['user'],
],
'transfer-received' => [
'class' => TransferReceived::class,
'description' => 'Transfer/Payment Received Notification',
'supports' => ['user', 'organization'],
],
'profile-link-changed' => [
'class' => ProfileLinkChangedMail::class,
'description' => 'Profile Link/Name Changed Notification',
'supports' => ['user', 'organization', 'admin', 'bank'],
],
'profile-edited-by-admin' => [
'class' => ProfileEditedByAdminMail::class,
'description' => 'Profile Edited by Admin Notification',
'supports' => ['user', 'organization'],
],
'verify-email' => [
'class' => VerifyProfileEmailMailable::class,
'description' => 'Email Verification Request',
'supports' => ['user', 'organization'],
],
'reservation-created' => [
'class' => ReservationCreatedMail::class,
'description' => 'Reservation Created Notification',
'supports' => ['user', 'organization'],
],
'reservation-cancelled' => [
'class' => ReservationCancelledMail::class,
'description' => 'Reservation Cancelled Notification',
'supports' => ['user', 'organization'],
],
'reservation-updated' => [
'class' => ReservationUpdateMail::class,
'description' => 'Reservation Updated Notification',
'supports' => ['user', 'organization'],
],
'reaction-created' => [
'class' => ReactionCreatedMail::class,
'description' => 'Reaction/Comment Created Notification',
'supports' => ['user', 'organization'],
],
'tag-added' => [
'class' => TagAddedMail::class,
'description' => 'Tag Added to Profile Notification',
'supports' => ['user', 'organization'],
],
'call-expired' => [
'class' => CallExpiredMail::class,
'description' => 'Call Expired Notification',
'supports' => ['user', 'organization', 'bank'],
],
'call-expiring' => [
'class' => CallExpiringMail::class,
'description' => 'Call Expiring Soon Warning',
'supports' => ['user', 'organization', 'bank'],
],
'call-blocked' => [
'class' => CallBlockedMail::class,
'description' => 'Call Blocked by Admin Notification',
'supports' => ['user', 'organization', 'bank'],
],
];
public function handle()
{
if ($this->option('list')) {
return $this->listEmailTypes();
}
$type = $this->option('type');
$receiverType = $this->option('receiver');
$receiverId = $this->option('id');
// Interactive mode if no options provided
if (!$type || !$receiverType || !$receiverId) {
return $this->interactiveMode();
}
return $this->sendEmail($type, $receiverType, $receiverId);
}
protected function listEmailTypes()
{
$this->info('Available Email Types:');
$this->newLine();
foreach ($this->emailTypes as $key => $config) {
$supports = implode(', ', $config['supports']);
$this->line(" <fg=cyan>{$key}</>");
$this->line(" Description: {$config['description']}");
$this->line(" Supports: {$supports}");
$this->newLine();
}
$this->info('Usage Example:');
$this->line(' php artisan email:send-test --type=inactive-warning-1 --receiver=user --id=102');
$this->newLine();
return 0;
}
protected function interactiveMode()
{
$this->info('📧 Test Email Sender - Interactive Mode');
$this->newLine();
// Select email type
$typeChoices = array_map(
fn ($key, $config) => "{$key} - {$config['description']}",
array_keys($this->emailTypes),
array_values($this->emailTypes)
);
$selectedIndex = array_search(
$this->choice('Select email type', $typeChoices),
$typeChoices
);
$type = array_keys($this->emailTypes)[$selectedIndex];
// Select receiver type
$supports = $this->emailTypes[$type]['supports'];
$receiverType = $this->choice('Select receiver type', $supports);
// Enter receiver ID
$receiverId = $this->ask('Enter receiver ID');
return $this->sendEmail($type, $receiverType, $receiverId);
}
protected function sendEmail($type, $receiverType, $receiverId)
{
if (!isset($this->emailTypes[$type])) {
$this->error("Invalid email type: {$type}");
$this->info('Use --list to see all available types');
return 1;
}
$config = $this->emailTypes[$type];
if (!in_array($receiverType, $config['supports'])) {
$this->error("Email type '{$type}' does not support receiver type '{$receiverType}'");
$this->info('Supported types: ' . implode(', ', $config['supports']));
return 1;
}
// Get receiver profile
$receiver = $this->getReceiver($receiverType, $receiverId);
if (!$receiver) {
$this->error("Receiver not found: {$receiverType} #{$receiverId}");
return 1;
}
$this->info("Sending '{$type}' email to {$receiver->name} ({$receiver->email})");
$this->newLine();
try {
$mailable = $this->buildMailable($type, $receiver, $receiverType);
if ($this->option('queue')) {
Mail::to($receiver->email)->queue($mailable);
$this->info('✅ Email queued successfully');
$this->line('Run queue worker: php artisan queue:work --stop-when-empty');
} else {
Mail::to($receiver->email)->send($mailable);
$this->info('✅ Email sent successfully');
}
$this->newLine();
$this->line("Recipient: {$receiver->email}");
$this->line("Profile: {$receiver->name}");
$this->line("Language: " . ($receiver->lang_preference ?? 'en'));
return 0;
} catch (\Exception $e) {
$this->error('Failed to send email: ' . $e->getMessage());
$this->line($e->getTraceAsString());
return 1;
}
}
protected function getReceiver($type, $id)
{
return match($type) {
'user' => User::find($id),
'organization' => Organization::find($id),
'admin' => Admin::find($id),
'bank' => Bank::find($id),
default => null,
};
}
protected function buildMailable($type, $receiver, $receiverType)
{
// Get test data
$accounts = $this->getAccountsData($receiver);
$totalBalance = $this->getTotalBalance($accounts);
return match($type) {
'inactive-warning-1' => new InactiveProfileWarning1Mail(
$receiver,
ucfirst($receiverType),
'2 weeks',
14,
$accounts,
$totalBalance,
351
),
'inactive-warning-2' => new InactiveProfileWarning2Mail(
$receiver,
ucfirst($receiverType),
'1 week',
7,
$accounts,
$totalBalance,
358
),
'inactive-warning-final' => new InactiveProfileWarningFinalMail(
$receiver,
ucfirst($receiverType),
'24 hours',
1,
$accounts,
$totalBalance,
365
),
'user-deleted' => new UserDeletedMail(
$receiver,
$accounts,
$totalBalance,
$this->getTransferTargetAccount()
),
'transfer-received' => $this->buildTransferReceivedMail($receiver),
'profile-link-changed' => new ProfileLinkChangedMail(
$receiver,
$this->getLinkedProfileForTest($receiver),
'attached'
),
'profile-edited-by-admin' => new ProfileEditedByAdminMail(
$receiver,
'Test Admin',
'Updated profile information for testing purposes'
),
'verify-email' => new VerifyProfileEmailMailable(
$receiver->email,
url('/verify-email/' . base64_encode($receiver->email))
),
'reservation-created' => $this->buildReservationMail($receiver, ReservationCreatedMail::class),
'reservation-cancelled' => $this->buildReservationMail($receiver, ReservationCancelledMail::class),
'reservation-updated' => $this->buildReservationMail($receiver, ReservationUpdateMail::class),
'reaction-created' => $this->buildReactionMail($receiver),
'tag-added' => $this->buildTagAddedMail($receiver),
'call-expired' => $this->buildCallExpiredMail($receiver, $receiverType),
'call-expiring' => $this->buildCallExpiringMail($receiver, $receiverType),
'call-blocked' => $this->buildCallBlockedMail($receiver, $receiverType),
default => throw new \Exception("Mailable builder not implemented for type: {$type}"),
};
}
protected function getAccountsData($profile)
{
$accounts = [];
$profileAccounts = $profile->accounts()->active()->notRemoved()->get();
foreach ($profileAccounts as $account) {
\Cache::forget("account_balance_{$account->id}");
$accounts[] = [
'id' => $account->id,
'name' => $account->name,
'balance' => $account->balance,
'balanceFormatted' => tbFormat($account->balance),
];
}
return $accounts;
}
protected function getTotalBalance($accounts)
{
return array_sum(array_column($accounts, 'balance'));
}
protected function getTransferTargetAccount()
{
// Get a random organization account or create test data
$account = Account::whereHasMorph('accountable', [Organization::class])
->active()
->notRemoved()
->first();
return $account ? [
'id' => $account->id,
'name' => $account->name,
] : [
'id' => 1,
'name' => 'Test Organization Account',
];
}
protected function buildTransferReceivedMail($receiver)
{
$senderAccount = $receiver->accounts()->active()->notRemoved()->first();
if (!$senderAccount) {
throw new \Exception('Receiver has no active accounts');
}
return new TransferReceived(
$receiver->name,
120, // 2 hours in minutes
tbFormat(120),
'Test Transfer',
$senderAccount->name,
$receiver->email,
url('/profile/' . $receiver->name)
);
}
protected function buildReservationMail($receiver, $mailClass)
{
$postTitle = 'Test Post - ' . $mailClass;
$postOwner = 'Test Post Owner';
$postUrl = url('/posts/test-post');
$reservationDate = now()->addDays(7)->format('Y-m-d H:i');
return new $mailClass(
$receiver->name,
$postTitle,
$postOwner,
$postUrl,
$reservationDate
);
}
protected function buildReactionMail($receiver)
{
return new ReactionCreatedMail(
$receiver->name,
'Test Commenter',
'Test Post Title',
'This is a test comment for email testing purposes.',
url('/posts/test-post')
);
}
protected function buildTagAddedMail($receiver)
{
return new TagAddedMail(
$receiver->name,
'Test Tag',
'Test Admin',
url('/profile/' . $receiver->name)
);
}
protected function getTestCall($receiver): Call
{
return Call::where('callable_type', get_class($receiver))
->where('callable_id', $receiver->id)
->with(['tag'])
->first()
?? Call::with(['tag'])->first()
?? throw new \Exception('No calls found for test');
}
protected function buildCallExpiredMail($receiver, $receiverType): CallExpiredMail
{
return new CallExpiredMail($this->getTestCall($receiver), $receiver, ucfirst($receiverType));
}
protected function buildCallExpiringMail($receiver, $receiverType): CallExpiringMail
{
return new CallExpiringMail($this->getTestCall($receiver), $receiver, ucfirst($receiverType), 7);
}
protected function buildCallBlockedMail($receiver, $receiverType): CallBlockedMail
{
return new CallBlockedMail($this->getTestCall($receiver), $receiver, ucfirst($receiverType));
}
protected function getLinkedProfileForTest($receiver)
{
// Find a different profile type to use as the linked profile
// If receiver is a User, find an Organization/Admin/Bank to link
// If receiver is Organization/Admin/Bank, find a User to link
if ($receiver instanceof User) {
// Try to find an organization first, then admin, then bank
$linked = Organization::first() ?? Admin::first() ?? Bank::first();
} else {
// For Organization/Admin/Bank, find a user
$linked = User::first();
}
// If we can't find any other profile, just return the same receiver
return $linked ?? $receiver;
}
}