feat: Prepare Zemailnator for Dokploy deployment

- Add highly optimized Dockerfile with Nginx and PHP-FPM 8.4
- Add docker-compose.yml configured with Redis and MariaDB 10.11
- Implement entrypoint.sh and supervisord.conf for background workers
- Refactor legacy IMAP scripts into scheduled Artisan Commands
- Secure app by removing old routes with hardcoded basic auth credentials
- Configure email attachments to use Laravel Storage instead of insecure public/tmp
This commit is contained in:
idevakk
2026-02-28 23:17:39 +05:30
parent bf5b797cd8
commit c312ec3325
78 changed files with 750 additions and 360 deletions

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Email;
use Illuminate\Console\Command;
class CleanAttachmentsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'attachments:clean';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clean old email attachments from disk';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting to clean attachments...');
Email::deleteBulkAttachments();
$this->info('Finished cleaning attachments.');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Email;
use Illuminate\Console\Command;
class CleanMailboxCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mailbox:clean';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clean old messages from the IMAP mailbox';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting mailbox cleanup...');
$result = Email::cleanMailbox();
$this->info($result);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Email;
use Illuminate\Console\Command;
class FetchEmailsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'emails:fetch';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fetch, process, and store emails from the IMAP server';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting to fetch emails from IMAP server...');
Email::fetchProcessStoreEmail();
$this->info('Finished fetching emails.');
}
}

View File

@@ -13,7 +13,6 @@ use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Inerba\DbConfig\AbstractPageSettings;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
class ImapSettings extends AbstractPageSettings
{
@@ -145,7 +144,7 @@ class ImapSettings extends AbstractPageSettings
$fields = ['host', 'port', 'username', 'password', 'encryption', 'validate_cert', 'protocol'];
foreach ($fields as $field) {
$key = $sectionName . '.' . $field;
$key = $sectionName.'.'.$field;
// Try different data structure approaches
$value = null;
@@ -186,8 +185,8 @@ class ImapSettings extends AbstractPageSettings
return [
'section' => ucfirst($sectionName),
'success' => false,
'message' => "Missing required fields: " . $missingFields->join(', '),
'details' => null
'message' => 'Missing required fields: '.$missingFields->join(', '),
'details' => null,
];
}
@@ -198,7 +197,7 @@ class ImapSettings extends AbstractPageSettings
'section' => ucfirst($sectionName),
'success' => false,
'message' => 'IMAP extension is not loaded in your web server. Please check your Herd PHP configuration or restart your server.',
'details' => null
'details' => null,
];
}
@@ -210,7 +209,7 @@ class ImapSettings extends AbstractPageSettings
'password' => $config['password'],
'encryption' => $config['encryption'] ?? 'none',
'validate_cert' => $config['validate_cert'] ?? false,
'protocol' => $config['protocol'] ?? 'imap'
'protocol' => $config['protocol'] ?? 'imap',
];
// Test connection using the existing ZEmail::connectMailBox method
@@ -224,8 +223,8 @@ class ImapSettings extends AbstractPageSettings
'host' => $config['host'],
'port' => $config['port'],
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
'protocol' => $config['protocol'] ?? 'imap',
],
];
} catch (\Exception $e) {
@@ -244,13 +243,12 @@ class ImapSettings extends AbstractPageSettings
'host' => $config['host'] ?? null,
'port' => $config['port'] ?? null,
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
'protocol' => $config['protocol'] ?? 'imap',
],
];
}
}
/**
* Send appropriate notification based on test results.
*/
@@ -294,6 +292,7 @@ class ImapSettings extends AbstractPageSettings
$details[] = "{$result['section']}: {$result['details']['messages']} messages";
}
}
return implode(' | ', $details);
}
@@ -306,6 +305,7 @@ class ImapSettings extends AbstractPageSettings
foreach ($results as $result) {
$details[] = "{$result['section']}: {$result['message']}";
}
return implode(' | ', $details);
}
@@ -319,6 +319,7 @@ class ImapSettings extends AbstractPageSettings
$status = $result['success'] ? '✅' : '❌';
$details[] = "{$status} {$result['section']}";
}
return implode(' | ', $details);
}
}

View File

