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:
33
app/Console/Commands/CleanAttachmentsCommand.php
Normal file
33
app/Console/Commands/CleanAttachmentsCommand.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/CleanMailboxCommand.php
Normal file
33
app/Console/Commands/CleanMailboxCommand.php
Normal 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);
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/FetchEmailsCommand.php
Normal file
33
app/Console/Commands/FetchEmailsCommand.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.'"',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -301,6 +301,7 @@ class PlanResource extends Resource
|
||||
|
||||
// Halt the bulk deletion process
|
||||
$action->halt();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 '{}';
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,7 @@ class Mailbox extends Component
|
||||
if (! ZEmail::getEmail()) {
|
||||
return to_route('home');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
|
||||
@@ -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')),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user