Files
imail/app/Livewire/Mailbox.php

302 lines
9.2 KiB
PHP

<?php
namespace App\Livewire;
use App\Jobs\TrackAnalytics;
use App\Models\Domain;
use App\Models\Email;
use App\Models\EmailBody;
use App\Models\Mailbox as MailboxModel;
use Illuminate\Support\Facades\Session;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('components.layouts.app')]
class Mailbox extends Component
{
use WithPagination;
public $currentMailboxId = null;
public $activeFolder = 'inbox';
public $selectedEmailId = null;
public $search = '';
public $viewMode = 'text'; // text | html
public $allowRemoteContent = false;
// Create State
public $showCreateModal = false;
public $createType = 'random'; // random | custom
public $customUsername = '';
public $customDomain = '';
public function mount()
{
$this->customDomain = Domain::where('is_active', true)->first()?->name ?? 'imail.app';
// Load current mailbox from session if exists
$savedId = Session::get('current_mailbox_id');
if ($savedId && $this->getActiveMailboxesProperty()->contains('id', $savedId)) {
$this->currentMailboxId = $savedId;
} else {
$this->currentMailboxId = $this->getActiveMailboxesProperty()->first()?->id;
}
}
public function getActiveMailboxesProperty()
{
return MailboxModel::query()
->where(function ($query) {
$query->where('session_id', Session::getId());
if (auth()->check()) {
$query->orWhere('user_id', auth()->id());
}
})
->where('is_blocked', false)
->get();
}
public function getAvailableDomainsProperty()
{
return Domain::where('is_active', true)->where('is_archived', false)->get();
}
/**
* Get Reverb/Echo event listeners for the current mailbox domain.
*
* @return array<string, string>
*/
public function getListeners(): array
{
$currentMailbox = $this->active_mailboxes->firstWhere('id', $this->currentMailboxId);
$domain = $currentMailbox ? explode('@', $currentMailbox->address)[1] ?? '' : '';
if (empty($domain)) {
return [];
}
return [
"echo:mailbox.{$domain},.new.email" => 'onNewEmail',
];
}
public function onNewEmail(array $eventData): void
{
// Simply refresh the list to pick up the new email from MariaDB
// Since we order by received_at DESC, it will appear on top.
$this->dispatch('$refresh');
}
public function getEmailsProperty()
{
$currentMailbox = $this->active_mailboxes->firstWhere('id', $this->currentMailboxId);
if (! $currentMailbox) {
return Email::query()->whereRaw('1 = 0')->paginate(10);
}
return Email::query()
->where('recipient_email', $currentMailbox->address)
->when($this->search, function ($query) {
$query->where(function ($q) {
$q->where('subject', 'like', "%{$this->search}%")
->orWhere('sender_email', 'like', "%{$this->search}%")
->orWhere('sender_name', 'like', "%{$this->search}%");
});
})
->orderByDesc('received_at')
->paginate(10);
}
public function selectEmail($id)
{
$this->selectedEmailId = $id;
$this->viewMode = 'text';
$this->allowRemoteContent = false;
$email = Email::find($id);
if ($email) {
if (! $email->is_read) {
$email->update(['is_read' => true]);
}
// Track analytics for reading email
$currentMailbox = $this->active_mailboxes->firstWhere('id', $this->currentMailboxId);
if ($currentMailbox) {
TrackAnalytics::dispatch(
eventType: 'email_read',
mailboxHash: $currentMailbox->mailbox_hash,
domainHash: $currentMailbox->domain_hash,
metadata: ['email_id' => $email->id, 'subject' => $email->subject],
userId: auth()->id(),
userType: auth()->check() ? 'authenticated' : 'guest',
ipAddress: request()->ip(),
userAgent: request()->userAgent()
);
}
}
}
public function switchMailbox($id)
{
$this->currentMailboxId = $id;
$this->selectedEmailId = null;
$this->search = '';
$this->resetPage();
Session::put('current_mailbox_id', $id);
// Track analytics for switching mailbox
$currentMailbox = $this->active_mailboxes->firstWhere('id', $id);
if ($currentMailbox) {
TrackAnalytics::dispatch(
eventType: 'mailbox_accessed',
mailboxHash: $currentMailbox->mailbox_hash,
domainHash: $currentMailbox->domain_hash,
userId: auth()->id(),
userType: auth()->check() ? 'authenticated' : 'guest',
ipAddress: request()->ip(),
userAgent: request()->userAgent()
);
$currentMailbox->update([
'last_accessed_at' => now(),
'last_accessed_ip' => request()->ip(),
]);
}
}
public function createMailbox()
{
$domainModel = Domain::where('name', $this->customDomain)->first();
if (! $domainModel) {
return;
}
$address = $this->createType === 'random'
? fake()->userName().'_'.rand(10, 99).'@'.$this->customDomain
: $this->customUsername.'@'.$this->customDomain;
$mailbox = MailboxModel::create([
'mailbox_hash' => bin2hex(random_bytes(32)),
'domain_hash' => $domainModel->domain_hash,
'user_id' => auth()->id(),
'session_id' => Session::getId(),
'address' => $address,
'type' => $this->createType === 'random' ? 'public' : 'custom',
'created_ip' => request()->ip(),
'last_accessed_ip' => request()->ip(),
'last_accessed_at' => now(),
'expires_at' => now()->addDays(7), // Default expiry
]);
$this->currentMailboxId = $mailbox->id;
$this->showCreateModal = false;
$this->customUsername = '';
Session::put('last_mailbox_id', $mailbox->id);
}
public function deleteMailbox($id)
{
$mailbox = MailboxModel::find($id);
if ($mailbox) {
$mailbox->delete();
}
if ($this->currentMailboxId === $id) {
$this->currentMailboxId = $this->active_mailboxes->first()?->id;
$this->selectedEmailId = null;
session(['current_mailbox_id' => $this->currentMailboxId]);
}
}
public function downloadEmail($id)
{
// Mock download logic
$this->js("alert('Downloading email #{$id}... (Mock Action)')");
}
public function printEmail($id)
{
// Mock print logic
$this->js('window.print()');
}
public function deleteEmail($id)
{
// Mock delete logic
$this->js("alert('Email #{$id} deleted successfully! (Mock Action)')");
$this->selectedEmailId = null;
}
public function nextPage()
{
if ($this->page < $this->totalPages) {
$this->page++;
$this->selectedEmailId = null;
}
}
public function previousPage()
{
if ($this->page > 1) {
$this->page--;
$this->selectedEmailId = null;
}
}
public function generateQrCode($address)
{
// Mock QR generation with a slight delay
usleep(800000); // 800ms
$this->dispatch('qrCodeGenerated', address: $address);
}
public function getProcessedContent($email)
{
$body = EmailBody::where('unique_id_hash', $email->unique_id_hash)->first();
if (! $body) {
return 'Email body not found.';
}
$content = $body->body_html ?? $body->body_text;
$isText = $this->viewMode === 'text';
// Fallback to HTML if text is selected but body_text is empty
if ($isText && ! empty($body->body_text)) {
return trim(e($body->body_text));
}
if ($isText) {
// If fallback occurred, we sanitize the HTML to text
return trim(strip_tags($content));
}
if (! $this->allowRemoteContent) {
// Block remote assets by replacing src with data-src for img tags
return preg_replace('/<img\s[^>]*?\bsrc\s*=\s*([\'"])(.*?)\1/i', '<img $2 data-blocked-src=$1$2$1 src="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 1 1\'%3E%3C/svg%3E" class="blocked-remote-asset shadow-sm border border-white/5 opacity-50"', $content);
}
return $content;
}
public function render()
{
$currentMailbox = $this->active_mailboxes->firstWhere('id', $this->currentMailboxId);
return view('livewire.mailbox', [
'emails' => $this->getEmailsProperty(),
'currentMailbox' => $currentMailbox,
]);
}
}