@@ -27,7 +27,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use UnitEnum;
class ImpersonationLogViewer extends Page implements HasForms, HasTable
@@ -132,10 +131,10 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
TextColumn::make('duration_in_minutes')
->label('Duration')
->formatStateUsing(function ($record) {
return match(true) {
!$record->duration_in_minutes => 'Active',
return match (true) {
! $record->duration_in_minutes => 'Active',
$record->duration_in_minutes < 60 => "{$record->duration_in_minutes}m",
default => round($record->duration_in_minutes / 60, 1) . 'h',
default => round($record->duration_in_minutes / 60, 1).'h',
};
})
->sortable()
@@ -324,7 +323,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
->latest('start_time')
->get();
$filename = 'impersonation_logs_' . now()->format('Y_m_d_H_i_s') . '.csv';
$filename = 'impersonation_logs_'.now()->format('Y_m_d_H_i_s').'.csv';
// Create a temporary file
$handle = fopen('php://temp', 'r+');
@@ -376,7 +375,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
$filename,
[
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
]
);
}

View File

@@ -2,13 +2,12 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
use App\Filament\Resources\BlogResource\Pages\EditBlog;
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
use App\Models\Blog;
use App\Models\Category;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -30,6 +29,7 @@ use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class BlogResource extends Resource
{

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
use App\Models\Category;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -23,6 +22,7 @@ use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class CategoryResource extends Resource
{
@@ -60,7 +60,7 @@ class CategoryResource extends Resource
TextColumn::make('slug'),
TextColumn::make('blogs_count')
->label('Blogs')
->getStateUsing(fn(Category $record): int => $record->blogs()->count()),
->getStateUsing(fn (Category $record): int => $record->blogs()->count()),
IconColumn::make('is_active')->label('Active')->boolean(),
])
->filters([

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
use App\Filament\Resources\MenuResource\Pages\EditMenu;
use App\Filament\Resources\MenuResource\Pages\ListMenus;
use App\Models\Menu;
use BackedEnum;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
@@ -21,6 +20,7 @@ use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use UnitEnum;
class MenuResource extends Resource
{

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\PageResource\Pages\CreatePage;
use App\Filament\Resources\PageResource\Pages\EditPage;
use App\Filament\Resources\PageResource\Pages\ListPages;
use App\Models\Page;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -29,6 +28,7 @@ use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class PageResource extends Resource
{

View File

@@ -2,12 +2,12 @@
namespace App\Filament\Resources\PaymentProviders\Tables;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;

View File

@@ -301,6 +301,7 @@ class PlanResource extends Resource
// Halt the bulk deletion process
$action->halt();
return;
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
use App\Filament\Resources\TicketResource\Pages\EditTicket;
use App\Filament\Resources\TicketResource\Pages\ListTickets;
@@ -11,6 +9,7 @@ use App\Filament\Resources\TicketResource\RelationManagers\ResponsesRelationMana
use App\Mail\TicketResponseNotification;
use App\Models\Ticket;
use App\Models\TicketResponse;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
@@ -33,6 +32,7 @@ use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\HtmlString;
use UnitEnum;
class TicketResource extends Resource
{
@@ -120,7 +120,7 @@ class TicketResource extends Resource
DatePicker::make('created_from')->label('Created From'),
DatePicker::make('created_until')->label('Created Until'),
])
->query(fn($query, array $data) => $query
->query(fn ($query, array $data) => $query
->when($data['created_from'], fn ($query, $date) => $query->whereDate('created_at', '>=', $date))
->when($data['created_until'], fn ($query, $date) => $query->whereDate('created_at', '<=', $date))),
])
@@ -134,7 +134,7 @@ class TicketResource extends Resource
Action::make('view')
->label('View & Respond')
->icon('heroicon-o-eye')
->schema(fn(Ticket $ticket): array => [
->schema(fn (Ticket $ticket): array => [
TextArea::make('response')
->label('Your Response')
->required()

View File

@@ -34,6 +34,7 @@ class TrialExtensionForm
if ($subscription->trial_ends_at) {
$label .= " ({$subscription->trial_ends_at->format('M j, Y')})";
}
return [$subscription->id => $label];
})
->toArray();
@@ -158,12 +159,14 @@ class TrialExtensionForm
if (! $subscriptionId || ! $extensionDays) {
$set('new_trial_ends_at', null);
return;
}
$subscription = Subscription::find($subscriptionId);
if (! $subscription) {
$set('new_trial_ends_at', null);
return;
}

View File

@@ -96,7 +96,7 @@ class TrialExtensionsTable
->label('View Subscription')
->icon('heroicon-o-rectangle-stack')
->color('blue')
->url(fn ($record) => route('filament.' . filament()->getCurrentPanel()->getId() . '.resources.subscriptions.edit', $record->subscription_id))
->url(fn ($record) => route('filament.'.filament()->getCurrentPanel()->getId().'.resources.subscriptions.edit', $record->subscription_id))
->openUrlInNewTab(),
])
->toolbarActions([

View File

@@ -7,7 +7,6 @@ use App\Filament\Resources\TrialExtensions\Pages\EditTrialExtension;
use App\Filament\Resources\TrialExtensions\Pages\ListTrialExtensions;
use App\Filament\Resources\TrialExtensions\Schemas\TrialExtensionForm;
use App\Filament\Resources\TrialExtensions\Tables\TrialExtensionsTable;
use App\Models\Subscription;
use App\Models\TrialExtension;
use BackedEnum;
use Filament\Resources\Resource;

View File

@@ -2,8 +2,6 @@
namespace App\Filament\Widgets;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use App\Models\Log;
use App\Models\Meta;
use App\Models\PremiumEmail;
@@ -11,6 +9,8 @@ use App\Models\Ticket;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class StatsOverview extends BaseWidget
{

View File

@@ -14,7 +14,7 @@ class ArrayHelper
$keys = explode('.', $key);
foreach ($keys as $segment) {
if (!isset($array[$segment])) {
if (! isset($array[$segment])) {
return null;
}
$array = $array[$segment];
@@ -32,7 +32,7 @@ class ArrayHelper
);
} catch (\JsonException $e) {
// Optional: Log the error
Log::error("JSON encode failed: " . $e->getMessage());
Log::error('JSON encode failed: '.$e->getMessage());
// Fallback: return empty object instead of crashing
return '{}';

View File

@@ -2,11 +2,10 @@
namespace App\Http\Controllers;
use Session;
use Illuminate\Routing\Redirector;
use Illuminate\Http\RedirectResponse;
use App\Models\Premium;
use App\Models\ZEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
class AppController extends Controller
{
@@ -56,6 +55,7 @@ class AppController extends Controller
return to_route('mailbox');
}
return to_route('home');
}
@@ -86,6 +86,7 @@ class AppController extends Controller
return to_route('dashboard.premium');
}
return to_route('dashboard');
}

View File

@@ -18,7 +18,7 @@ class PaymentCancelController extends Controller
Log::info('PaymentCancelController: Cancellation page accessed', [
'user_id' => auth()->id(),
'session_token' => $sessionToken ? substr($sessionToken, 0, 20) . '...' : 'none',
'session_token' => $sessionToken ? substr($sessionToken, 0, 20).'...' : 'none',
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
@@ -48,4 +48,4 @@ class PaymentCancelController extends Controller
'recentSubscription' => $recentSubscription,
]);
}
}
}

View File

@@ -2,9 +2,9 @@
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class CheckUserBanned

View File

@@ -2,8 +2,8 @@
namespace App\Livewire\Actions;
use Illuminate\Routing\Redirector;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

View File

@@ -2,10 +2,10 @@
namespace App\Livewire;
use Illuminate\Support\Facades\Session;
use App\Models\ZEmail;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use App\Models\ZEmail;
use Illuminate\Support\Facades\Session;
use Livewire\Component;
class AddOn extends Component
@@ -31,6 +31,7 @@ class AddOn extends Component
if (count($messages['data']) > 0) {
return to_route('mailbox');
}
return null;
}
@@ -48,7 +49,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -66,7 +67,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -85,7 +86,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -103,7 +104,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -121,7 +122,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -139,7 +140,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [
@@ -157,7 +158,7 @@ class AddOn extends Component
$this->faqSchema = [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
'@type' => 'Question',
'name' => $faq['title'],
'acceptedAnswer' => [

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Dashboard;
use App\Models\Subscription;
use App\Models\UsageLog;
use Exception;
use Illuminate\Http\Request;
@@ -10,7 +11,6 @@ use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Stripe\StripeClient;
use App\Models\Subscription;
class Dashboard extends Component
{

View File

@@ -2,11 +2,11 @@
namespace App\Livewire\Dashboard;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Request;
use App\Models\Ticket;
use App\Models\TicketResponse;
use Exception;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Str;
use Livewire\Component;
class Support extends Component
@@ -129,9 +129,9 @@ class Support extends Component
public function updateTicketCounts(): void
{
$this->open = $this->tickets->filter(fn($ticket): bool => in_array($ticket->status, ['open', 'pending']))->count();
$this->open = $this->tickets->filter(fn ($ticket): bool => in_array($ticket->status, ['open', 'pending']))->count();
$this->closed = $this->tickets->filter(fn($ticket): bool => $ticket->status === 'closed')->count();
$this->closed = $this->tickets->filter(fn ($ticket): bool => $ticket->status === 'closed')->count();
}
protected function getClientIp()

View File

@@ -2,9 +2,9 @@
namespace App\Livewire\Frontend;
use Illuminate\Routing\Redirector;
use Illuminate\Http\RedirectResponse;
use App\Models\ZEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Livewire\Component;
class Email extends Component

View File

@@ -40,6 +40,7 @@ class Mailbox extends Component
if (! ZEmail::getEmail()) {
return to_route('home');
}
return null;
}

View File

@@ -2,9 +2,9 @@
namespace App\Livewire;
use App\Models\ZEmail;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use App\Models\ZEmail;
use Livewire\Component;
class Home extends Component
@@ -18,6 +18,7 @@ class Home extends Component
if (count($messages['data']) > 0) {
return to_route('mailbox');
}
return null;
}

View File

@@ -18,9 +18,7 @@ class TicketResponseNotification extends Mailable
/**
* Create a new message instance.
*/
public function __construct(public Ticket $ticket, public Collection $responses)
{
}
public function __construct(public Ticket $ticket, public Collection $responses) {}
/**
* Get the message envelope.

View File

@@ -2,18 +2,17 @@
namespace App\Models;
use DateTimeImmutable;
use Illuminate\Support\Facades\Date;
use App\ColorPicker;
use Carbon\CarbonImmutable;
use DateTime;
use DateTimeImmutable;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Search\Date\Since;
use Ddeboer\Imap\Server;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
@@ -78,7 +77,7 @@ class Email extends Model
$sender = $message->getFrom();
$date = $message->getDate();
if (!$date instanceof DateTimeImmutable) {
if (! $date instanceof DateTimeImmutable) {
$date = new DateTime;
if ($message->getHeaders()->get('udate')) {
$date->setTimestamp($message->getHeaders()->get('udate'));
@@ -100,11 +99,11 @@ class Email extends Model
$obj = [];
$to = $message->getHeaders()->get('To') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
$to = $message->getHeaders()->get('To') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
$cc = $message->getHeaders()->get('Cc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
$cc = $message->getHeaders()->get('Cc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
$messageTime = $message->getDate();
$utcTime = CarbonImmutable::instance($messageTime)->setTimezone('UTC')->toDateTimeString();
@@ -127,28 +126,27 @@ class Email extends Model
if ($message->hasAttachments()) {
$attachments = $message->getAttachments();
$directory = './tmp/attachments/'.$obj['id'].'/';
$directoryPath = 'attachments/'.$obj['id'];
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
foreach ($attachments as $attachment) {
$filenameArray = explode('.', (string) $attachment->getFilename());
$extension = $filenameArray[count($filenameArray) - 1];
if (in_array($extension, $allowed)) {
if (! file_exists($directory.$attachment->getFilename())) {
$filePath = $directoryPath.'/'.$attachment->getFilename();
if (! \Illuminate\Support\Facades\Storage::disk('public')->exists($filePath)) {
try {
file_put_contents(
$directory.$attachment->getFilename(),
\Illuminate\Support\Facades\Storage::disk('public')->put(
$filePath,
$attachment->getDecodedContent()
);
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
if ($attachment->getFilename() !== 'undefined') {
$url = config('app.settings.app_base_url').str_replace('./', '/', $directory.$attachment->getFilename());
$url = config('app.settings.app_base_url').'/storage/'.$filePath;
$structure = $attachment->getStructure();
if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
$obj['content'] = str_replace('cid:'.trim($structure->id, '<>'), $url, $obj['content']);
@@ -159,9 +157,7 @@ class Email extends Model
];
}
}
}
}
$response['data'][] = $obj;
@@ -354,11 +350,9 @@ class Email extends Model
public static function deleteBulkAttachments(): void
{
$dir = public_path('/tmp/attachments');
try {
if (File::exists($dir)) {
File::cleanDirectory($dir);
if (\Illuminate\Support\Facades\Storage::disk('public')->exists('attachments')) {
\Illuminate\Support\Facades\Storage::disk('public')->deleteDirectory('attachments');
}
} catch (Exception $e) {
Log::error($e->getMessage());
@@ -431,6 +425,7 @@ class Email extends Model
$currentTime = Date::now('UTC');
$lastRecordTime = Date::parse($latestRecord->timestamp);
return $lastRecordTime->diffInMinutes($currentTime) < 5;
}

View File

@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Support\Facades\Date;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Date;
class Log extends Model
{

View File

@@ -2,10 +2,10 @@
namespace App\Models;
use DateTimeImmutable;
use App\ColorPicker;
use Carbon\Carbon;
use DateTime;
use DateTimeImmutable;
use Ddeboer\Imap\Search\Email\Cc;
use Ddeboer\Imap\Search\Email\To;
use Ddeboer\Imap\SearchExpression;
@@ -41,7 +41,7 @@ class Message extends Model
$message->attachments = $request->get('attachment-info');
$message->save();
$directory = './attachments/'.$message->id;
if (!is_dir($directory)) {
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
$attachment_ids = json_decode((string) $request->get('attachment-info'));
@@ -146,7 +146,7 @@ class Message extends Model
$blocked = false;
$sender = $message->getFrom();
$date = $message->getDate();
if (!$date instanceof DateTimeImmutable) {
if (! $date instanceof DateTimeImmutable) {
$date = new DateTime;
if ($message->getHeaders()->get('udate')) {
$date->setTimestamp($message->getHeaders()->get('udate'));
@@ -193,7 +193,7 @@ class Message extends Model
if ($message->hasAttachments() && ! $blocked) {
$attachments = $message->getAttachments();
$directory = './tmp/attachments/'.$obj['id'].'/';
if (!is_dir($directory)) {
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
foreach ($attachments as $attachment) {

View File

@@ -2,11 +2,11 @@
namespace App\Models;
use Illuminate\Support\Facades\Date;
use App\ColorPicker;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Validator;
class PremiumEmail extends Model

View File

@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Server;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cookie;
@@ -14,6 +14,7 @@ class ZEmail extends Model
{
use HasFactory;
use HasFactory;
public static function connectMailBox($imap = null): ConnectionInterface
{
if ($imap === null) {
@@ -38,6 +39,7 @@ class ZEmail extends Model
if (Email::mailToDBStatus()) {
return Email::parseEmail($email, $deleted);
}
return Message::fetchMessages($email, $type, $deleted);
}
@@ -57,6 +59,7 @@ class ZEmail extends Model
if (Cookie::has('email')) {
return Cookie::get('email');
}
return $generate ? ZEmail::generateRandomEmail() : null;
}
@@ -65,6 +68,7 @@ class ZEmail extends Model
if (Cookie::has('emails')) {
return unserialize(Cookie::get('emails'));
}
return [];
}

View File

@@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider
$this->appConfig = [
'website_settings' => [],
'imap_settings' => [],
'configuration_settings' => []
'configuration_settings' => [],
];
Log::error($e->getMessage());
}
@@ -114,40 +114,40 @@ class AppServiceProvider extends ServiceProvider
private function loadLegacySettings(): array
{
return [
"app_name" => $this->getConfig('website_settings.app_name'),
"app_version" => $this->getConfig('website_settings.app_version'),
"app_base_url" => $this->getConfig('website_settings.app_base_url'),
"app_admin" => $this->getConfig('website_settings.app_admin'),
"app_title" => $this->getConfig('website_settings.app_title'),
"app_description" => $this->getConfig('website_settings.app_description'),
"app_keywords" => $this->getConfig('website_settings.app_keywords'),
"app_contact" => $this->getConfig('website_settings.app_contact'),
"app_meta" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_meta')),
"app_social" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_social')),
"app_header" => $this->getConfig('website_settings.app_header'),
"app_footer" => $this->getConfig('website_settings.app_footer'),
"imap_settings" => ArrayHelper::jsonEncodeSafe([
"host" => $this->getConfig('imap_settings.public.host'),
"port" => $this->getConfig('imap_settings.public.port'),
"username" => $this->getConfig('imap_settings.public.username'),
"password" => $this->getConfig('imap_settings.public.password'),
"encryption" => $this->getConfig('imap_settings.public.encryption'),
"validate_cert" => $this->getConfig('imap_settings.public.validate_cert'),
"default_account" => $this->getConfig('imap_settings.public.default_account'),
"protocol" => $this->getConfig('imap_settings.public.protocol'),
"cc_check" => $this->getConfig('imap_settings.public.cc_check'),
"premium_host" => $this->getConfig('imap_settings.premium.host'),
"premium_port" => $this->getConfig('imap_settings.premium.port'),
"premium_username" => $this->getConfig('imap_settings.premium.username'),
"premium_password" => $this->getConfig('imap_settings.premium.password'),
"premium_encryption" => $this->getConfig('imap_settings.premium.premium_encryption'),
"premium_validate_cert" => $this->getConfig('imap_settings.premium.validate_cert'),
"premium_default_account" => $this->getConfig('imap_settings.premium.default_account'),
"premium_protocol" => $this->getConfig('imap_settings.premium.protocol'),
"premium_cc_check" => $this->getConfig('imap_settings.premium.cc_check'),
'app_name' => $this->getConfig('website_settings.app_name'),
'app_version' => $this->getConfig('website_settings.app_version'),
'app_base_url' => $this->getConfig('website_settings.app_base_url'),
'app_admin' => $this->getConfig('website_settings.app_admin'),
'app_title' => $this->getConfig('website_settings.app_title'),
'app_description' => $this->getConfig('website_settings.app_description'),
'app_keywords' => $this->getConfig('website_settings.app_keywords'),
'app_contact' => $this->getConfig('website_settings.app_contact'),
'app_meta' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_meta')),
'app_social' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_social')),
'app_header' => $this->getConfig('website_settings.app_header'),
'app_footer' => $this->getConfig('website_settings.app_footer'),
'imap_settings' => ArrayHelper::jsonEncodeSafe([
'host' => $this->getConfig('imap_settings.public.host'),
'port' => $this->getConfig('imap_settings.public.port'),
'username' => $this->getConfig('imap_settings.public.username'),
'password' => $this->getConfig('imap_settings.public.password'),
'encryption' => $this->getConfig('imap_settings.public.encryption'),
'validate_cert' => $this->getConfig('imap_settings.public.validate_cert'),
'default_account' => $this->getConfig('imap_settings.public.default_account'),
'protocol' => $this->getConfig('imap_settings.public.protocol'),
'cc_check' => $this->getConfig('imap_settings.public.cc_check'),
'premium_host' => $this->getConfig('imap_settings.premium.host'),
'premium_port' => $this->getConfig('imap_settings.premium.port'),
'premium_username' => $this->getConfig('imap_settings.premium.username'),
'premium_password' => $this->getConfig('imap_settings.premium.password'),
'premium_encryption' => $this->getConfig('imap_settings.premium.premium_encryption'),
'premium_validate_cert' => $this->getConfig('imap_settings.premium.validate_cert'),
'premium_default_account' => $this->getConfig('imap_settings.premium.default_account'),
'premium_protocol' => $this->getConfig('imap_settings.premium.protocol'),
'premium_cc_check' => $this->getConfig('imap_settings.premium.cc_check'),
]),
"configuration_settings" => ArrayHelper::jsonEncodeSafe($this->appConfig['configuration_settings']),
"ads_settings" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.ads_settings')),
'configuration_settings' => ArrayHelper::jsonEncodeSafe($this->appConfig['configuration_settings']),
'ads_settings' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.ads_settings')),
];
}

View File

@@ -35,7 +35,7 @@ class OxapayProvider implements PaymentProviderContract
$config = array_merge($dbConfig, $config);
$this->config = $config;
$this->sandbox = $config['sandbox'] === "true" ?? false;
$this->sandbox = $config['sandbox'] === 'true' ?? false;
$this->merchantApiKey = $this->sandbox ? ($config['sandbox_merchant_api_key'] ?? '') : ($config['merchant_api_key'] ?? '');
$this->baseUrl = 'https://api.oxapay.com/v1';